From ba6ebec892f63879e628a4572d8a35fe4774c661 Mon Sep 17 00:00:00 2001 From: Xu Liang Date: Fri, 1 May 2026 18:05:12 +0800 Subject: [PATCH] fix(desktop-client): prevent duplicate window creation and serialize mode switches - Add creation promise guards (mainWindowCreatePromise etc.) so concurrent ensureXxxWindow() calls share a single in-flight BrowserWindow, avoiding duplicate windows on rapid IPC - Add windowModeSwitchQueue to serialize desktop:set-window-mode calls and the initial startup switch, preventing races when login/logout fire quickly - Fix retireInactiveAppWindows: also retire existing login windows when switching to login mode, so stale login windows don't accumulate Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop-client/src/main/bootstrap.ts | 64 ++++++++++++++++++----- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/apps/desktop-client/src/main/bootstrap.ts b/apps/desktop-client/src/main/bootstrap.ts index a3c77c0..525e26a 100644 --- a/apps/desktop-client/src/main/bootstrap.ts +++ b/apps/desktop-client/src/main/bootstrap.ts @@ -97,6 +97,10 @@ 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(); const forceCloseWindows = new WeakSet(); const appWindowModes = new WeakMap(); @@ -281,7 +285,7 @@ function retireInactiveAppWindows(mode: WindowMode, target: ElectronBrowserWindo const candidateMode = rendererModeFromWindow(candidate); if ( (mode === "main" && (candidateMode === "login" || candidateMode === "main")) - || (mode === "login" && (candidateMode === "main" || candidateMode === "settings")) + || (mode === "login" && (candidateMode === "login" || candidateMode === "main" || candidateMode === "settings")) ) { retireWindowAfterIpcReturn(candidate); } @@ -333,9 +337,17 @@ async function ensureMainWindow(): Promise { if (existing) { return existing; } - const window = await createAppWindow("main"); - mainWindow = window; - return window; + if (!mainWindowCreatePromise) { + mainWindowCreatePromise = createAppWindow("main") + .then((window) => { + mainWindow = window; + return window; + }) + .finally(() => { + mainWindowCreatePromise = null; + }); + } + return mainWindowCreatePromise; } async function ensureLoginWindow(): Promise { @@ -343,9 +355,17 @@ async function ensureLoginWindow(): Promise { if (existing) { return existing; } - const window = await createAppWindow("login"); - loginWindow = window; - return window; + if (!loginWindowCreatePromise) { + loginWindowCreatePromise = createAppWindow("login") + .then((window) => { + loginWindow = window; + return window; + }) + .finally(() => { + loginWindowCreatePromise = null; + }); + } + return loginWindowCreatePromise; } async function ensureSettingsWindow(): Promise { @@ -353,9 +373,17 @@ async function ensureSettingsWindow(): Promise { if (existing) { return existing; } - const window = await createSettingsWindow(); - settingsWindow = window; - return window; + if (!settingsWindowCreatePromise) { + settingsWindowCreatePromise = createSettingsWindow() + .then((window) => { + settingsWindow = window; + return window; + }) + .finally(() => { + settingsWindowCreatePromise = null; + }); + } + return settingsWindowCreatePromise; } async function ensureWindow(mode: WindowMode): Promise { @@ -424,6 +452,18 @@ async function switchWindowMode( 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(); await revealWindow(window); @@ -722,7 +762,7 @@ function registerBridgeHandlers(): void { }); ipcMain.handle("desktop:set-window-mode", async (event, mode: WindowMode, request?: unknown) => { if (mode === "login" || mode === "main") { - await switchWindowMode(mode, normalizeWindowModeRequest(request), windowFromIpcEvent(event)); + await queueWindowModeSwitch(mode, normalizeWindowModeRequest(request), windowFromIpcEvent(event)); } return null; }); @@ -755,7 +795,7 @@ if (!hasSingleInstanceLock) { } mainRendererContents.send("desktop:runtime-invalidated", event); }); - await switchWindowMode(currentWindowMode); + await queueWindowModeSwitch(currentWindowMode); initTray(() => { revealActiveWindowSafely("tray"); });