import { hostname } from "node:os"; import { readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { shell } from "electron"; import { BrowserWindow, app, ipcMain, nativeTheme } from "electron/main"; import type { BrowserWindow as ElectronBrowserWindow, WebContents as ElectronWebContents, } from "electron/main"; import type { DesktopAccountInfo, DesktopRuntimeSessionSyncRequest } from "@geo/shared-types"; import { initAccountHealth } from "./account-health"; import { bindPublishAccount, openPublishAccountConsole } from "./account-binder"; import { installObservedGlobalFetch, registerObservedRequestRendererTarget } from "./network-observer"; import { initProcessMetricsSampler } from "./process-metrics"; import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from "./playwright-cdp"; import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy"; import { onRuntimeInvalidated } from "./runtime-events"; import { noteRuntimeAccountBound, 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; installObservedGlobalFetch(); 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; type WindowMode = "login" | "main"; const WINDOW_SIZES: Record = { login: { width: 340, height: 540, resizable: false }, main: { width: 1320, height: 860, resizable: true }, }; function windowModePath(): string { return join(app.getPath("userData"), "window-mode.json"); } function readPersistedWindowMode(): WindowMode { try { const raw = readFileSync(windowModePath(), "utf8"); const data = JSON.parse(raw) as { mode?: unknown }; if (data?.mode === "main" || data?.mode === "login") { return data.mode; } } catch { // First run, missing file, or corrupt — fall through to default. } return "login"; } function persistWindowMode(mode: WindowMode): void { try { writeFileSync(windowModePath(), JSON.stringify({ mode }), "utf8"); } catch (err) { console.warn("[desktop-main] persist window mode failed", err); } } let currentWindowMode: WindowMode = "login"; let windowModeApplied = false; function applyWindowMode(window: ElectronBrowserWindow, mode: WindowMode): void { const isFirstApply = !windowModeApplied; const sameMode = windowModeApplied && currentWindowMode === mode; if (!sameMode) { currentWindowMode = mode; persistWindowMode(mode); const target = WINDOW_SIZES[mode]; window.setResizable(true); window.setMinimumSize(1, 1); window.setMaximumSize(0, 0); window.setSize(target.width, target.height, false); window.center(); if (mode === "login") { window.setMinimumSize(target.width, target.height); window.setMaximumSize(target.width, target.height); window.setResizable(false); } else { window.setMinimumSize(800, 600); window.setResizable(true); } } windowModeApplied = true; if (isFirstApply || !window.isVisible()) { window.show(); window.focus(); } } function currentMainWindow(): ElectronBrowserWindow | null { if (!mainWindow) { return null; } if (mainWindow.isDestroyed()) { mainWindow = null; windowModeApplied = false; return null; } return mainWindow; } async function ensureMainWindow(): Promise { const existing = currentMainWindow(); if (existing) { return existing; } const window = await createMainWindow(); mainWindow = window; return window; } async function revealMainWindow(): Promise { const window = await ensureMainWindow(); applyWindowMode(window, currentWindowMode); } function revealMainWindowSafely(source: string): void { void revealMainWindow().catch((error) => { console.error("[desktop-main] reveal main window failed", { source, error }); }); } function rendererURL(): string | null { return process.env.ELECTRON_RENDERER_URL ?? null; } function preloadPath(): string { return join(__dirname, "../preload/bridge.cjs"); } async function mountRendererWebContents(window: ElectronBrowserWindow): Promise { const contents = window.webContents; contents.setWindowOpenHandler(() => ({ action: "deny" })); registerRendererDevtoolsProxyTarget(contents); registerObservedRequestRendererTarget(contents); mainRendererContents = contents; window.on("closed", () => { if (mainRendererContents === contents) { mainRendererContents = null; } }); const devServerURL = rendererURL(); if (devServerURL) { await contents.loadURL(devServerURL); contents.openDevTools({ mode: "detach" }); return; } await contents.loadFile(join(__dirname, "../renderer/index.html")); } async function createMainWindow(): Promise { const initial = WINDOW_SIZES[currentWindowMode]; const window = new BrowserWindow({ width: initial.width, height: initial.height, show: false, title: "GEO Rankly Desktop", titleBarStyle: "hiddenInset", trafficLightPosition: { x: 12, y: 14 }, backgroundColor: nativeTheme.shouldUseDarkColors ? "#15191a" : "#eaf2ff", webPreferences: { preload: preloadPath(), sandbox: true, contextIsolation: true, nodeIntegration: false, }, }); const fallbackReveal = setTimeout(() => { if (!windowModeApplied && !window.isDestroyed()) { applyWindowMode(window, currentWindowMode); } }, 1500); window.once("closed", () => { clearTimeout(fallbackReveal); if (mainWindow === window) { mainWindow = null; windowModeApplied = false; } }); window.on("minimize", () => { console.warn("[desktop-main] main window minimize event", { at: new Date().toISOString(), stack: new Error("trace").stack, }); }); window.on("hide", () => { console.warn("[desktop-main] main window hide event", { at: new Date().toISOString(), stack: new Error("trace").stack, }); }); window.on("blur", () => { console.warn("[desktop-main] main window blur event", { at: new Date().toISOString(), visible: window.isVisible(), minimized: window.isMinimized(), }); }); await mountRendererWebContents(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 isSafeExternalUrl(url: string): boolean { try { const parsed = new URL(url); return parsed.protocol === "http:" || parsed.protocol === "https:"; } catch { return false; } } 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; noteRuntimeAccountBound(account); void refreshRuntimeAccounts().catch((error) => { console.warn("[desktop-bind] background account refresh failed", { message: error instanceof Error ? error.message : String(error), }); }); 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); }); safeHandle("desktop:open-external-url", async (_event, url: string) => { if (!isSafeExternalUrl(url)) { throw new Error("unsupported_external_url"); } await shell.openExternal(url); return null; }); 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; }); ipcMain.handle("desktop:set-window-mode", (_event, mode: WindowMode) => { const window = currentMainWindow(); if (window && (mode === "login" || mode === "main")) { applyWindowMode(window, mode); } return null; }); } if (!initSingleInstance(() => { revealMainWindowSafely("single-instance"); })) { app.quit(); } app.whenReady().then(async () => { currentWindowMode = readPersistedWindowMode(); registerBridgeHandlers(); await initSessionRegistry(); initAccountHealth(); initTransport(); initScheduler(); initProcessMetricsSampler(); startHotViewReaper(); startHiddenPlaywrightReaper(); onRuntimeInvalidated((event) => { if (!mainRendererContents || mainRendererContents.isDestroyed()) { return; } mainRendererContents.send("desktop:runtime-invalidated", event); }); await ensureMainWindow(); initTray(() => { revealMainWindowSafely("tray"); }); }).catch((error) => { console.error("[desktop-main] app.whenReady failed", error); }); app.on("activate", () => { revealMainWindowSafely("activate"); }); 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(); }); });