/** * WireTransport — main 端的真 Electron wire 桥接。 * * 责任: * - 在 `ipcMain.handle('__electron-deck:invoke')` / `'__electron-deck:probe'` 上路由 * webview → main 的 RPC 请求,按 `senderPolicy` gating + kind 派发到 host / * simulator handler,序列化成 `InvokeResponse` 帧。 * - 订阅 framework `EventBus`,把 declared `HostEvent.publish(payload)` 通过 * `webContents.send('__electron-deck:event', envelope)` 推送给所有 trusted * webContents。 * * 该类只持有 deps 引用,不直接 import 'electron';测试用 plain DI 注入 fake。 * 生命周期一次性:start → dispose 后不能再 start(避免 listener 泄露 / 句柄 * 双重注册);hot-restart 由 host 重新构造新实例。 * * @internal */ import type { JsonValue, SenderPolicy } from '../types.js'; import type { EventBus } from './event-bus.js'; /** * Framework-reserved invoke failure codes —— 全部带 `DECK_` 前缀,避免与 * host 自定义 code 冲突。Client / host 都不应抛同前缀。 */ export declare const DECK_CODE: { readonly UntrustedSender: "DECK_UNTRUSTED_SENDER"; readonly UntrustedFrame: "DECK_UNTRUSTED_FRAME"; readonly UnknownKind: "DECK_UNKNOWN_KIND"; readonly BadRequest: "DECK_BAD_REQUEST"; /** The grant gate denied a privileged command for this sender. */ readonly Forbidden: "DECK_FORBIDDEN"; }; /** Minimal RenderFrameHost identity used for main-frame discrimination. */ export interface FrameRef { readonly routingId: number; readonly processId: number; } /** * Per-invoke context threaded from {@link WireTransport.handleInvoke} into the * host/simulator invoke seams. Constructed ONLY after the wire's trust gate + * main-frame gate have both passed, so `senderId` is a real trusted webContents * id (never undefined). The grant gate (in `ControlBus.dispatch`) reads * this; it is a REQUIRED param so a missing call * site is a COMPILE error, not a silent security downgrade. */ export interface InvokeCtx { /** webContents id of the invoke sender (after the wire's trust + main-frame gates). */ readonly senderId: number; /** sender frame ref; main-frame already validated. Kept for future per-frame use; may be null. */ readonly senderFrame: FrameRef | null; } export interface MinimalIpcMain { handle(channel: string, handler: (event: { sender: { id: number; mainFrame?: FrameRef | null; }; senderFrame?: FrameRef | null; }, ...args: unknown[]) => unknown | Promise): void; removeHandler(channel: string): void; } export interface MinimalWebContents { readonly id: number; isDestroyed(): boolean; send(channel: string, payload: unknown): void; } export interface WireTransportDeps { readonly ipcMain: MinimalIpcMain; readonly bus: EventBus; readonly senderPolicy: SenderPolicy; /** 取当前 trusted webContents 快照;用于 event push 广播。lazy:每次 publish 重调。 */ readonly trustedWebContents: () => readonly MinimalWebContents[]; /** 路由 host kind 调用;handler 抛错由 WireTransport 接住 → InvokeFailure。 * `ctx` 必填:携带已过 trust + main-frame gate 的 senderId(授权门读它)。 */ readonly invokeHost: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise; /** 路由 simulator kind 调用;同上(`ctx` 必填)。 */ readonly invokeSimulator: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise; /** * 已声明的 event name 集合,作为 wire fanout 的 allowlist。**必填, * default-deny** —— 未在该集合内的 event name 不会跨进程下发(防止 * framework 内部代码意外调 `bus.publish('foo')` 时 leak 给 webview)。 * 调用方需保证每次返回的是当前 declared event 名字列表(lazy 快照,每次 * publish 重读)。 */ readonly declaredEvents: () => readonly string[]; /** * OPTIONAL slot-token inbound: `__electron-deck:snapshot` apply path. When * provided, `start()` registers the `Snapshot` handler (same trust + main-frame * gate as invoke). On gate pass → `onSnapshot(senderId, rawSnapshot)`. The * payload (the renderer's window-level placement table) is opaque here and * authorized/validated downstream — the wire only enforces the trust boundary. */ readonly onSnapshot?: (senderId: number, rawSnapshot: unknown) => void; /** * OPTIONAL slot-token inbound: `__electron-deck:layout-subscribe` per-wc * replay request. When provided, `start()` registers the `LayoutSubscribe` * handler (same gate). On gate pass → `onLayoutSubscribe(senderId)`. */ readonly onLayoutSubscribe?: (senderId: number) => void; } export declare class WireTransport { private readonly deps; private state; private busSubscription; /** Whether the optional Snapshot / LayoutSubscribe handlers were registered * (either eagerly at start() via deps, or lazily via armSlotChannels) — so * dispose() only removes channels it actually registered. */ private snapshotRegistered; private layoutSubscribeRegistered; /** Effective slot callbacks. Populated from deps at start() (eager path, used * by the wire unit tests) OR by {@link armSlotChannels} (lazy path, used by * deck-app so an app with no anchored views registers only Invoke + Probe). */ private onSnapshotCb; private onLayoutSubscribeCb; constructor(deps: WireTransportDeps); start(): void; private tryRemoveHandler; private registerSnapshotHandler; private unregisterSnapshotHandler; private registerLayoutSubscribeHandler; private unregisterLayoutSubscribeHandler; /** * Lazily register the slot-token inbound channels (`Place` + `LayoutSubscribe`) * on an already-started wire, wiring them to the given callbacks. Idempotent: * subsequent calls just refresh the callbacks (the handlers are registered * once). deck-app calls this on the FIRST anchored `placeIn`, so an app with * no anchored views never registers these channels (keeping the wire's * handler footprint at Invoke + Probe until a slot is actually minted). */ armSlotChannels(onSnapshot: (senderId: number, rawSnapshot: unknown) => void, onLayoutSubscribe: (senderId: number) => void): void; dispose(): void; private handleProbe; /** * Defense-in-depth main-frame check (mirrors devtools' verified * `isMainFrameSender`). A trusted webContents may embed a sub-frame of * arbitrary origin; only its top frame should reach gated invoke, so a * sub-frame can't spoof the trusted sender id. * * - NEITHER field modeled → frame-unaware stub → not a real frame boundary; * the sender-id gate is the boundary → allow (back-compat with the legacy * `{ sender: { id } }` stubs). * - either null (incl. a real event whose `senderFrame` resolved to null after * navigate-after-send / frame destruction, or a partial/malformed event) → * fail-closed → reject. * - main frame (routingId + processId match) → allow; else (sub frame) reject. */ private isMainFrameSender; private handleInvoke; /** * `__electron-deck:snapshot` inbound. Same gate as {@link handleInvoke} (trust + * main-frame). On any gate failure (non-object payload, untrusted sender, * sub-frame) → DROP silently. The payload's inner shape is opaque to the wire — * authorized + validated downstream. */ private handleSnapshot; /** * `__electron-deck:layout-subscribe` inbound. Same gate as {@link handleInvoke}. * On gate pass → `onLayoutSubscribe(senderId)`; else DROP silently. */ private handleLayoutSubscribe; private fanoutEvent; } //# sourceMappingURL=wire-transport.d.ts.map