Skip to content

Commit 931f2ad

Browse files
authored
🤖 fix: prevent duplicate updater IPC handler registration on window recreate (#851)
When a window is recreated (e.g., clicking dock icon on macOS after closing all windows), `createWindow()` was trying to register the same IPC handlers again, causing the error: > Attempted to register a second handler for 'update:check' Added a guard flag matching the pattern already used by `ipcMain.register()`. _Generated with `mux`_
1 parent 12cf0f4 commit 931f2ad

File tree

1 file changed

+54
-35
lines changed

1 file changed

+54
-35
lines changed

‎src/desktop/main.ts‎

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ let config: Config | null = null;
4444
let ipcMain: IpcMain | null = null;
4545
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
4646
let updaterService: typeof import("@/desktop/updater").UpdaterService.prototype | null = null;
47+
let updaterHandlersRegistered = false;
4748
const isE2ETest = process.env.MUX_E2E === "1";
4849
const forceDistLoad = process.env.MUX_E2E_LOAD_DIST === "1";
4950

@@ -377,42 +378,47 @@ function createWindow() {
377378
console.log(`[${timestamp()}] [window] Registering IPC handlers...`);
378379
ipcMain.register(electronIpcMain, mainWindow);
379380

380-
// Register updater IPC handlers (available in both dev and prod)
381-
electronIpcMain.handle(IPC_CHANNELS.UPDATE_CHECK, () => {
382-
// Note: log interface already includes timestamp and file location
383-
log.debug(`UPDATE_CHECK called (updaterService: ${updaterService ? "available" : "null"})`);
384-
if (!updaterService) {
385-
// Send "idle" status if updater not initialized (dev mode without DEBUG_UPDATER)
386-
if (mainWindow) {
387-
mainWindow.webContents.send(IPC_CHANNELS.UPDATE_STATUS, {
388-
type: "idle" as const,
389-
});
381+
// Register updater IPC handlers once (available in both dev and prod)
382+
// Guard prevents "Attempted to register a second handler" error when window is recreated
383+
if (!updaterHandlersRegistered) {
384+
updaterHandlersRegistered = true;
385+
386+
electronIpcMain.handle(IPC_CHANNELS.UPDATE_CHECK, () => {
387+
// Note: log interface already includes timestamp and file location
388+
log.debug(`UPDATE_CHECK called (updaterService: ${updaterService ? "available" : "null"})`);
389+
if (!updaterService) {
390+
// Send "idle" status if updater not initialized (dev mode without DEBUG_UPDATER)
391+
if (mainWindow) {
392+
mainWindow.webContents.send(IPC_CHANNELS.UPDATE_STATUS, {
393+
type: "idle" as const,
394+
});
395+
}
396+
return;
390397
}
391-
return;
392-
}
393-
log.debug("Calling updaterService.checkForUpdates()");
394-
updaterService.checkForUpdates();
395-
});
398+
log.debug("Calling updaterService.checkForUpdates()");
399+
updaterService.checkForUpdates();
400+
});
396401

397-
electronIpcMain.handle(IPC_CHANNELS.UPDATE_DOWNLOAD, async () => {
398-
if (!updaterService) throw new Error("Updater not available in development");
399-
await updaterService.downloadUpdate();
400-
});
402+
electronIpcMain.handle(IPC_CHANNELS.UPDATE_DOWNLOAD, async () => {
403+
if (!updaterService) throw new Error("Updater not available in development");
404+
await updaterService.downloadUpdate();
405+
});
401406

402-
electronIpcMain.handle(IPC_CHANNELS.UPDATE_INSTALL, () => {
403-
if (!updaterService) throw new Error("Updater not available in development");
404-
updaterService.installUpdate();
405-
});
407+
electronIpcMain.handle(IPC_CHANNELS.UPDATE_INSTALL, () => {
408+
if (!updaterService) throw new Error("Updater not available in development");
409+
updaterService.installUpdate();
410+
});
406411

407-
// Handle status subscription requests
408-
// Note: React StrictMode in dev causes components to mount twice, resulting in duplicate calls
409-
electronIpcMain.on(IPC_CHANNELS.UPDATE_STATUS_SUBSCRIBE, () => {
410-
log.debug("UPDATE_STATUS_SUBSCRIBE called");
411-
if (!mainWindow) return;
412-
const status = updaterService ? updaterService.getStatus() : { type: "idle" };
413-
log.debug("Sending current status to renderer:", status);
414-
mainWindow.webContents.send(IPC_CHANNELS.UPDATE_STATUS, status);
415-
});
412+
// Handle status subscription requests
413+
// Note: React StrictMode in dev causes components to mount twice, resulting in duplicate calls
414+
electronIpcMain.on(IPC_CHANNELS.UPDATE_STATUS_SUBSCRIBE, () => {
415+
log.debug("UPDATE_STATUS_SUBSCRIBE called");
416+
if (!mainWindow) return;
417+
const status = updaterService ? updaterService.getStatus() : { type: "idle" };
418+
log.debug("Sending current status to renderer:", status);
419+
mainWindow.webContents.send(IPC_CHANNELS.UPDATE_STATUS, status);
420+
});
421+
}
416422

417423
// Set up updater service with the main window (only in production)
418424
if (updaterService) {
@@ -555,9 +561,22 @@ if (gotTheLock) {
555561
// This prevents "Cannot create BrowserWindow before app is ready" error
556562
if (app.isReady() && mainWindow === null) {
557563
void (async () => {
558-
await showSplashScreen();
559-
await loadServices();
560-
createWindow();
564+
try {
565+
await showSplashScreen();
566+
await loadServices();
567+
createWindow();
568+
} catch (error) {
569+
console.error(`[${timestamp()}] Failed to recreate window:`, error);
570+
closeSplashScreen();
571+
572+
const errorMessage =
573+
error instanceof Error ? `${error.message}\n\n${error.stack ?? ""}` : String(error);
574+
575+
dialog.showErrorBox(
576+
"Window Creation Failed",
577+
`Failed to recreate the application window:\n\n${errorMessage}`
578+
);
579+
}
561580
})();
562581
}
563582
});

0 commit comments

Comments
 (0)