npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-pack

Node 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 old reConnect event spelling has been removed from public types.
  • ReConnectType is now only "roundIn" | "round"; the unused "online-round" branch was removed.
  • sendError now receives { data, target, error, reason? }; encode failures use reason: "ENCODE_ERROR".
  • Heartbeat ping is no longer encoded by codec. Configure heartbeat.ping directly 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 .WebSocket aliases were removed.
  • transformData and transformResponse were removed. Use codec.