← [ BACK_TO_NOTES ]

Electron IPC 模式

Electron IPC 的模式选择与安全边界:renderer→main 单向消息、invoke/handle 请求响应、main→renderer 推送分别解决不同通信需求;所有高权限 IPC 都应经 preload/contextBridge 暴露窄能力并在 main 验证 sender 与参数。

#desktop #electron #synthesis #evergreen

[!info] related notes

Electron IPC 模式

一句话关系

Electron IPC 不是一个万能 RPC API。

常见通信需求至少分成三类:

renderer → main one-way
renderer → main request/response
main → renderer event/push

每类应该使用不同模式,并通过 preload 暴露固定 capability。

为什么需要 IPC

Electron 的 main 与 renderer 承担不同职责。

Renderer:

  • UI;
  • DOM;
  • Web APIs;
  • 低权限页面逻辑。

Main:

  • BrowserWindow;
  • filesystem / OS integration;
  • native dialog;
  • app lifecycle;
  • privileged operations。

因此 renderer 需要高权限能力时,不应直接拥有该能力,而应:

renderer
→ preload API
→ IPC
→ main
→ privileged operation

Pattern 1:Renderer → Main 单向消息

典型 API:

ipcRenderer.send
→ ipcMain.on

适合:

  • fire-and-forget;
  • analytics/event;
  • 无返回值 command;
  • UI 通知 main。

例如:

setWindowTitle(title)

如果 renderer 不需要 result,就没必要构造 request/response。

Pattern 2:Renderer → Main 请求响应

典型 API:

ipcRenderer.invoke
→ ipcMain.handle

它返回 Promise,适合:

  • 打开文件对话框;
  • 读取配置;
  • 获取 app version;
  • 执行有结果的 privileged command。

例如:

window.appApi.openFile()
→ invoke fixed channel
→ main handle
→ return selected file metadata

对于 request/response,这是当前更自然的默认选择。

官方 Electron IPC 教程也优先使用 invoke/handle,而不是手工维护 request-id + reply channel。

Pattern 3:Main → Renderer 推送

典型 API:

webContents.send
→ ipcRenderer.on

适合:

  • update available;
  • native menu action;
  • OS event;
  • main process state change。

Main 必须明确发送给哪个 WebContents。

它不是全局 broadcast bus。

sendSync

ipcRenderer.sendSync 会阻塞 renderer,直到 main 返回。

这会:

  • 卡住 UI;
  • 放大死锁/卡顿风险;
  • 让 privileged operation 阻塞页面。

Electron 官方建议尽量避免它。

如果一个操作本质异步,优先 invoke/handle。

旧式 send + reply

历史上可以:

send(request)
→ ipcMain.on
→ event.reply(response)

构造双向通信。

但它需要自己处理:

  • request correlation;
  • listener lifecycle;
  • error mapping;
  • concurrent requests。

对普通 request/response,invoke/handle 更简洁。

Preload 不应该暴露原始 IPC

错误方向:

window.ipc.send(channel, payload)
window.ipc.invoke(channel, payload)

页面可以自己选择 channel。

更安全:

window.files.open()
window.settings.load()
window.updater.install()

每个方法映射到固定 IPC capability。

Sender Validation

IPC 来自 renderer 时,main 不能因为“这是我自己的窗口”就默认信任。

Electron 官方安全清单明确建议:

validate the sender of all IPC messages.

尤其当存在:

  • iframe;
  • remote content;
  • child window;
  • navigation;
  • multiple WebContents;

时,要验证 event.senderFrame / origin 等来源信息。

参数 Validation

即使 channel 固定:

deleteFile(path)

仍然必须验证:

  • path 类型;
  • path 是否在允许目录;
  • symlink / traversal;
  • 当前用户/业务权限;
  • 当前 app state。

preload 的 TypeScript 类型不是安全验证。

Runtime input 仍应视为不可信。

Error Contract

ipcMain.handle 内抛出的 Error 跨 IPC 后不会透明保留完整对象。

Electron 当前文档明确指出,main handler 的 error 会序列化,renderer 主要得到 message 等有限信息。

因此复杂系统应自己定义稳定 error contract,例如:

{ ok: false, code: “FILE_NOT_ALLOWED”, message: ”…” }

而不是依赖 Node Error prototype 跨进程传播。

Event Subscription

对于 main → renderer event:

onUpdateAvailable(callback)

preload 应:

  • 注册固定 channel;
  • 丢弃底层 IPC event;
  • 只把 payload 传给 callback;
  • 返回 unsubscribe function。

这样 renderer 能在组件 unmount 时清理监听器。

Channel 命名

channel 名称本身没有语义魔法。

类似:

dialog:openFile
settings:get
updater:install

只是 namespace convention。

真正安全性来自:

  • fixed capability;
  • sender validation;
  • input validation;
  • authorization;
  • narrow return value。

大数据与高频通信

IPC 有 serialization / copy / scheduling 成本。

不要把:

  • 高频鼠标事件;
  • 大二进制对象;
  • 每帧状态;
  • 超大 JSON;

机械地通过普通 invoke/send 往返。

需要时考虑:

  • batch;
  • MessagePort / postMessage;
  • Transferable;
  • shared/streaming design;
  • 将计算放在正确进程。

Renderer 不是 Trusted Backend

一个重要心智模型:

Renderer compromise 应被当作现实威胁模型。

所以 main IPC handler 类似本地 privileged API endpoint。

应像服务端 API 一样:

  • authenticate source;
  • validate input;
  • authorize action;
  • minimize output;
  • log sensitive operation。

模式选择表

需求首选模式
renderer 通知 main,无结果send / on
renderer 调 main,需要结果invoke / handle
main 主动通知 rendererwebContents.send / renderer listener
同步拿结果尽量避免 sendSync
任意 channel RPC不推荐
高权限操作fixed API + sender/input validation

常见失败模式

万能 RPC

invoke(channel, args)

→ renderer 获得过宽能力。

没验证 sender

→ 意外 frame / remote page 可能调用 privileged handler。

只做 TypeScript 校验

→ runtime attacker 不受 TS 类型限制。

main 抛原始 Error 给 renderer

→ contract 不稳定,内部细节可能泄漏。

on() 监听器永不清理

→ 重复回调与内存泄漏。

sendSync 做文件或网络 I/O

→ renderer 卡顿。

学习检查

  1. send/on、invoke/handle、webContents.send 分别解决哪类通信?
  2. 为什么 request/response 通常优先 invoke/handle?
  3. 为什么万能 invoke(channel) 会削弱 preload capability boundary?
  4. sender validation 与 payload validation 为什么必须同时存在?
  5. 为什么 TypeScript 类型不能替代 runtime validation?
  6. main handler 的 Error 为什么不适合作为跨 IPC 稳定 contract?
  7. main→renderer event 的 preload wrapper 为什么应该隐藏原始 IPC event 并返回 unsubscribe?
  8. 给“打开文件、保存设置、更新可用通知”三个需求分别选择 IPC 模式。
创建于 2026/4/13 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本