File size: 3,537 Bytes
35743bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
 * 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",
    "check-for-updates",
    "download-update",
    "install-update",
    "get-app-version",
  ],
  send: ["window-minimize", "window-maximize", "window-close"],
  receive: ["server-status", "port-changed", "update-status"],
};

// ── 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"),
  getAppVersion: () => safeInvoke("get-app-version"),

  // ── Auto-Update ──────────────────────────────────────────
  checkForUpdates: () => safeInvoke("check-for-updates"),
  downloadUpdate: () => safeInvoke("download-update"),
  installUpdate: () => safeInvoke("install-update"),

  // ── 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),
  onUpdateStatus: (callback) => safeOn("update-status", callback),

  // ── Static Properties ────────────────────────────────────
  isElectron: true,
  platform: process.platform,
});