import { type RetryConfig, type Scheduler } from "@kuralle-syrinx/core"; /** A WebSocket text or binary frame, normalized across runtimes. */ export type SocketData = string | Uint8Array; /** * The minimal socket the connection manager drives. Implemented over Node `ws` * (createNodeWsSocket) or the built-in WebSocket (createWebSocketAdapter). Keeping the * manager behind this seam is what makes it portable to Cloudflare Workers, * where `ws` does not run and there are no ping frames. */ export interface ManagedSocket { readonly isOpen: boolean; send(data: SocketData): void; /** Fire-and-forget liveness ping (Node WS ping frame). No-op where unsupported. */ keepAlivePing(): void; /** When true, verify() sends a WS ping frame and awaits a pong (Node/Bun only). */ readonly supportsFramePing?: boolean; /** Probe liveness: Node pings and awaits a pong; the built-in WebSocket just reports readyState. */ verify(timeoutMs: number): Promise; /** Remove listeners and close — used when replacing or tearing down. */ dispose(): void; onOpen(handler: () => void): void; onMessage(handler: (data: SocketData, isBinary: boolean) => void): void; onClose(handler: (code: number, reason: string) => void): void; onError(handler: (err: Error) => void): void; } export type SocketFactory = (url: string, headers: Record) => ManagedSocket | Promise; export interface WebSocketConnectionOptions { /** Build the connection URL fresh on every (re)connect. */ readonly url: () => string; readonly headers?: Record; /** Creates the underlying socket for the host runtime (Node, Bun, Workers, browser). */ readonly socketFactory: SocketFactory; /** Backoff schedule for reconnect attempts (reused from the plugin's retry config). */ readonly retry: RetryConfig; /** Max reconnect attempts per disconnect burst before giving up. Defaults to retry.maxAttempts. */ readonly maxReconnectAttempts?: number; /** Periodic KeepAlive to stop idle providers from closing the socket. 0 disables. */ readonly keepAliveIntervalMs?: number; /** App-level KeepAlive payload (e.g. Deepgram `{"type":"KeepAlive"}`). When omitted a WS ping is used * (which is a no-op on built-in WebSockets — provide a message for KeepAlive on Workers/browser). */ readonly keepAliveMessage?: () => SocketData; /** A reconnect that re-opens then dies within this window counts as a quick failure. */ readonly minStableMs?: number; /** Consecutive quick failures before giving up (backoff can't fix an instantly-closing socket). */ readonly maxQuickFailures?: number; readonly connectTimeoutMs?: number; /** App-level round-trip liveness check used on runtimes without WS ping frames (web/workers). */ readonly livenessProbe?: (socket: ManagedSocket) => Promise; /** Max wall-clock time for a reconnect burst before giving up. */ readonly maxReconnectDurationMs?: number; readonly scheduler?: Scheduler; readonly onMessage: (data: SocketData, isBinary: boolean) => void; /** Called once when a live connection drops unexpectedly, with the close cause — for * failing in-flight work and dropping stale provider state before reconnecting. */ readonly onConnectionLost?: (err: Error) => void; /** Called before each reconnect attempt so the consumer can drop stale provider state. */ readonly onReconnecting?: () => void; /** Called after the socket is open/verified and before replay frames flush. */ readonly onReadyBeforeReplay?: () => void; readonly onReconnected?: () => void; /** Called when reconnection is abandoned (quick-failure loop or attempts exhausted). */ readonly onUnrecoverable?: (err: Error) => void; /** * Max frames to buffer for replay-on-reconnect. 0 (default) disables replay — `send()` to a * closed socket throws as before. When > 0, a `send()` that fails because the socket is not open * (so the frame PROVABLY never reached the wire) is buffered and re-sent in order on the next * reconnect. Frames that were sent on an open socket are never buffered, so a frame the provider * may already have received is never replayed — no duplicate speech. */ readonly replayBufferSize?: number; /** Observe replay activity: "deferred" (buffered), "replayed" (flushed on reconnect), "overflow" (dropped). */ readonly onReplay?: (event: "deferred" | "replayed" | "overflow", count: number) => void; } export declare class WebSocketConnection { private readonly opts; private socket; private ready; /** True only once the connection is verified and onReadyBeforeReplay has been * given the chance to run — send() must not reach an open-but-unconfigured * socket, or caller frames beat the provider config frame on reconnect. */ private established; private closed; private reconnecting; private connResolver; private connRejecter; private abortOpen; private readonly scheduler; private readonly keepAliveKey; private keepAliveScheduled; private lastConnectAtMs; private quickFailures; private reconnectBurstStartedAtMs; private reconnectBurstResetKey; private pendingReplay; constructor(opts: WebSocketConnectionOptions); private get replayBufferSize(); /** Open the initial connection. Rejects if it cannot be established (so init fails loudly). */ connect(): Promise; get isReady(): boolean; /** * Send a frame. If the socket is not open: when replay is enabled (`replayBufferSize > 0`) the * frame is buffered for replay on reconnect (it provably never reached the wire); otherwise this * throws and the caller decides how to retry/report. */ send(payload: SocketData): void; private bufferForReplay; /** Re-send frames buffered during a disconnect gap, in order, on the reconnected socket. */ private flushReplay; ensureReady(): Promise; close(): Promise; /** * Force a reconnect now — for when the provider stream is wedged but the socket * still looks open (e.g. an unanswered Finalize). Safe no-op if closed or a * reconnect is already running. */ reset(): void; private get connectTimeoutMs(); private openSocket; private verifyConnection; private abortPendingOpen; private tryReconnect; private giveUp; private startKeepAlive; private stopKeepAlive; private get minStableMs(); private get maxQuickFailures(); private cancelReconnectBurstReset; private scheduleReconnectBurstReset; } //# sourceMappingURL=index.d.ts.map