node-ws-pack
v1.0.0
Published
Lightweight WebSocket client wrapper with reconnects, heartbeat checks, send queueing, and pluggable codecs.
Downloads
191
Readme
node-ws-pack
Thin Node.js WebSocket client wrapper built on ws: reconnect, heartbeat,
send queue, JSON defaults, and pluggable codecs for binary protocols.
Install
npm i node-ws-packNode 20 or later is supported. The package exposes both CJS and ESM entries.
Basic Usage
import { VERSION, create } from "node-ws-pack";
const socket = create({
url: "ws://127.0.0.1:8080",
connectInterval: 2000,
maxQueueSize: 1000,
reConnectInterval: 3000,
heartbeat: {
ping: JSON.stringify({ id: 1, method: 100000 }),
pong: "pong",
interval: 3000,
timeoutInterval: 2000,
maxTimeoutTime: 2,
},
});
socket.connect();
console.info("node-ws-pack", VERSION, socket.currentUrl);
socket.on("reconnect", event => {
// event.type is "roundIn" or "round"
console.info("websocket reconnect", event);
});
socket.on("connectTimeMax", round => {
console.info("websocket reconnect round exhausted", round);
});
socket.on("socketError", error => {
console.info("websocket error", error);
});
socket.on("sendError", ({ data, error, reason }) => {
console.info("websocket send failed", reason, data, error);
});
socket.on("data", data => {
console.info("websocket decoded data", data);
});By default, plain objects are JSON-stringified before sending and inbound JSON
strings are parsed. Strings and binary values are sent as-is.
The default decoder is intentionally permissive: if an inbound text frame is not
valid JSON, the original string is emitted as business data instead of raising a
decode error. Encoding is stricter; values that cannot be represented as JSON
fail with reason: "ENCODE_ERROR".
Codec
Use codec when the wire protocol is not JSON. The library does not load proto
files or bind to a protobuf runtime; the caller owns encode/decode.
const socket = create({
url,
heartbeat: {
// Heartbeat ping is sent as configured. It is not passed through codec.encode.
ping: Buffer.from([0x01]),
interval: 10000,
timeoutInterval: 10000,
maxTimeoutTime: 3,
},
codec: {
encode(message) {
return encodeMyMessage(message);
},
decode(raw) {
return decodeMyMessage(raw);
},
isHeartbeatPong(message) {
return message.type === "pong";
},
},
});
socket.connect();The previous transformData and transformResponse hooks were removed in the
major version. Use codec.encode and codec.decode.
If codec.decode, codec.heartbeatDecode, or codec.isHeartbeatPong throws,
the client emits socketError and, if listened to, error. Low-level ws
errors are normalized to Error before they are emitted. codec.decode
failures also emit decodeError with the raw message. The raw message event
is emitted before decode is attempted, so it is observed before decodeError.
A heartbeatDecode or isHeartbeatPong failure does not drop the already
decoded business data event.
Heartbeat
Heartbeat is only a liveness check. Any inbound message refreshes heartbeat
liveness, preserving the previous behavior where normal business messages count
as server activity. heartbeat.pong and codec.isHeartbeatPong only decide
whether the public pong event is emitted.
Heartbeat pong payloads are also emitted through data by default for backward
compatibility. Set emitHeartbeatPongAsData: false when heartbeat pong frames
should stay protocol-only:
const socket = create({
url,
emitHeartbeatPongAsData: false,
heartbeat: {
ping: "ping",
pong: "pong",
},
});After maxTimeoutTime heartbeat misses, the client closes the current socket
with close code 4001 and reason "heartbeat timeout", then follows the normal
reconnect policy.
heartbeat.ping is sent directly as configured. If a binary protocol needs a
binary ping, configure heartbeat.ping as a Buffer, Uint8Array, or another
value accepted by ws.send.
Reconnect
Events:
| Event | Payload | Meaning |
| --- | --- | --- |
| reconnect | { type, time, description } | type is "roundIn" for attempts inside a round or "round" for the next round. |
| connectTimeMax | number | One reconnect round has exhausted maxReconnectTime. |
| socketError | Error | Safe error event that never crashes when no error listener exists. |
| error | Error | Emitted only when an error listener is registered. |
If maxReconnectTime <= 0, automatic reconnect is disabled. If
reConnectInterval <= 0, reconnect stops after connectTimeMax. The client
then emits socketError and, if listened to, error with:
{
code: "RECONNECT_EXHAUSTED",
time: number
}Call connect() again to start a new connection attempt. connect() returns
true when it starts an attempt and false when no URL is available or a
connection attempt is already in progress.
client.option is the frozen construction/default configuration. Arguments
passed to connect(url?, protocols?, options?) are stored separately for the
active connection and subsequent reconnects. Use currentUrl,
currentProtocols, and currentOptions to inspect that active connection
configuration; returned protocol arrays and options objects are defensive copies.
When a URL object is provided, the client snapshots it as its href string so
later mutations of the original URL cannot change reconnect targets.
Sending
send(data) returns true when data is queued or sent, and false when the
client cannot accept the data. While the socket is connecting, at most
maxQueueSize messages are queued. The default is 1000; use 0 to disable
queueing. Use sendResult(data) when callers need to distinguish "queued",
"sent", and "rejected":
const result = socket.sendResult({ id: 1 });
if (result.status === "queued") {
// accepted for later flush when the connection opens
}Failed sends emit:
socket.on("sendError", ({ data, target, error, reason }) => {
// reason is "CLOSING", "CLOSED", "NOT_READY", "ENCODE_ERROR",
// "QUEUE_FULL", "QUEUE_DISABLED", or "SEND_FAILED".
});sendResult(data).status === "sent" means the payload was accepted by
ws.send. It is not a remote delivery acknowledgement. Listen to sendSuccess
and sendError, or build an application-level acknowledgement, when the caller
needs to know the final write result.
If codec.encode throws, the original input is not queued or sent. The client
emits sendError with reason: "ENCODE_ERROR" and send(data) returns
false.
Queued messages are flushed in order when the socket opens. If a queued send throws synchronously, that failed message and all later queued messages remain queued so a later reconnect can retry them in the same order.
Closing
close() actively closes the current socket, clears timers and queued messages,
and disables automatic reconnect for that close. Calling connect() again starts
a new attempt and enables automatic reconnect for that new connection.
destroy() does everything close() does, detaches the active socket, and marks
the client as terminal. Use it when the instance will not be reused.
Listeners registered on the wrapper client are preserved across reconnects.
Listeners registered directly on CodecContext.socket are not removed by the
client when it detaches its own internal listeners, but they still belong to that
underlying ws instance and must be attached again for a new socket after
reconnect.
Limits
Inbound message size is controlled by the underlying ws maxPayload option.
Pass it through options when a stricter business limit is needed.
Timing and count options are validated at construction time. connectInterval,
heartbeat.interval, heartbeat.timeoutInterval, and
heartbeat.maxTimeoutTime must be positive integers. maxQueueSize,
maxReconnectTime, and reConnectInterval must be non-negative integers. Use
maxQueueSize: 0 to disable queueing, maxReconnectTime: 0 to disable
automatic reconnect, and reConnectInterval: 0 to stop after one reconnect
round is exhausted.
perMessageDeflate defaults to false to avoid compression CPU overhead and
latency for small realtime messages. Pass options: { perMessageDeflate: true }
when the connection mainly transports large text payloads and bandwidth matters
more than compression cost.
Heartbeat payloads are sent as regular WebSocket frames. Do not put secrets in
heartbeat.ping unless the connection transport and server handling are
appropriate for that data.
Migration Notes
- Use
reconnect; the oldreConnectevent spelling has been removed from public types. ReConnectTypeis now only"roundIn" | "round"; the unused"online-round"branch was removed.sendErrornow receives{ data, target, error, reason? }; encode failures usereason: "ENCODE_ERROR".- Heartbeat ping is no longer encoded by codec. Configure
heartbeat.pingdirectly in the wire format expected by the server. - Use the named
create(options)export. The old default callable client wrapper, import-time singleton,.instance,.create, and.WebSocketaliases were removed. transformDataandtransformResponsewere removed. Usecodec.
