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 settingsWindow: ElectronBrowserWindow | null = null;
|
||||||
let mainRendererContents: ElectronWebContents | null = null;
|
let mainRendererContents: ElectronWebContents | null = null;
|
||||||
let quitReleaseInFlight = false;
|
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 forceCloseWindows = new WeakSet<ElectronBrowserWindow>();
|
||||||
const appWindowModes = new WeakMap<ElectronBrowserWindow, RendererWindowMode>();
|
const appWindowModes = new WeakMap<ElectronBrowserWindow, RendererWindowMode>();
|
||||||
|
|
||||||
@@ -281,7 +285,7 @@ function retireInactiveAppWindows(mode: WindowMode, target: ElectronBrowserWindo
|
|||||||
const candidateMode = rendererModeFromWindow(candidate);
|
const candidateMode = rendererModeFromWindow(candidate);
|
||||||
if (
|
if (
|
||||||
(mode === "main" && (candidateMode === "login" || candidateMode === "main"))
|
(mode === "main" && (candidateMode === "login" || candidateMode === "main"))
|
||||||
|| (mode === "login" && (candidateMode === "main" || candidateMode === "settings"))
|
|| (mode === "login" && (candidateMode === "login" || candidateMode === "main" || candidateMode === "settings"))
|
||||||
) {
|
) {
|
||||||
retireWindowAfterIpcReturn(candidate);
|
retireWindowAfterIpcReturn(candidate);
|
||||||
}
|
}
|
||||||
@@ -333,9 +337,17 @@ async function ensureMainWindow(): Promise<ElectronBrowserWindow> {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const window = await createAppWindow("main");
|
if (!mainWindowCreatePromise) {
|
||||||
mainWindow = window;
|
mainWindowCreatePromise = createAppWindow("main")
|
||||||
return window;
|
.then((window) => {
|
||||||
|
mainWindow = window;
|
||||||
|
return window;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
mainWindowCreatePromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return mainWindowCreatePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureLoginWindow(): Promise<ElectronBrowserWindow> {
|
async function ensureLoginWindow(): Promise<ElectronBrowserWindow> {
|
||||||
@@ -343,9 +355,17 @@ async function ensureLoginWindow(): Promise<ElectronBrowserWindow> {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const window = await createAppWindow("login");
|
if (!loginWindowCreatePromise) {
|
||||||
loginWindow = window;
|
loginWindowCreatePromise = createAppWindow("login")
|
||||||
return window;
|
.then((window) => {
|
||||||
|
loginWindow = window;
|
||||||
|
return window;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loginWindowCreatePromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return loginWindowCreatePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSettingsWindow(): Promise<ElectronBrowserWindow> {
|
async function ensureSettingsWindow(): Promise<ElectronBrowserWindow> {
|
||||||
@@ -353,9 +373,17 @@ async function ensureSettingsWindow(): Promise<ElectronBrowserWindow> {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const window = await createSettingsWindow();
|
if (!settingsWindowCreatePromise) {
|
||||||
settingsWindow = window;
|
settingsWindowCreatePromise = createSettingsWindow()
|
||||||
return window;
|
.then((window) => {
|
||||||
|
settingsWindow = window;
|
||||||
|
return window;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
settingsWindowCreatePromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return settingsWindowCreatePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureWindow(mode: WindowMode): Promise<ElectronBrowserWindow> {
|
async function ensureWindow(mode: WindowMode): Promise<ElectronBrowserWindow> {
|
||||||
@@ -424,6 +452,18 @@ async function switchWindowMode(
|
|||||||
retireInactiveAppWindows(mode, target);
|
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> {
|
async function openSettingsWindow(): Promise<void> {
|
||||||
const window = await ensureSettingsWindow();
|
const window = await ensureSettingsWindow();
|
||||||
await revealWindow(window);
|
await revealWindow(window);
|
||||||
@@ -722,7 +762,7 @@ function registerBridgeHandlers(): void {
|
|||||||
});
|
});
|
||||||
ipcMain.handle("desktop:set-window-mode", async (event, mode: WindowMode, request?: unknown) => {
|
ipcMain.handle("desktop:set-window-mode", async (event, mode: WindowMode, request?: unknown) => {
|
||||||
if (mode === "login" || mode === "main") {
|
if (mode === "login" || mode === "main") {
|
||||||
await switchWindowMode(mode, normalizeWindowModeRequest(request), windowFromIpcEvent(event));
|
await queueWindowModeSwitch(mode, normalizeWindowModeRequest(request), windowFromIpcEvent(event));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@@ -755,7 +795,7 @@ if (!hasSingleInstanceLock) {
|
|||||||
}
|
}
|
||||||
mainRendererContents.send("desktop:runtime-invalidated", event);
|
mainRendererContents.send("desktop:runtime-invalidated", event);
|
||||||
});
|
});
|
||||||
await switchWindowMode(currentWindowMode);
|
await queueWindowModeSwitch(currentWindowMode);
|
||||||
initTray(() => {
|
initTray(() => {
|
||||||
revealActiveWindowSafely("tray");
|
revealActiveWindowSafely("tray");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user