From 12496589a36012595cd89e7b5c35e002f83b7c50 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 11 Jul 2025 07:23:48 +0000 Subject: [PATCH] Add single-instance enforcement to Electron app - Implement app.requestSingleInstanceLock() to prevent multiple instances - Add user-friendly dialog when second instance is attempted - Focus and restore existing window instead of creating new instance - Prevent database corruption and resource conflicts - Update documentation with single-instance behavior details --- BUILDING.md | 18 ++++++++++++++++++ electron/README-BUILDING.md | 19 +++++++++++++++++++ electron/src/index.ts | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/BUILDING.md b/BUILDING.md index f6070560..933df67e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -246,6 +246,24 @@ npm run build:electron:deb:prod npm run build:electron:dmg:prod ``` +### Single Instance Enforcement + +The Electron app enforces single-instance operation to prevent: +- Database corruption from multiple instances +- Resource conflicts and performance issues +- User confusion from multiple windows + +**Behavior:** +- Only one instance can run at a time +- Attempting to launch a second instance shows a user-friendly dialog +- The existing window is focused and restored if minimized +- Second instance exits gracefully + +**Implementation:** +- Uses Electron's `requestSingleInstanceLock()` API +- Handles `second-instance` events to focus existing window +- Shows informative dialog explaining why only one instance is allowed + #### Direct Script Usage ```bash diff --git a/electron/README-BUILDING.md b/electron/README-BUILDING.md index ca1681fa..552ed742 100644 --- a/electron/README-BUILDING.md +++ b/electron/README-BUILDING.md @@ -117,6 +117,25 @@ dist/ └── linux-unpacked/ # Unpacked directory ``` +## Features + +### Single Instance Enforcement +TimeSafari Electron enforces single-instance operation to prevent: +- Database corruption from multiple instances +- Resource conflicts and performance issues +- User confusion from multiple windows + +**Behavior:** +- Only one instance can run at a time +- Attempting to launch a second instance shows a user-friendly dialog +- The existing window is focused and restored if minimized +- Second instance exits gracefully + +**Implementation:** +- Uses Electron's `requestSingleInstanceLock()` API +- Handles `second-instance` events to focus existing window +- Shows informative dialog explaining why only one instance is allowed + ## Configuration ### App Metadata diff --git a/electron/src/index.ts b/electron/src/index.ts index a49e3395..f631c281 100644 --- a/electron/src/index.ts +++ b/electron/src/index.ts @@ -1,7 +1,7 @@ import type { CapacitorElectronConfig } from '@capacitor-community/electron'; import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron'; import type { MenuItemConstructorOptions } from 'electron'; -import { app, MenuItem, ipcMain } from 'electron'; +import { app, MenuItem, ipcMain, dialog } from 'electron'; import electronIsDev from 'electron-is-dev'; import unhandled from 'electron-unhandled'; import { autoUpdater } from 'electron-updater'; @@ -10,6 +10,27 @@ import { join } from 'path'; import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup'; +// Single instance enforcement +const gotTheLock = app.requestSingleInstanceLock(); + +if (!gotTheLock) { + console.log('[Electron] Another instance is already running. Exiting...'); + + // Show user-friendly dialog before quitting + dialog.showMessageBox({ + type: 'info', + title: 'TimeSafari Already Running', + message: 'TimeSafari is already running in another window.', + detail: 'Please use the existing TimeSafari window instead of opening a new one.', + buttons: ['OK'] + }).then(() => { + app.quit(); + }); + + // Exit immediately + process.exit(0); +} + // Graceful handling of unhandled errors. unhandled({ logger: (error) => { @@ -79,6 +100,20 @@ autoUpdater.on('error', (error) => { // Initialize our app, build windows, and load content. await myCapacitorApp.init(); + // Handle second instance launch (focus existing window) + app.on('second-instance', (event, commandLine, workingDirectory) => { + console.log('[Electron] Second instance attempted to launch'); + + // Someone tried to run a second instance, we should focus our window instead + const mainWindow = myCapacitorApp.getMainWindow(); + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.focus(); + } + }); + // Only check for updates in production builds, not in development or AppImage if (!electronIsDev && !process.env.APPIMAGE) { try {