/** * preload.js * * Runs in a privileged context but is sandboxed from the renderer. * Use contextBridge to expose a safe, narrow API surface to the renderer. * Never expose raw ipcRenderer — always whitelist specific channels. */ const { contextBridge, ipcRenderer } = require('electron') // ─── Allowed IPC channels ───────────────────────────────────────────────── // Only messages on these channels will be forwarded. const ALLOWED_SEND_CHANNELS = [ 'patient:update-status', 'patient:fetch-list', 'schedule:fetch', 'app:ready', ] const ALLOWED_RECEIVE_CHANNELS = [ 'patient:status-updated', 'patient:list-response', 'schedule:response', 'app:get-version', ] // ─── Exposed API ────────────────────────────────────────────────────────── contextBridge.exposeInMainWorld('api', { /** * Send a one-way message to the main process. * @param {string} channel - Must be in ALLOWED_SEND_CHANNELS * @param {*} data */ send(channel, data) { if (ALLOWED_SEND_CHANNELS.includes(channel)) { ipcRenderer.send(channel, data) } else { console.warn(`[preload] Blocked send on unknown channel: "${channel}"`) } }, /** * Invoke a main-process handler and await its response. * @param {string} channel - Must be in ALLOWED_SEND_CHANNELS * @param {...*} args * @returns {Promise<*>} */ invoke(channel, ...args) { if (ALLOWED_SEND_CHANNELS.includes(channel) || ALLOWED_RECEIVE_CHANNELS.includes(channel)) { return ipcRenderer.invoke(channel, ...args) } return Promise.reject(new Error(`[preload] Blocked invoke on unknown channel: "${channel}"`)) }, /** * Subscribe to messages from the main process. * @param {string} channel - Must be in ALLOWED_RECEIVE_CHANNELS * @param {Function} callback - Called with (event, ...args) on each message * @returns {Function} Unsubscribe function — call it to remove the listener */ receive(channel, callback) { if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) { const handler = (_event, ...args) => callback(...args) ipcRenderer.on(channel, handler) // Return cleanup function so React effects can unsubscribe return () => ipcRenderer.removeListener(channel, handler) } console.warn(`[preload] Blocked receive on unknown channel: "${channel}"`) return () => {} }, })