/** * Multiplexed transport (WebSocket with optional WebTransport upgrade). * * A single connection to the gateway carries traffic for all destinations. * Each destination gets a lightweight "channel" that implements * {@link BlitTransport} so it can be handed directly to a * {@link BlitConnection}. * * When a `wtUrl` is provided and the browser supports WebTransport, the * transport will try QUIC first and fall back to WebSocket on failure. * * Wire format (after authentication): * * Data frame: [channel_id:2 LE][blit_payload:N] * Control frame: [0xFFFF][opcode:1][...] * * Over WebSocket each frame is a single binary message. * Over WebTransport frames are length-prefixed on a bidirectional stream: * [frame_len:4 LE][mux_frame] * * Control opcodes: * C2S OPEN 0x01 [ch:2][name_len:2][name:N] * C2S CLOSE 0x02 [ch:2] * S2C OPENED 0x81 [ch:2] * S2C CLOSED 0x82 [ch:2] * S2C ERROR 0x83 [ch:2][msg_len:2][msg:N] */ import { type BlitDebug, type BlitTransport, type BlitTransportMessage, type BlitTransportOptions, type ConnectionStatus } from "../types"; export interface MuxTransportOptions extends BlitTransportOptions { /** WebTransport URL (e.g. `https://host:3264/mux`). When set and the * browser supports WebTransport, QUIC is tried first. */ wtUrl?: string; /** SHA-256 cert hash (hex) for self-signed WebTransport certs. */ wtCertHash?: string; /** Timeout for the optional WebTransport attempt before falling back to WebSocket. Default: 3000 ms. */ wtConnectTimeoutMs?: number; /** Timeout waiting for a virtual channel OPEN acknowledgement before retrying. Default: 10000 ms. */ channelConnectTimeoutMs?: number; /** How long to stay on WebSocket after a WebTransport attempt fails before * probing QUIC again. Default: 300000 ms (5 min). */ wtReprobeMs?: number; /** Optional debug logger for connection diagnostics. */ debug?: BlitDebug; } /** * Manages a single multiplexed connection and exposes per-destination * channels that each implement {@link BlitTransport}. */ export declare class MuxTransport { private ws; private wt; private wtWriter; private wtReadAbort; private bufferRecycler; private _status; private _authRejected; private reconnectTimer; private currentDelay; private disposed; /** True while an async WT connect attempt is in progress. */ private wtConnecting; private readonly wsUrl; private readonly passphrase; private readonly _reconnect; private readonly initialDelay; private readonly maxDelay; private readonly backoff; private wtUrl; private wtCertHash; private readonly wtConnectTimeoutMs; private readonly channelConnectTimeoutMs; private readonly wtReprobeMs; /** Set after a WT failure to keep us on WebSocket. Cleared by * `wtReprobeTimer` so a transient QUIC problem — a moment of UDP loss, a * network that blocks it, a gateway still binding its endpoint — costs one * cooldown rather than WebTransport for the life of the page. blit sessions * run for days; a permanent flag turns a one-second event into a * permanent downgrade. */ private wtFailed; private wtReprobeTimer; private readonly dbg; /** Exact-size reusable WS frames for high-rate small messages (notably * surface ACKs). WebSocket.send snapshots BufferSource data synchronously. */ private readonly wsSmallSendFrames; /** All channels keyed by channel ID. */ private readonly channels; /** Next channel ID to assign. */ private nextChannelId; /** Channels that were open/opening when the connection dropped — need re-open on reconnect. */ private readonly pendingReopen; /** Per-channel reconnect timers for channels that received S2C_CLOSED/ERROR. */ private readonly channelReconnectTimers; /** Per-channel timers while waiting for S2C_OPENED. */ private readonly channelConnectTimers; constructor(wsUrl: string, passphrase: string, options?: MuxTransportOptions); /** * Adopt a rotated WebTransport certificate hash. * * The gateway regenerates its self-signed cert every 13 days and publishes * the new hash over the config WebSocket. Without this the hash captured at * construction goes stale, every later WT attempt fails cert validation, and * the session is stuck on WebSocket until the page is reloaded. * * Deliberately does not reconnect: tearing down a healthy connection to * switch protocols would interrupt live terminals for no gain. The new hash * is used by the next connection attempt, whenever that happens. */ updateWtCertHash(hexHash: string, wtUrl?: string): void; /** Allow WebTransport to be probed again. */ private clearWtFailure; /** Stay on WebSocket, but only until the cooldown expires. */ private markWtFailed; /** Current transport-level status. */ get status(): ConnectionStatus; /** True when connected over WebTransport (QUIC) rather than WebSocket. */ get isWebTransport(): boolean; connect(): void; close(): void; /** * Create a channel for the given destination name. The channel is not * opened until its `connect()` method is called (which happens * automatically when a {@link BlitConnection} is created with * `autoConnect: true`). */ createChannel(destName: string, channelId?: number): MuxChannel; /** * Remove a channel. Sends CLOSE if the underlying connection is open. * Called internally by {@link MuxChannel.close}. */ _removeChannel(ch: MuxChannel): void; /** @internal Pause one channel without removing it, so it can be reopened manually. */ _suspendChannel(ch: MuxChannel): void; /** @internal Cancel any pending per-channel reconnect timer. */ _cancelChannelReconnect(channelId: number): void; /** Send a raw mux frame. Over WS this is a single binary message; * over WT it is length-prefixed on the bidirectional stream. */ _sendRaw(data: Uint8Array): void; /** Send one channel payload without first building an intermediate mux * frame. WT gets one combined allocation instead of two; WS reuses exact * small buffers after their first send. */ _sendChannel(channelId: number, data: Uint8Array): void; /** Send an OPEN control message for a channel. */ _sendOpen(ch: MuxChannel): void; private connectWs; private cleanupWs; private shouldTryWt; private connectWt; private connectWtAsync; private wtReadLoop; private cleanupWt; private recycleBuffer; private setStatus; private handleDisconnect; private scheduleReconnect; /** * Schedule a re-open attempt for a single channel after it received * S2C_CLOSED or S2C_ERROR. Uses per-channel exponential backoff. */ private scheduleChannelReconnect; private armChannelConnectTimer; cancelChannelConnectTimer(channelId: number): void; /** @internal */ _sendClose(channelId: number): void; /** * A successful (re-)authentication retires any earlier rejection. Channels * parked in `error` by a rejection were never queued for re-open — handleDisconnect * returns before touching `pendingReopen` when `_authRejected` is set — so * requeue them here or the transport reconnects with every channel wedged. */ private clearAuthRejection; private reopenChannels; private handleMuxFrame; private handleControl; } /** * A single virtual channel on a {@link MuxTransport}. * Implements {@link BlitTransport} so it can be used directly by * {@link BlitConnection}. */ export declare class MuxChannel implements BlitTransport { /** @internal */ _internalStatus: ConnectionStatus; /** @internal */ _lastError: string | null; /** @internal Per-channel backoff delay for reconnect scheduling. */ /** @internal */ _reconnectDelay: number; /** @internal */ _suspended: boolean; private readonly mux; readonly channelId: number; readonly destName: string; private _authRejected; private messageListeners; private statusListeners; constructor(mux: MuxTransport, channelId: number, destName: string, initialDelay: number); get status(): ConnectionStatus; get authRejected(): boolean; get lastError(): string | null; connect(): void; reconnect(): void; send(data: Uint8Array): void; close(): void; suspend(): void; addEventListener(type: "message", listener: (data: BlitTransportMessage) => void): void; addEventListener(type: "statuschange", listener: (status: ConnectionStatus) => void): void; removeEventListener(type: "message", listener: (data: BlitTransportMessage) => void): void; removeEventListener(type: "statuschange", listener: (status: ConnectionStatus) => void): void; /** @internal */ _setStatus(status: ConnectionStatus): void; /** @internal */ _setAuthRejected(): void; /** @internal */ _clearAuthRejected(): void; /** @internal */ _deliverMessage(data: BlitTransportMessage): void; } //# sourceMappingURL=mux.d.ts.map