import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; import { MessagePortMain } from '@electron/internal/browser/message-port-main'; import type { ServiceWorkerMain } from 'electron/main'; import { ipcMain } from 'electron/main'; const v8Util = process._linkedBinding('electron_common_v8_util'); const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main'); const addReplyToEvent = (event: Electron.IpcMainEvent) => { const { processId, frameId } = event; event.reply = (channel: string, ...args: any[]) => { event.sender.sendToFrame([processId, frameId], channel, ...args); }; }; const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent) => { Object.defineProperty(event, 'returnValue', { set: (value) => event._replyChannel.sendReply(value), get: () => {} }); }; const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => { return event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId); }; const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => { Object.defineProperty(event, 'serviceWorker', { get: () => event.session.serviceWorkers.getWorkerFromVersionID(event.versionId) }); }; /** * Cached IPC emitters sorted by dispatch priority. * Caching is used to avoid frequent array allocations. */ const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [ undefined, // WebFrameMain ipc undefined, // WebContents ipc ipcMain ]; // Get list of relevant IPC emitters for dispatch. const getIpcEmittersForFrameEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => { // Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are // always received. This occurs when a RenderFrame sends an IPC while it's // unloading and its internal state is pending deletion. const { frameTreeNodeId } = event; const webFrameByFtn = frameTreeNodeId ? webFrameMainBinding._fromFtnIdIfExists(frameTreeNodeId) : undefined; cachedIpcEmitters[0] = webFrameByFtn?.ipc; cachedIpcEmitters[1] = event.sender.ipc; return cachedIpcEmitters; }; /** * Listens for IPC dispatch events on `api`. */ export function addIpcDispatchListeners (api: NodeJS.EventEmitter) { api.on('-ipc-message' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) { const internal = v8Util.getHiddenValue(event, 'internal'); if (internal) { ipcMainInternal.emit(channel, event, ...args); } else if (event.type === 'frame') { addReplyToEvent(event); event.sender.emit('ipc-message', event, channel, ...args); for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) { ipcEmitter?.emit(channel, event, ...args); } } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args); } } as any); api.on('-ipc-invoke' as any, async function (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, channel: string, args: any[]) { const internal = v8Util.getHiddenValue(event, 'internal'); const replyWithResult = (result: any) => event._replyChannel.sendReply({ result }); const replyWithError = (error: Error) => { console.error(`Error occurred in handler for '${channel}':`, error); event._replyChannel.sendReply({ error: error.toString() }); }; const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = []; if (internal) { targets.push(ipcMainInternal); } else if (event.type === 'frame') { targets.push(...getIpcEmittersForFrameEvent(event)); } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); const workerIpc = getServiceWorkerFromEvent(event)?.ipc; targets.push(workerIpc); } const target = targets.find(target => (target as any)?._invokeHandlers.has(channel)); if (target) { const handler = (target as any)._invokeHandlers.get(channel); try { replyWithResult(await Promise.resolve(handler(event, ...args))); } catch (err) { replyWithError(err as Error); } } else { replyWithError(new Error(`No handler registered for '${channel}'`)); } } as any); api.on('-ipc-message-sync' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) { const internal = v8Util.getHiddenValue(event, 'internal'); addReturnValueToEvent(event); if (internal) { ipcMainInternal.emit(channel, event, ...args); } else if (event.type === 'frame') { addReplyToEvent(event); const webContents = event.sender; const ipcEmitters = getIpcEmittersForFrameEvent(event); if ( webContents.listenerCount('ipc-message-sync') === 0 && ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0) ) { console.warn(`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`); } webContents.emit('ipc-message-sync', event, channel, ...args); for (const ipcEmitter of ipcEmitters) { ipcEmitter?.emit(channel, event, ...args); } } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args); } } as any); api.on('-ipc-message-host', function (event: Electron.IpcMainEvent, channel: string, args: any[]) { event.sender.emit('-ipc-message-host', event, channel, args); }); api.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, message: any, ports: any[]) { event.ports = ports.map(p => new MessagePortMain(p)); if (event.type === 'frame') { const ipcEmitters = getIpcEmittersForFrameEvent(event); for (const ipcEmitter of ipcEmitters) { ipcEmitter?.emit(channel, event, message); } } if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message); } } as any); }