/** * ControlBus — a thin, domain-neutral facade over the existing * {@link WireTransport} + {@link EventBus} + an injectable refcount * {@link TrustSet}. * * Three verbs, no new gating logic of its own: * * - `command(name, handler)` : webview → main RPC. The facade owns the single * command table and exposes {@link ControlBus.dispatch}; the wire's * `invokeHost` (and `invokeSimulator`) seam calls `dispatch(name, args)` so a * real webview→main invoke (after the wire's sender + main-frame gate) lands on * the registered handler. Callers never see the wire `kind`. Trust + main-frame * gating are entirely the wire's (`handleInvoke`): a trusted main-frame sender * reaches the handler, an untrusted sender → `DECK_UNTRUSTED_SENDER`, a * sub-frame → `DECK_UNTRUSTED_FRAME`. * - `event(name)` : main → webview push, default-deny. `name` is added to the * declared-event allowlist the wire reads; `publish` goes through `bus.publish` * → wire fanout (undeclared names are dropped by the wire). `dispose` revokes * the declaration. * - `trust(wc, owner)` : delegates to `trustSet.admit` (refcount membership owned * by `owner` Scope, so the lease is released when the owner tears down). * * Real wiring (Bug C): the command table is the SOLE command authority. The * production caller builds the {@link WireTransport} with * `invokeHost = (name, args) => controlBus.dispatch(name, args)` (and the same * for `invokeSimulator`, since the facade hides the kind), and `declaredEvents` * reading {@link ControlBus.declaredEvents}. So a real IPC invoke reaches the * command handler through the wire — not a private test-only seam. Any * config-declared host services register into this SAME table via `command()`, * so there is one namespace (the host keeps names unique), never two registries * that can collide. * * @internal exported via `/host`. */ import type { Disposable, JsonValue } from '../types.js'; import type { EventBus } from '../internal/event-bus.js'; import type { InvokeCtx, MinimalWebContents, WireTransport } from '../internal/wire-transport.js'; import type { TrustSet } from '../internal/trust-set.js'; import type { Scope } from '../main/scope.js'; import type { CapabilityPolicy } from './capability.js'; export type { TrustSet }; export { createTrustSet } from '../internal/trust-set.js'; type CommandHandler = (...args: JsonValue[]) => JsonValue | Promise; export interface ControlBusEventHandle

{ publish(payload: P): void; dispose(): void; } export interface ControlBus { /** Register a webview → main RPC handler under a domain-neutral `name`. */ command(name: string, handler: CommandHandler): Disposable; /** Declare a main → webview push event (default-deny allowlist). */ event

(name: string): ControlBusEventHandle

; /** Admit a webContents into the refcount trust set, owned by `owner` Scope * (the lease is released when `owner` resets/closes). */ trust(wc: MinimalWebContents, owner: Scope): Disposable; /** * Real wire entry point — the {@link WireTransport}'s `invokeHost` / * `invokeSimulator` seam calls this with a domain-neutral `name` AFTER its * trust + main-frame gate. Resolves the command table; throws if `name` is * unregistered (the wire serialises that into an `InvokeFailure`). `ctx` * carries the gated senderId (the grant gate reads it when a policy is * configured; otherwise it is plumbed through unused). */ dispatch(name: string, args: readonly JsonValue[], ctx: InvokeCtx): Promise; /** * Current declared-event allowlist snapshot — the wire's `declaredEvents` * seam reads this (lazy, per publish) so default-deny tracks `event()` / * `dispose()` in real time. */ declaredEvents(): readonly string[]; } export interface CreateControlBusDeps { /** * Vestigial — the facade never calls into the wire from inside `dispatch` / * `command` / `event` / `trust` (the wire is the one that calls `dispatch`, * not the reverse). Made OPTIONAL so deck-app can construct the ControlBus * BEFORE the WireTransport (which references the ControlBus from its * `invokeHost` seam) — no circular dependency at construction time. */ readonly transport?: WireTransport; readonly bus: EventBus; readonly trustSet: TrustSet; /** * Privileged-command grant gate. When provided, `dispatch` * default-DENIES any command not authorized by a live grant for the gated * `ctx.senderId`. Omitted → no gate (backward-compatible "trusted may * dispatch" behaviour). */ readonly policy?: CapabilityPolicy; } export declare function createControlBus(deps: CreateControlBusDeps): ControlBus; //# sourceMappingURL=control-bus.d.ts.map