import type { InstanceService } from '../instances/instance-service.js'; import type { InstanceSessionStore, UpstreamResponse, UpstreamSessionClientOptions, } from '../connections/upstream-session-client.js'; export interface InstanceResources { readonly cpuPercent?: number; readonly memoryPercent?: number; readonly maxTemperatureCelsius?: number; readonly phoneNumbers?: readonly string[]; readonly version?: string; readonly platform?: string; } const MAX_BODY_BYTES = 32_768; const record = (value: unknown): Record | undefined => value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : undefined; const ranged = (value: unknown, minimum: number, maximum: number): number | undefined => typeof value === 'number' && Number.isFinite(value) && value >= minimum && value <= maximum ? value : undefined; function data(response: UpstreamResponse): Record | undefined { if (response.status < 200 || response.status >= 300) return undefined; if (Buffer.byteLength(response.body, 'utf8') > MAX_BODY_BYTES) return undefined; try { return record(record(JSON.parse(response.body))?.data); } catch { return undefined; } } const safeText = (value: unknown, maximum = 128): string | undefined => typeof value === 'string' && value.length > 0 && value.length <= maximum && !/[\u0000-\u001f\u007f]/u.test(value) ? value : undefined; export function parseHealth(response: UpstreamResponse): InstanceResources { if (response.status < 200 || response.status >= 300) return {}; if (Buffer.byteLength(response.body, 'utf8') > MAX_BODY_BYTES) return {}; try { const root = record(JSON.parse(response.body)); if (!root) return {}; const version = safeText(root.version); const platform = safeText(root.platform); return { ...(version ? { version } : {}), ...(platform ? { platform } : {}), }; } catch { return {}; } } export function parseStats(response: UpstreamResponse): InstanceResources { const value = data(response); if (!value) return {}; const cpuPercent = ranged(record(value.cpu_load)?.load_percent, 0, 100); const memoryPercent = ranged(record(value.memory)?.used_percent, 0, 100); const temperatures = Array.isArray(value.temperature) ? value.temperature .map((item) => ranged(record(item)?.temperature, -100, 300)) .filter((item): item is number => item !== undefined) : []; const maxTemperatureCelsius = temperatures.length > 0 ? Math.max(...temperatures) : undefined; const system = record(value.system); const version = safeText(value.version) ?? safeText(value.current_version) ?? safeText(system?.version) ?? safeText(system?.app_version); const platform = safeText(value.platform) ?? safeText(system?.platform) ?? safeText(system?.architecture); return { ...(cpuPercent === undefined ? {} : { cpuPercent }), ...(memoryPercent === undefined ? {} : { memoryPercent }), ...(maxTemperatureCelsius === undefined ? {} : { maxTemperatureCelsius }), ...(version ? { version } : {}), ...(platform ? { platform } : {}), }; } export function parseSim(response: UpstreamResponse): InstanceResources { const value = data(response); if (!value || !Array.isArray(value.phone_numbers)) return {}; const phoneNumbers = value.phone_numbers.filter( (item): item is string => typeof item === 'string' && item.length > 0 && item.length <= 32 && /^[+0-9 ()-]+$/.test(item), ); return phoneNumbers.length > 0 ? { phoneNumbers: [...new Set(phoneNumbers)].slice(0, 16) } : {}; } export class InstanceResourceService { constructor( private readonly options: { readonly instances: InstanceService; readonly sessions: InstanceSessionStore; readonly request: UpstreamSessionClientOptions['request']; readonly ensureSession?: ( instanceId: string, origin: string, force?: boolean, ) => Promise; }, ) {} async get(instanceId: string): Promise { const [instance, session] = await Promise.all([ this.options.instances.get(instanceId), Promise.resolve(this.options.sessions.sessionFor(instanceId)), ]); if (!instance) return {}; if (session && session.origin !== instance.origin) return {}; if (!session && this.options.ensureSession) { try { await this.options.ensureSession(instanceId, instance.origin); } catch { // Passwordless and temporarily unavailable credentials still permit anonymous reads. } } const paths = ['/api/stats', '/api/sim', '/api/health'] as const; const readAll = () => { const activeSession = this.options.sessions.sessionFor(instanceId); return Promise.allSettled( paths.map((path) => this.options.request({ url: `${instance.origin}${path}`, method: 'GET', headers: { accept: 'application/json', ...(activeSession?.origin === instance.origin ? { cookie: activeSession.cookie } : {}), }, }), ), ); }; let results = await readAll(); const unauthorized = results.some( (result) => result.status === 'fulfilled' && [401, 403].includes(result.value.status), ); if (unauthorized && this.options.ensureSession) { try { await this.options.ensureSession(instanceId, instance.origin, true); results = await readAll(); } catch { // Return the safe partial result when re-authentication is unavailable. } } const [stats, sim, health] = results; return { ...(stats?.status === 'fulfilled' ? parseStats(stats.value) : {}), ...(sim?.status === 'fulfilled' ? parseSim(sim.value) : {}), ...(health?.status === 'fulfilled' ? parseHealth(health.value) : {}), }; } }