架構(gòu)解析:四層模型、版本化契約與 IPC 傳輸設(shè)計(jì))
OpenScreen 原生橋Native Bridge架構(gòu)解析四層模型、版本化契約與 IPC 傳輸設(shè)計(jì)【免費(fèi)下載鏈接】openscreenCreate stunning demos for free. Open-source, no subscriptions, no watermarks, and free for commercial use. An alternative to Screen Studio.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/open/openscreen本文基于 OpenScreen 倉(cāng)庫(kù)的架構(gòu)文檔 docs/architecture/native-bridge.md系統(tǒng)講解其 Native Bridge 的設(shè)計(jì)目標(biāo)、四層架構(gòu)分層、版本化契約versioned contracts與統(tǒng)一結(jié)果封裝并結(jié)合 src/native/contracts.ts、electron/ipc/nativeBridge.ts 等源碼說(shuō)明各層的實(shí)際實(shí)現(xiàn)。讀完后你將能夠理解 Electron 應(yīng)用中如何為渲染進(jìn)程提供一套“單一事實(shí)來(lái)源、能力可探測(cè)、錯(cuò)誤可預(yù)期”的平臺(tái)原生能力訪問(wèn)層并掌握在其中新增 domain/action 的完整路徑。一、設(shè)計(jì)目標(biāo)薄傳輸、厚契約文檔開(kāi)宗明義地給出了 Native Bridge 的目標(biāo)Provide a single, resilient source of truth for platform-native capabilities while keeping Electron transport thin and renderer APIs unified.在保持 Electron 傳輸層薄、渲染端 API 統(tǒng)一的前提下為平臺(tái)原生能力提供一個(gè)單一且堅(jiān)韌的事實(shí)來(lái)源。拆開(kāi)來(lái)看包含三個(gè)約束單一事實(shí)來(lái)源Single source of truth所有運(yùn)行時(shí)原生狀態(tài)當(dāng)前平臺(tái)、當(dāng)前項(xiàng)目路徑、當(dāng)前視頻路徑、光標(biāo)遙測(cè)加載記錄等集中在 Electron 主進(jìn)程而不是散落在渲染進(jìn)程的組件狀態(tài)里傳輸層要薄Thin transportIPC 通道不承載業(yè)務(wù)邏輯只負(fù)責(zé)把一個(gè)結(jié)構(gòu)化的NativeBridgeRequest傳進(jìn)主進(jìn)程、把結(jié)構(gòu)化的NativeBridgeResponse傳回來(lái)渲染端 API 統(tǒng)一Unified renderer APIsReact 代碼只依賴一個(gè)客戶端src/native/client.ts不直接綁定零散的 Electron IPC 通道。這套設(shè)計(jì)的收益在于渲染進(jìn)程永遠(yuǎn)不需要知道“光標(biāo)數(shù)據(jù)到底來(lái)自 macOS 的 ScreenCaptureKit helper 還是 Windows 的 WGC 采集器”參見(jiàn) electron/native/screencapturekit、electron/native/wgc-capture 兩個(gè)平臺(tái)原生實(shí)現(xiàn)目錄它只面對(duì)統(tǒng)一的 cursor domain API。二、四層架構(gòu)從原生適配器到渲染客戶端文檔把整個(gè)橋接體系劃分為四層倉(cāng)庫(kù)中的目錄結(jié)構(gòu)與之一一對(duì)應(yīng)。下面逐層展開(kāi)并給出每層的落地代碼。1. Native adapters原生適配器層平臺(tái)特定的 provider 實(shí)現(xiàn)穩(wěn)定的領(lǐng)域接口例如光標(biāo)遙測(cè)或系統(tǒng)資產(chǎn)發(fā)現(xiàn)。該層的核心是一份“能力接口”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; }關(guān)鍵點(diǎn)在于kind字段適配器必須自報(bào)家門(mén)是native真正的平臺(tái)原生實(shí)現(xiàn)還是none回退實(shí)現(xiàn)。當(dāng)前倉(cāng)庫(kù)中注冊(cè)的適配器是 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 內(nèi)部先 resolveVideoPath // 解析不到視頻路徑時(shí)返回空數(shù)據(jù)而不是拋錯(cuò) }它通過(guò)構(gòu)造函數(shù)注入三個(gè)依賴loadRecordingData、resolveVideoPath、loadTelemetry自身不含任何平臺(tái)代碼。這正是“適配器模式”的價(jià)值上層服務(wù)只依賴CursorNativeAdapter接口未來(lái)把 macOS/Windows 原生光標(biāo)采集接進(jìn)來(lái)時(shí)只需新增一個(gè)kind: native的實(shí)現(xiàn)并在 electron/ipc/nativeBridge.ts 的裝配處替換服務(wù)層與渲染端零改動(dòng)。此外electron/native-bridge/cursor/recording/ 目錄下的factory.ts、windowsNativeRecordingSession.ts、macNativeCursorRecordingSession.ts等文件就是各平臺(tái)錄制會(huì)話的具體實(shí)現(xiàn)載體由工廠按平臺(tái)選擇。2. Main-process services主進(jìn)程服務(wù)層服務(wù)編排適配器持有運(yùn)行時(shí)狀態(tài)并對(duì)外暴露領(lǐng)域級(jí)操作。服務(wù)層位于 electron/native-bridge/services/當(dāng)前有三個(gè)服務(wù)職責(zé)關(guān)鍵行為cursorService.ts光標(biāo)遙測(cè)與錄制數(shù)據(jù)從適配器取數(shù)據(jù)后把“最近一次遙測(cè)加載”視頻路徑 樣本數(shù) 時(shí)間戳寫(xiě)入狀態(tài)存儲(chǔ)projectService.ts項(xiàng)目/視頻上下文每個(gè)變更類操作保存、加載、設(shè)置當(dāng)前視頻路徑等執(zhí)行完都會(huì)調(diào)用getCurrentContext()刷新 store 中的項(xiàng)目上下文systemService.ts平臺(tái)與能力信息聚合平臺(tái)、光標(biāo)能力、項(xiàng)目能力生成SystemCapabilities并緩存到 store以CursorService.getTelemetry為例可以看到服務(wù)層在“取數(shù)據(jù)”之外承擔(dān)的兩件事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; }失敗語(yǔ)義轉(zhuǎn)換適配器返回的軟失敗success: false 消息被轉(zhuǎn)換為異常交由最外層統(tǒng)一封裝成INTERNAL_ERROR錯(cuò)誤響應(yīng)狀態(tài)回寫(xiě)成功加載后記錄lastTelemetryLoad使主進(jìn)程可以回答“當(dāng)前遙測(cè)對(duì)應(yīng)哪個(gè)視頻、多少樣本”。3. Unified IPC transport統(tǒng)一 IPC 傳輸層渲染代碼只與單個(gè)native-bridge:invoke通道通信使用版本化契約。這是整個(gè)架構(gòu)中最“薄”的一層由兩端各半段組成Preload 端electron/preload.ts 中只暴露了一個(gè)橋接方法contextBridge.exposeInMainWorld(electronAPI, { invokeNativeBridge: TData(request: NativeBridgeRequest) { return ipcRenderer.invoke(NATIVE_BRIDGE_CHANNEL, request) as PromiseTData; }, // ... 其余為向后兼容的 legacy 方法 });主進(jìn)程端electron/ipc/nativeBridge.ts 的registerNativeBridgeHandlers是唯一注冊(cè)點(diǎn)。它做了三件體現(xiàn)“堅(jiān)韌性”的事冪等注冊(cè)入口處先ipcMain.removeHandler(NATIVE_BRIDGE_CHANNEL)再ipcMain.handle重復(fù)調(diào)用不會(huì)因通道已存在而崩潰入?yún)⑿r?yàn)isBridgeRequest先確認(rèn)請(qǐng)求是帶domain和action字符串的對(duì)象否則直接返回INVALID_REQUEST錯(cuò)誤封裝路由 兜底按domainsystem/project/cursor二級(jí) switch 分發(fā)到對(duì)應(yīng)服務(wù)未識(shí)別的 domain 或 action 返回UNSUPPORTED_ACTION任何未捕獲異常統(tǒng)一轉(zhuǎn)換為帶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 二級(jí)路由 ... } 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 的具體實(shí)現(xiàn)而是依賴一組注入的回調(diào)getPlatform、saveProjectFile、loadCursorRecordingData等。這種依賴注入使路由邏輯與主進(jìn)程的窗口管理、文件 I/O 解耦也更便于測(cè)試。4. Renderer client渲染端客戶端層React 代碼應(yīng)當(dāng)消費(fèi)src/native/client.ts而不是直接綁定臨時(shí)的 Electron API。src/native/client.ts 對(duì)外導(dǎo)出nativeBridgeClient按 domain 組織了三個(gè)命名空間system、project、cursor外加一個(gè)原始入口rawInvokenativeBridgeClient.cursor.getRecordingData?.(videoPath) // → CursorRecordingData nativeBridgeClient.system.getCapabilities() // → SystemCapabilities nativeBridgeClient.project.saveProjectFile(data, name) // → ProjectFileResult兩個(gè)細(xì)節(jié)值得注意請(qǐng)求追蹤invokeNativeBridge會(huì)在請(qǐng)求未帶requestId時(shí)自動(dòng)生成一個(gè)優(yōu)先crypto.randomUUID()回退為req-時(shí)間戳-隨機(jī)數(shù)見(jiàn) src/native/client.ts#L15-L21主進(jìn)程在響應(yīng)的meta.requestId中原樣帶回可用于日志串聯(lián)兩種消費(fèi)姿勢(shì)invokeNativeBridge返回完整的結(jié)果封裝ok判別聯(lián)合requireNativeBridgeData則在ok: false時(shí)直接拋出Error(response.error.message)。命名空間方法統(tǒng)一采用后者讓調(diào)用方拿到“已保證成功”的數(shù)據(jù)類型而需要區(qū)分錯(cuò)誤碼的場(chǎng)景如判斷是否可重試則使用rawInvoke拿原始封裝??蛻舳藢?duì)window.electronAPI.invokeNativeBridge缺失時(shí)會(huì)拋出明確的Native bridge unavailable錯(cuò)誤src/native/client.ts#L23-L31提示開(kāi)發(fā)者 preload 未正確暴露傳輸屬于快速失敗fail fast設(shè)計(jì)。三、版本化契約Channel、Version 與 Domain/Action 路由契約文件 src/native/contracts.ts 被主進(jìn)程與渲染進(jìn)程共同引用這是“契約版本化”能成立的前提——兩端看到的是同一份 TypeScript 類型export const NATIVE_BRIDGE_CHANNEL native-bridge:invoke; export const NATIVE_BRIDGE_VERSION 1;請(qǐng)求形態(tài)domain action payloadNativeBridgeRequest是一個(gè)判別聯(lián)合discriminated union當(dāng)前收錄了三個(gè) domain 共 13 個(gè) actiondomainaction說(shuō)明載荷systemgetPlatform歸一化后的平臺(tái)darwin/win32/linux無(wú)systemgetAssetBasePath渲染端資源基路徑無(wú)systemgetCapabilities能力總覽含橋版本、平臺(tái)、各域能力無(wú)projectgetCurrentContext當(dāng)前項(xiàng)目路徑 當(dāng)前視頻路徑無(wú)projectsaveProjectFile保存工程文件projectData、suggestedName?、existingProjectPath?projectloadProjectFile打開(kāi)工程文件可預(yù)填目錄projectFolder?projectloadCurrentProjectFile加載當(dāng)前工程無(wú)projectloadProjectFileFromPath按路徑加載pathprojectsetCurrentVideoPath設(shè)置當(dāng)前視頻pathprojectgetCurrentVideoPath查詢當(dāng)前視頻路徑無(wú)projectclearCurrentVideoPath清除當(dāng)前視頻路徑無(wú)cursorgetCapabilities光標(biāo)能力無(wú)cursorgetTelemetry光標(biāo)遙測(cè)點(diǎn)序列videoPath?cursorgetRecordingData完整光標(biāo)錄制數(shù)據(jù)樣本 資產(chǎn)videoPath?主進(jìn)程對(duì)平臺(tái)做了歸一化處理electron/ipc/nativeBridge.ts#L42-L48只有darwin與win32原樣保留其余 Node 平臺(tái)一律折疊為linux保證契約枚舉封閉。結(jié)果封裝Envelope 與穩(wěn)定錯(cuò)誤碼文檔原則中的“每個(gè)響應(yīng)使用一致的結(jié)果封裝與穩(wěn)定錯(cuò)誤碼”對(duì)應(yīng)契約中的這組類型export type NativeBridgeErrorCode | INVALID_REQUEST // 入?yún)⒉皇呛戏ǖ臉蛘?qǐng)求 | UNSUPPORTED_ACTION // domain 存在但 action 未實(shí)現(xiàn) | NOT_FOUND | UNAVAILABLE | INTERNAL_ERROR; // 服務(wù)端異常且 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 };這個(gè)封裝帶來(lái)三個(gè)工程收益錯(cuò)誤可分類渲染端可以依據(jù)code做差異化處理例如UNAVAILABLE時(shí)切換 UI 降級(jí)方案retryable為 true 時(shí)重試而不必解析錯(cuò)誤字符串版本可協(xié)商meta.version與NATIVE_BRIDGE_VERSION一同下發(fā)未來(lái)契約升級(jí)時(shí)可做新舊版本的兼容性判斷可觀測(cè)性requestId貫穿請(qǐng)求-響應(yīng)兩端便于在主進(jìn)程日志與渲染端表現(xiàn)之間建立因果。此外契約還定義了主進(jìn)程 → 渲染進(jìn)程的事件名project.contextChanged、cursor.providerChanged、cursor.telemetryLoaded見(jiàn) src/native/contracts.ts#L230-L239為“推送式”狀態(tài)變更預(yù)留了通道與“拉取式”的 invoke 請(qǐng)求互補(bǔ)。四、狀態(tài)存儲(chǔ)主進(jìn)程內(nèi)的 Single Source of Truth文檔第一條原則——“運(yùn)行時(shí)原生狀態(tài)活在 Electron 主進(jìn)程”——由 electron/native-bridge/store.ts 中的NativeBridgeStateStore落實(shí)。它的狀態(tài)結(jié)構(gòu)按三個(gè) 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; }; }實(shí)現(xiàn)上有兩個(gè)特點(diǎn)不可變更新所有 settersetProjectContext、setSystemCapabilities、setCursorCapabilities、markCursorTelemetryLoaded都通過(guò)展開(kāi)舊對(duì)象創(chuàng)建新?tīng)顟B(tài)避免服務(wù)層意外持有可變引用共享單例在 registerNativeBridgeHandlers 中同一個(gè)store實(shí)例被注入三個(gè)服務(wù)因此project域的上下文刷新ProjectService每次變更操作后調(diào)用getCurrentContext()能立即被cursor域感知——TelemetryCursorAdapter的resolveVideoPath正是靠這個(gè)共享狀態(tài)在調(diào)用方未顯式傳videoPath時(shí)兜底解析出當(dāng)前視頻。從源碼結(jié)構(gòu)看store目前尚未訂閱任何持久化事件它是進(jìn)程內(nèi)易失狀態(tài)跨會(huì)話記憶如上次打開(kāi)的工程目錄由渲染端偏好存儲(chǔ)體系負(fù)責(zé)兩者職責(zé)分離。五、Capability-first先探測(cè)再行動(dòng)第二條原則“能力優(yōu)先”在契約層面體現(xiàn)為三級(jí)能力查詢鏈cursor.getCapabilities返回CursorCapabilitiestelemetry是否支持、systemAssets是否提供系統(tǒng)光標(biāo)資產(chǎn)、當(dāng)前provider是native還是nonesystem.getCapabilities將其聚合進(jìn)SystemCapabilities連同bridgeVersion、platform和project.currentContext一起下發(fā)systemService.ts#L27-L42渲染端組件據(jù)此決定 UI例如當(dāng)provider為none時(shí)編輯器不應(yīng)承諾“逐像素還原系統(tǒng)光標(biāo)”而是使用遙測(cè)點(diǎn)位 自繪光標(biāo)渲染。當(dāng)前TelemetryCursorAdapter聲明的是{ telemetry: true, systemAssets: false, provider: none }意味著本倉(cāng)庫(kù)現(xiàn)階段提供的是遙測(cè)級(jí)光標(biāo)能力而NativeCursorAsset含imageDataUrl、hotspotX/Y、scaleFactor、cursorType等字段契約已經(jīng)就緒是為 macOS/Windows 原生光標(biāo)資產(chǎn)provider: native預(yù)留的數(shù)據(jù)結(jié)構(gòu)。六、當(dāng)前落地范圍與 Legacy 兼容策略文檔“Current rollout”一節(jié)列出的初始腳手架在倉(cāng)庫(kù)中均可驗(yàn)證文檔列出的組件倉(cāng)庫(kù)中的對(duì)應(yīng)文件共享契約src/native/contracts.ts渲染端 SDKsrc/native/client.ts主進(jìn)程狀態(tài)存儲(chǔ)electron/native-bridge/store.ts光標(biāo)遙測(cè)適配器electron/native-bridge/cursor/telemetryCursorAdapter.ts領(lǐng)域服務(wù)electron/native-bridge/services/cursor / project / system 三個(gè)服務(wù)統(tǒng)一處理器注冊(cè)electron/ipc/nativeBridge.ts文檔同時(shí)明確指出legacy 的window.electronAPI表面仍然存在以兼容舊代碼新特性應(yīng)優(yōu)先使用統(tǒng)一橋接客戶端。這一點(diǎn)在 electron/preload.ts 中可以直觀印證——invokeNativeBridge與大量既有方法getCursorTelemetry、saveProjectFile、getPlatform等零散通道并存于同一個(gè)electronAPI對(duì)象上。因此實(shí)際維護(hù)時(shí)的遷移紀(jì)律是新代碼只 importsrc/native/client.ts的nativeBridgeClient不新增對(duì)ipcRenderer直接綁定的 preload 方法舊代碼逐步替換為橋接調(diào)用替換一個(gè)刪一個(gè) legacy 通道最終讓electronAPI收斂為只剩invokeNativeBridge與assetBaseUrl這類非領(lǐng)域性暴露的極簡(jiǎn)表面。七、設(shè)計(jì)原則小結(jié)與擴(kuò)展路徑回到文檔的四條原則它們分別落到了具體機(jī)制上原則實(shí)現(xiàn)機(jī)制Single source of truthNativeBridgeStateStore集中持有系統(tǒng)/項(xiàng)目/光標(biāo)狀態(tài)服務(wù)層每次變更后回寫(xiě)Capability-firstcursor/system兩級(jí)getCapabilities渲染端先探測(cè)再行動(dòng)Versioned contracts單一契約文件被兩端共享NATIVE_BRIDGE_VERSIONmeta.version隨響應(yīng)下發(fā)請(qǐng)求為判別聯(lián)合擴(kuò)展新 action 時(shí)編譯期即可暴露兩端遺漏Resilience統(tǒng)一NativeBridgeResponse封裝、五個(gè)穩(wěn)定錯(cuò)誤碼、入?yún)⑿r?yàn)、removeHandler冪等注冊(cè)、異常兜底為retryable的INTERNAL_ERROR基于這套結(jié)構(gòu)向橋中新增一個(gè) domain例如webcam的路徑是明確的在 src/native/contracts.ts 的NativeBridgeRequest聯(lián)合中追加該 domain 的 action 分支并補(bǔ)充對(duì)應(yīng)響應(yīng)數(shù)據(jù)類型與錯(cuò)誤碼必要時(shí)在 electron/native-bridge/services/ 新增服務(wù)類構(gòu)造函數(shù)注入 store 與具體依賴在 electron/ipc/nativeBridge.ts 的裝配函數(shù)中實(shí)例化服務(wù)并在domainswitch 中新增一個(gè) case 分支未知 action 會(huì)自動(dòng)落入U(xiǎn)NSUPPORTED_ACTION;在 src/native/client.ts 的nativeBridgeClient上新增對(duì)應(yīng)命名空間方法渲染端只通過(guò)該命名空間訪問(wèn)。整個(gè)過(guò)程中IPC 通道數(shù)量保持為 1preload 保持零改動(dòng)——這正是“薄傳輸、厚契約”架構(gòu)的最終體現(xiàn)擴(kuò)展性來(lái)自契約的類型系統(tǒng)而不是新增傳輸面。【免費(fèi)下載鏈接】openscreenCreate stunning demos for free. Open-source, no subscriptions, no watermarks, and free for commercial use. An alternative to Screen Studio.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/open/openscreen創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考