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

@simpill/socket.utils

v1.0.0

Published

Reconnecting WebSocket client with optional heartbeat (Node and Edge).

Downloads

138

Readme

Features: Type-safe · Node & Edge · Lightweight · Uses @simpill/async.utils for backoff


Installation

From npm

npm install @simpill/socket.utils

From GitHub

To use this package from the monorepo source:

git clone https://github.com/SkinnnyJay/simpill.git
cd simpill/utils/@simpill-socket.utils
npm install && npm run build

In your project you can then install from the local path: npm install /path/to/simpill/utils/@simpill-socket.utils or use npm link from the package directory.


Usage

import { createReconnectingWebSocket } from "@simpill/socket.utils";

const { ws, reconnect, close } = createReconnectingWebSocket("wss://example.com", {
  reconnect: {
    maxAttempts: 10,
    initialDelayMs: 1000,
    maxDelayMs: 30000,
    backoffMultiplier: 1.5,
  },
  heartbeat: { intervalMs: 30000, message: "ping" },
});

ws.onmessage = (e) => console.log(e.data);
reconnect(); // manual reconnect
close(); // stop reconnecting and close

API

  • createReconnectingWebSocket(url, options?) — Returns { ws, reconnect(), close(), open(), getState(), send(data) }. Options: reconnect, heartbeat, WebSocketCtor, signal, autoConnect, hooks, limits, queue, retryPolicy, message.
  • ReconnectOptions, HeartbeatOptions, ReconnectingWebSocketHooks, MessageQueueOptions, RetryPolicyOptions, MessageHelpersOptions, ReconnectingWebSocketState — Shared types.

Reconnect options

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxAttempts | number | 10 | Stop reconnecting after this many attempts. | | initialDelayMs | number | 1000 | Delay before first reconnect. | | maxDelayMs | number | 30000 | Cap on delay between attempts. | | backoffMultiplier | number | 1.5 | Multiply delay by this after each attempt. | | jitter | "none" | "full" | "equal" | "none" | Apply jitter to delay to avoid thundering herd. | | jitterRatio | number (0–1) | 0.5 | Used when jitter is "equal" for min/max range. |

Use retryPolicy.maxElapsedMs and retryPolicy.shouldReconnect to cap by time or close event.

Heartbeat options

| Option | Type | Default | Description | |--------|------|---------|-------------| | intervalMs | number | (required) | Send ping at this interval while open. | | message | string | () => string | "" | Ping payload; empty string skips send. | | timeoutMs | number | — | Optional; when expectPong, pongTimeoutMs is used for pong wait. | | expectPong | boolean | false | When true, expect pong and close after maxMisses without pong. | | pongTimeoutMs | number | 5000 | Time to wait for pong before counting a miss. | | maxMisses | number | 3 | Close and reconnect after this many missed pongs. | | isPong | (data: unknown) => boolean | string "pong" or { type: "pong" } | Detects pong in incoming message. |

Send queue and backpressure

When queue.enabled is true, send(data) while the socket is not open pushes messages into an outbound queue. When the socket opens, the queue is flushed (oldest first). queue.maxSize caps the queue; exceeding it drops the oldest messages and calls queue.onDrop(count). queue.ttlMs drops messages older than that when flushing. There is no backpressure API (e.g. callback when send is safe); use getState().status === "open" or queue.onDrop to react.

Typed message codecs

message.serialize(value) is used when you call send(non-string); default is JSON.stringify. message.parse and message.validate are not applied by the library—use them in hooks.onMessage to parse and validate incoming data (e.g. const parsed = message.parse(ev.data); if (message.validate?.(parsed)) { ... }).

Heartbeat semantics

heartbeat.intervalMs and heartbeat.message (string or function) send a ping on that interval while open. If heartbeat.expectPong is true, the client expects a pong reply; heartbeat.isPong(data) (default: string "pong" or object { type: "pong" }) detects it. If no pong is received within heartbeat.pongTimeoutMs (default 5000), a “miss” is counted; after heartbeat.maxMisses (default 3) the socket is closed so reconnect can run. So heartbeat both keeps the connection alive and can detect dead connections when the server stops replying.

