import { contextBridge, ipcRenderer } from "electron/renderer"; import type { IpcRendererEvent } from "electron"; import type { CreatePublishJobResponse, DesktopAccountInfo, DesktopPublishTaskListResponse, DesktopRuntimeSessionSyncRequest, ListDesktopPublishTasksParams, } from "@geo/shared-types"; import type { DesktopRuntimeSnapshot } from "../renderer/types"; import type { DesktopObservedRequest } from "../shared/network-debug"; interface DesktopDeviceInfo { device_name: string; os: string; cpu_arch: string; } const rendererProxyRequestChannel = "desktop:renderer-devtools-proxy:request"; const rendererProxyResponseChannel = "desktop:renderer-devtools-proxy:response"; 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, runtimeSnapshot: () => ipcRenderer.invoke("desktop:runtime-snapshot") as Promise, bindPublishAccount: (platformId: string) => ipcRenderer.invoke("desktop:bind-publish-account", platformId) as Promise, refreshRuntimeAccounts: () => ipcRenderer.invoke("desktop:refresh-runtime-accounts") as Promise, 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, syncRuntimeSession: (session: DesktopRuntimeSessionSyncRequest | null) => ipcRenderer.invoke("desktop:runtime-session-sync", session) as Promise, releaseRuntimeSession: (revoke?: boolean) => ipcRenderer.invoke("desktop:runtime-session-release", revoke) as Promise, setWindowMode: (mode: "login" | "main") => ipcRenderer.invoke("desktop:set-window-mode", mode) as Promise, onRuntimeInvalidated: ( listener: (event: { reason: "account-health"; at: number }) => void, ) => { const wrapped = (_event: IpcRendererEvent, payload: { reason: "account-health"; at: number }) => { 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"); }