API Reference
WebSocketClient
Constructor
new WebSocketClient(options?: ClientOptions)
Creates a new client instance.
Constructor options
maxBufferSize?: number- Maximum number of incoming messages to keep buffered before they are consumed
- Default:
0(unlimited) - When the limit is reached, the oldest buffered message is dropped
keepAlive?: KeepAliveOptions- Enables automatic ping/pong keep-alive (Node.js only)
- Throws if used in a browser environment
Properties
client.readyState
readonly readyState: WebSocketState
Returns the current client state: "idle", "connecting", "open", "closing", "closed", or "errored".
client.protocol
readonly protocol: string
Returns the negotiated subprotocol, or "" when not connected.
client.url
readonly url: string
Returns the URL of the WebSocket connection, or "" when not connected.
client.bufferedAmount
readonly bufferedAmount: number
Returns the number of bytes queued for transmission, or 0 when not connected.
client.extensions
readonly extensions: string
Returns the negotiated extensions, or "" when not connected.
client.lastCloseInfo
readonly lastCloseInfo: WebSocketCloseInfo | null
Returns close metadata from the most recent close event, or null if the socket has not closed yet.
Static methods
fromSocket()
static fromSocket(rawSocket: unknown, options?: ClientOptions): WebSocketClient
Wraps an already-open WebSocket into a WebSocketClient in the "open" state, ready to send and receive. Intended for server scenarios where a WebSocketServer hands you an established connection.
Node.js only. Throws in browser builds.
import { WebSocketServer } from "ws";
import { WebSocketClient } from "@culpeo/async-ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", async (socket) => {
const client = WebSocketClient.fromSocket(socket);
for await (const msg of client) {
console.log("received:", msg.data);
await client.send("echo: " + msg.data);
}
});
The client takes ownership of the socket lifecycle — calling close() will close the underlying socket. Call fromSocket() immediately in the connection handler to avoid missing messages.
Accepts any WebSocket-compatible object (validated structurally, not via instanceof), so it works even when multiple copies of the ws package are installed.
Instance methods
connect()
connect(url: string | URL, options?: ConnectOptions): Promise<void>
Opens a WebSocket connection and resolves when the connection is established.
Rejects when:
- the client is already connecting, open, or closing
- the socket constructor throws
- the connection errors before opening
- the socket closes before opening
ConnectOptions
protocols?: string | string[]— WebSocket subprotocols to requestheaders?: Record<string, string>— custom handshake headers in Node.jstimeout?: number— connection timeout in milliseconds; rejects if the connection is not established within this timesignal?: AbortSignal— an abort signal to cancel the connection attempt
In browsers, passing
headersthrows because the native WebSocket API does not support custom headers.
send()
send(data: string | ArrayBuffer | ArrayBufferView): Promise<void>
Sends text or binary data. Resolves when the underlying socket accepts the payload. Rejects if the client is not open or if the underlying adapter reports an error.
receive()
receive(): Promise<WebSocketMessage>
Resolves with the next incoming message.
Behavior:
- If buffered messages exist, returns the oldest buffered message immediately
- If no buffered message exists, waits for the next incoming message
- If the socket closes after buffering messages, buffered messages are still drained first
- Rejects when the client is not open and no buffered messages remain
close()
close(code?: number, reason?: string): Promise<void>
Starts the close handshake and resolves when the socket closes.
Behavior:
- Resolves immediately if the client is idle, already closed, or errored
- If a close is already in progress, waits for the close event
- Validates custom close codes before calling the underlying socket
- Accepts
1000or values in the range3000-4999
Async iterator
client[Symbol.asyncIterator](): AsyncGenerator<WebSocketMessage>
Allows consumption with for await...of.
Behavior:
- Yields incoming messages as they arrive
- Ends iteration on a clean close
- Throws on unexpected or error-driven termination
- Does not automatically close the socket if you
breakout of the loop
const client = new WebSocketClient();
await client.connect("wss://example.com/ws");
try {
for await (const message of client) {
if (!message.binary) {
console.log("text:", message.data);
}
}
} finally {
await client.close();
}
Types
ConnectOptions
interface ConnectOptions {
protocols?: string | string[];
headers?: Record<string, string>;
timeout?: number;
signal?: AbortSignal;
}
ClientOptions
interface ClientOptions {
maxBufferSize?: number;
keepAlive?: KeepAliveOptions;
}
KeepAliveOptions
interface KeepAliveOptions {
interval: number;
timeout?: number;
}
interval— milliseconds between pingstimeout— milliseconds to wait for a pong before terminating the connection (default:interval)
WebSocketMessage
interface WebSocketMessage {
data: string | ArrayBuffer;
binary: boolean;
}
WebSocketCloseInfo
interface WebSocketCloseInfo {
code: number;
reason: string;
wasClean: boolean;
}
WebSocketState
type WebSocketState =
"idle" | "connecting" | "open" | "closing" | "closed" | "errored";
Browser vs Node
@culpeo/async-ws ships one API for both environments:
- Node.js build uses the
wspackage internally - Browser build uses the native
WebSocketimplementation
This is handled at build time with Rollup. The browser bundle aliases the Node adapter module to a browser-specific adapter, so application code does not need environment checks or separate imports.
Error handling
All core operations are async and communicate failure by rejecting:
connect()rejects on invalid state, connection failure, early close, timeout, or abortsend()rejects when called before the socket is open or when the adapter fails to sendreceive()rejects when the client is not in a receivable state and no buffered messages remainclose()rejects for invalid close codes
Additional notes:
- Connection errors are treated as terminal for pending receivers
- A socket error is typically followed by a close event; close metadata is exposed through
lastCloseInfo - If buffered messages exist when a close happens, those messages are still delivered before
receive()starts rejecting