主进程设计说明¶
主进程说明 包含当前规则和文件归属图。本文档解释了导致这些规则和归属的决策与失败经历。
IPC 覆盖与特权导入¶
主进程以用户的完整权限运行。Agent 已经使用 danger-full-access;Electron 沙箱、上下文隔离、导航策略和发送方验证保护了剩余的进程边界。渲染器功能必须被视为受损渲染器可用的一种能力。
IPC 领域模块返回类型化的处理器组。缺失的端点会产生 TS2741;多余的端点名称会产生 TS2353。registerIpcGroups 中缺失的组同样会产生 TS2741。这取代了一种源代码扫描方式——原先扫描用于查找调度器未调用的模块。现在,同时移除注册器导入及其展开语法会导致编译失败。
载荷绑定类型使 (input: unknown) => Result 与无载荷的 () => Result 形式不兼容。这要求显式的解码器。解码在发送方验证之后进行,因此不受信任的 frame 无法到达解析器。
ipc-channel-coverage.test.ts 将名称 ipcMain 限制在 trusted-ipc.ts 中,后者持有发送方检查。否则,别处使用别名导入就可能在没有发送方检查的情况下注册处理器。该测试将受信任的包装器名称限制在 ipc/define-ipc-group.ts:这些包装器接受字符串通道,因此直接使用可能会在已声明组之外创建端点。直接的 IPC_ENDPOINTS.group.name 引用使渲染器的发送行为对相同的覆盖检查保持可见。
index.ts 在模块作用域调用 app.setPath、app.enableSandbox 和 protocol.registerSchemesAsPrivileged。导入它的测试需要 Electron mock。源代码覆盖测试改为读取该文件。
构造与关闭¶
服务构造、窗口、已保存的窗口边界、关闭、开发环境远程设置、会话配置、渲染器事件转发和 IPC 处理器都已移出 index.ts。该文件现在只保留生命周期和分发工作。
createApplicationServices 仍然是一个函数,因为控制流收窄使构造顺序显式化:赋给 const 的服务可以满足后续非空的构造函数参数。将其拆分为多个阶段,就需要在阶段之间传递一个约含 15 个字段的对象,并重新引入顺序风险。它只负责构造。添加事件接线将需要输入多于输出,并会使依赖方向反转。一个自包含且输入很少的代码块(例如 createMessagingServices)可以移到同一文件中的辅助函数,主函数在同一位置等待该辅助函数;辅助函数保留自己的销毁序号。
关闭大体上按构造顺序,而非逆序。浏览器宿主先于其画中画窗口关闭。Provider 运行时先于拥有它们的 Agent 服务停止。销毁序号在构造旁表明这一顺序,而不是在其他地方重复说明。
在 app.whenReady() 之前接线的依赖需要 getter,因为服务尚不存在。在那里捕获 null 永远无法恢复。渲染器转发器和窗口模块现在各自读取一个服务句柄,而不是十一个独立句柄。在构造期间接线的代码可以直接接收值。getter 和可空值都能通过类型检查,因此编译器无法选择正确的生命周期。
类似的生存期问题也适用于窗口:构造函数需要当前窗口,但稍后运行的回调必须再次获取它。macOS 可以在不结束应用程序的情况下关闭并重建主窗口。
MCP 登录重定向地址¶
要求用户登录的 MCP 服务器会将授权许可发送回一个重定向地址。OpenBot 向授权服务器(RFC 7591)注册该地址,并在 /authorize 和 /token 处再次发送它。三者必须一致,否则授权服务器会在它自己的页面上停止登录流程,而 OpenBot 在那里既看不到任何信息,也无法做出任何解释。Canva 就是这样:它在注册时接受 openbot://mcp-auth,然后在 /authorize 处返回 Invalid redirect URI.。
因此,src/main/mcp-oauth-redirect-server.ts 监听 127.0.0.1,它监听的地址就是重定向地址。这是 RFC 8252 的回环重定向,也是目录中每个已登录条目所接受的形式。openbot://mcp-auth 仍作为无法绑定端口的机器上的回退方案,src/main/deep-link-router.ts 负责处理这类链接。
对于开发者或打包后的构建,没有任何配置:
- 端口是操作系统分配的,并且每次启动都会变化。授权服务器必须忽略回环重定向的端口;由于注册是动态的,所以不会有人手工注册任何端口。
- 存储的注册信息如果指定了不同地址,会被替换,但仅限于需要浏览器的这次尝试。当存储的
redirect_uris不包含本次运行的地址时,src/backend/mcp-oauth-provider.ts中的clientInformation()返回undefined,这会使 SDK 重新注册。一条仍持有刷新令牌的记录会先让现有客户端再使用一次:刷新不使用重定向地址,而如果用新的client_id消费刷新令牌,会被拒绝,并导致用户重新登录。以下两种尝试绝不会替换注册:静默刷新,以及交换已经到手的授权许可——后者必须使用该授权许可签发时所用的client_id。 describeUnusableRedirectUrl会拒绝无法接收授权许可的地址:https地址,或不在本机上的纯文本地址。
远程服务与解耦的协议¶
remote-server-manager.ts 在将其关注点移至扁平的 remote-server-* 和 remote-*-decoding 系列之前,共有 2,828 行。管理器通过回调连接这些模块。请求路径无需了解事件流,事件流也无需了解错误路径。回调连接保持了这些依赖边界。
Team API 路由与 IPC 处理器采用相同的窄依赖风格。以下四条行为规则说明了分派顺序:
- 兼容性检查最先运行,以便旧客户端能够了解如何更新,而不是在每个请求上都收到 426。
- 远程屏幕委派先于协议门禁执行,因为加载查看器的浏览器不会发送协议头或令牌。
- 身份验证先于路由选择,因此没有令牌的未知路径将返回 401。
- 分派器处理未匹配的路径和方法。已发布的客户端在已知路径上使用错误方法时会收到 404。
一个分派器的 catch 块能让意外错误对日志记录器可见并返回 500。局部 catch 块可能会错误地将它们转换为 400。这里只有一个 HttpError 类,因为 instanceof 无法识别第二个定义,从而会把预期的 400 变成 500。
显式的 Promise<RouteOutcome> 返回类型注解会在路由执行到末尾时产生 TS2366。本项目没有启用 noImplicitReturns 防护。如果没有该注解,类型推断可能接受 undefined,从而变成静默的 404。
主机端(Host)和预加载(preload)解码器保持分离,因为它们验证不同的信任边界。HTTPS V1/V3 和 WebRTC V2 保持分离,因为它们的编码是已发布的契约。控制面方法保留在 RemoteTeamDirectory 上,因为它们联系的是与宿主机不同的服务器。
ipc-channel-coverage.test.ts 检查 src/main 下的 decode*FromHost 与预加载中的 decode*FromMain 之间的名称一一对应关系。这可以防止删除一侧并将其调用者重定向到另一侧,从而可能将受信任的主进程验证应用于远程服务器。第二个测试会拒绝一个仅作为其孪生对象别名的解码器。审查仍需检测调用另一个解码器的包装器。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 openbo/openbot