import { readFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import type { DesktopAccountInfo, DesktopClientRotateResponse, DesktopRuntimeSessionSyncRequest, } from '@geo/shared-types' import { isAIPlatformId } from '@geo/shared-types' import { shell } from 'electron' import type { BrowserWindow as ElectronBrowserWindow, WebContents as ElectronWebContents, IpcMainInvokeEvent, } from 'electron/main' import { BrowserWindow, Menu, app, ipcMain, nativeTheme } from 'electron/main' import { bindPublishAccount, openPublishAccountConsole } from './account-binder' import { initAccountHealth } from './account-health' import { applyDesktopHealthIndicatorFromSnapshot, createDesktopHealthIcon, startDesktopHealthIndicator, syncDesktopHealthIndicator, } from './app-issue-indicator' import { getDesktopAppSettings, hasShownBackgroundBalloon, initDesktopAppSettings, markBackgroundBalloonShown, setDesktopAppSetting, type DesktopAppSettingKey, } from './app-settings' import { resolveDesktopClientID, resolveDesktopDeviceInfo } from './device-id' import { installObservedGlobalFetch, registerObservedRequestRendererTarget, } from './network-observer' import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from './playwright-cdp' import { initProcessMetricsSampler } from './process-metrics' import { registerRendererDevtoolsProxyTarget } from './renderer-devtools-proxy' import { noteRuntimeAccountBound, refreshRuntimeAccounts, getRuntimeControllerSnapshot, releaseRuntimeSession, requestRuntimeAccountProbe, syncRuntimeSession, unbindRuntimeAccount, } from './runtime-controller' import { onRuntimeInvalidated } from './runtime-events' import { createRuntimeAccountSnapshot, createRuntimeSnapshot } from './runtime-snapshot' import { initScheduler } from './scheduler' import { initSessionRegistry } from './session-registry' import { initSingleInstance } from './single-instance' import { initTransport, listDesktopPublishTasks, retryDesktopPublishTask, rotateDesktopClient, } from './transport/api-client' import { initTray, showTrayBalloon } 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') // CDP is required by the hidden Playwright execution pool in packaged builds. app.commandLine.appendSwitch('remote-debugging-port', String(getPlaywrightCDPPort())) app.userAgentFallback = STANDARD_USER_AGENT // Windows 10+ routes tray.displayBalloon through the Toast Notification API, // which requires a registered AppUserModelID. electron-builder writes this on // the Start Menu shortcut at install time, but we set it explicitly so dev // runs and edge-case installs also fire the balloon reliably. if (process.platform === 'win32') { app.setAppUserModelId('com.geo.rankly.desktop') } const isDevelopmentRuntime = !app.isPackaged const desktopDeepLinkScheme = 'shengxintui' function silencePackagedConsole(): void { if (isDevelopmentRuntime) { return } const noop = () => undefined console.log = noop console.info = noop console.warn = noop console.error = noop console.debug = noop } silencePackagedConsole() if (isDevelopmentRuntime) { 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 loginWindow: ElectronBrowserWindow | null = null let settingsWindow: ElectronBrowserWindow | null = null let mainRendererContents: ElectronWebContents | null = null let quitReleaseInFlight = false let mainWindowCreatePromise: Promise | null = null let loginWindowCreatePromise: Promise | null = null let settingsWindowCreatePromise: Promise | null = null let windowModeSwitchQueue: Promise = Promise.resolve() let clientTokenRotatePromise: Promise | null = null let canHandleDesktopDeepLinks = false const pendingDesktopDeepLinks: string[] = [] const forceCloseWindows = new WeakSet() const appWindowModes = new WeakMap() type WindowMode = 'login' | 'main' type RendererWindowMode = WindowMode | 'settings' type WindowModeSource = 'auth-state' | 'login' | 'logout' interface WindowSizePreset { width: number height: number minWidth: number minHeight: number resizable: boolean } interface WindowModeRequest { source?: WindowModeSource animate?: boolean } const WINDOW_READY_TIMEOUT_MS = 1200 const WINDOW_SIZES: Record = { login: { width: 340, height: 540, minWidth: 340, minHeight: 540, resizable: false }, main: { width: 1420, height: 960, minWidth: 900, minHeight: 600, resizable: true }, } const SETTINGS_WINDOW_SIZE: WindowSizePreset = { width: 920, height: 640, minWidth: 760, minHeight: 520, resizable: true, } const TITLE_BAR_OVERLAY_HEIGHT = 24 function appTitleBarStyle(): 'hidden' | 'hiddenInset' { return process.platform === 'darwin' ? 'hiddenInset' : 'hidden' } function appTitleBarOverlay( color: string, ): { color: string; symbolColor: string; height: number } | undefined { if (process.platform === 'darwin') { return undefined } return { color, symbolColor: nativeTheme.shouldUseDarkColors ? '#f8fafc' : '#111827', height: TITLE_BAR_OVERLAY_HEIGHT, } } function suppressNativeWindowMenu(window?: ElectronBrowserWindow): void { if (process.platform === 'darwin') { return } Menu.setApplicationMenu(null) window?.setMenu(null) window?.setMenuBarVisibility(false) } 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' function normalizeWindowModeRequest(input: unknown): WindowModeRequest { if (!input || typeof input !== 'object') { return {} } const candidate = input as { source?: unknown; animate?: unknown } return { source: candidate.source === 'login' || candidate.source === 'logout' || candidate.source === 'auth-state' ? candidate.source : undefined, animate: candidate.animate === true, } } function currentMainWindow(): ElectronBrowserWindow | null { if (!mainWindow) { return null } if (mainWindow.isDestroyed()) { mainWindow = null return null } return mainWindow } function currentLoginWindow(): ElectronBrowserWindow | null { if (!loginWindow) { return null } if (loginWindow.isDestroyed()) { loginWindow = null return null } return loginWindow } function currentSettingsWindow(): ElectronBrowserWindow | null { if (!settingsWindow) { return null } if (settingsWindow.isDestroyed()) { settingsWindow = null return null } return settingsWindow } function currentActiveWindow(): ElectronBrowserWindow | null { return currentMainWindow() ?? currentLoginWindow() } function rendererModeFromWindow(window: ElectronBrowserWindow): RendererWindowMode | null { if (window.isDestroyed()) { return null } const knownMode = appWindowModes.get(window) if (knownMode) { return knownMode } try { const url = new URL(window.webContents.getURL()) const mode = url.searchParams.get('window') return mode === 'login' || mode === 'main' || mode === 'settings' ? mode : null } catch { return null } } function retireWindowAfterIpcReturn(window: ElectronBrowserWindow): void { if (window.isDestroyed()) { return } forceCloseWindows.add(window) if (window.isVisible()) { window.hide() } setTimeout(() => { if (!window.isDestroyed()) { window.destroy() } }, 50) } function retireInactiveAppWindows(mode: WindowMode, target: ElectronBrowserWindow): void { for (const candidate of BrowserWindow.getAllWindows()) { if (candidate === target || candidate.isDestroyed()) { continue } const candidateMode = rendererModeFromWindow(candidate) if ( (mode === 'main' && (candidateMode === 'login' || candidateMode === 'main')) || (mode === 'login' && (candidateMode === 'login' || candidateMode === 'main' || candidateMode === 'settings')) ) { retireWindowAfterIpcReturn(candidate) } } } async function waitForWindowReady(window: ElectronBrowserWindow): Promise { if (window.isDestroyed() || !window.webContents.isLoading()) { return } await new Promise((resolve) => { let settled = false let timeout: ReturnType | null = null const finish = () => { if (settled) { return } settled = true if (timeout) { clearTimeout(timeout) } window.off('ready-to-show', finish) window.webContents.off('did-finish-load', finish) window.webContents.off('did-fail-load', finish) resolve() } timeout = setTimeout(finish, WINDOW_READY_TIMEOUT_MS) window.once('ready-to-show', finish) window.webContents.once('did-finish-load', finish) window.webContents.once('did-fail-load', finish) }) } function captureRuntimeSnapshot() { const snapshot = createRuntimeSnapshot() applyDesktopHealthIndicatorFromSnapshot(snapshot, currentMainWindow()) return snapshot } function captureRuntimeAccountSnapshot(accountId: string) { return createRuntimeAccountSnapshot(accountId) } async function clearRendererDesktopSession(window: ElectronBrowserWindow | null): Promise { if (!window || window.isDestroyed()) { return } await window.webContents .executeJavaScript("window.localStorage.removeItem('geo.desktop.session.v1')", true) .catch((error) => { console.warn('[desktop-main] clear renderer desktop session failed', { message: error instanceof Error ? error.message : String(error), }) }) } async function clearRendererDesktopSessions(): Promise { await Promise.all( BrowserWindow.getAllWindows().map((window) => clearRendererDesktopSession(window).catch((error) => { console.warn('[desktop-main] clear window desktop session failed', { message: error instanceof Error ? error.message : String(error), }) }), ), ) } async function ensureMainWindow(): Promise { const existing = currentMainWindow() if (existing) { return existing } if (!mainWindowCreatePromise) { mainWindowCreatePromise = createAppWindow('main') .then((window) => { mainWindow = window return window }) .finally(() => { mainWindowCreatePromise = null }) } return mainWindowCreatePromise } async function ensureLoginWindow(): Promise { const existing = currentLoginWindow() if (existing) { return existing } if (!loginWindowCreatePromise) { loginWindowCreatePromise = createAppWindow('login') .then((window) => { loginWindow = window return window }) .finally(() => { loginWindowCreatePromise = null }) } return loginWindowCreatePromise } async function ensureSettingsWindow(): Promise { const existing = currentSettingsWindow() if (existing) { return existing } if (!settingsWindowCreatePromise) { settingsWindowCreatePromise = createSettingsWindow() .then((window) => { settingsWindow = window return window }) .finally(() => { settingsWindowCreatePromise = null }) } return settingsWindowCreatePromise } async function ensureWindow(mode: WindowMode): Promise { return mode === 'main' ? ensureMainWindow() : ensureLoginWindow() } async function revealWindow(window: ElectronBrowserWindow): Promise { await waitForWindowReady(window) if (window.isMinimized()) { window.restore() } if (!window.isVisible()) { window.show() } window.focus() app.focus({ steal: true }) } async function revealActiveWindow(): Promise { const window = currentActiveWindow() ?? (await ensureWindow(currentWindowMode)) await revealWindow(window) } function revealActiveWindowSafely(source: string): void { void revealActiveWindow().catch((error) => { console.error('[desktop-main] reveal active window failed', { source, error }) }) } function windowFromIpcEvent(event: IpcMainInvokeEvent): ElectronBrowserWindow | null { const window = BrowserWindow.fromWebContents(event.sender) if (!window || window.isDestroyed()) { return null } return window } async function switchWindowMode( mode: WindowMode, _request: WindowModeRequest = {}, sourceWindow: ElectronBrowserWindow | null = null, ): Promise { currentWindowMode = mode persistWindowMode(mode) const target = await ensureWindow(mode) const previous = sourceWindow && sourceWindow !== target ? sourceWindow : mode === 'main' ? currentLoginWindow() : currentMainWindow() if (!target.isVisible()) { target.center() } await revealWindow(target) if (previous && previous !== target && !previous.isDestroyed()) { retireWindowAfterIpcReturn(previous) } const settings = currentSettingsWindow() if (mode === 'login' && settings) { retireWindowAfterIpcReturn(settings) } retireInactiveAppWindows(mode, target) } function queueWindowModeSwitch( mode: WindowMode, request: WindowModeRequest = {}, sourceWindow: ElectronBrowserWindow | null = null, ): Promise { const nextSwitch = windowModeSwitchQueue .catch(() => undefined) .then(() => switchWindowMode(mode, request, sourceWindow)) windowModeSwitchQueue = nextSwitch.catch(() => undefined) return nextSwitch } async function openSettingsWindow(): Promise { const window = await ensureSettingsWindow() const parent = settingsWindowParent() if (parent && window.getParentWindow() !== parent) { window.setParentWindow(parent) } await revealWindow(window) } function settingsWindowParent(): ElectronBrowserWindow | undefined { if (process.platform !== 'win32') { return undefined } return currentMainWindow() ?? undefined } function rendererURL(): string | null { return process.env.ELECTRON_RENDERER_URL ?? null } function preloadPath(): string { return join(__dirname, '../preload/bridge.cjs') } async function loadRenderer( window: ElectronBrowserWindow, mode: RendererWindowMode, ): Promise { const devServerURL = rendererURL() if (devServerURL) { const url = new URL(devServerURL) url.searchParams.set('window', mode) await window.webContents.loadURL(url.toString()) if (mode === 'main' && isDevelopmentRuntime) { window.webContents.openDevTools({ mode: 'detach' }) } return } await window.webContents.loadFile(join(__dirname, '../renderer/index.html'), { query: { window: mode }, }) } async function mountRendererWebContents( window: ElectronBrowserWindow, mode: RendererWindowMode, ): Promise { const contents = window.webContents contents.setWindowOpenHandler(() => ({ action: 'deny' })) if (mode === 'main') { if (isDevelopmentRuntime) { registerRendererDevtoolsProxyTarget(contents) registerObservedRequestRendererTarget(contents) } mainRendererContents = contents window.on('closed', () => { if (mainRendererContents === contents) { mainRendererContents = null } }) } await loadRenderer(window, mode) } async function createAppWindow(mode: WindowMode): Promise { const initial = WINDOW_SIZES[mode] const backgroundColor = mode === 'login' ? '#eaf2ff' : nativeTheme.shouldUseDarkColors ? '#15191a' : '#f0f2f5' const window = new BrowserWindow({ width: initial.width, height: initial.height, minWidth: initial.minWidth, minHeight: initial.minHeight, show: false, title: '省心推', icon: createDesktopHealthIcon('normal'), titleBarStyle: appTitleBarStyle(), titleBarOverlay: appTitleBarOverlay('#00000000'), trafficLightPosition: { x: 12, y: 14 }, resizable: initial.resizable, minimizable: mode !== 'login', maximizable: initial.resizable, fullscreenable: initial.resizable, autoHideMenuBar: true, backgroundColor, webPreferences: { preload: preloadPath(), sandbox: true, contextIsolation: true, nodeIntegration: false, }, }) appWindowModes.set(window, mode) suppressNativeWindowMenu(window) if (!initial.resizable) { window.setMinimumSize(initial.minWidth, initial.minHeight) window.setMaximumSize(initial.width, initial.height) } window.once('closed', () => { if (mode === 'main' && mainWindow === window) { mainWindow = null } if (mode === 'login' && loginWindow === window) { loginWindow = null } }) window.on('close', (event) => { if ( mode === 'main' && !quitReleaseInFlight && !forceCloseWindows.has(window) && getDesktopAppSettings().keepRunningInBackground ) { event.preventDefault() window.hide() // Windows: notify users once that the app is still running in the tray. // displayBalloon is a no-op on macOS/Linux so the guard is in tray.ts. if (process.platform === 'win32' && !hasShownBackgroundBalloon()) { showTrayBalloon('省心推已最小化到托盘', '应用仍在后台运行,右键托盘图标可退出。') markBackgroundBalloonShown() } } }) 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, mode) return window } async function createSettingsWindow(): Promise { const initial = SETTINGS_WINDOW_SIZE const backgroundColor = nativeTheme.shouldUseDarkColors ? '#15191a' : '#f2f4f7' const window = new BrowserWindow({ parent: settingsWindowParent(), width: initial.width, height: initial.height, minWidth: initial.minWidth, minHeight: initial.minHeight, show: false, title: '省心推设置', icon: createDesktopHealthIcon('normal'), titleBarStyle: appTitleBarStyle(), titleBarOverlay: appTitleBarOverlay(backgroundColor), trafficLightPosition: { x: 14, y: 15 }, resizable: initial.resizable, maximizable: initial.resizable, fullscreenable: false, autoHideMenuBar: true, backgroundColor, webPreferences: { preload: preloadPath(), sandbox: true, contextIsolation: true, nodeIntegration: false, }, }) appWindowModes.set(window, 'settings') suppressNativeWindowMenu(window) window.once('closed', () => { if (settingsWindow === window) { settingsWindow = null } }) await mountRendererWebContents(window, 'settings') 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 registerDesktopProtocolClient(): void { if (app.isDefaultProtocolClient(desktopDeepLinkScheme)) { return } if (process.defaultApp && process.execPath) { app.setAsDefaultProtocolClient(desktopDeepLinkScheme, process.execPath, [ process.argv[1] ?? '', ]) return } app.setAsDefaultProtocolClient(desktopDeepLinkScheme) } function extractDesktopDeepLink(argv: string[]): string | null { return argv.find((item) => item.startsWith(`${desktopDeepLinkScheme}://`)) ?? null } function queueDesktopDeepLink(rawUrl: string | null | undefined): void { if (!rawUrl) { return } if (!canHandleDesktopDeepLinks) { pendingDesktopDeepLinks.push(rawUrl) return } handleDesktopDeepLink(rawUrl) } function flushPendingDesktopDeepLinks(): void { canHandleDesktopDeepLinks = true const links = pendingDesktopDeepLinks.splice(0) for (const link of links) { handleDesktopDeepLink(link) } } function handleDesktopDeepLink(rawUrl: string): void { let parsed: URL try { parsed = new URL(rawUrl) } catch { return } if (parsed.protocol !== `${desktopDeepLinkScheme}:` || parsed.hostname !== 'desktop') { return } if ( parsed.pathname === '/workbench' || parsed.searchParams.get('action') === 'open-publish-account-console' ) { const account = { id: parsed.searchParams.get('account_id') ?? '', platform: parsed.searchParams.get('platform') ?? '', platformUid: parsed.searchParams.get('platform_uid') ?? '', displayName: parsed.searchParams.get('display_name') ?? '', } if (!account.id || !account.platform) { return } void openPublishAccountConsole(account).catch((error) => { console.warn('[desktop-deeplink] open publish account console failed', { accountId: account.id, platform: account.platform, message: error instanceof Error ? error.message : String(error), }) }) } } function registerBridgeHandlers(): void { ipcMain.handle('desktop:ping', () => 'pong') ipcMain.handle('desktop:device-info', () => resolveDesktopDeviceInfo()) safeHandle( 'desktop:client-id', (_event, scope: { tenant_id?: unknown; workspace_id?: unknown; user_id?: unknown }) => resolveDesktopClientID({ tenant_id: typeof scope?.tenant_id === 'number' ? scope.tenant_id : 0, workspace_id: typeof scope?.workspace_id === 'number' ? scope.workspace_id : 0, user_id: typeof scope?.user_id === 'number' ? scope.user_id : 0, }), ) ipcMain.handle('desktop:runtime-snapshot', () => captureRuntimeSnapshot()) ipcMain.handle('desktop:runtime-account-snapshot', (_event, accountId: string) => captureRuntimeAccountSnapshot(accountId), ) 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:probe-runtime-account', async (_event, accountId: string) => { await requestRuntimeAccountProbe(accountId) return captureRuntimeAccountSnapshot(accountId) }) safeHandle( 'desktop:open-publish-account-console', async ( _event, account: { id: string; platform: string; platformUid: string; displayName: string }, ) => { await openPublishAccountConsole(account) if (isAIPlatformId(account.platform)) { return null } void requestRuntimeAccountProbe(account.id, { account, force: true, }).catch((error) => { console.warn('[desktop-console] background account probe failed', { accountId: account.id, platform: account.platform, message: error instanceof Error ? error.message : String(error), }) }) 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 }) safeHandle('desktop:open-settings-window', async () => { await openSettingsWindow() return null }) ipcMain.handle('desktop:get-app-settings', () => getDesktopAppSettings()) safeHandle( 'desktop:set-app-setting', async (_event, key: DesktopAppSettingKey, value: boolean) => { if (key !== 'openAtLogin' && key !== 'keepRunningInBackground') { throw new Error('unsupported_app_setting') } return setDesktopAppSetting(key, value) }, ) ipcMain.handle( 'desktop:runtime-session-sync', (_event, session: DesktopRuntimeSessionSyncRequest | null) => { syncRuntimeSession(session) return null }, ) safeHandle('desktop:runtime-session-rotate-client-token', async () => { if (!clientTokenRotatePromise) { clientTokenRotatePromise = rotateDesktopClient() .then((rotated) => { const currentSession = getRuntimeControllerSnapshot().session if ( currentSession?.mode === 'authenticated' && currentSession.desktop_client?.id === rotated.client.id ) { syncRuntimeSession({ ...currentSession, client_token: rotated.client_token, desktop_client: rotated.client, }) } return rotated }) .finally(() => { clientTokenRotatePromise = null }) } return clientTokenRotatePromise }) safeHandle('desktop:runtime-session-release', async (_event, revoke?: boolean) => { if (revoke) { currentWindowMode = 'login' persistWindowMode('login') await clearRendererDesktopSessions() } await releaseRuntimeSession({ revoke: Boolean(revoke) }) return null }) ipcMain.handle('desktop:set-window-mode', async (event, mode: WindowMode, request?: unknown) => { if (mode === 'login' || mode === 'main') { await queueWindowModeSwitch( mode, normalizeWindowModeRequest(request), windowFromIpcEvent(event), ) } return null }) } const hasSingleInstanceLock = initSingleInstance((argv) => { revealActiveWindowSafely('single-instance') queueDesktopDeepLink(extractDesktopDeepLink(argv)) }) if (!hasSingleInstanceLock) { console.info('[desktop-main] another instance is already running; exiting') app.exit(0) } else { app .whenReady() .then(async () => { suppressNativeWindowMenu() registerDesktopProtocolClient() currentWindowMode = 'login' persistWindowMode('login') initDesktopAppSettings() registerBridgeHandlers() await initSessionRegistry() initAccountHealth() initTransport() initScheduler() initProcessMetricsSampler() startHotViewReaper() startHiddenPlaywrightReaper() onRuntimeInvalidated((event) => { syncDesktopHealthIndicator(currentMainWindow()) if (!mainRendererContents || mainRendererContents.isDestroyed()) { return } mainRendererContents.send('desktop:runtime-invalidated', event) }) await queueWindowModeSwitch(currentWindowMode) initTray(() => { revealActiveWindowSafely('tray') }) startDesktopHealthIndicator(() => currentMainWindow()) queueDesktopDeepLink(extractDesktopDeepLink(process.argv)) flushPendingDesktopDeepLinks() }) .catch((error) => { console.error('[desktop-main] app.whenReady failed', error) }) app.on('activate', () => { revealActiveWindowSafely('activate') }) app.on('open-url', (event, url) => { event.preventDefault() revealActiveWindowSafely('deep-link') queueDesktopDeepLink(url) }) app.on('window-all-closed', () => { if (getDesktopAppSettings().keepRunningInBackground) { return } if (process.platform !== 'darwin') { app.quit() } }) app.on('before-quit', (event) => { if (quitReleaseInFlight) { return } quitReleaseInFlight = true event.preventDefault() currentWindowMode = 'login' persistWindowMode('login') void Promise.race([ (async () => { await clearRendererDesktopSessions() await releaseRuntimeSession() })(), new Promise((resolve) => setTimeout(resolve, 1500)), ]).finally(() => { app.quit() }) }) }