From be288ae85dc77cd51652c39a8ac7e6a52e76aeb9 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Wed, 9 Sep 2026 15:31:51 +0800 Subject: [PATCH] fix(desktop): keep app alive during renderer migration --- apps/desktop/src/main/index.mjs | 7 ++++++- apps/desktop/src/main/window-lifecycle.mjs | 4 ++++ .../src/main/window-lifecycle.test.mjs | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/main/window-lifecycle.mjs create mode 100644 apps/desktop/src/main/window-lifecycle.test.mjs diff --git a/apps/desktop/src/main/index.mjs b/apps/desktop/src/main/index.mjs index a068503d..4856d074 100644 --- a/apps/desktop/src/main/index.mjs +++ b/apps/desktop/src/main/index.mjs @@ -41,6 +41,7 @@ import { } from "./windows-update-trust.mjs"; import electronUpdater from "electron-updater"; import { createPluginPublicNetworkRuntime } from "./plugin-public-network.mjs"; +import { shouldQuitAfterAllWindowsClosed } from "./window-lifecycle.mjs"; import { DESKTOP_APP_ENTRY_URL, DESKTOP_APP_ORIGIN, @@ -132,6 +133,7 @@ const pluginPublicNetwork = createPluginPublicNetworkRuntime(); let rendererUnresponsiveDialogOpen = false; let recoveredAfterAbnormalExit = false; let usePrivateAppProtocol = false; +let rendererOriginMigrationInProgress = false; const pendingScheduledTaskRuns = []; const sendScheduledTaskRun = (task, scheduledFor) => { const payload = { task, scheduledFor: scheduledFor.toISOString() }; @@ -761,6 +763,7 @@ const preparePackagedRendererOrigin = async () => { } const bridgePath = join(process.resourcesPath, "web/desktop-storage-bridge.html"); + rendererOriginMigrationInProgress = true; try { const result = await migrateRendererStorageOrigin({ createWindow: () => new BrowserWindow({ @@ -782,6 +785,8 @@ const preparePackagedRendererOrigin = async () => { void writeDiagnostic("renderer.origin-migration-failed", { message: String(error?.message || error).slice(0, 2000), }); + } finally { + rendererOriginMigrationInProgress = false; } }; @@ -1621,7 +1626,7 @@ app.on("second-instance", (_event, commandLine) => { }); app.on("window-all-closed", () => { - if (process.platform !== "darwin") app.quit(); + if (shouldQuitAfterAllWindowsClosed({ rendererOriginMigrationInProgress })) app.quit(); }); app.on("before-quit", (event) => { diff --git a/apps/desktop/src/main/window-lifecycle.mjs b/apps/desktop/src/main/window-lifecycle.mjs new file mode 100644 index 00000000..7ed19b29 --- /dev/null +++ b/apps/desktop/src/main/window-lifecycle.mjs @@ -0,0 +1,4 @@ +export const shouldQuitAfterAllWindowsClosed = ({ + platform = process.platform, + rendererOriginMigrationInProgress = false, +} = {}) => platform !== "darwin" && !rendererOriginMigrationInProgress; diff --git a/apps/desktop/src/main/window-lifecycle.test.mjs b/apps/desktop/src/main/window-lifecycle.test.mjs new file mode 100644 index 00000000..35ba68bb --- /dev/null +++ b/apps/desktop/src/main/window-lifecycle.test.mjs @@ -0,0 +1,21 @@ +import { describe, expect, test } from "bun:test"; +import { shouldQuitAfterAllWindowsClosed } from "./window-lifecycle.mjs"; + +describe("desktop window lifecycle", () => { + test("keeps the app alive while the renderer origin migration window closes", () => { + expect(shouldQuitAfterAllWindowsClosed({ + platform: "linux", + rendererOriginMigrationInProgress: true, + })).toBe(false); + expect(shouldQuitAfterAllWindowsClosed({ + platform: "win32", + rendererOriginMigrationInProgress: true, + })).toBe(false); + }); + + test("preserves normal platform window close behavior", () => { + expect(shouldQuitAfterAllWindowsClosed({ platform: "linux" })).toBe(true); + expect(shouldQuitAfterAllWindowsClosed({ platform: "win32" })).toBe(true); + expect(shouldQuitAfterAllWindowsClosed({ platform: "darwin" })).toBe(false); + }); +});