import { hostname } from "node:os"; import { join } from "node:path"; import { BrowserWindow, WebContentsView, app, ipcMain, nativeTheme } from "electron/main"; import type { BrowserWindow as ElectronBrowserWindow, WebContents as ElectronWebContents, WebContentsView as ElectronWebContentsView, } from "electron/main"; import type { DesktopAccountInfo, DesktopRuntimeSessionSyncRequest } from "@geo/shared-types"; import { initAccountHealth } from "./account-health"; import { bindPublishAccount, openPublishAccountConsole } from "./account-binder"; import { initProcessMetricsSampler } from "./process-metrics"; import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from "./playwright-cdp"; import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy"; import { onRuntimeInvalidated } from "./runtime-events"; import { refreshRuntimeAccounts, releaseRuntimeSession, syncRuntimeSession, unbindRuntimeAccount, } from "./runtime-controller"; import { createRuntimeSnapshot } from "./runtime-snapshot"; import { initScheduler } from "./scheduler"; import { initSessionRegistry } from "./session-registry"; import { initSingleInstance } from "./single-instance"; import { initTransport, listDesktopPublishTasks, retryDesktopPublishTask } from "./transport/api-client"; import { initTray } from "./tray"; import { STANDARD_USER_AGENT } from "./user-agent"; import { startHotViewReaper } from "./view-pool"; app.commandLine.appendSwitch( "disable-features", "PostQuantumKyber,EncryptedClientHello,UseDnsHttpsSvcb,UseDnsHttpsSvcbAlpn", ); app.commandLine.appendSwitch("disable-quic"); app.commandLine.appendSwitch("disable-background-networking"); app.commandLine.appendSwitch("remote-debugging-port", String(getPlaywrightCDPPort())); app.userAgentFallback = STANDARD_USER_AGENT; console.info("[desktop-main] boot", { electron: process.versions.electron, chrome: process.versions.chrome, node: process.versions.node, ua: STANDARD_USER_AGENT, }); process.on("unhandledRejection", (reason) => { console.error("[desktop-main] unhandled rejection", reason); }); process.on("uncaughtException", (error) => { console.error("[desktop-main] uncaught exception", error); }); let mainWindow: ElectronBrowserWindow | null = null; let mainRendererContents: ElectronWebContents | null = null; let quitReleaseInFlight = false; function rendererURL(): string | null { return process.env.ELECTRON_RENDERER_URL ?? null; } function preloadPath(): string { return join(__dirname, "../preload/bridge.cjs"); } function syncViewBounds(window: ElectronBrowserWindow, view: ElectronWebContentsView): void { const bounds = window.getContentBounds(); view.setBounds({ x: 0, y: 0, width: bounds.width, height: bounds.height }); } async function mountRendererView(window: ElectronBrowserWindow): Promise { const view = new WebContentsView({ webPreferences: { preload: preloadPath(), sandbox: true, contextIsolation: true, nodeIntegration: false, }, }); view.webContents.setWindowOpenHandler(() => ({ action: "deny" })); registerRendererDevtoolsProxyTarget(view.webContents); mainRendererContents = view.webContents; window.contentView.addChildView(view); syncViewBounds(window, view); window.on("resize", () => syncViewBounds(window, view)); window.on("closed", () => { if (mainRendererContents === view.webContents) { mainRendererContents = null; } }); const devServerURL = rendererURL(); if (devServerURL) { await view.webContents.loadURL(devServerURL); view.webContents.openDevTools({ mode: "detach" }); return; } await view.webContents.loadFile(join(__dirname, "../renderer/index.html")); } async function createMainWindow(): Promise { const window = new BrowserWindow({ width: 1320, height: 860, title: "GEO Rankly Desktop", backgroundColor: nativeTheme.shouldUseDarkColors ? "#15191a" : "#f4f1ea", }); await mountRendererView(window); return window; } function flattenError(error: unknown): Error { if (error instanceof Error) { const copy = new Error(error.message || "ipc_handler_failed"); copy.name = error.name; return copy; } return new Error(typeof error === "string" ? error : "ipc_handler_failed"); } function safeHandle( channel: string, handler: (...args: Args) => Promise | Result, ): void { ipcMain.handle(channel, async (...args) => { try { return await handler(...(args as unknown as Args)); } catch (error) { throw flattenError(error); } }); } function registerBridgeHandlers(): void { ipcMain.handle("desktop:ping", () => "pong"); ipcMain.handle("desktop:device-info", () => ({ device_name: hostname() || `Desktop ${process.platform}`, os: process.platform, cpu_arch: process.arch, })); ipcMain.handle("desktop:runtime-snapshot", () => createRuntimeSnapshot()); safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => { const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo; await refreshRuntimeAccounts(); return account; }); safeHandle("desktop:refresh-runtime-accounts", async () => { await refreshRuntimeAccounts(); return null; }); safeHandle( "desktop:open-publish-account-console", async ( _event, account: { id: string; platform: string; platformUid: string; displayName: string }, ) => { await openPublishAccountConsole(account); return null; }, ); safeHandle( "desktop:unbind-publish-account", async (_event, accountId: string, syncVersion: number) => { await unbindRuntimeAccount(accountId, syncVersion); return null; }, ); safeHandle("desktop:list-publish-tasks", async (_event, params?: { page?: number; page_size?: number; title?: string }) => { return listDesktopPublishTasks(params); }); safeHandle("desktop:retry-publish-task", async (_event, taskId: string) => { return retryDesktopPublishTask(taskId); }); ipcMain.handle( "desktop:runtime-session-sync", (_event, session: DesktopRuntimeSessionSyncRequest | null) => { syncRuntimeSession(session); return null; }, ); safeHandle("desktop:runtime-session-release", async (_event, revoke?: boolean) => { await releaseRuntimeSession({ revoke: Boolean(revoke) }); return null; }); } if (!initSingleInstance(() => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } })) { app.quit(); } app.whenReady().then(async () => { registerBridgeHandlers(); await initSessionRegistry(); initAccountHealth(); initTransport(); initScheduler(); initProcessMetricsSampler(); startHotViewReaper(); startHiddenPlaywrightReaper(); onRuntimeInvalidated((event) => { if (!mainRendererContents || mainRendererContents.isDestroyed()) { return; } mainRendererContents.send("desktop:runtime-invalidated", event); }); mainWindow = await createMainWindow(); initTray(() => { if (!mainWindow) { return; } mainWindow.show(); mainWindow.focus(); }); }).catch((error) => { console.error("[desktop-main] app.whenReady failed", error); }); app.on("activate", async () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); return; } mainWindow = await createMainWindow(); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); app.on("before-quit", (event) => { if (quitReleaseInFlight) { return; } quitReleaseInFlight = true; event.preventDefault(); void Promise.race([ releaseRuntimeSession(), new Promise((resolve) => setTimeout(resolve, 1500)), ]).finally(() => { app.quit(); }); });