fix(desktop-client): prevent duplicate window creation and serialize mode switches
Frontend CI / Frontend (push) Successful in 2m24s
Frontend CI / Frontend (push) Successful in 2m24s
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<ElectronBrowserWindow> | null = null;
|
||||
let loginWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null;
|
||||
let settingsWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null;
|
||||
let windowModeSwitchQueue: Promise<void> = Promise.resolve();
|
||||
const forceCloseWindows = new WeakSet<ElectronBrowserWindow>();
|
||||
const appWindowModes = new WeakMap<ElectronBrowserWindow, RendererWindowMode>();
|
||||
|
||||
@@ -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<ElectronBrowserWindow> {
|
||||
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<ElectronBrowserWindow> {
|
||||
@@ -343,9 +355,17 @@ async function ensureLoginWindow(): Promise<ElectronBrowserWindow> {
|
||||
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<ElectronBrowserWindow> {
|
||||
@@ -353,9 +373,17 @@ async function ensureSettingsWindow(): Promise<ElectronBrowserWindow> {
|
||||
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<ElectronBrowserWindow> {
|
||||
@@ -424,6 +452,18 @@ async function switchWindowMode(
|
||||
retireInactiveAppWindows(mode, target);
|
||||
}
|
||||
|
||||
function queueWindowModeSwitch(
|
||||
mode: WindowMode,
|
||||
request: WindowModeRequest = {},
|
||||
sourceWindow: ElectronBrowserWindow | null = null,
|
||||
): Promise<void> {
|
||||
const nextSwitch = windowModeSwitchQueue
|
||||
.catch(() => undefined)
|
||||
.then(() => switchWindowMode(mode, request, sourceWindow));
|
||||
windowModeSwitchQueue = nextSwitch.catch(() => undefined);
|
||||
return nextSwitch;
|
||||
}
|
||||
|
||||
async function openSettingsWindow(): Promise<void> {
|
||||
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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user