import type { StreamMessageHandler, StreamSubscription, StreamTransport, StreamWebSocketConstructor, SubscribeCallbacks, SubscribeOptions } from "./types"; export interface PhoenixTransportOptions { /** Base socket URL, e.g. `wss://stream-api.opensea.io/socket`. */ endpoint: string; /** Query parameters appended to the socket URL, e.g. the API key. */ params?: Record; /** WebSocket implementation. Defaults to the global `WebSocket`. */ transport?: StreamWebSocketConstructor; /** Milliseconds to wait for a subscribe acknowledgement. Default 10000. */ timeout?: number; /** Milliseconds between heartbeats. Default 30000. */ heartbeatIntervalMs?: number; /** Backoff for reconnect attempt `tries` (1-indexed). */ reconnectAfterMs?: (tries: number) => number; /** Optional debug logger. */ logger?: (message: string) => void; } export declare class PhoenixChannelsTransport implements StreamTransport { private readonly endpoint; private readonly params; private readonly webSocketCtor; private readonly timeout; private readonly heartbeatIntervalMs; private readonly reconnectAfterMs; private readonly logger?; private socket; private readonly subscriptions; private readonly pendingReplies; private errorHandlers; /** * Unsubscribe callbacks still waiting for a `phx_leave` reply. `disconnect()` * closes the socket without a round trip, so these are drained there instead; * otherwise a caller awaiting unsubscribe confirmation waits forever. */ private readonly pendingUnsubscribes; private refCounter; private reconnectTries; private reconnectTimer; private heartbeatTimer; private pendingHeartbeatRef; private disconnectedIntentionally; /** * Whether the current socket has ever answered us. An open socket is not * proof of a working connection: a server that accepts the TCP/WS handshake * but never replies would otherwise reset the backoff on every attempt and * be hammered at the shortest delay forever. */ private connectionProven; constructor(options: PhoenixTransportOptions); endpointUrl(): string; protocol(): string; connect(): void; isConnected(): boolean; disconnect(onDisconnect?: () => void): void; onError(handler: (error: unknown) => void): void; /** Surface an error raised by consumer code, used by subscriptions. */ reportError(error: unknown): void; /** * Run a consumer callback without letting it escape into our state machine. * * These fire from inside teardown and reply handling, where an escaping * exception would skip the work that follows: a throwing unsubscribe * callback would abort `handleClose` before `scheduleReconnect`, leaving the * client permanently disconnected, and a throwing subscribe callback would * starve every later subscriber to the same topic. */ private safeInvoke; private handleOpen; private handleClose; private scheduleReconnect; private clearReconnectTimer; private startHeartbeat; private stopHeartbeat; private sendHeartbeat; /** Close the socket without marking the disconnect intentional. */ private teardownForReconnect; subscribe(topic: string, options?: SubscribeOptions, callbacks?: SubscribeCallbacks): StreamSubscription; private sendJoin; /** * Rejoin a topic whose channel died while the socket stayed open. Uses the * same backoff schedule as reconnects, so a channel that keeps crashing does * not turn into a join loop. */ private scheduleRejoin; private clearRejoinTimer; /** * A topic is joined once and shared, and the join payload carries the * server-side `event_types` filter. A later subscriber asking for an event * outside that filter would otherwise register a handler the server never * feeds, which fails silently. Widen the filter and re-join instead. * * `undefined` means no filter, so it is the widest value rather than the * narrowest: an individual `on*` call subscribes to everything. */ private widenFilterIfNeeded; private drainPendingUnsubscribes; /** Called by a subscription, and directly by the client. */ unsubscribeTopic(topic: string, onUnsubscribed?: () => void): void; /** Remove a single handler, unsubscribing only when the topic goes quiet. */ removeHandler(topic: string, event: string, handler: StreamMessageHandler, onUnsubscribed?: () => void): void; private handleMessage; private handleReply; private awaitReply; /** * Drop every in-flight reply without invoking its handler. The socket going * away is not a rejection: a join whose ack never arrives is re-sent by * `handleOpen` after the reconnect, so reporting it as a subscribe failure * here would be both wrong and noisy. * * Unsubscribes are the exception and are settled rather than dropped. A left * topic stays left across a reconnect, so its acknowledgement is never coming * and a caller waiting on one would hang. Draining here covers every teardown * path: `disconnect`, `handleClose`, and `teardownForReconnect`. */ private clearPendingReplies; private push; private decode; private nextRef; private emitError; private log; }