Files
geo/apps/desktop-client/src/main/bootstrap.ts
T
root 53bebb46be perf(bind): speed up account bind detection and runtime sync
Replace the 1.8s polling interval with a 650ms tick plus a debounced
navigation-driven detect, listen for page-title-updated to catch SPA
transitions, and flush the session asynchronously. The bind handler
now optimistically seeds the runtime account list via
noteRuntimeAccountBound and fires the full server refresh in the
background, so the UI reflects the new binding without waiting on a
round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:57:26 +08:00

410 lines
12 KiB
TypeScript

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<WindowMode, { width: number; height: number; resizable: boolean }> = {
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<ElectronBrowserWindow> {
const existing = currentMainWindow();
if (existing) {
return existing;
}
const window = await createMainWindow();
mainWindow = window;
return window;
}
async function revealMainWindow(): Promise<void> {
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<void> {
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<ElectronBrowserWindow> {
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<Args extends unknown[], Result>(
channel: string,
handler: (...args: Args) => Promise<Result> | 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<void>((resolve) => setTimeout(resolve, 1500)),
]).finally(() => {
app.quit();
});
});