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

@p-vbordei/ws-reconnect

v0.2.1

Published

Pure helpers for WebSocket reconnection: exponential backoff with jitter, sequence-gap classifier, RFC 6455 close-code lookup. Zero dependencies.

Readme

ws-reconnect

ci

npm downloads bundle

Three small, framework-agnostic helpers for building robust WebSocket clients. No WebSocket implementation included — bring your own. These are the pieces around it.

import { backoff, BackoffState, checkSequence, describeCloseCode } from "@p-vbordei/ws-reconnect";

const state = new BackoffState({ baseMs: 250, maxMs: 30_000 });
ws.addEventListener("open",  () => state.reset());
ws.addEventListener("close", () => setTimeout(reconnect, state.next()));

const r = checkSequence(prevSeq, msg.seq);
if (r.kind === "gap")   triggerResync(r.missing);
if (r.kind === "reset") clearLocalCache();

const info = describeCloseCode(event.code);
if (!info.retriable) abort();

Install

npm install @p-vbordei/ws-reconnect

Works with Node 20+, browsers, Bun, Deno. ESM + CJS.

Why

Every WebSocket client needs the same three pieces:

  1. Exponential backoff with jitter so clients don't all reconnect at the same moment after an outage.
  2. Sequence-gap detection to know when the server dropped messages and you need to resync.
  3. Close-code interpretation so you don't retry forever after a 4401 unauthorized.

These never live well inside the socket class — they want unit tests, injectable clocks, and to be shared across web/Node clients. Easier to pull them out.

Recipes

Reconnecting WebSocket client (browser)

import { BackoffState, describeCloseCode } from "@p-vbordei/ws-reconnect";

class ReconnectingWS {
  private ws?: WebSocket;
  private state = new BackoffState({ baseMs: 250, maxMs: 30_000 });

  constructor(private url: string) { this.connect(); }

  private connect() {
    this.ws = new WebSocket(this.url);
    this.ws.addEventListener("open",  () => this.state.reset());
    this.ws.addEventListener("close", (e) => {
      const info = describeCloseCode(e.code);
      if (info.retriable) {
        setTimeout(() => this.connect(), this.state.next());
      } else {
        console.warn(`won't retry: ${info.name} - ${info.description}`);
      }
    });
  }

  send(data: string) { this.ws?.send(data); }
  close() { this.ws?.close(1000, "client closing"); }
}

Resync on detected gap

import { checkSequence } from "@p-vbordei/ws-reconnect";

let lastSeq = 0;

ws.onmessage = async (e) => {
  const msg = JSON.parse(e.data);
  const r = checkSequence(lastSeq, msg.seq);

  if (r.kind === "gap") {
    console.warn(`missed ${r.missing} messages, resyncing`);
    await fetchSinceSeq(lastSeq);
  } else if (r.kind === "reset") {
    console.warn("server restarted, clearing cache");
    cache.clear();
  } else if (r.kind === "duplicate") {
    return;  // ignore replays
  }

  lastSeq = msg.seq;
  handle(msg);
};

Tune backoff for fast vs slow services

import { backoff } from "@p-vbordei/ws-reconnect";

// Real-time game client: reconnect fast
backoff(attempt, { baseMs: 100, maxMs: 2_000, jitter: "equal" });

// Analytics WebSocket: reconnect slow, don't hammer
backoff(attempt, { baseMs: 1_000, maxMs: 60_000, jitter: "full" });

Detect rate-limit close codes

import { describeCloseCode } from "@p-vbordei/ws-reconnect";

ws.addEventListener("close", (e) => {
  const info = describeCloseCode(e.code);
  if (info.code === 4429) {
    setTimeout(connect, 60_000);  // back off aggressively
  }
});

API

backoff(attempt, opts?): number

Returns the delay (ms) for a 1-based attempt number. Clamped to maxMs.

| Option | Type | Default | Meaning | |---|---|---|---| | baseMs | number | 250 | Delay for attempt #1 (no jitter) | | maxMs | number | 30000 | Cap | | factor | number | 2 | Multiplier per attempt | | jitter | "none" \| "full" \| "equal" | "full" | See below | | random | () => number | Math.random | Injectable for tests |

Jitter strategies:

  • none — deterministic exponential
  • full — random() * cappedExponential (recommended; avoids reconnect stampedes)
  • equal — half + random() * half

class BackoffState(opts?)

state.next()    // returns delay AND increments the internal attempt counter
state.reset()   // back to zero
state.attempts  // current count

checkSequence(prev, next, opts?): SequenceCheck

Classifies the relationship between two sequence numbers:

| kind | Meaning | |---|---| | continuous | next === prev + 1 (happy path) | | gap | next > prev + 1 — server may have dropped messages; trigger resync. Includes missing: number. | | duplicate | next === prev — replay, safe to ignore | | rewind | next < prev by a small amount — likely server bug or out-of-order | | reset | next < prev by more than resetThreshold * wrapAt — server restarted |

| Option | Type | Default | |---|---|---| | wrapAt | number | Number.MAX_SAFE_INTEGER | | resetThreshold | number | 0.5 |

describeCloseCode(code): CloseCodeInfo

Returns { code, name, description, retriable }. Covers all standard RFC 6455 codes (1000–1015) plus common application codes (4401 unauthorized, 4429 rate-limited, ...). Unknown codes return a sensible default rather than throwing.

Caveats

  • Pure helpers — no WebSocket impl. Bring your own (WebSocket, ws, partysocket, etc.).
  • No heartbeat helper. Some servers expect periodic pings to keep the connection alive — add your own setInterval ping; that's app-specific.

License

Apache-2.0 © Vlad Bordei