十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Electron IpcMainServiceWorker 详解:主进程与 Service Worker 的异步 IPC 通信

Electron IpcMainServiceWorker 详解:主进程与 Service Worker 的异步 IPC 通信 Electron IpcMainServiceWorker 详解主进程与 Service Worker 的异步 IPC 通信【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronIpcMainServiceWorker是 Electron 主进程中专用于和 Service Worker 通信的类它是IpcMain的“精细变体”同样是on/once/handle这套监听与请求-响应模型但消息会被精确路由到发出消息的那个 Service Worker而不会泄漏到全局的ipcMain。读完本文你将掌握serviceWorker.ipc实例的获取方式、全部 7 个实例方法的用法、两类事件对象IpcMainServiceWorkerEvent、IpcMainServiceWorkerInvokeEvent的完整字段并能对照源码理解消息从 Service Worker 到主进程的完整分派链路最后给出一套可复制的主进程 Service Worker 双向通信示例。类概述与适用边界IpcMainServiceWorker的官方定义只有一句话Communicate asynchronously from the main process to service workers.从主进程与 Service Worker 异步通信进程模型标注为 Main见 docs/api/ipc-main-service-worker.md。文档中有一个明确的使用边界提示This API is a subtle variation ofIpcMain—targeted for communicating with service workers. For communicating with web frames, consult theIpcMaindocumentation.也就是说与 Web Frame渲染页面通信请用ipcMain与 Service Worker 通信请用serviceWorker.ipc二者是隔离的通道。此外文档沿用了 Electron 内置类的统一约束内置类不能被用户代码继承详见 FAQ: Class inheritance does not work with Electron built-in modules。如何拿到 IpcMainServiceWorker 实例该类实例不通过模块直接导出而是挂在每个ServiceWorkerMain实例的ipc属性上_Readonly_ _Experimental_serviceWorker.ipc → 一个作用域限定在该 Service Worker 的 IpcMainServiceWorker 实例典型获取路径是通过Session的serviceWorkers集合例如监听运行状态变化后按versionId取出 Workerconst { session } require(electron); const ses session.fromPartition(sw-example); const serviceWorkers ses.serviceWorkers; serviceWorkers.on(running-status-changed, ({ versionId, runningStatus }) { if (runningStatus running) { const serviceWorker serviceWorkers.getWorkerFromVersionID(versionId); // serviceWorker.ipc 就是本文的主角 serviceWorker.ipc.on(ping, (event) { console.log(SW said: ping, from version, event.versionId); }); } });仓库测试代码 spec/api-service-worker-main-spec.ts 中的waitForServiceWorker辅助函数采用了完全相同的模式可直接参考。实例方法一览IpcMainServiceWorker提供 7 个实例方法分为“事件监听”与“请求-响应”两组签名与IpcMain高度对称完整签名以 官方文档 为准事件监听组方法说明ipcMainServiceWorker.on(channel, listener)监听channel新消息到达时以listener(event, ...args)调用ipcMainServiceWorker.once(channel, listener)一次性监听器仅在下一条发送到channel的消息时触发触发后自动移除ipcMainServiceWorker.removeListener(channel, listener)从指定channel的监听器数组中移除指定listeneripcMainServiceWorker.removeAllListeners([channel])移除指定channel的全部监听器channel可选其中listener的第一个参数是IpcMainServiceWorkerEvent其余参数为...args。请求-响应组invoke 模型方法说明ipcMainServiceWorker.handle(channel, listener)注册一个可处理invoke消息的 handlerlistener签名FunctionPromiseany \| any首参为IpcMainServiceWorkerInvokeEventipcMainServiceWorker.handleOnce(channel, listener)只处理下一条invoke消息随后自动移除 handler语义同handleipcMainServiceWorker.removeHandler(channel)若存在移除channel上已注册的 handlerhandle/handleOnce对应 Service Worker 侧的ipcRenderer.invoke(channel, ...args)返回 Promiseon/once等对应ipcRenderer.send(channel, ...args)单向通知。这一对应关系可以从仓库测试 fixture spec/fixtures/api/preload-realm/preload-tests.js 得到印证testSend调用ipcRenderer.send(name, ...args)testInvoke调用await ipcRenderer.invoke(name, ...args)。两类事件对象的字段Service Worker 通道上的事件对象与IpcMainEvent最大的不同是没有senderWebContents取而代之的是serviceWorker。IpcMainServiceWorkerEventipcRenderer.send路径上收到的事件继承Event定义见 docs/api/structures/ipc-main-service-worker-event.md字段类型说明typeString固定取值service-workerserviceWorkerServiceWorkerMainReadonly发出该消息的 Service WorkerversionIdNumberService Worker 的版本 IDsessionSession事件关联的Session实例returnValueany将其设置为要回传的值用于同步消息的返回portsMessagePortMain[]随消息传输的 MessagePort 列表replyFunctionreply(channel, ...args)向发出原始消息的发送方回送一条 IPC 消息为保证回复到达正确的进程与上下文应优先使用它来“回应”当前正在处理的消息IpcMainServiceWorkerInvokeEventipcRenderer.invoke路径上收到的事件定义见 docs/api/structures/ipc-main-service-worker-invoke-event.md字段类型说明typeString固定取值service-workerserviceWorkerServiceWorkerMainReadonly发出该消息的 Service WorkerversionIdNumberService Worker 的版本 IDsessionSession事件关联的 Session 实例注意 invoke 事件不含returnValue/ports/reply返回值通过 handler 的 return 值或 Promise resolve 值自动回传底层经event._replyChannel.sendReply({ result })完成见下文源码分析。TS 侧的类型声明位于 typings/internal-electron.d.ts。源码级剖析消息如何路由到正确的 Service Worker主进程的 IPC 总入口在 lib/browser/ipc-dispatch.ts 的addIpcDispatchListeners它订阅了底层api的-ipc-message、-ipc-invoke、-ipc-message-sync、-ipc-ports四类内部事件并按event.type分派。对 Service Worker 通道关键逻辑如下。1. 由 versionId 反查 ServiceWorkerMainService Worker 消息事件本身只携带versionId主进程通过session.serviceWorkers上的两个方法把它解析为 Worker 实例// lib/browser/ipc-dispatch.ts#L24-L35 const getServiceWorkerFromEvent (event) event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId); const addServiceWorkerPropertyToEvent (event) { Object.defineProperty(event, serviceWorker, { get: () event.session.serviceWorkers.getWorkerFromVersionID(event.versionId) }); };这解释了事件对象里serviceWorker是懒加载 getter、而路由用的是带 IfExists 的内部方法——若该版本 Worker 已被销毁getServiceWorkerFromEvent返回undefined消息会被静默丢弃?.短路而不是抛错。2. 单向消息-ipc-message / -ipc-ports直达 Worker 作用域 emitter// lib/browser/ipc-dispatch.ts#L78-L81 } else if (event.type service-worker) { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args); }也就是说send消息只 emit 到那一个serviceWorker.ipcemitter 上不经过webContents.emit(ipc-message, ...)也不会到达全局ipcMain。-ipc-ports路径L160-L178额外先把原生 port 包装为MessagePortMain挂到event.ports再按同样逻辑 emit——这与字段表中ports的说明一一对应。3. invoke 消息按_invokeHandlers查找并回传结果/错误// lib/browser/ipc-dispatch.ts#L106-L122节选 } else if (event.type service-worker) { addServiceWorkerPropertyToEvent(event); const workerIpc getServiceWorkerFromEvent(event)?.ipc; targets.push(workerIpc); } const target targets.find((target) target?._invokeHandlers.has(channel)); if (target) { const handler target._invokeHandlers.get(channel); try { replyWithResult(await Promise.resolve(handler(event, ...args))); } catch (err) { replyWithError(err); } } else { replyWithError(new Error(No handler registered for ${channel})); }由此可以得到三条实操层面的事实handler 的返回值会被Promise.resolve包裹后再回传因此同步返回值和 Promise 都能用handler 抛错时错误会被序列化后回传给调用方Worker 侧invoke的 Promise reject同时主进程console.error一条Error occurred in handler for channel未注册 handler 的channel收到 invoke 不会挂起调用方会收到No handler registered for channel错误——排错时可直接搜索该字符串。4. 与 ipcMain 的隔离有测试背书仓库测试 spec/api-service-worker-main-spec.ts 中专门有一条用例 does not receive message on ipcMainService Worker 通过 preload 调用ipcRenderer.send(ping)后断言全局ipcMain的once(ipcMain, ping)没有被触发。这从测试层面证实了上文源码分析两个通道完全隔离send的消息只会落在对应serviceWorker.ipc上。端到端示例主进程与 Service Worker 双向通信Service Worker 侧没有页面上下文ipcRenderer是通过Service Worker 类型的 preload 脚本注入的主进程先用session.registerPreloadScript({ type: service-worker, filePath })注册模式见 spec/api-service-worker-main-spec.ts 的registerPreload函数Worker 脚本加载后即可require(electron).ipcRenderer。主进程侧Node/CommonJS 示例const { app, session } require(electron); app.whenReady().then(() { const ses session.fromPartition(sw-demo); // 1. 注册 service-worker 类型的 preload向 Worker 注入 ipcRenderer 能力 const preloadId ses.registerPreloadScript({ type: service-worker, filePath: require(path).resolve(__dirname, sw-preload.js) }); ses.serviceWorkers.on(running-status-changed, ({ versionId, runningStatus }) { if (runningStatus ! running) return; const sw ses.serviceWorkers.getWorkerFromVersionID(versionId); if (!sw) return; // 2. 监听单向消息Worker 侧 ipcRenderer.send sw.ipc.on(sw-log, (event) { console.log([${event.serviceWorker.scriptURL}], event.returnValue undefined ? : ); }); // 3. 处理请求-响应Worker 侧 ipcRenderer.invoke sw.ipc.handle(platform-info, () ({ platform: process.platform, electron: process.versions.electron })); // 4. 主进程主动向 Worker 推送消息Worker 侧 ipcRenderer.on sw.send(reload-config, { theme: dark }); }); });Service Worker preload 侧对应 spec/fixtures/api/preload-realm/preload-tests.js 的真实形态const { ipcRenderer } require(electron); ipcRenderer.send(sw-log, worker booted); // 请求-响应resolve 值回传给 invoke 的 Promise ipcRenderer.invoke(platform-info).then(console.log); // 接收主进程 sw.send 推送 ipcRenderer.on(reload-config, (_event, config) { console.log(new config:, config); });Worker 脚本本体只需注册即可例如 spec/fixtures/api/service-workers/sw.js 中的self.addEventListener(install, ...)。测试用例验证的关键行为spec/api-service-worker-main-spec.ts 的describe(ipc)区块覆盖了本文涉及的核心行为可作为功能验收清单启动期即可收到消息L341-L347serviceWorker.ipc在 Workerstarting阶段就能挂once监听器preload 在启动时发出的send(ping)不丢消息常规接收L349-L355Worker 进入running后触发testSend主进程once(serviceWorker.ipc, ping)正常收到通道隔离L357-L373ipcMain收不到 SW 消息见上文第 4 节handle 的 invoke 闭环L377-L383serviceWorker.ipc.handle(ping, () pong)后Worker 侧ipcRenderer.invoke(ping)的 Promise 解析为pongWorker 重启后 handler 依然有效L385-L394_stopAllWorkers()再startWorkerForScope(scope)后主进程已注册的handle仍能响应新的 invoke。这说明handler 注册在主进程侧、与 Worker 进程生命周期解耦——重启 Worker 不需要重新handle。使用注意事项serviceWorker.ipc属性在 ServiceWorkerMain 文档 中标注为Experimental升级 Electron 大版本时建议核对属性可用性由于消息按versionId路由同一session下若存在同一 scope 的多个版本 Worker各自拥有独立的ipcemitter互不串扰event.serviceWorker是 Readonly getter 且按需解析在 handler 中访问该属性时Worker 实例必须仍存在内部走getWorkerFromVersionID跨长时间异步操作后使用请做好空值判断本文所有源码与测试路径均基于当前仓库状态验证方式阅读 lib/browser/ipc-dispatch.ts、docs/api/ipc-main-service-worker.md以及 spec/api-service-worker-main-spec.ts 中的describe(ipc)区块。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表