import type { CreatePublishJobResponse, DesktopAccountInfo, DesktopClientRotateResponse, DesktopPublishTaskListResponse, DesktopRuntimeSessionSyncRequest, ListDesktopPublishTasksParams, } from '@geo/shared-types' import type { IpcRendererEvent } from 'electron' import { contextBridge, ipcRenderer } from 'electron/renderer' import type { DesktopRuntimeSnapshot } from '../renderer/types' import type { DesktopObservedRequest } from '../shared/network-debug' interface DesktopDeviceInfo { device_name: string os: string cpu_arch: string } interface DesktopClientIDScope { tenant_id: number workspace_id: number user_id: number } interface WindowModeRequest { source?: 'auth-state' | 'login' | 'logout' animate?: boolean } interface DesktopAppSettings { openAtLogin: boolean keepRunningInBackground: boolean } const rendererProxyRequestChannel = 'desktop:renderer-devtools-proxy:request' const rendererProxyResponseChannel = 'desktop:renderer-devtools-proxy:response' if (import.meta.env.DEV) { ipcRenderer.on( rendererProxyRequestChannel, async ( _event, payload: { requestId?: unknown request?: { url?: unknown method?: unknown headers?: unknown body?: unknown } }, ) => { const requestId = typeof payload?.requestId === 'string' ? payload.requestId : '' const request = payload?.request if (!requestId || !request || typeof request !== 'object') { return } const url = typeof request.url === 'string' ? request.url : '' const method = typeof request.method === 'string' ? request.method : 'GET' const headers = isStringRecord(request.headers) ? request.headers : {} const body = typeof request.body === 'string' ? request.body : undefined const response = await (async () => { try { const fetchResponse = await fetch(url, { method, headers, body, }) const bodyText = await fetchResponse.text() return { ok: fetchResponse.ok, status: fetchResponse.status, statusText: fetchResponse.statusText, headers: Object.fromEntries(fetchResponse.headers.entries()), bodyText, } } catch (error) { return { ok: false, status: 0, statusText: '', headers: {}, bodyText: '', error: String(error && ((error as Error).message || error)), } } })() ipcRenderer.send(rendererProxyResponseChannel, { requestId, response, }) }, ) } const desktopBridge = { app: { ping: () => ipcRenderer.invoke('desktop:ping') as Promise, deviceInfo: () => ipcRenderer.invoke('desktop:device-info') as Promise, clientId: (scope: DesktopClientIDScope) => ipcRenderer.invoke('desktop:client-id', scope) as Promise, runtimeSnapshot: () => ipcRenderer.invoke('desktop:runtime-snapshot') as Promise, runtimeAccountSnapshot: (accountId: string) => ipcRenderer.invoke('desktop:runtime-account-snapshot', accountId) as Promise< DesktopRuntimeSnapshot['accounts'][number] | null >, bindPublishAccount: (platformId: string) => ipcRenderer.invoke('desktop:bind-publish-account', platformId) as Promise, refreshRuntimeAccounts: () => ipcRenderer.invoke('desktop:refresh-runtime-accounts') as Promise, probeRuntimeAccount: (accountId: string) => ipcRenderer.invoke('desktop:probe-runtime-account', accountId) as Promise< DesktopRuntimeSnapshot['accounts'][number] | null >, openPublishAccountConsole: (account: { id: string platform: string platformUid: string displayName: string }) => ipcRenderer.invoke('desktop:open-publish-account-console', account) as Promise, unbindPublishAccount: (accountId: string, syncVersion: number) => ipcRenderer.invoke('desktop:unbind-publish-account', accountId, syncVersion) as Promise, listPublishTasks: (params?: ListDesktopPublishTasksParams) => ipcRenderer.invoke( 'desktop:list-publish-tasks', params, ) as Promise, retryPublishTask: (taskId: string) => ipcRenderer.invoke('desktop:retry-publish-task', taskId) as Promise, openExternalUrl: (url: string) => ipcRenderer.invoke('desktop:open-external-url', url) as Promise, openSettingsWindow: () => ipcRenderer.invoke('desktop:open-settings-window') as Promise, getAppSettings: () => ipcRenderer.invoke('desktop:get-app-settings') as Promise, setAppSetting: (key: keyof DesktopAppSettings, value: boolean) => ipcRenderer.invoke('desktop:set-app-setting', key, value) as Promise, syncRuntimeSession: (session: DesktopRuntimeSessionSyncRequest | null) => ipcRenderer.invoke('desktop:runtime-session-sync', session) as Promise, rotateRuntimeClientToken: () => ipcRenderer.invoke( 'desktop:runtime-session-rotate-client-token', ) as Promise, releaseRuntimeSession: (revoke?: boolean) => ipcRenderer.invoke('desktop:runtime-session-release', revoke) as Promise, setWindowMode: (mode: 'login' | 'main', request?: WindowModeRequest) => ipcRenderer.invoke('desktop:set-window-mode', mode, request) as Promise, onRuntimeInvalidated: ( listener: (event: { reason: 'account-health' | 'publish-task-lease' at: number accountId?: string taskId?: string }) => void, ) => { const wrapped = ( _event: IpcRendererEvent, payload: { reason: 'account-health' | 'publish-task-lease' at: number accountId?: string taskId?: string }, ) => { listener(payload) } ipcRenderer.on('desktop:runtime-invalidated', wrapped) return () => { ipcRenderer.off('desktop:runtime-invalidated', wrapped) } }, onNetworkObserved: (listener: (event: DesktopObservedRequest) => void) => { const wrapped = (_event: IpcRendererEvent, payload: DesktopObservedRequest) => { listener(payload) } ipcRenderer.on('desktop:network-observed', wrapped) return () => { ipcRenderer.off('desktop:network-observed', wrapped) } }, }, } contextBridge.exposeInMainWorld('desktopBridge', desktopBridge) function isStringRecord(value: unknown): value is Record { if (!value || typeof value !== 'object') { return false } return Object.values(value).every((item) => typeof item === 'string') }