Files
OmniRoute/electron/preload.js
T
diegosouzapw d624ddde03 fix(electron): code review hardening — 16 fixes for security, performance, robustness
## Critical Fixes
- #1: Server readiness — waitForServer() polls before loading window
- #2: Restart timeout — 5s + SIGKILL prevents IPC handler from hanging
- #3: changePort — now stops/restarts server on new port

## Important Fixes
- #4: Tray cleanup — destroy old Tray before recreating
- #5: IPC emission — server-status & port-changed events
- #6: Disposer pattern — replaces removeAllListeners
- #7: useSyncExternalStore — eliminates 5x re-renders

## Minor: #8-#16 (dead code, CSP, platform titlebar, types, errors, version)

Tests: 76 / 15 suites (was 64/9)
2026-02-28 08:15:04 -03:00

66 lines
2.9 KiB
JavaScript

/**
* OmniRoute Electron Desktop App - Preload Script
*
* Secure bridge between renderer (Next.js) and main process (Electron).
* Uses contextIsolation: true for maximum security.
*
* Code Review Fixes Applied:
* #6 Listener accumulation — return disposer functions instead of using removeAllListeners
* #16 Simplified channel validation — generic wrapper reduces boilerplate
*/
const { contextBridge, ipcRenderer } = require("electron");
// ── Channel Whitelist ──────────────────────────────────────
const VALID_CHANNELS = {
invoke: ["get-app-info", "open-external", "get-data-dir", "restart-server"],
send: ["window-minimize", "window-maximize", "window-close"],
receive: ["server-status", "port-changed"],
};
// ── Fix #16: Generic IPC wrappers ──────────────────────────
function safeInvoke(channel, ...args) {
if (!VALID_CHANNELS.invoke.includes(channel)) {
return Promise.reject(new Error(`Blocked IPC invoke: ${channel}`));
}
return ipcRenderer.invoke(channel, ...args);
}
function safeSend(channel, ...args) {
if (VALID_CHANNELS.send.includes(channel)) {
ipcRenderer.send(channel, ...args);
}
}
// Fix #6: Return disposer function for proper listener cleanup
function safeOn(channel, callback) {
if (!VALID_CHANNELS.receive.includes(channel)) return () => {};
const handler = (_event, data) => callback(data);
ipcRenderer.on(channel, handler);
// Return a disposer — caller removes only THIS specific listener
return () => ipcRenderer.removeListener(channel, handler);
}
// ── Expose API to Renderer ─────────────────────────────────
contextBridge.exposeInMainWorld("electronAPI", {
// ── Invoke (async, returns Promise) ──────────────────────
getAppInfo: () => safeInvoke("get-app-info"),
openExternal: (url) => safeInvoke("open-external", url),
getDataDir: () => safeInvoke("get-data-dir"),
restartServer: () => safeInvoke("restart-server"),
// ── Send (fire-and-forget) ───────────────────────────────
minimizeWindow: () => safeSend("window-minimize"),
maximizeWindow: () => safeSend("window-maximize"),
closeWindow: () => safeSend("window-close"),
// ── Receive (event listeners) ────────────────────────────
// Fix #6: Returns a disposer function for precise cleanup
onServerStatus: (callback) => safeOn("server-status", callback),
onPortChanged: (callback) => safeOn("port-changed", callback),
// ── Static Properties ────────────────────────────────────
isElectron: true,
platform: process.platform,
});