import { hasDesktopInvoke, invokeDesktop } from '@/lib/desktop'; type DesktopInvoke = (cmd: string, args?: Record) => Promise; type DesktopBridgeGlobal = { listen?: ( event: string, handler: (evt: { payload?: unknown }) => void, ) => Promise<() => void>; }; type DesktopSshRemoteMode = 'managed' | 'external'; type DesktopSshInstallMethod = 'auto' | 'npm' | 'bun'; type DesktopSshSecretStore = 'never' | 'settings'; type DesktopSshStoredSecret = { enabled: boolean; value?: string; store: DesktopSshSecretStore; }; export type DesktopSshPortForwardType = 'local' | 'remote' | 'dynamic'; export type DesktopSshPortForward = { id: string; enabled: boolean; type: DesktopSshPortForwardType; localHost?: string; localPort?: number; remoteHost?: string; remotePort?: number; }; export type DesktopSshInstance = { id: string; nickname?: string; sshCommand: string; sshParsed?: { destination: string; args: string[]; }; connectionTimeoutSec: number; remoteOpenchamber: { mode: DesktopSshRemoteMode; keepRunning: boolean; preferredPort?: number; /** Interface the managed remote server listens on. '0.0.0.0' also exposes it to the remote machine's network. */ bindHost: '127.0.0.1' | '0.0.0.0'; installMethod: DesktopSshInstallMethod; uploadBundleOverSsh: boolean; }; localForward: { preferredLocalPort?: number; bindHost: '127.0.0.1' | 'localhost' | '0.0.0.0'; }; auth: { sshPassword?: DesktopSshStoredSecret; openchamberPassword?: DesktopSshStoredSecret; }; portForwards: DesktopSshPortForward[]; }; export type DesktopSshInstancesConfig = { instances: DesktopSshInstance[]; }; type DesktopSshPhase = | 'idle' | 'config_resolved' | 'auth_check' | 'master_connecting' | 'remote_probe' | 'installing' | 'updating' | 'server_detecting' | 'server_starting' | 'forwarding' | 'ready' | 'degraded' | 'error'; export type DesktopSshInstanceStatus = { id: string; phase: DesktopSshPhase; detail?: string; localUrl?: string; localPort?: number; remotePort?: number; startedByUs: boolean; retryAttempt: number; requiresUserAction: boolean; updatedAtMs: number; }; export type DesktopSshImportCandidate = { host: string; pattern: boolean; source: string; sshCommand: string; }; const isRecord = (value: unknown): value is Record => { return typeof value === 'object' && value !== null; }; const readString = (obj: Record, key: string): string | null => { const value = obj[key]; return typeof value === 'string' ? value : null; }; const readNumber = (obj: Record, key: string): number | null => { const value = obj[key]; return typeof value === 'number' && Number.isFinite(value) ? value : null; }; const readBoolean = (obj: Record, key: string): boolean | null => { const value = obj[key]; return typeof value === 'boolean' ? value : null; }; const asStringArray = (value: unknown): string[] => { if (!Array.isArray(value)) return []; return value.filter((item): item is string => typeof item === 'string'); }; const getInvoke = (): DesktopInvoke | null => { if (!hasDesktopInvoke()) return null; return (command, args) => invokeDesktop(command, args) as Promise; }; const parseStoredSecret = (value: unknown): DesktopSshStoredSecret | undefined => { if (!isRecord(value)) return undefined; const enabled = readBoolean(value, 'enabled') ?? false; const rawStore = readString(value, 'store')?.toLowerCase(); const store: DesktopSshSecretStore = rawStore === 'settings' ? 'settings' : 'never'; const rawValue = readString(value, 'value'); return { enabled, store, ...(rawValue ? { value: rawValue } : {}), }; }; const parseForwardType = (value: unknown): DesktopSshPortForwardType => { return value === 'remote' || value === 'dynamic' ? value : 'local'; }; const parseForward = (value: unknown): DesktopSshPortForward | null => { if (!isRecord(value)) return null; const id = readString(value, 'id'); if (!id) return null; const enabled = readBoolean(value, 'enabled') ?? true; const type = parseForwardType(readString(value, 'type')); const localHost = readString(value, 'localHost') || readString(value, 'local_host') || undefined; const localPort = readNumber(value, 'localPort') ?? readNumber(value, 'local_port') ?? undefined; const remoteHost = readString(value, 'remoteHost') || readString(value, 'remote_host') || undefined; const remotePort = readNumber(value, 'remotePort') ?? readNumber(value, 'remote_port') ?? undefined; return { id, enabled, type, ...(localHost ? { localHost } : {}), ...(typeof localPort === 'number' ? { localPort } : {}), ...(remoteHost ? { remoteHost } : {}), ...(typeof remotePort === 'number' ? { remotePort } : {}), }; }; const parseInstance = (value: unknown): DesktopSshInstance | null => { if (!isRecord(value)) return null; const id = readString(value, 'id'); const sshCommand = readString(value, 'sshCommand') || readString(value, 'ssh_command'); if (!id || !sshCommand) return null; const nickname = readString(value, 'nickname'); const parsedRaw = value.sshParsed; const parsed = isRecord(parsedRaw) ? { destination: readString(parsedRaw, 'destination') || '', args: asStringArray(parsedRaw.args), } : undefined; const remoteRaw = isRecord(value.remoteOpenchamber) ? value.remoteOpenchamber : isRecord(value.remote_openchamber) ? value.remote_openchamber : {}; const localRaw = isRecord(value.localForward) ? value.localForward : isRecord(value.local_forward) ? value.local_forward : {}; const authRaw = isRecord(value.auth) ? value.auth : {}; const rawMode = readString(remoteRaw, 'mode')?.toLowerCase(); const mode: DesktopSshRemoteMode = rawMode === 'external' ? 'external' : 'managed'; const rawInstallMethod = readString(remoteRaw, 'installMethod') || readString(remoteRaw, 'install_method'); // Legacy 'download_release'/'upload_bundle' never had their own remote path: // they fell through to the same bun-then-npm attempt as 'auto'. Read them as // 'auto' so the stored value matches what actually happens. const installMethod: DesktopSshInstallMethod = rawInstallMethod === 'npm' || rawInstallMethod === 'bun' ? rawInstallMethod : 'auto'; const bindHostRaw = readString(localRaw, 'bindHost') || readString(localRaw, 'bind_host') || '127.0.0.1'; const bindHost: '127.0.0.1' | 'localhost' | '0.0.0.0' = bindHostRaw === 'localhost' || bindHostRaw === '0.0.0.0' ? bindHostRaw : '127.0.0.1'; const forwardsRaw = Array.isArray(value.portForwards) ? value.portForwards : Array.isArray(value.port_forwards) ? value.port_forwards : []; const portForwards = forwardsRaw .map((item) => parseForward(item)) .filter((item): item is DesktopSshPortForward => Boolean(item)); const preferredPort = readNumber(remoteRaw, 'preferredPort') ?? readNumber(remoteRaw, 'preferred_port'); const rawRemoteBindHost = readString(remoteRaw, 'bindHost') || readString(remoteRaw, 'bind_host'); const remoteBindHost: '127.0.0.1' | '0.0.0.0' = rawRemoteBindHost === '0.0.0.0' ? '0.0.0.0' : '127.0.0.1'; const preferredLocalPort = readNumber(localRaw, 'preferredLocalPort') ?? readNumber(localRaw, 'preferred_local_port'); const sshPassword = parseStoredSecret(authRaw.sshPassword || authRaw.ssh_password); const openchamberPassword = parseStoredSecret(authRaw.openchamberPassword || authRaw.openchamber_password); return { id, ...(nickname ? { nickname } : {}), sshCommand, ...(parsed && parsed.destination ? { sshParsed: parsed } : {}), connectionTimeoutSec: readNumber(value, 'connectionTimeoutSec') ?? readNumber(value, 'connection_timeout_sec') ?? 60, remoteOpenchamber: { mode, keepRunning: readBoolean(remoteRaw, 'keepRunning') ?? readBoolean(remoteRaw, 'keep_running') ?? true, bindHost: remoteBindHost, ...(preferredPort ? { preferredPort } : {}), installMethod, uploadBundleOverSsh: readBoolean(remoteRaw, 'uploadBundleOverSsh') ?? readBoolean(remoteRaw, 'upload_bundle_over_ssh') ?? false, }, localForward: { ...(preferredLocalPort ? { preferredLocalPort } : {}), bindHost, }, auth: { ...(sshPassword ? { sshPassword } : {}), ...(openchamberPassword ? { openchamberPassword } : {}), }, portForwards, }; }; const parsePhase = (value: unknown): DesktopSshPhase => { switch (value) { case 'config_resolved': case 'auth_check': case 'master_connecting': case 'remote_probe': case 'installing': case 'updating': case 'server_detecting': case 'server_starting': case 'forwarding': case 'ready': case 'degraded': case 'error': return value; default: return 'idle'; } }; const parseStatus = (value: unknown): DesktopSshInstanceStatus | null => { if (!isRecord(value)) return null; const id = readString(value, 'id'); if (!id) return null; return { id, phase: parsePhase(readString(value, 'phase')), ...(readString(value, 'detail') ? { detail: readString(value, 'detail') || undefined } : {}), ...(readString(value, 'localUrl') || readString(value, 'local_url') ? { localUrl: readString(value, 'localUrl') || readString(value, 'local_url') || undefined } : {}), ...(typeof (readNumber(value, 'localPort') ?? readNumber(value, 'local_port')) === 'number' ? { localPort: readNumber(value, 'localPort') ?? readNumber(value, 'local_port') ?? undefined } : {}), ...(typeof (readNumber(value, 'remotePort') ?? readNumber(value, 'remote_port')) === 'number' ? { remotePort: readNumber(value, 'remotePort') ?? readNumber(value, 'remote_port') ?? undefined, } : {}), startedByUs: readBoolean(value, 'startedByUs') ?? readBoolean(value, 'started_by_us') ?? false, retryAttempt: readNumber(value, 'retryAttempt') ?? readNumber(value, 'retry_attempt') ?? 0, requiresUserAction: readBoolean(value, 'requiresUserAction') ?? readBoolean(value, 'requires_user_action') ?? false, updatedAtMs: readNumber(value, 'updatedAtMs') ?? readNumber(value, 'updated_at_ms') ?? Date.now(), }; }; const parseImportCandidate = (value: unknown): DesktopSshImportCandidate | null => { if (!isRecord(value)) return null; const host = readString(value, 'host'); const source = readString(value, 'source'); const sshCommand = readString(value, 'sshCommand') || readString(value, 'ssh_command'); if (!host || !source || !sshCommand) return null; return { host, source, sshCommand, pattern: readBoolean(value, 'pattern') ?? false, }; }; export const createDesktopSshInstance = (id: string, sshCommand: string): DesktopSshInstance => { return { id, sshCommand, connectionTimeoutSec: 60, remoteOpenchamber: { mode: 'managed', keepRunning: true, bindHost: '127.0.0.1', installMethod: 'auto', uploadBundleOverSsh: false, }, localForward: { bindHost: '127.0.0.1', }, auth: {}, portForwards: [], }; }; export const desktopSshInstancesGet = async (): Promise => { const invoke = getInvoke(); if (!invoke) { return { instances: [] }; } const raw = await invoke('desktop_ssh_instances_get'); if (!isRecord(raw)) { return { instances: [] }; } const listRaw = Array.isArray(raw.instances) ? raw.instances : Array.isArray(raw.desktopSshInstances) ? raw.desktopSshInstances : []; const instances = listRaw .map((item) => parseInstance(item)) .filter((item): item is DesktopSshInstance => Boolean(item)); return { instances }; }; export const desktopSshInstancesSet = async (config: DesktopSshInstancesConfig): Promise => { const invoke = getInvoke(); if (!invoke) return; await invoke('desktop_ssh_instances_set', { config: { instances: config.instances, }, }); }; export const desktopSshImportHosts = async (): Promise => { const invoke = getInvoke(); if (!invoke) return []; const raw = await invoke('desktop_ssh_import_hosts'); if (!Array.isArray(raw)) return []; return raw .map((item) => parseImportCandidate(item)) .filter((item): item is DesktopSshImportCandidate => Boolean(item)); }; export const desktopSshConnect = async (id: string): Promise => { const invoke = getInvoke(); if (!invoke) return; await invoke('desktop_ssh_connect', { id }); }; export const desktopSshDisconnect = async (id: string): Promise => { const invoke = getInvoke(); if (!invoke) return; await invoke('desktop_ssh_disconnect', { id }); }; export const desktopSshStatus = async (id?: string): Promise => { const invoke = getInvoke(); if (!invoke) return []; const raw = await invoke('desktop_ssh_status', { ...(id ? { id } : {}), }); if (!Array.isArray(raw)) return []; return raw .map((item) => parseStatus(item)) .filter((item): item is DesktopSshInstanceStatus => Boolean(item)); }; export const desktopSshLogs = async (id: string, limit?: number): Promise => { const invoke = getInvoke(); if (!invoke) return []; const raw = await invoke('desktop_ssh_logs', { id, ...(typeof limit === 'number' ? { limit } : {}), }); if (!Array.isArray(raw)) return []; return raw.filter((line): line is string => typeof line === 'string'); }; export const desktopSshLogsClear = async (id: string): Promise => { const invoke = getInvoke(); if (!invoke) return; await invoke('desktop_ssh_logs_clear', { id }); }; export const listenDesktopSshStatus = async ( listener: (status: DesktopSshInstanceStatus) => void, ): Promise<() => Promise> => { if (!hasDesktopInvoke()) { return async () => {}; } const desktop = (window as unknown as { __OPENCHAMBER_DESKTOP__?: DesktopBridgeGlobal }).__OPENCHAMBER_DESKTOP__; const listen = desktop?.listen; if (typeof listen !== 'function') { return async () => {}; } const unlisten = await listen('openchamber:ssh-instance-status', (event) => { const status = parseStatus(event?.payload); if (!status) return; listener(status); }); return async () => { await unlisten(); }; };