import { type LeaderToFollowerMessage, type TrayChunkFrame } from './_shared/index.js'; import { type FederatedCdpInspectableTarget } from './electron-federated-cdp.js'; /** The data-channel the leader opens; mirrors `tray-webrtc.ts`. */ export declare const TRAY_CONTROL_CHANNEL_LABEL = "tray-control"; /** Runtime tag advertised on attach + `hello`. */ export declare const FOLLOWER_RUNTIME_TAG = "slicc-electron"; interface IceServerConfig { urls: string | string[]; username?: string; credential?: string; } /** Minimal tray follower-bootstrap signalling client (mirrors slicc-cli's * `internal/signaling`): POSTs join/poll/answer/ice to the join URL. */ export declare class TrayFollowerSignaling { private readonly joinUrl; private readonly fetchImpl; constructor(joinUrl: string, fetchImpl?: typeof fetch); private post; /** * Attach to the tray. Unlike poll/answer/ice, this tolerates a non-OK status * and returns the decoded body: the tray signals `TRAY_SUPERSEDED` with an * HTTP 409 + a replacement `joinUrl`, and `wait` plans (leader not yet * elected/connected) can also arrive on a non-2xx status. Throwing before * decoding — as `post()` does — would strand the follower on both. * `attachWithRedirects` interprets `result.code` / `result.action`. */ attach(controllerId: string, runtime: string): Promise<{ status: number; body: Record; }>; poll(controllerId: string, bootstrapId: string, cursor: number): Promise>; sendAnswer(controllerId: string, bootstrapId: string, sdp: string): Promise>; sendIceCandidate(controllerId: string, bootstrapId: string, candidate: Record): Promise>; } export interface ElectronTrayFollowerOptions { /** Tray join URL (the slicc-server's `--join` value). */ joinUrl: string; /** Browser-level CDP debugger websocket of the attached app (from `/json/version`). */ browserWsUrl: string; /** Enumerate the app's inspectable targets (from `/json/list`) to advertise. */ listTargets: () => Promise; /** Stable id for this follower runtime. Defaults to a fresh UUID. */ runtimeId?: string; fetchImpl?: typeof fetch; pollIntervalMs?: number; logger?: (message: string) => void; } /** * Ties the signalling + WebRTC + tray-sync + federated-CDP pieces together. The * pure message-dispatch logic is exposed via {@link dispatchLeaderMessage} for * unit testing; the WebRTC/signalling glue is validated end-to-end against a * mock leader + a live CDP target. */ export declare class ElectronTrayFollower { private readonly opts; private readonly runtimeId; private readonly controllerId; /** Reassigned when the tray supersedes and hands us a fresh join URL. */ private signaling; private readonly fetchImpl; private readonly log; private pc; private channel; private cdp; private bootstrapId; private pollTimer; private cursor; private stopped; private readonly seenEvents; /** Bounded reconnect state — a dropped tray-control channel re-joins. */ private reconnectTimer; private reconnecting; private reconnectAttempts; private readonly maxReconnects; /** Reassembles oversize (`__chunk`-framed) leader messages before dispatch. */ private readonly reassembler; constructor(options: ElectronTrayFollowerOptions); /** * Attach to the tray, resolving the non-terminal attach outcomes the worker * returns before a bootstrap is available: * - `TRAY_SUPERSEDED` (HTTP 409 + `joinUrl`): the leader reconnected onto a * fresh tray — follow the redirect (bounded hops). * - `wait` (leader not yet elected/connected, carries `retryAfterMs`): sleep * then re-attach (bounded), as the shared Swift/Go followers do — otherwise * a follower that raced leader election resolves "started" but is never * discovered. * - bootstrap present: return the ICE servers to start WebRTC. * * Exposed for unit tests (signalling only, no WebRTC). Returns null on a * terminal failure or once the follower is stopped. */ attachWithRedirects(maxHops?: number, maxWaits?: number): Promise; /** Join the tray, answer the leader's offer, and start servicing CDP. */ start(): Promise; /** One join attempt: attach → WebRTC answerer → federated-CDP servicer. Runs * again on reconnect after the tray-control channel drops. */ private joinOnce; stop(): void; /** * The tray-control channel left `open` (ICE failure, leader restart, or * transient network loss). Re-join the tray with capped exponential backoff, * as the shared follower transport does — a persistent follower must survive a * dropped channel instead of silently discarding every response (`sendRaw` * no-ops while `readyState !== 'open'`) until the whole server restarts. */ private scheduleReconnect; /** Close the WebRTC peer + CDP link and reset per-connection state so a fresh * `joinOnce()` re-attaches cleanly (the tray may have superseded). Does NOT * set `stopped` — that is reserved for a caller-initiated `stop()`. */ private teardownPeer; private wireControlChannel; private onChannelOpen; /** Parse one inbound data-channel frame and route it. Transport `__chunk` * frames (an oversize message the leader split below the SCTP limit — e.g. a * long `Runtime.evaluate` expression) are reassembled first, then the * recovered message is dispatched; ping is auto-answered; `cdp.request` is * serviced by the federated-CDP servicer. */ dispatchRaw(text: string): void; /** Pure dispatch (exposed for tests). */ dispatchLeaderMessage(message: LeaderToFollowerMessage | { type: string; }): void; private sendToLeader; private sendRaw; private schedulePoll; private pollOnce; private handleBootstrapEvent; } /** * Reassembles `TrayChunkFrame` transport frames into the original serialized * message. Frames of one message share a `chunkId`; `push` returns the joined * message once every index has arrived, else null. Bounded to * {@link TRAY_MAX_PENDING_REASSEMBLIES} concurrent groups (oldest evicted * first) so a peer can't exhaust memory with many partial reassemblies. * Mirrors the receive side of the webapp's `TraySyncChannel` and the shared * Swift/Go followers. */ export declare class ChunkReassembler { private readonly pending; push(frame: TrayChunkFrame): string | null; } /** Normalize the worker's ICE server list into werift's config shape. */ export declare function normalizeIceServers(raw: unknown): IceServerConfig[]; export {};