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

sse-coordinator

v0.3.0

Published

Share a single SSE connection across browser tabs using BroadcastChannel leader election

Readme

sse-coordinator

npm version bundle size license TypeScript

Share a single SSE connection across all browser tabs using BroadcastChannel leader election.

The Problem

Browsers limit HTTP/1.1 connections to 6–8 per domain. Each tab opening its own EventSource exhausts this pool — 10 tabs means 10 SSE connections, blocking all other requests.

The Solution

sse-coordinator elects one tab as the leader. Only the leader holds an EventSource connection. All other tabs receive events via the BroadcastChannel API — zero extra connections.

Tab 1 (LEADER) ──── EventSource ────▶ Your Server
Tab 2 (follower) ◀──┐
Tab 3 (follower) ◀──┤── BroadcastChannel
Tab 4 (follower) ◀──┘

When the leader tab closes, a follower automatically promotes itself and opens a new connection.

Install

npm install sse-coordinator
# or
bun add sse-coordinator
# or
pnpm add sse-coordinator

Usage

import { SSECoordinator } from 'sse-coordinator';

const coordinator = new SSECoordinator();

coordinator.connect({
  url: 'https://api.example.com/events/stream',
  eventTypes: ['notification.created', 'job.completed'],

  onEvent(event) {
    console.log(event.type, event.data);
  },

  onConnectionChange(connected) {
    console.log('connected:', connected);
  },

  onError(error) {
    console.error('SSE error:', error);
  },
});

// Later:
coordinator.disconnect();

API

new SSECoordinator()

Creates a coordinator instance. Each tab should create its own instance.

coordinator.connect(options)

Starts the coordinator. The tab will either become leader (opening an EventSource) or follower (listening via BroadcastChannel).

| Option | Type | Required | Default | Description | |---|---|---|---|---| | url | string | ✓ | — | SSE endpoint URL | | eventTypes | string[] | ✓ | — | Named event types to listen for | | onEvent | (event: SSEEvent) => void | ✓ | — | Called for every SSE event | | channelName | string | | 'sse-coordinator' | BroadcastChannel name — must match across tabs | | withCredentials | boolean | | false | Pass cookies with the EventSource request | | parseJson | boolean | | true | JSON.parse event data; set false to receive the raw string (plain-text/custom streams) | | maxReconnectAttempts | number | | 10 | Max reconnection attempts before calling onError (and, unless reconnectForever, giving up) | | reconnectForever | boolean | | false | Keep retrying at the capped interval after maxReconnectAttempts instead of giving up. onError still fires once when the cap is reached | | lastEventIdParam | string | | none | Query param to carry the last event id on reconnect/handover so a cooperating server can resume the stream | | logger | Logger | | none | Optional logger ({ debug, info, warn, error }) | | onError | (error: Error) => void | | — | Called when max reconnections are exceeded | | onConnectionChange | (connected: boolean) => void | | — | Called when the leader's connection opens or closes |

coordinator.disconnect()

Closes the connection and BroadcastChannel. If this tab is the leader, it releases the leader lock so a queued tab is promoted automatically.

coordinator.isLeader()

Returns true if this tab currently holds the EventSource connection.

SSEEvent

interface SSEEvent {
  type: string;
  data: unknown;
  id: string;
  timestamp: string; // ISO 8601
}

How It Works

  1. Leader election — on connect(), every tab requests the same named lock via the Web Locks API. The browser grants it to exactly one tab, which becomes leader and opens the EventSource. The rest queue.
  2. Event fan-out — the leader publishes each SSE event over a BroadcastChannel; followers receive them with zero extra connections.
  3. Failover — when the leader tab closes or crashes, the browser releases the lock and grants it to the next queued tab, which promotes itself automatically. No heartbeats, no timeouts.
  4. Reconnection — the leader reconnects with exponential backoff (capped at 30 seconds) up to maxReconnectAttempts (or forever, with reconnectForever).
  5. Focus / online recovery — when the tab regains focus (visibilitychange) or the network returns (online), the leader immediately revives a dead connection, resetting the backoff. This recovers from frozen background tabs and machine sleep, where the backoff timer is throttled or has already given up.
  6. Standalone fallback — if the Web Locks API is unavailable, coordination is impossible, so each tab runs its own EventSource and logs a warning. Functionality is preserved; the shared-connection optimization is not.

Multiple Apps on the Same Domain

If you run multiple independent apps on the same domain, give each a unique channelName:

coordinator.connect({
  url: '...',
  eventTypes: [...],
  channelName: 'my-app-sse',
  onEvent: () => {},
});

Browser Support

Requires the BroadcastChannel API and the Web Locks API — both available in Chrome 69+, Firefox 96+, Safari 15.4+, and Edge 79+. Where the Web Locks API is missing, the coordinator falls back to one EventSource per tab (see "How It Works" → Standalone fallback). crypto.randomUUID() is used when present, with a Math.random() fallback otherwise.

License

MIT


Made by Pareo