JSPM

tiny-resilient-websocket

0.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 16
  • Score
    100M100P100Q70529F
  • License MIT

A tiny, dependency-free WebSocket client with exponential-backoff reconnect, heartbeats, an outgoing queue, and pluggable transports. Works in browsers, Node, Deno, Bun, and edge runtimes.

Package Exports

  • tiny-resilient-websocket
  • tiny-resilient-websocket/package.json

Readme

resilient-websocket

A tiny, dependency-free WebSocket client with everything you actually need in production:

  • Exponential-backoff reconnect with jitter, capped by attempts and/or wall-clock duration.
  • Pluggable transport — works in browsers, Node (ws), Deno, Bun, and edge runtimes.
  • Dynamic URL provider for token refresh on every connection attempt.
  • Heartbeats with stale-connection detection.
  • Outgoing message queue that survives reconnects.
  • First-class state machine with hooks and a typed on()/off() API.
  • ~6 KB minified, zero runtime dependencies, full TypeScript types.
npm install resilient-websocket
# or: pnpm add resilient-websocket
# or: yarn add resilient-websocket

Quick start

Browser

import { ResilientWebSocketClient } from 'resilient-websocket';

const client = new ResilientWebSocketClient({
    url: 'wss://example.com/feed',
    onConnected: (socket) => {
        socket.send(JSON.stringify({ type: 'subscribe', channel: 'ticks' }));
    },
    hooks: {
        onMessage: (data) => console.log('msg', data),
        onClose: (event, willReconnect) => console.log('closed', event.code, { willReconnect }),
    },
    policy: {
        initialReconnectDelayMs: 500,
        maxReconnectDelayMs: 30_000,
        maxReconnectDurationMs: 5 * 60_000,
    },
});

await client.connect();

Node.js (with the ws package)

import WebSocket from 'ws';
import { ResilientWebSocketClient } from 'resilient-websocket';

const client = new ResilientWebSocketClient({
    urlProvider: async () => `wss://example.com/feed?token=${await getToken()}`,
    webSocketFactory: (url, protocols) =>
        new WebSocket(url, protocols, { headers: { 'User-Agent': 'my-app/1.0' } }),
    heartbeat: { intervalMs: 15_000, pongTimeoutMs: 5_000 },
});

await client.connect();

API

new ResilientWebSocketClient(options)

Required (one of):

  • url: string — static endpoint.
  • urlProvider: () => string | Promise<string> — invoked before every connect attempt. Use for token refresh.

Optional:

Option Type Default Description
protocols string | string[] WebSocket subprotocols.
webSocketFactory WebSocketFactory global WebSocket Override the constructor (Node, mocks, custom headers).
binaryType 'arraybuffer' | 'blob' 'arraybuffer' Underlying socket binaryType.
onConnected (socket) => void Called on every successful open (good place for subscribe frames).
queueOutgoingWhileDisconnected boolean true Buffer send() while disconnected and flush on next open.
maxQueuedMessages number 1000 Drop oldest beyond this cap.
policy ResilientWebSocketPolicy see below Backoff & give-up policy.
heartbeat ResilientWebSocketHeartbeat off Enable application-level keep-alive.
hooks ResilientWebSocketHooks Lifecycle hooks.
logger ResilientWebSocketLogger console-shaped logger for breadcrumbs.

Policy defaults

Field Default
maxReconnectAttempts Infinity
maxReconnectDurationMs Infinity
initialReconnectDelayMs 1_000
maxReconnectDelayMs 30_000
reconnectBackoffMultiplier 2
reconnectJitterRatio 0.15

Methods

  • connect(): Promise<void> — start (or restart after stop() / give-up). Resolves on next open, rejects on give-up.
  • stop(code?, reason?): voidsoft shutdown: closes the socket and prevents reconnects, but keeps on() listeners and queued messages so a follow-up connect() resumes with the same wiring.
  • disconnect(options?): voidfull teardown: stop() + clears the outgoing queue + removes every listener registered via on()/once() + resets state to 'idle'. Use this to "unsubscribe everything" before reusing the instance with fresh listeners or discarding it.
  • reconnect(): Promise<void> — force-close and reconnect immediately.
  • send(data): booleantrue if sent, false if queued.
  • clearQueue(): void — empty the outgoing queue.
  • on(event, listener) / off(event, listener) / once(event, listener) — typed event subscriptions.
  • removeAllListeners(): void

stop() vs disconnect() — at a glance

Behavior stop() disconnect()
Closes the socket
Cancels reconnect / heartbeat timers
Rejects pending connect() promises
Keeps queued outgoing messages — (cleared)
Keeps on()/once() listeners — (removed)
Final state closed idle

Getters

  • state'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed' | 'gaveUp'
  • isConnectedtrue when the socket is OPEN.
  • hasGivenUptrue after policy give-up.
  • queuedMessageCount
  • rawSocket — the underlying WebSocketLike, or null.

Events

Event Payload
open void
message { data: string; event: MessageEvent }
error Event
close { event: CloseEvent; willReconnect: boolean }
reconnectScheduled { attempt: number; delayMs: number }
giveUp { reason; reconnectAttempts; elapsedSinceReconnectPhaseMs }
stateChange { next; previous }

Patterns

Authentication that may rotate

new ResilientWebSocketClient({
    urlProvider: async () => `wss://api.example.com/ws?token=${await refreshToken()}`,
});

Graceful shutdown on page unload

window.addEventListener('beforeunload', () => client.stop(1001, 'page unload'));

Pause/resume on offline/online

window.addEventListener('offline', () => client.stop());
window.addEventListener('online', () => client.connect());

Resetting after give-up

onGiveUp is the policy saying "I've tried enough." The socket is closed; counters stay frozen so you can inspect them. Call connect() again to reset everything and try once more.

Browser / runtime support

Anything with a global WebSocket (all modern browsers, Deno, Bun, Cloudflare Workers, Node 22 with --experimental-websocket or via the ws package).

License

MIT