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

shared-socket

v1.0.0

Published

Drop-in WebSocket replacement with single-connection-per-URL across browser tabs

Downloads

15

Readme

shared-socket

Drop-in WebSocket replacement that shares a single connection across browser tabs. Only one tab maintains the real WebSocket connection — all others send and receive through it transparently.

Why

Every new WebSocket(url) opens a separate TCP connection. If a user has your app open in 5 tabs, that's 5 connections to the same server doing the same thing. shared-socket uses the Web Locks API to elect a single leader tab that holds the real connection, and BroadcastChannel to relay events to all tabs.

Install

npm install shared-socket

Usage

Replace WebSocket with SharedWebSocket. The API is the same.

import { SharedWebSocket } from 'shared-socket';

// Instead of: const ws = new WebSocket('wss://example.com/feed');
const ws = new SharedWebSocket('wss://example.com/feed');

ws.onopen = () => {
  console.log('connected');
  ws.send('hello');
};

ws.onmessage = (event) => {
  console.log('received:', event.data);
};

ws.onclose = (event) => {
  console.log('closed:', event.code, event.reason);
};

ws.onerror = () => {
  console.log('error');
};

addEventListener works too:

ws.addEventListener('message', (event) => {
  console.log(event.data);
});

How It Works

   Tab B (follower)        Tab A (leader)        Tab C (follower)
  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
  │ SharedWebSocket │    │ SharedWebSocket │    │ SharedWebSocket │
  │                 │    │                 │    │                 │
  │  onopen         │    │     Real WS     │    │  onopen         │
  │  onmessage      ├────┼──► connection ◄─┼────┤  onmessage      │
  │  onclose        │    │        │        │    │  onclose        │
  │  onerror        │    │        │        │    │  onerror        │
  └─────────────────┘    └────────┼────────┘    └─────────────────┘
   BroadcastChannel               │              BroadcastChannel
                                  │
                             ┌────▼────┐
                             │  Server │
                             └─────────┘

  events:  leader ──► all tabs
  send():  any tab ──► leader ──► server
  1. The first tab to create a SharedWebSocket for a given URL acquires an exclusive Web Lock and becomes the leader. It opens the real WebSocket connection.

  2. Subsequent tabs for the same URL enter the lock queue and become followers. They communicate with the leader through a BroadcastChannel.

  3. When the leader's WebSocket receives a message, it broadcasts the event to all followers. Every tab fires onmessage.

  4. When any tab calls send(), the message reaches the real WebSocket — either directly (leader) or via BroadcastChannel relay (follower).

  5. If the leader tab closes, the browser releases the lock. The next tab in the queue automatically becomes the new leader and reconnects.

API

SharedWebSocket implements the same interface as the native WebSocket.

Constructor

new SharedWebSocket(url: string | URL, protocols?: string | string[])

Properties

| Property | Type | Description | |----------|------|-------------| | url | string | The URL passed to the constructor | | readyState | number | Connection state (CONNECTING, OPEN, CLOSING, CLOSED) | | protocol | string | Subprotocol selected by the server | | extensions | string | Always '' (v1) | | bufferedAmount | number | Always 0 (v1) | | binaryType | BinaryType | Accepted but ignored (v1) |

Static Constants

SharedWebSocket.CONNECTING // 0
SharedWebSocket.OPEN       // 1
SharedWebSocket.CLOSING    // 2
SharedWebSocket.CLOSED     // 3

Methods

send(data: string): void

Sends data through the WebSocket connection. Throws DOMException with InvalidStateError if readyState is not OPEN.

close(code?: number, reason?: string): void

Closes this instance only. Other tabs are unaffected. If this tab is the leader, the next tab takes over and reconnects.

Events

| Event | Handler | Event Type | Description | |-------|---------|------------|-------------| | open | onopen | Event | Connection established | | message | onmessage | MessageEvent | Message received (access data via event.data) | | error | onerror | Event | Connection error | | close | onclose | CloseEvent | Connection closed (access code, reason, wasClean) |

Behavior Details

Per-URL Isolation

Each unique URL gets its own independent shared connection. Two different URLs = two separate leader elections, two separate WebSocket connections.

const feed = new SharedWebSocket('wss://example.com/feed');
const chat = new SharedWebSocket('wss://example.com/chat');
// These are completely independent

Leader Failover

When the leader tab is closed or crashes:

  1. The browser releases the Web Lock
  2. The next tab in the queue becomes the new leader
  3. The new leader opens a fresh WebSocket connection
  4. All remaining tabs receive onopen when the new connection is established

This happens automatically with no application code needed.

close() is Per-Instance

Calling close() on one tab does not affect other tabs. If the leader calls close(), it releases leadership and the next tab reconnects.

// Tab A
const ws = new SharedWebSocket('wss://example.com/feed');
ws.close(); // Only Tab A is closed. Tab B continues normally.

send() Throws When Not Connected

If readyState is not OPEN, send() throws an InvalidStateError. There is no message buffering during leader transitions.

Late Joiners

If a tab creates a SharedWebSocket after the connection is already open, it receives the current state from the leader and fires onopen immediately.

Browser Requirements

Current Limitations (v1)

  • String messages only — no Blob or ArrayBuffer support
  • extensions always returns ''
  • bufferedAmount always returns 0
  • binaryType is accepted but ignored
  • No message buffering during leader transitions — send() throws if not connected
  • readyState on follower tabs is eventually consistent (synced via BroadcastChannel, not instant)

Development

# Install dependencies
bun install

# Type check
bun run typecheck

# Run unit tests
bun test tests/unit

# Lint
bun run lint

# Build
bun run build

License

MIT