///
/**
* Reconnecting WebSocket with automatic retry logic.
*
* Features:
* - **Drop-in replacement** — standard `WebSocket` API, swap one line
* - **Auto-reconnection** — configurable retries and delay strategy
* - **Message buffering** — `send()` queues data while offline, flushes on reconnect
* - **Persistent listeners** — `addEventListener` and `on*` handlers survive reconnections
* - **Dynamic URL & protocols** — resolve fresh values on each reconnection
* - **Zero dependencies** — works in Node.js, Deno, Bun, and browsers
*
* @example
* ```ts
* import { ReconnectingWebSocket } from "@nktkas/rews";
*
* const ws = new ReconnectingWebSocket("wss://api.example.com/ws", {
* maxRetries: 10,
* reconnectionDelay: (attempt) => Math.min(2 ** attempt * 100, 5_000),
* });
*
* ws.addEventListener("open", () => console.log("connected")); // fires on every reconnect
* ws.addEventListener("message", (event) => console.log(event.data));
*
* ws.send("hello"); // buffered while offline, sent once connected
* ```
*
* @module
*/
/**
* Specifies the type of binary data being received over a `WebSocket` connection.
*
* - `"blob"`: Binary data is returned as `Blob` objects
* - `"arraybuffer"`: Binary data is returned as `ArrayBuffer` objects
*
* @example
* ```ts
* // Setting up WebSocket for binary data as ArrayBuffer
* const ws = new WebSocket("ws://localhost:8080");
* ws.binaryType = "arraybuffer";
*
* ws.onmessage = (event) => {
* if (event.data instanceof ArrayBuffer) {
* // Process binary data
* const view = new Uint8Array(event.data);
* console.log(`Received binary data of ${view.length} bytes`);
* } else {
* // Process text data
* console.log(`Received text: ${event.data}`);
* }
* };
*
* // Sending binary data
* const binaryData = new Uint8Array([1, 2, 3, 4]);
* ws.send(binaryData.buffer);
* ```
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType
* @category WebSockets
*/
export type BinaryType = "blob" | "arraybuffer";
/**
* Interface mapping `WebSocket` event names to their corresponding event types.
* Used for strongly typed event handling with `addEventListener` and `removeEventListener`.
*
* @example
* ```ts
* // Using with TypeScript for strongly-typed event handling
* const ws = new WebSocket("ws://localhost:8080");
*
* ws.addEventListener("open", (event) => {
* console.log("Connection established");
* });
*
* ws.addEventListener("message", (event: MessageEvent) => {
* console.log(`Received: ${event.data}`);
* });
* ```
*
* @category WebSockets
*/
export interface WebSocketEventMap {
/** Connection closed. */
close: CloseEvent;
/** Connection failed. */
error: Event;
/** Data received. */
message: MessageEvent;
/** Connection established. */
open: Event;
}
/**
* Callback function invoked when an event fires.
*
* @category Events
*/
export interface EventListener {
/**
* The `EventListener` interface represents a callback function to be called
* whenever an event of a specific type occurs on a target object.
*
* This is a basic event listener, represented by a simple function
* that receives an Event object as its only parameter.
*
* @example
* ```ts
* // Create an event listener function
* const handleEvent = (event: Event) => {
* console.log(`Event of type "${event.type}" occurred`);
* console.log(`Event phase: ${event.eventPhase}`);
*
* // Access event properties
* if (event.cancelable) {
* event.preventDefault();
* }
* };
*
* // Attach the event listener to a target
* const target = new EventTarget();
* target.addEventListener('custom', handleEvent);
*
* // Or create a listener inline
* target.addEventListener('message', (event) => {
* console.log('Message received:', event);
* });
* ```
*
* @category Events
*/
(evt: Event): void;
}
/**
* The `EventListenerObject` interface represents an object that can handle events
* dispatched by an `EventTarget` object.
*
* This interface provides an alternative to using a function as an event listener.
* When implementing an object with this interface, the `handleEvent()` method
* will be called when the event is triggered.
*
* @example
* ```ts
* // Creating an object that implements `EventListenerObject`
* const myEventListener = {
* handleEvent(event: Event) {
* console.log(`Event of type ${event.type} occurred`);
*
* // You can use 'this' to access other methods or properties
* this.additionalProcessing(event);
* },
*
* additionalProcessing(event: Event) {
* // Additional event handling logic
* console.log('Additional processing for:', event);
* }
* };
*
* // Using with any EventTarget (server or client contexts)
* const target = new EventTarget();
* target.addEventListener('message', myEventListener);
*
* // Later, to remove it:
* target.removeEventListener('message', myEventListener);
* ```
*
* @category Events
*/
export interface EventListenerObject {
/** Called when a subscribed event fires. */
handleEvent(evt: Event): void;
}
/**
* Event listener in either callback or object form.
*
* @category Events
*/
export type EventListenerOrEventListenerObject = EventListener | EventListenerObject;
/** URL or factory function that returns a URL for the WebSocket connection (sync or async). */
export type UrlProvider = string | URL | (() => string | URL | Promise);
/** Subprotocol(s) or factory function that returns subprotocol(s) for the WebSocket connection (sync or async). */
export type ProtocolsProvider = string | string[] | undefined | (() => string | string[] | undefined | Promise);
/** Error code indicating the type of reconnection failure. */
export type ReconnectingWebSocketErrorCode = "RECONNECTION_LIMIT" | "RECONNECTION_DECLINED" | "TERMINATED_BY_USER" | "UNKNOWN_ERROR";
/** Configuration options for the {@link ReconnectingWebSocket}. */
export interface ReconnectingWebSocketOptions {
/**
* Maximum number of consecutive failed reconnection attempts.
*
* The counter resets after a connection stays open for {@link stableTimeout}.
*
* @default `Infinity`
*/
maxRetries?: number;
/**
* Maximum number of messages buffered while disconnected; sends beyond the limit are silently dropped.
*
* @default `Infinity`
*/
maxEnqueuedMessages?: number;
/**
* Maximum time in ms to wait for a connection to open. Set to `null` to disable.
*
* Does not limit the time spent in url/protocols factories.
*
* @default `10_000`
*/
connectionTimeout?: number | null;
/**
* Time in ms a connection must stay open before the retry counter resets.
*
* @default `3_000`
*/
stableTimeout?: number;
/**
* Delay before reconnection in ms, or a function of the attempt number (0-based).
*
* @default Exponential backoff `2 ** n * 150` capped at 10s, with equal jitter.
*/
reconnectionDelay?: number | ((attempt: number) => number);
/**
* Decide whether to reconnect after a non-user closure. Return `false` to permanently terminate.
*
* Receives the close event and the attempt number (0-based).
* Not consulted for `close()` and `reconnect()` calls.
*
* @default `() => true`
*/
shouldReconnect?: (event: CloseEvent, attempt: number) => boolean;
}
/**
* Error thrown when reconnection fails in {@link ReconnectingWebSocket}.
*
* @example
* ```ts
* import { ReconnectingWebSocket, ReconnectingWebSocketError } from "@nktkas/rews";
*
* const ws = new ReconnectingWebSocket("wss://api.example.com/ws");
*
* ws.terminationSignal.aborted; // boolean
* ws.terminationSignal.reason; // ReconnectingWebSocketError, once aborted
*
* ws.terminationSignal.addEventListener("abort", () => {
* ws.terminationSignal.reason.code; // ReconnectingWebSocketErrorCode
* ws.terminationSignal.reason.cause; // original error, if any
* });
* ```
*/
export declare class ReconnectingWebSocketError extends Error {
/**
* Error code indicating the type of reconnection error:
* - `RECONNECTION_LIMIT`: Maximum reconnection attempts reached.
* - `RECONNECTION_DECLINED`: `shouldReconnect` returned `false`.
* - `TERMINATED_BY_USER`: Closed via `close()` method.
* - `UNKNOWN_ERROR`: Unhandled error outside a connection attempt (e.g. in `reconnectionDelay` or `shouldReconnect`).
*/
readonly code: ReconnectingWebSocketErrorCode;
/**
* Create a termination error.
*
* @param code Reconnection failure code.
* @param cause Underlying error, stored as [`Error#cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause).
*/
constructor(code: ReconnectingWebSocketErrorCode, cause?: unknown);
}
export interface ReconnectingWebSocket {
addEventListener(type: K, listener: (this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener(type: K, listener: (this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
/**
* Drop-in replacement for [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) that automatically reconnects.
*
* @example
* ```ts
* import { ReconnectingWebSocket } from "@nktkas/rews";
*
* const ws = new ReconnectingWebSocket("wss://api.example.com/ws", {
* maxRetries: 10,
* reconnectionDelay: (attempt) => Math.min(2 ** attempt * 100, 5_000),
* });
*
* ws.addEventListener("open", () => console.log("connected")); // fires on every reconnect
* ws.addEventListener("message", (event) => console.log(event.data));
*
* ws.send("hello"); // buffered while offline, sent once connected
* ```
*/
export declare class ReconnectingWebSocket extends EventTarget implements WebSocket {
/** URL provider for creating new connections. */
private readonly _urlProvider;
/** Protocols provider for creating new connections. */
private readonly _protocolsProvider;
/** Current underlying WebSocket instance. */
private _socket;
/** Binary data type for the WebSocket. */
private _binaryType;
/** Buffer for messages sent while disconnected. */
private _messageBuffer;
/** Current reconnection attempt number. */
private _retryCount;
/** Resolves the in-flight {@link _awaitSocketLifecycle} promise; `undefined` when idle. */
private _settleLifecycle;
/** Resolves the in-flight retry delay; `undefined` when not sleeping. */
private _skipDelay;
/** Set while a user-requested reconnect closes the current socket; that close is not counted as a retry. */
private _reconnectRequested;
/** Skips the upcoming retry delay; set by {@link reconnect} when no sleep is active. */
private _skipNextDelay;
/** Attribute-style listener for the `close` event. */
private _onclose;
/** Attribute-style listener for the `error` event. */
private _onerror;
/** Attribute-style listener for the `message` event. */
private _onmessage;
/** Attribute-style listener for the `open` event. */
private _onopen;
/** Map of currently active attribute-style listeners. */
private _attributeListeners;
/** Controller used to signal permanent termination. */
private readonly _abortController;
/** Reconnection configuration options. Read on every attempt, so changes apply live. */
reconnectOptions: Required;
/**
* AbortSignal that is aborted when the instance is permanently terminated.
* The abort reason is always a {@link ReconnectingWebSocketError}.
*/
get terminationSignal(): AbortSignal;
/**
* Create a new ReconnectingWebSocket with URL and options.
*
* @param url URL or factory function for the WebSocket connection.
* @param options Configuration options.
*
* @throws {TypeError} If no WebSocket implementation is available.
*/
constructor(url: UrlProvider, options?: ReconnectingWebSocketOptions);
/**
* Create a new ReconnectingWebSocket with URL, protocols and options.
*
* @param url URL or factory function for the WebSocket connection.
* @param protocols Subprotocol(s) or factory function.
* @param options Configuration options.
*
* @throws {TypeError} If no WebSocket implementation is available.
*/
constructor(url: UrlProvider, protocols?: ProtocolsProvider, options?: ReconnectingWebSocketOptions);
/** Run the main reconnection loop. */
private _runLoop;
/** Resolve the connection URL and protocols from their providers. */
private _resolveEndpoint;
/**
* Close the socket and settle its lifecycle if it does not open within the timeout.
*
* @param socket Socket to monitor.
*/
private _armConnectionTimeout;
/** Await the full lifecycle of the given socket until it closes. */
private _awaitSocketLifecycle;
/** Flush buffered messages; a message whose send() throws is dropped. */
private _flushBuffer;
/** Wait between retries; cut short by reconnect() and termination. */
private _delay;
/** Close the current socket and settle its lifecycle if it was still connecting. */
private _dropSocket;
/** Permanently terminate the instance and clean up resources. */
private _terminate;
/**
* Returns the URL that was used to establish the WebSocket connection.
*
* ---
*
* `""` before the first connection for a factory.
*/
get url(): string;
/**
* Returns the state of the WebSocket object's connection. It can have the values described below.
*
* ---
*
* `CLOSED` only after permanent termination; any reconnection phase reports `CONNECTING`.
*/
get readyState(): 0 | 1 | 2 | 3;
/** Number of consecutive failed reconnection attempts; resets after a connection stays open for `stableTimeout`. */
get retryCount(): number;
/**
* Returns the number of bytes of application data (UTF-8 text and binary data) that have been queued using send() but not yet been transmitted to the network.
*
* If the WebSocket connection is closed, this attribute's value will only increase with each call to the send() method. (The number does not reset to zero once the connection closes.)
*
* ```ts
* const ws = new WebSocket("ws://localhost:8080");
* ws.send("Hello, world!");
* console.log(ws.bufferedAmount); // 13
* ```
*/
get bufferedAmount(): number;
/**
* Returns the extensions selected by the server, if any.
*
* WebSocket extensions add optional features negotiated during the handshake via
* the `Sec-WebSocket-Extensions` header.
*
* At the time of writing, there are two registered extensions:
*
* - [`permessage-deflate`](https://www.rfc-editor.org/rfc/rfc7692.html): Enables per-message compression using DEFLATE.
* - [`bbf-usp-protocol`](https://usp.technology/): Used by the Broadband Forum's User Services Platform (USP).
*
* See the full list at [IANA WebSocket Extensions](https://www.iana.org/assignments/websocket/websocket.xml#extension-name).
*
* Example:
*
* ```ts
* const ws = new WebSocket("ws://localhost:8080");
* console.log(ws.extensions); // e.g., "permessage-deflate"
* ```
*/
get extensions(): string;
/**
* Returns the subprotocol selected by the server, if any.
* It can be used in conjunction with the array form of the constructor's second argument to perform subprotocol negotiation.
*/
get protocol(): string;
/**
* Returns a string that indicates how binary data from the WebSocket object is exposed to scripts:
*
* Can be set, to change how binary data is returned. The default is "blob".
*
* ```ts
* const ws = new WebSocket("ws://localhost:8080");
* ws.binaryType = "arraybuffer";
* ```
*/
get binaryType(): BinaryType;
set binaryType(value: BinaryType);
/** Connection is being established. */
readonly CONNECTING = 0;
/** Connection is open and ready to communicate. */
readonly OPEN = 1;
/** Connection is in the process of closing. */
readonly CLOSING = 2;
/** Connection is closed. */
readonly CLOSED = 3;
/** Connection is being established. */
static readonly CONNECTING = 0;
/** Connection is open and ready to communicate. */
static readonly OPEN = 1;
/** Connection is in the process of closing. */
static readonly CLOSING = 2;
/** Connection is closed. */
static readonly CLOSED = 3;
/** Attribute-style handler for `close` events. */
get onclose(): ((this: WebSocket, ev: CloseEvent) => any) | null;
/** Set the attribute-style handler for `close` events. */
set onclose(handler: ((this: WebSocket, ev: CloseEvent) => any) | null);
/** Attribute-style handler for `error` events. */
get onerror(): ((this: WebSocket, ev: Event) => any) | null;
/** Set the attribute-style handler for `error` events. */
set onerror(handler: ((this: WebSocket, ev: Event) => any) | null);
/** Attribute-style handler for `message` events. */
get onmessage(): ((this: WebSocket, ev: MessageEvent) => any) | null;
/** Set the attribute-style handler for `message` events. */
set onmessage(handler: ((this: WebSocket, ev: MessageEvent) => any) | null);
/** Attribute-style handler for `open` events. */
get onopen(): ((this: WebSocket, ev: Event) => any) | null;
/** Set the attribute-style handler for `open` events. */
set onopen(handler: ((this: WebSocket, ev: Event) => any) | null);
/** Attach or detach the dispatcher for an attribute-style event handler. */
private _setAttributeListener;
/**
* Closes the WebSocket connection, optionally using code as the WebSocket connection close code and reason as the WebSocket connection close reason.
*
* @param code Status code for the closure.
* @param reason Human-readable reason for the closure.
*
* @throws {DOMException} If the code is not 1000 or in 3000-4999, or the reason is longer than 123 UTF-8 bytes.
*
* @example
* ```ts ignore
* ws.close(1000, "work complete");
* ```
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
*/
close(code?: number, reason?: string): void;
/**
* Drop the current connection and reconnect immediately.
*
* - The closed socket does not count towards `maxRetries`.
* - The current or next retry delay is skipped.
* - Cannot interrupt an in-flight url/protocols factory.
* - Does nothing once permanently terminated.
*
* @param code Status code for closing the current socket.
* @param reason Human-readable reason for the closure.
*
* @throws {DOMException} If the code is not 1000 or in 3000-4999, or the reason is longer than 123 UTF-8 bytes.
*
* @example
* ```ts ignore
* ws.reconnect(4000, "auth token refreshed");
* ```
*/
reconnect(code?: number, reason?: string): void;
/**
* Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView.
*
* If the connection is not open, the data is buffered (up to `maxEnqueuedMessages`)
* and sent once it is established. After permanent termination, data is silently discarded.
*
* @param data Payload to transmit or buffer.
*
* @example
* ```ts ignore
* ws.send("hello");
* ```
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
*/
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
}