feat: implement single instance lock to ensure data integrity and resource efficiency
This commit is contained in:
@@ -146,6 +146,13 @@ A fully managed update lifecycle powered by `electron-updater`. Background downl
|
|||||||
|
|
||||||
A transparent proxy mechanism that handles cross-origin requests by sanitizing non-standard `app://` and `file://` Origin headers on outgoing requests and injecting permissive CORS response headers on incoming responses — allowing seamless integration with cloud APIs without server-side configuration changes.
|
A transparent proxy mechanism that handles cross-origin requests by sanitizing non-standard `app://` and `file://` Origin headers on outgoing requests and injecting permissive CORS response headers on incoming responses — allowing seamless integration with cloud APIs without server-side configuration changes.
|
||||||
|
|
||||||
|
### 🔒 Single Instance Lock & Data Integrity
|
||||||
|
|
||||||
|
The application enforces a **single running instance** via `app.requestSingleInstanceLock()`. If a user attempts to launch a second instance, the duplicate process is terminated immediately and the existing window is restored and focused. This mechanism serves two critical purposes:
|
||||||
|
|
||||||
|
- **Data Integrity**: Prevents race conditions and write conflicts in local databases (IndexedDB/PouchDB) that could arise from concurrent access by multiple Electron processes.
|
||||||
|
- **Resource Efficiency**: Avoids duplicate memory allocation, IPC handler registration, and protocol handler conflicts.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Hardened Security Perimeter
|
## Hardened Security Perimeter
|
||||||
|
|||||||
@@ -358,60 +358,82 @@ function sendToRenderer(channel: string, ...args: unknown[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 7. App Lifecycle ───────────────────────────────────────────
|
// ─── 7. Single Instance Lock & App Lifecycle ────────────────────
|
||||||
|
// Prevent multiple instances to protect local database integrity
|
||||||
|
// (IndexedDB/PouchDB) and avoid resource contention.
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
// Register the custom protocol before creating the window
|
|
||||||
registerAppProtocol();
|
|
||||||
|
|
||||||
// Setup CORS bypass for API calls
|
if (!gotTheLock) {
|
||||||
setupCorsBypass();
|
// Another instance is already running — terminate immediately.
|
||||||
|
app.quit();
|
||||||
// Setup IPC handlers
|
} else {
|
||||||
setupPrinterIPC();
|
// ── Handle second-instance launch attempts ──────────────────
|
||||||
setupAutoUpdaterIPC();
|
// If a user tries to open a second instance, restore and
|
||||||
setupAutoUpdaterEvents();
|
// focus the existing window instead.
|
||||||
|
app.on('second-instance', () => {
|
||||||
// Create the main window
|
if (mainWindow) {
|
||||||
createWindow();
|
if (mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore();
|
||||||
// Check for updates on startup (production only)
|
|
||||||
if (!IS_DEV) {
|
|
||||||
setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
await autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
} catch (err) {
|
|
||||||
// Gracefully handle offline or network errors
|
|
||||||
console.error('[AutoUpdater] Startup check failed (possibly offline):', err);
|
|
||||||
}
|
}
|
||||||
}, 3000);
|
mainWindow.focus();
|
||||||
}
|
|
||||||
|
|
||||||
// macOS: re-create window when dock icon is clicked
|
|
||||||
app.on('activate', () => {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Quit when all windows are closed (except macOS)
|
// ── Primary initialization ──────────────────────────────────
|
||||||
app.on('window-all-closed', () => {
|
app.whenReady().then(() => {
|
||||||
if (process.platform !== 'darwin') {
|
// Register the custom protocol before creating the window
|
||||||
app.quit();
|
registerAppProtocol();
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Security: prevent navigation to unexpected URLs
|
// Setup CORS bypass for API calls
|
||||||
app.on('web-contents-created', (_event, contents) => {
|
setupCorsBypass();
|
||||||
contents.on('will-navigate', (event, url) => {
|
|
||||||
// Allow navigation within the app protocol and dev server
|
// Setup IPC handlers
|
||||||
if (
|
setupPrinterIPC();
|
||||||
url.startsWith('app://') ||
|
setupAutoUpdaterIPC();
|
||||||
(IS_DEV && url.startsWith(DEV_SERVER_URL))
|
setupAutoUpdaterEvents();
|
||||||
) {
|
|
||||||
return;
|
// Create the main window
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
// Check for updates on startup (production only)
|
||||||
|
if (!IS_DEV) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
} catch (err) {
|
||||||
|
// Gracefully handle offline or network errors
|
||||||
|
console.error('[AutoUpdater] Startup check failed (possibly offline):', err);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
|
// macOS: re-create window when dock icon is clicked
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// Quit when all windows are closed (except macOS)
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Security: prevent navigation to unexpected URLs
|
||||||
|
app.on('web-contents-created', (_event, contents) => {
|
||||||
|
contents.on('will-navigate', (event, url) => {
|
||||||
|
// Allow navigation within the app protocol and dev server
|
||||||
|
if (
|
||||||
|
url.startsWith('app://') ||
|
||||||
|
(IS_DEV && url.startsWith(DEV_SERVER_URL))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user