Files
baseos/ts/classes.supervisorclient.ts
T

128 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-05-07 15:53:15 +00:00
import type { IBalenaDeviceState, IBalenaStateStatus } from './types.ts';
export interface ISupervisorClientOptions {
address?: string;
apiKey?: string;
}
export class SupervisorClient {
public readonly address?: string;
private readonly apiKey?: string;
constructor(optionsArg: ISupervisorClientOptions = {}) {
this.address = optionsArg.address || Deno.env.get('BALENA_SUPERVISOR_ADDRESS') || undefined;
this.apiKey = optionsArg.apiKey || Deno.env.get('BALENA_SUPERVISOR_API_KEY') || undefined;
}
public isConfigured() {
return Boolean(this.address);
}
public async isAvailable() {
if (!this.address) {
return false;
}
try {
const response = await fetch(this.buildUrl('/ping', false));
const text = await response.text();
return response.ok && text.trim() === 'OK';
} catch {
return false;
}
}
public async getDeviceState(): Promise<IBalenaDeviceState> {
return await this.requestJson<IBalenaDeviceState>('/v1/device');
}
public async getStateStatus(): Promise<IBalenaStateStatus> {
return await this.requestJson<IBalenaStateStatus>('/v2/state/status');
}
2026-05-07 20:33:14 +00:00
public async getLocalTargetState(): Promise<Record<string, unknown>> {
const response = await this.requestJson<{ state?: Record<string, unknown> }>(
'/v2/local/target-state',
{},
false,
);
return response.state || {};
}
public async setLocalTargetState(targetStateArg: Record<string, unknown>) {
await this.requestJson<{ status?: string; message?: string }>(
'/v2/local/target-state',
{
method: 'POST',
body: JSON.stringify(targetStateArg),
headers: {
'content-type': 'application/json',
},
},
false,
);
}
2026-05-07 15:53:15 +00:00
public async getSupervisorVersion(): Promise<string | undefined> {
const response = await this.requestJson<{ version?: string }>('/v2/version');
return response.version;
}
public async triggerUpdate(optionsArg: { force?: boolean; cancel?: boolean } = {}) {
await this.requestEmpty('/v1/update', {
method: 'POST',
body: JSON.stringify({
force: Boolean(optionsArg.force),
cancel: Boolean(optionsArg.cancel),
}),
headers: {
'content-type': 'application/json',
},
});
}
public async reboot(optionsArg: { force?: boolean } = {}) {
await this.requestEmpty('/v1/reboot', {
method: 'POST',
body: JSON.stringify({
force: Boolean(optionsArg.force),
}),
headers: {
'content-type': 'application/json',
},
});
}
private async requestJson<TResponse>(
pathArg: string,
initArg: RequestInit = {},
2026-05-07 20:33:14 +00:00
includeApiKeyArg = true,
2026-05-07 15:53:15 +00:00
): Promise<TResponse> {
2026-05-07 20:33:14 +00:00
const response = await fetch(this.buildUrl(pathArg, includeApiKeyArg), initArg);
2026-05-07 15:53:15 +00:00
if (!response.ok) {
throw new Error(`Supervisor request failed: ${pathArg} -> HTTP ${response.status}`);
}
return await response.json() as TResponse;
}
private async requestEmpty(pathArg: string, initArg: RequestInit = {}) {
const response = await fetch(this.buildUrl(pathArg, true), initArg);
if (!response.ok) {
throw new Error(`Supervisor request failed: ${pathArg} -> HTTP ${response.status}`);
}
}
private buildUrl(pathArg: string, includeApiKeyArg: boolean) {
if (!this.address) {
throw new Error('Balena Supervisor address is not configured');
}
const url = new URL(pathArg, this.address.endsWith('/') ? this.address : `${this.address}/`);
if (includeApiKeyArg) {
if (!this.apiKey) {
throw new Error('Balena Supervisor API key is not configured');
}
url.searchParams.set('apikey', this.apiKey);
}
return url;
}
}