import type { CanvasClearResult, CanvasDeleteResult, CanvasGetResult, CanvasListResult, CanvasPutResult, CompanionRuntimeClient, DeleteCanvasArtifactInput, GetCanvasArtifactInput, NodeCapabilitiesResult, NodeLocationGetResult, NodeLocationSetResult, NodeRegisterResult, NodeStatusSetResult, PutCanvasArtifactInput, NodePushTokenSetResult, SetNodeLocationInput, SystemCapabilitiesResult, SystemNodesResult, } from './runtimeClient.js'; import { CompanionHeartbeatLoop, } from './heartbeatLoop.js'; import type { CompanionHeartbeatLoopOptions, } from './heartbeatLoop.js'; export interface PlatformClientOptions { runtime: CompanionRuntimeClient; nodeId: string; role?: string; capabilities?: string[]; protocolVersion?: number; defaultSessionId?: string; } export interface RegisterPushTokenInput { token: string; topic?: string; environment?: 'sandbox' | 'production'; } export type SharedStatusInput = Omit< Parameters[0], 'platform' >; export interface HeartbeatStatusInput { appVersion?: SharedStatusInput['appVersion']; deviceName?: SharedStatusInput['deviceName']; statusText?: SharedStatusInput['statusText']; batteryPct?: SharedStatusInput['batteryPct']; powerSource?: SharedStatusInput['powerSource']; } export interface PlatformBootstrapResult { register: NodeRegisterResult; capabilities: NodeCapabilitiesResult; } export interface PlatformPutCanvasArtifactInput extends Omit { sessionId?: string; } export interface PlatformGetCanvasArtifactInput extends Omit { sessionId?: string; } export interface PlatformDeleteCanvasArtifactInput extends Omit { sessionId?: string; } export class MacOSCompanionClient { private readonly runtime: CompanionRuntimeClient; private readonly nodeId: string; private readonly role: string; private readonly capabilities: string[]; private readonly protocolVersion?: number; private readonly defaultSessionId?: string; constructor(options: PlatformClientOptions) { this.runtime = options.runtime; this.nodeId = options.nodeId; this.role = options.role ?? 'companion'; this.capabilities = options.capabilities ?? ['ui.canvas', 'node.location.write', 'node.push.register']; this.protocolVersion = options.protocolVersion; this.defaultSessionId = options.defaultSessionId; } connect(): Promise { return this.runtime.connect(); } disconnect(): void { this.runtime.disconnect(); } register(): Promise { return this.runtime.registerNode({ nodeId: this.nodeId, role: this.role, protocolVersion: this.protocolVersion, capabilities: this.capabilities, }); } async bootstrap(): Promise { const register = await this.register(); const capabilities = await this.getCapabilities(); return { register, capabilities }; } getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); } setStatus(status: SharedStatusInput): Promise { return this.runtime.setNodeStatus({ platform: 'macos', appVersion: status.appVersion, deviceName: status.deviceName, statusText: status.statusText, batteryPct: status.batteryPct, powerSource: status.powerSource, }); } publishHeartbeat(input: HeartbeatStatusInput = {}): Promise { return this.setStatus({ appVersion: input.appVersion, deviceName: input.deviceName, statusText: input.statusText ?? 'heartbeat', batteryPct: input.batteryPct, powerSource: input.powerSource ?? 'unknown', }); } createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop { return new CompanionHeartbeatLoop(this, options); } setLocation(location: SetNodeLocationInput): Promise { return this.runtime.setNodeLocation(location); } getLocation(): Promise { return this.runtime.getNodeLocation(); } registerPushToken(input: RegisterPushTokenInput): Promise { return this.runtime.setNodePushToken({ provider: 'apns', token: input.token, topic: input.topic, environment: input.environment, }); } getSystemCapabilities(): Promise { return this.runtime.getSystemCapabilities(); } listNodes(): Promise { return this.runtime.listSystemNodes({ platform: 'macos', role: this.role }); } putCanvasArtifact(input: PlatformPutCanvasArtifactInput): Promise { return this.runtime.putCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, type: input.type, title: input.title, content: input.content, metadata: input.metadata, }); } getCanvasArtifact(input: PlatformGetCanvasArtifactInput): Promise { return this.runtime.getCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } listCanvasArtifacts(sessionId?: string): Promise { return this.runtime.listCanvasArtifacts(this.resolveSessionId(sessionId)); } deleteCanvasArtifact(input: PlatformDeleteCanvasArtifactInput): Promise { return this.runtime.deleteCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } clearCanvasArtifacts(sessionId?: string): Promise { return this.runtime.clearCanvasArtifacts(this.resolveSessionId(sessionId)); } private resolveSessionId(sessionId?: string): string { const resolved = sessionId ?? this.defaultSessionId; if (!resolved) { throw new Error('sessionId is required (provide one or configure defaultSessionId)'); } return resolved; } } export class IOSCompanionClient { private readonly runtime: CompanionRuntimeClient; private readonly nodeId: string; private readonly role: string; private readonly capabilities: string[]; private readonly protocolVersion?: number; private readonly defaultSessionId?: string; constructor(options: PlatformClientOptions) { this.runtime = options.runtime; this.nodeId = options.nodeId; this.role = options.role ?? 'companion'; this.capabilities = options.capabilities ?? ['node.location.write', 'node.push.register']; this.protocolVersion = options.protocolVersion; this.defaultSessionId = options.defaultSessionId; } connect(): Promise { return this.runtime.connect(); } disconnect(): void { this.runtime.disconnect(); } register(): Promise { return this.runtime.registerNode({ nodeId: this.nodeId, role: this.role, protocolVersion: this.protocolVersion, capabilities: this.capabilities, }); } async bootstrap(): Promise { const register = await this.register(); const capabilities = await this.getCapabilities(); return { register, capabilities }; } getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); } setStatus(status: SharedStatusInput): Promise { return this.runtime.setNodeStatus({ platform: 'ios', appVersion: status.appVersion, deviceName: status.deviceName, statusText: status.statusText, batteryPct: status.batteryPct, powerSource: status.powerSource, }); } publishHeartbeat(input: HeartbeatStatusInput = {}): Promise { return this.setStatus({ appVersion: input.appVersion, deviceName: input.deviceName, statusText: input.statusText ?? 'heartbeat', batteryPct: input.batteryPct, powerSource: input.powerSource ?? 'unknown', }); } createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop { return new CompanionHeartbeatLoop(this, options); } setLocation(location: SetNodeLocationInput): Promise { return this.runtime.setNodeLocation(location); } getLocation(): Promise { return this.runtime.getNodeLocation(); } registerPushToken(input: RegisterPushTokenInput): Promise { return this.runtime.setNodePushToken({ provider: 'apns', token: input.token, topic: input.topic, environment: input.environment, }); } getSystemCapabilities(): Promise { return this.runtime.getSystemCapabilities(); } listNodes(): Promise { return this.runtime.listSystemNodes({ platform: 'ios', role: this.role }); } putCanvasArtifact(input: PlatformPutCanvasArtifactInput): Promise { return this.runtime.putCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, type: input.type, title: input.title, content: input.content, metadata: input.metadata, }); } getCanvasArtifact(input: PlatformGetCanvasArtifactInput): Promise { return this.runtime.getCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } listCanvasArtifacts(sessionId?: string): Promise { return this.runtime.listCanvasArtifacts(this.resolveSessionId(sessionId)); } deleteCanvasArtifact(input: PlatformDeleteCanvasArtifactInput): Promise { return this.runtime.deleteCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } clearCanvasArtifacts(sessionId?: string): Promise { return this.runtime.clearCanvasArtifacts(this.resolveSessionId(sessionId)); } private resolveSessionId(sessionId?: string): string { const resolved = sessionId ?? this.defaultSessionId; if (!resolved) { throw new Error('sessionId is required (provide one or configure defaultSessionId)'); } return resolved; } } export class AndroidCompanionClient { private readonly runtime: CompanionRuntimeClient; private readonly nodeId: string; private readonly role: string; private readonly capabilities: string[]; private readonly protocolVersion?: number; private readonly defaultSessionId?: string; constructor(options: PlatformClientOptions) { this.runtime = options.runtime; this.nodeId = options.nodeId; this.role = options.role ?? 'companion'; this.capabilities = options.capabilities ?? ['node.location.write', 'node.push.register']; this.protocolVersion = options.protocolVersion; this.defaultSessionId = options.defaultSessionId; } connect(): Promise { return this.runtime.connect(); } disconnect(): void { this.runtime.disconnect(); } register(): Promise { return this.runtime.registerNode({ nodeId: this.nodeId, role: this.role, protocolVersion: this.protocolVersion, capabilities: this.capabilities, }); } async bootstrap(): Promise { const register = await this.register(); const capabilities = await this.getCapabilities(); return { register, capabilities }; } getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); } setStatus(status: SharedStatusInput): Promise { return this.runtime.setNodeStatus({ platform: 'android', appVersion: status.appVersion, deviceName: status.deviceName, statusText: status.statusText, batteryPct: status.batteryPct, powerSource: status.powerSource, }); } publishHeartbeat(input: HeartbeatStatusInput = {}): Promise { return this.setStatus({ appVersion: input.appVersion, deviceName: input.deviceName, statusText: input.statusText ?? 'heartbeat', batteryPct: input.batteryPct, powerSource: input.powerSource ?? 'unknown', }); } createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop { return new CompanionHeartbeatLoop(this, options); } setLocation(location: SetNodeLocationInput): Promise { return this.runtime.setNodeLocation(location); } getLocation(): Promise { return this.runtime.getNodeLocation(); } registerPushToken(token: string): Promise { return this.runtime.setNodePushToken({ provider: 'fcm', token, }); } getSystemCapabilities(): Promise { return this.runtime.getSystemCapabilities(); } listNodes(): Promise { return this.runtime.listSystemNodes({ platform: 'android', role: this.role }); } putCanvasArtifact(input: PlatformPutCanvasArtifactInput): Promise { return this.runtime.putCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, type: input.type, title: input.title, content: input.content, metadata: input.metadata, }); } getCanvasArtifact(input: PlatformGetCanvasArtifactInput): Promise { return this.runtime.getCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } listCanvasArtifacts(sessionId?: string): Promise { return this.runtime.listCanvasArtifacts(this.resolveSessionId(sessionId)); } deleteCanvasArtifact(input: PlatformDeleteCanvasArtifactInput): Promise { return this.runtime.deleteCanvasArtifact({ sessionId: this.resolveSessionId(input.sessionId), artifactId: input.artifactId, }); } clearCanvasArtifacts(sessionId?: string): Promise { return this.runtime.clearCanvasArtifacts(this.resolveSessionId(sessionId)); } private resolveSessionId(sessionId?: string): string { const resolved = sessionId ?? this.defaultSessionId; if (!resolved) { throw new Error('sessionId is required (provide one or configure defaultSessionId)'); } return resolved; } }