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

@nktkas/rews

v4.0.1

Published

Drop-in WebSocket replacement with automatic reconnection.

Readme

@nktkas/rews

npm JSR bundlejs

Drop-in WebSocket replacement with automatic reconnection.


Without rews — manual reconnection, listener re-attachment, message queuing:

let ws: WebSocket;
let attempts = 0;
const queue: string[] = [];
const onMessage = (e: MessageEvent) => console.log(e.data);

function connect() {
  ws = new WebSocket("wss://example.com");
  ws.addEventListener("message", onMessage);
  ws.onopen = () => {
    attempts = 0;
    while (queue.length) ws.send(queue.shift()!);
  };
  ws.onclose = () => {
    if (attempts++ < 3) setTimeout(connect, 1000);
  };
}

function send(data: string) {
  ws.readyState === WebSocket.OPEN ? ws.send(data) : queue.push(data);
}

connect();
send("hello");

With rews — standard WebSocket API, no changes needed:

import { ReconnectingWebSocket } from "@nktkas/rews";

const ws = new ReconnectingWebSocket("wss://example.com");
ws.addEventListener("message", (e) => console.log(e.data));
ws.send("hello");

How It Works

sequenceDiagram
    participant App
    participant rews
    participant Server

    Server-->>rews: open  
    rews-->>App: open event

    App->>rews: send("hello")
    rews->>Server: "hello"
    Server-->>rews: "world"
    rews-->>App: message event

    Server--xrews: connection lost
    rews-->>App: close event
    Note over rews: ← standard WebSocket dies here,<br/>App must handle reconnection manually

    Note over rews: reconnecting...
    rews->>Server: reconnect

    App->>rews: send("hello")
    Note over rews: buffered

    Server-->>rews: open
    rews-->>App: open event
    rews->>Server: "hello" (from buffer)
    Server-->>rews: "world"
    rews-->>App: message event

    Note over App: App didn't notice the disruption

Features

  • Drop-in replacement — standard WebSocket API, swap one line
  • Auto-reconnection — configurable retries and delay strategy
  • Message bufferingsend() queues data while offline, flushes on reconnect
  • Persistent listenersaddEventListener and on* handlers survive reconnections
  • Dynamic URL & protocols — resolve fresh values on each reconnection
  • Zero dependencies — works in Node.js, Deno, Bun, and browsers

Install

npm i @nktkas/rews          # npm
pnpm add @nktkas/rews       # pnpm
yarn add @nktkas/rews       # yarn
deno add jsr:@nktkas/rews   # Deno
bun add @nktkas/rews        # Bun

Or import directly via CDN (no install):

<script type="module">
  import { ReconnectingWebSocket } from "https://esm.sh/@nktkas/rews";
</script>

React Native

Hermes lacks the global EventTarget / Event that rews needs — polyfill them before importing rews:

import { Event, EventTarget } from "event-target-shim";
if (!globalThis.EventTarget) globalThis.EventTarget = EventTarget;
if (!globalThis.Event) globalThis.Event = Event;

React Native ships an AbortController polyfill that drops the abort() reason, leaving terminationSignal.reason undefined — replace it with one that keeps it:

import { AbortController, AbortSignal } from "abortcontroller-polyfill/dist/cjs-ponyfill";
globalThis.AbortController = AbortController;
globalThis.AbortSignal = AbortSignal;

Options

interface ReconnectingWebSocketOptions {
  /** Maximum number of consecutive failed reconnection attempts. @default Infinity */
  maxRetries?: number;
  /** Connection timeout in ms (null to disable). @default 10_000 */
  connectionTimeout?: number | null;
  /** Time in ms a connection must stay open before the retry counter resets. @default 3_000 */
  stableTimeout?: number;
  /** Delay before reconnection in ms, or a function of attempt number. @default exponential backoff with jitter, max 10s */
  reconnectionDelay?: number | ((attempt: number) => number);
  /** Decide whether to reconnect after a non-user closure. @default () => true */
  shouldReconnect?: (event: CloseEvent, attempt: number) => boolean;
}

Beyond Standard WebSocket

Dynamic URL & Protocols

url and protocols accept functions (mb async), invoked on each reconnection:

const ws = new ReconnectingWebSocket(
  () => `wss://example.com?token=${getToken()}`,
  () => ["v2"],
);

Errors thrown by these functions count as failed connection attempts and follow the normal retry flow. The same applies to a permanently invalid URL: with the default maxRetries: Infinity it is retried forever, each close event carrying the error in its reason.

Event Lifecycle

Standard open, close, error, and message events fire on every connection cycle — not just the first one. A single ReconnectingWebSocket instance may emit multiple open/close pairs over its lifetime as it reconnects.

ws.addEventListener("open", () => console.log("connected")); // fires on each (re)connection
ws.addEventListener("close", () => console.log("disconnected")); // fires on each disconnection

// use { once: true } if you only need the first occurrence
ws.addEventListener("open", () => init(), { once: true });

readyState

CLOSED means permanently terminated. While reconnecting — a retry pause, a connection attempt, a url/protocols factory await — readyState is CONNECTING; CLOSING is never reported.

Termination

terminationSignal is an AbortSignal that aborts when the connection is permanently closed. The abort reason is always a ReconnectingWebSocketError:

| Code | Description | | ----------------------- | -------------------------------------------------------- | | RECONNECTION_LIMIT | Max retries exceeded | | RECONNECTION_DECLINED | shouldReconnect returned false | | TERMINATED_BY_USER | close() called | | UNKNOWN_ERROR | Unhandled error in reconnectionDelay/shouldReconnect |

ws.terminationSignal.aborted; // boolean
ws.terminationSignal.reason; // ReconnectingWebSocketError, once aborted

ws.terminationSignal.addEventListener("abort", () => {
  ws.terminationSignal.reason.code; // ReconnectingWebSocketErrorCode
  ws.terminationSignal.reason.cause; // original error, if any
});

Being a standard AbortSignal, it composes with AbortSignal.any(), fetch(), addEventListener(..., { signal }), and other platform APIs.

Closing Behavior

ws.close(code?, reason?); // permanently close — the standard WebSocket method
ws.reconnect(code?, reason?); // drop the current connection and reconnect immediately

reconnect() skips the current retry delay, is not counted towards maxRetries, and does nothing once the instance is permanently terminated.

License

@nktkas/rews is licensed under the MIT License.

Copyright © 2025-present nktkas and contributors.