OpenScreen 原生桥Native Bridge架构解析四层模型、版本化契约与 IPC 传输设计【免费下载链接】openscreenCreate stunning demos for free. Open-source, no subscriptions, no watermarks, and free for commercial use. An alternative to Screen Studio.项目地址: https://gitcode.com/GitHub_Trending/open/openscreen本文基于 OpenScreen 仓库的架构文档 docs/architecture/native-bridge.md系统讲解其 Native Bridge 的设计目标、四层架构分层、版本化契约versioned contracts与统一结果封装并结合 src/native/contracts.ts、electron/ipc/nativeBridge.ts 等源码说明各层的实际实现。读完后你将能够理解 Electron 应用中如何为渲染进程提供一套“单一事实来源、能力可探测、错误可预期”的平台原生能力访问层并掌握在其中新增 domain/action 的完整路径。一、设计目标薄传输、厚契约文档开宗明义地给出了 Native Bridge 的目标Provide a single, resilient source of truth for platform-native capabilities while keeping Electron transport thin and renderer APIs unified.在保持 Electron 传输层薄、渲染端 API 统一的前提下为平台原生能力提供一个单一且坚韧的事实来源。拆开来看包含三个约束单一事实来源Single source of truth所有运行时原生状态当前平台、当前项目路径、当前视频路径、光标遥测加载记录等集中在 Electron 主进程而不是散落在渲染进程的组件状态里传输层要薄Thin transportIPC 通道不承载业务逻辑只负责把一个结构化的NativeBridgeRequest传进主进程、把结构化的NativeBridgeResponse传回来渲染端 API 统一Unified renderer APIsReact 代码只依赖一个客户端src/native/client.ts不直接绑定零散的 Electron IPC 通道。这套设计的收益在于渲染进程永远不需要知道“光标数据到底来自 macOS 的 ScreenCaptureKit helper 还是 Windows 的 WGC 采集器”参见 electron/native/screencapturekit、electron/native/wgc-capture 两个平台原生实现目录它只面对统一的 cursor domain API。二、四层架构从原生适配器到渲染客户端文档把整个桥接体系划分为四层仓库中的目录结构与之一一对应。下面逐层展开并给出每层的落地代码。1. Native adapters原生适配器层平台特定的 provider 实现稳定的领域接口例如光标遥测或系统资产发现。该层的核心是一份“能力接口”electron/native-bridge/cursor/adapter.ts 定义了CursorNativeAdapter接口export interface CursorNativeAdapter { readonly kind: CursorProviderKind; // native | none getCapabilities(): PromiseCursorCapabilities; getRecordingData(videoPath?: string | null): PromiseCursorRecordingData; getTelemetry(videoPath?: string | null): PromiseCursorTelemetryLoadResult; }关键点在于kind字段适配器必须自报家门是native真正的平台原生实现还是none回退实现。当前仓库中注册的适配器是 electron/native-bridge/cursor/telemetryCursorAdapter.tsexport class TelemetryCursorAdapter implements CursorNativeAdapter { readonly kind none as const; async getCapabilities(): PromiseCursorCapabilities { return { telemetry: true, systemAssets: false, provider: this.kind }; } // getRecordingData / getTelemetry 内部先 resolveVideoPath // 解析不到视频路径时返回空数据而不是抛错 }它通过构造函数注入三个依赖loadRecordingData、resolveVideoPath、loadTelemetry自身不含任何平台代码。这正是“适配器模式”的价值上层服务只依赖CursorNativeAdapter接口未来把 macOS/Windows 原生光标采集接进来时只需新增一个kind: native的实现并在 electron/ipc/nativeBridge.ts 的装配处替换服务层与渲染端零改动。此外electron/native-bridge/cursor/recording/ 目录下的factory.ts、windowsNativeRecordingSession.ts、macNativeCursorRecordingSession.ts等文件就是各平台录制会话的具体实现载体由工厂按平台选择。2. Main-process services主进程服务层服务编排适配器持有运行时状态并对外暴露领域级操作。服务层位于 electron/native-bridge/services/当前有三个服务职责关键行为cursorService.ts光标遥测与录制数据从适配器取数据后把“最近一次遥测加载”视频路径 样本数 时间戳写入状态存储projectService.ts项目/视频上下文每个变更类操作保存、加载、设置当前视频路径等执行完都会调用getCurrentContext()刷新 store 中的项目上下文systemService.ts平台与能力信息聚合平台、光标能力、项目能力生成SystemCapabilities并缓存到 store以CursorService.getTelemetry为例可以看到服务层在“取数据”之外承担的两件事async getTelemetry(videoPath?: string | null): PromiseCursorTelemetryPoint[] { const result await this.options.adapter.getTelemetry(videoPath); if (!result.success) { throw new Error(result.message || result.error || Failed to load cursor telemetry); } const resolvedVideoPath videoPath ?? this.options.store.getState().project.currentVideoPath; if (resolvedVideoPath) { this.options.store.markCursorTelemetryLoaded(resolvedVideoPath, result.samples.length); } return result.samples; }失败语义转换适配器返回的软失败success: false 消息被转换为异常交由最外层统一封装成INTERNAL_ERROR错误响应状态回写成功加载后记录lastTelemetryLoad使主进程可以回答“当前遥测对应哪个视频、多少样本”。3. Unified IPC transport统一 IPC 传输层渲染代码只与单个native-bridge:invoke通道通信使用版本化契约。这是整个架构中最“薄”的一层由两端各半段组成Preload 端electron/preload.ts 中只暴露了一个桥接方法contextBridge.exposeInMainWorld(electronAPI, { invokeNativeBridge: TData(request: NativeBridgeRequest) { return ipcRenderer.invoke(NATIVE_BRIDGE_CHANNEL, request) as PromiseTData; }, // ... 其余为向后兼容的 legacy 方法 });主进程端electron/ipc/nativeBridge.ts 的registerNativeBridgeHandlers是唯一注册点。它做了三件体现“坚韧性”的事幂等注册入口处先ipcMain.removeHandler(NATIVE_BRIDGE_CHANNEL)再ipcMain.handle重复调用不会因通道已存在而崩溃入参校验isBridgeRequest先确认请求是带domain和action字符串的对象否则直接返回INVALID_REQUEST错误封装路由 兜底按domainsystem/project/cursor二级 switch 分发到对应服务未识别的 domain 或 action 返回UNSUPPORTED_ACTION任何未捕获异常统一转换为带retryable: true的INTERNAL_ERRORipcMain.handle(NATIVE_BRIDGE_CHANNEL, async (_, request: unknown) { if (!isBridgeRequest(request)) { return createErrorResponse(undefined, INVALID_REQUEST, Invalid native bridge request.); } // ... domain/action 二级路由 ... } catch (error) { return createErrorResponse(requestId, INTERNAL_ERROR, error instanceof Error ? error.message : Unknown native bridge error., true); });注意NativeBridgeContext接口electron/ipc/nativeBridge.ts#L19-L40处理器并不直接依赖 Electron 的具体实现而是依赖一组注入的回调getPlatform、saveProjectFile、loadCursorRecordingData等。这种依赖注入使路由逻辑与主进程的窗口管理、文件 I/O 解耦也更便于测试。4. Renderer client渲染端客户端层React 代码应当消费src/native/client.ts而不是直接绑定临时的 Electron API。src/native/client.ts 对外导出nativeBridgeClient按 domain 组织了三个命名空间system、project、cursor外加一个原始入口rawInvokenativeBridgeClient.cursor.getRecordingData?.(videoPath) // → CursorRecordingData nativeBridgeClient.system.getCapabilities() // → SystemCapabilities nativeBridgeClient.project.saveProjectFile(data, name) // → ProjectFileResult两个细节值得注意请求追踪invokeNativeBridge会在请求未带requestId时自动生成一个优先crypto.randomUUID()回退为req-时间戳-随机数见 src/native/client.ts#L15-L21主进程在响应的meta.requestId中原样带回可用于日志串联两种消费姿势invokeNativeBridge返回完整的结果封装ok判别联合requireNativeBridgeData则在ok: false时直接抛出Error(response.error.message)。命名空间方法统一采用后者让调用方拿到“已保证成功”的数据类型而需要区分错误码的场景如判断是否可重试则使用rawInvoke拿原始封装。客户端对window.electronAPI.invokeNativeBridge缺失时会抛出明确的Native bridge unavailable错误src/native/client.ts#L23-L31提示开发者 preload 未正确暴露传输属于快速失败fail fast设计。三、版本化契约Channel、Version 与 Domain/Action 路由契约文件 src/native/contracts.ts 被主进程与渲染进程共同引用这是“契约版本化”能成立的前提——两端看到的是同一份 TypeScript 类型export const NATIVE_BRIDGE_CHANNEL native-bridge:invoke; export const NATIVE_BRIDGE_VERSION 1;请求形态domain action payloadNativeBridgeRequest是一个判别联合discriminated union当前收录了三个 domain 共 13 个 actiondomainaction说明载荷systemgetPlatform归一化后的平台darwin/win32/linux无systemgetAssetBasePath渲染端资源基路径无systemgetCapabilities能力总览含桥版本、平台、各域能力无projectgetCurrentContext当前项目路径 当前视频路径无projectsaveProjectFile保存工程文件projectData、suggestedName?、existingProjectPath?projectloadProjectFile打开工程文件可预填目录projectFolder?projectloadCurrentProjectFile加载当前工程无projectloadProjectFileFromPath按路径加载pathprojectsetCurrentVideoPath设置当前视频pathprojectgetCurrentVideoPath查询当前视频路径无projectclearCurrentVideoPath清除当前视频路径无cursorgetCapabilities光标能力无cursorgetTelemetry光标遥测点序列videoPath?cursorgetRecordingData完整光标录制数据样本 资产videoPath?主进程对平台做了归一化处理electron/ipc/nativeBridge.ts#L42-L48只有darwin与win32原样保留其余 Node 平台一律折叠为linux保证契约枚举封闭。结果封装Envelope 与稳定错误码文档原则中的“每个响应使用一致的结果封装与稳定错误码”对应契约中的这组类型export type NativeBridgeErrorCode | INVALID_REQUEST // 入参不是合法的桥请求 | UNSUPPORTED_ACTION // domain 存在但 action 未实现 | NOT_FOUND | UNAVAILABLE | INTERNAL_ERROR; // 服务端异常且 retryable: true export interface NativeBridgeMeta { version: typeof NATIVE_BRIDGE_VERSION; requestId: string; timestampMs: number; } export type NativeBridgeResponseTData unknown | { ok: true; data: TData; meta: NativeBridgeMeta } | { ok: false; error: { code: NativeBridgeErrorCode; message: string; retryable: boolean }; meta: NativeBridgeMeta };这个封装带来三个工程收益错误可分类渲染端可以依据code做差异化处理例如UNAVAILABLE时切换 UI 降级方案retryable为 true 时重试而不必解析错误字符串版本可协商meta.version与NATIVE_BRIDGE_VERSION一同下发未来契约升级时可做新旧版本的兼容性判断可观测性requestId贯穿请求-响应两端便于在主进程日志与渲染端表现之间建立因果。此外契约还定义了主进程 → 渲染进程的事件名project.contextChanged、cursor.providerChanged、cursor.telemetryLoaded见 src/native/contracts.ts#L230-L239为“推送式”状态变更预留了通道与“拉取式”的 invoke 请求互补。四、状态存储主进程内的 Single Source of Truth文档第一条原则——“运行时原生状态活在 Electron 主进程”——由 electron/native-bridge/store.ts 中的NativeBridgeStateStore落实。它的状态结构按三个 domain 分片export interface NativeBridgeState { system: { platform: NativePlatform; capabilities: SystemCapabilities | null; }; project: ProjectContext; // currentProjectPath / currentVideoPath cursor: { capabilities: CursorCapabilities | null; lastTelemetryLoad: { videoPath: string; sampleCount: number; loadedAt: number; } | null; }; }实现上有两个特点不可变更新所有 settersetProjectContext、setSystemCapabilities、setCursorCapabilities、markCursorTelemetryLoaded都通过展开旧对象创建新状态避免服务层意外持有可变引用共享单例在 registerNativeBridgeHandlers 中同一个store实例被注入三个服务因此project域的上下文刷新ProjectService每次变更操作后调用getCurrentContext()能立即被cursor域感知——TelemetryCursorAdapter的resolveVideoPath正是靠这个共享状态在调用方未显式传videoPath时兜底解析出当前视频。从源码结构看store目前尚未订阅任何持久化事件它是进程内易失状态跨会话记忆如上次打开的工程目录由渲染端偏好存储体系负责两者职责分离。五、Capability-first先探测再行动第二条原则“能力优先”在契约层面体现为三级能力查询链cursor.getCapabilities返回CursorCapabilitiestelemetry是否支持、systemAssets是否提供系统光标资产、当前provider是native还是nonesystem.getCapabilities将其聚合进SystemCapabilities连同bridgeVersion、platform和project.currentContext一起下发systemService.ts#L27-L42渲染端组件据此决定 UI例如当provider为none时编辑器不应承诺“逐像素还原系统光标”而是使用遥测点位 自绘光标渲染。当前TelemetryCursorAdapter声明的是{ telemetry: true, systemAssets: false, provider: none }意味着本仓库现阶段提供的是遥测级光标能力而NativeCursorAsset含imageDataUrl、hotspotX/Y、scaleFactor、cursorType等字段契约已经就绪是为 macOS/Windows 原生光标资产provider: native预留的数据结构。六、当前落地范围与 Legacy 兼容策略文档“Current rollout”一节列出的初始脚手架在仓库中均可验证文档列出的组件仓库中的对应文件共享契约src/native/contracts.ts渲染端 SDKsrc/native/client.ts主进程状态存储electron/native-bridge/store.ts光标遥测适配器electron/native-bridge/cursor/telemetryCursorAdapter.ts领域服务electron/native-bridge/services/cursor / project / system 三个服务统一处理器注册electron/ipc/nativeBridge.ts文档同时明确指出legacy 的window.electronAPI表面仍然存在以兼容旧代码新特性应优先使用统一桥接客户端。这一点在 electron/preload.ts 中可以直观印证——invokeNativeBridge与大量既有方法getCursorTelemetry、saveProjectFile、getPlatform等零散通道并存于同一个electronAPI对象上。因此实际维护时的迁移纪律是新代码只 importsrc/native/client.ts的nativeBridgeClient不新增对ipcRenderer直接绑定的 preload 方法旧代码逐步替换为桥接调用替换一个删一个 legacy 通道最终让electronAPI收敛为只剩invokeNativeBridge与assetBaseUrl这类非领域性暴露的极简表面。七、设计原则小结与扩展路径回到文档的四条原则它们分别落到了具体机制上原则实现机制Single source of truthNativeBridgeStateStore集中持有系统/项目/光标状态服务层每次变更后回写Capability-firstcursor/system两级getCapabilities渲染端先探测再行动Versioned contracts单一契约文件被两端共享NATIVE_BRIDGE_VERSIONmeta.version随响应下发请求为判别联合扩展新 action 时编译期即可暴露两端遗漏Resilience统一NativeBridgeResponse封装、五个稳定错误码、入参校验、removeHandler幂等注册、异常兜底为retryable的INTERNAL_ERROR基于这套结构向桥中新增一个 domain例如webcam的路径是明确的在 src/native/contracts.ts 的NativeBridgeRequest联合中追加该 domain 的 action 分支并补充对应响应数据类型与错误码必要时在 electron/native-bridge/services/ 新增服务类构造函数注入 store 与具体依赖在 electron/ipc/nativeBridge.ts 的装配函数中实例化服务并在domainswitch 中新增一个 case 分支未知 action 会自动落入UNSUPPORTED_ACTION;在 src/native/client.ts 的nativeBridgeClient上新增对应命名空间方法渲染端只通过该命名空间访问。整个过程中IPC 通道数量保持为 1preload 保持零改动——这正是“薄传输、厚契约”架构的最终体现扩展性来自契约的类型系统而不是新增传输面。【免费下载链接】openscreenCreate stunning demos for free. Open-source, no subscriptions, no watermarks, and free for commercial use. An alternative to Screen Studio.项目地址: https://gitcode.com/GitHub_Trending/open/openscreen创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考