MUNDERDIFFLIN / LEARN
CHAPTER 04
04 · PRIVILEGE BOUNDARY10 分钟阅读进阶

Main、Preload、Renderer、Shared 的权限分界

Renderer 被 Chromium sandbox 隔离;Preload 用 contextBridge 暴露明确方法;所有进程、文件、Git、数据库与系统能力留在 Main。

本课核心问题为什么一个桌面应用要拆成 Main、Preload、Renderer?
学完你能
  • 判断一项能力应放在哪个进程。
  • 理解 IPC 是权限边界而非普通函数调用。
01
四层职责

Shared 共享契约,Preload 共享能力但不共享权限

Shared 只能放无 Electron/UI 依赖的类型与 provider 规则;Renderer 负责视图和本地交互状态;Preload 把 IPC 包装成 window.cth;Main 是唯一可信执行层。

交互图解

Electron 权限分层

01
RendererReact · Zustand · Pixi · xterm · Monaco
↓
02
Preload / window.cthcontextBridge · typed IPC capabilities
↓
03
Main ProcessPTY · FS · Git · SQLite · Hive · network
↓
04
OS 与外部进程node-pty · git/gh · CLI agents · filesystem
所有 Node 能力都留在 Main;Renderer 只能经 preload 暴露的 cth API 请求。
02
为什么需要 Preload

页面不能直接 import Node,也不能拿到任意 IPC

如果把 ipcRenderer 或 fs 暴露给页面,任何 renderer 注入都能升级为本地文件/进程权限。当前 bridge 只暴露命名方法,Main 再做类型、路径、scheme 与状态校验。

  • 01进程:pty:spawn / write / resize / kill 全部在 Main。
  • 02文件:fs:* IPC 调用 main/fs.ts 的 safeJoin 等路径保护。
  • 03Git/GitHub:child_process 调 git/gh,Renderer 只接收结构化结果。
  • 04数据库:better-sqlite3 是 native、同步模块,只在 Main 打开。
03
事件返回

请求用 invoke/handle,流式事件用动态 channel

spawn/write 等请求用 Promise 风格 invoke/handle。PTY 输出与退出用 `pty:data:<id>`、`pty:exit:<id>` 推送;preload 返回取消订阅函数,renderer 的 terminalPool 全生命周期只订阅一次。

交互图解

Prompt 与终端输出往返

消息队列prompt 等待
安全投递门idle · quiet · no picker
pty:write × 2文本 → 140ms → Enter
CLI Agent模型 / tool loop
pty:data:idowner window
xterm + parser显示 + 状态
输入需要等待安全窗口;输出按 PTY owner 回送,并同时进入 xterm 与状态解析。
04
设计练习

把“给 Renderer 一个能力”改写成最小契约

好的 Preload API 以用户意图命名,参数窄、结果可序列化、错误可分类;Main 对路径、id 与枚举重新校验。

暴露 openProject(projectId) 比暴露 readFile(path) 更容易守住项目边界。

LESSON CHECKPOINT

能用自己的话回答核心问题了吗?

Renderer 只表达意图,Main 才执行特权动作。