Backoff jitter

reconnect.jitter can be "none" (no jitter), "full" (delay = random 0..delayMs), or "equal" (delay in [delayMs*(1-ratio), delayMs*(1+ratio)]). reconnect.jitterRatio (0–1, default 0.5) is used for "equal" only. Jitter is applied to the delay before each reconnect attempt to avoid thundering herd.

Hooks and events

options.hooks can set:

| Hook | When | |------|------| | onOpen | Socket opened (after connect). | | onClose | Socket closed (before reconnect scheduled if any). | | onReconnect | Reconnect scheduled (attempt number and delayMs). | | onMessage | Any message received (use for parse/validate if desired). | | onError | WebSocket error event. |

You can also attach ws.onopen, ws.onmessage, etc.; hooks are in addition to that.

Server helpers

This package provides a reconnecting client only. There are no WebSocket server helpers; use ws, uWebSockets.js, or your runtime’s server API.

WebSocketCtor (browser vs Node)

In the browser, globalThis.WebSocket is used by default. In Node, there is no built-in WebSocket; pass WebSocketCtor from ws (or another compatible constructor). The constructor must support new WebSocketCtor(url) and the same readyState, send, close, and event (onopen, onclose, onmessage, onerror) contract. Small API differences (e.g. binary type, protocol list) depend on the implementation.

close() semantics

close() sets an internal closed flag, clears reconnect and heartbeat timers, and closes the underlying ws if it is not already CLOSED or CLOSING. After close(), no further reconnects occur. reconnect() clears closed and resets the attempt count so the client can connect again. Calling close() multiple times is safe.

Node.js with ws example

import WebSocket from "ws";
import { createReconnectingWebSocket } from "@simpill/socket.utils";

const { ws, close } = createReconnectingWebSocket("wss://example.com", {
  WebSocketCtor: WebSocket as typeof globalThis.WebSocket,
  reconnect: { maxAttempts: 5, initialDelayMs: 1000 },
});
ws?.on("open", () => console.log("open"));
// In Node, ws may use .on("message", ...) depending on version; check ws docs.
close();

Retry cap guidance

Reconnects are limited by reconnect.maxAttempts (default 10) and optionally by retryPolicy.maxElapsedMs (stop after total time since first connect) and retryPolicy.shouldReconnect({ attempt, closeEvent }) (return false to stop). For “infinite” retries use a high maxAttempts and/or omit maxElapsedMs and shouldReconnect; cap by time or close code in shouldReconnect to avoid endless reconnect loops (e.g. 401/403 or server “go away” close code).

What we don't provide

  • Server helpers — Reconnecting client only; for WebSocket server use ws, uWebSockets.js, or your runtime’s server API.
  • Backpressure — No callback when send is “safe”; use getState().status === "open" or queue.onDrop to react.
  • Message parse/validatemessage.serialize is used for send; use hooks.onMessage and your own message.parse / message.validate for incoming data.

When to use

| Use case | Recommendation | |----------|----------------| | Browser or Node WebSocket client with auto-reconnect | Use createReconnectingWebSocket with WebSocketCtor in Node. | | Keep connection alive / detect dead server | Use heartbeat with expectPong and maxMisses. | | Send before open / avoid drops | Enable queue with maxSize and onDrop to handle overflow. | | Custom backoff / stop after time | Use retryPolicy.shouldReconnect and maxElapsedMs. | | Observability | Use hooks and getState() for logging and metrics. |

Subpaths: @simpill/socket.utils, ./client, ./server (types), ./shared.

Examples

npx ts-node examples/01-basic-usage.ts

| Example | Description | |---------|-------------| | 01-basic-usage.ts | createReconnectingWebSocket, reconnect options, heartbeat, close |

Development

npm install
npm test
npm run build
npm run verify

Documentation

License

ISC