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

omnitab

v1.0.3

Published

Cross-tab communication made simple

Readme

🗂️ Omnitab

Real-time cross-tab communication for the browser - zero dependencies.

npm version npm downloads bundle size license TypeScript

Installation · Quick Start · API Reference · Configuration · Browser Support · Examples


What is Omnitab?

Browser tabs are isolated from each other by default - making it hard to coordinate behavior across multiple tabs of the same app. Omnitab solves this with a simple pub/sub message bus that works across all open tabs, handling browser inconsistencies automatically behind the scenes.

It selects the best available transport via an automatic fallback chain:

SharedWorker  →  BroadcastChannel  →  StorageEvent (localStorage)
  (fastest)         (native)           (universal fallback)

No configuration required to get started. Works in Safari, IE11, and mobile browsers where native APIs may be unavailable.


✨ Features

| Feature | Description | |---|---| | 🔁 Cross-tab pub/sub | Publish events to all open tabs with a single call | | 🔀 Auto fallback transport | Selects SharedWorker → BroadcastChannel → StorageEvent | | 🛡️ Storage safety | TTL, size limits, and eviction policies for localStorage transport | | 💓 Health checks | Periodic transport health verification with auto-reconnect | | 📬 Message queue | Queued sends with exponential backoff retry on failure | | 🔍 Tab discovery | Track and enumerate all open tabs | | ⚠️ Compat warnings | Console report on initialization with browser capability info | | 0️⃣ Zero dependencies | No external packages - lightweight and self-contained | | 🟦 TypeScript support | Full type definitions included |


📦 Installation

# npm
npm install omnitab

# yarn
yarn add omnitab

# pnpm
pnpm add omnitab

🚀 Quick Start

import { createBus } from 'omnitab';

// Create a scoped message bus
const bus = createBus('my-app');

// Subscribe to an event
const unsubscribe = bus.subscribe('user:update', (payload) => {
  console.log('Received update:', payload);
});

// Publish to all other tabs
bus.publish('user:update', { name: 'Alice' });

// Cleanup when done
unsubscribe();
bus.disconnect();

That's it. Omnitab automatically picks the best transport available in the current browser.


🧠 How the Transport Fallback Chain Works

Omnitab evaluates the browser environment on initialization and selects the most capable transport:

┌─────────────────────────────────────────────────────────┐
│                   createBus() called                    │
└────────────────────────┬────────────────────────────────┘
                         │
              ┌──────────▼──────────┐
              │   SharedWorker      │  ✅ Fastest — shared across all tabs
              │   available?        │     of the same origin
              └──────────┬──────────┘
              No         │ Yes → use it
                         │
              ┌──────────▼──────────┐
              │  BroadcastChannel   │  ✅ Native pub/sub — no worker needed
              │   available?        │
              └──────────┬──────────┘
              No         │ Yes → use it
                         │
              ┌──────────▼──────────┐
              │   StorageEvent      │  ✅ Universal fallback via localStorage
              │   (always works)    │     Works in Safari, IE11, mobile
              └─────────────────────┘

If a transport fails during runtime (e.g. SharedWorker crash), Omnitab can detect this via health checks and reconnect or fall back gracefully.


📖 API Reference

createBus(namespace?, config?)

Creates and returns a message bus scoped to a namespace. Use different namespaces to isolate multiple apps or features sharing the same origin.

import { createBus } from 'omnitab';

const bus = createBus('my-app', {
  enableHealthChecks: true,
  enableMessageQueue: true,
  retryDelay: 1500,
});

| Parameter | Type | Default | Description | |---|---|---|---| | namespace | string | 'omnitab' | Scopes messages - tabs on the same namespace communicate | | config | FallbackChainOptions | {} | Optional configuration (see below) |


bus.publish(event, payload?)

Broadcasts a message to all other open tabs subscribed to event. Does not fire in the sending tab.

bus.publish('cart:update', { items: [...], total: 99.99 });
bus.publish('session:expired'); // payload is optional

| Parameter | Type | Description | |---|---|---| | event | string | Event name / channel | | payload | any | Optional data to send with the event |


bus.subscribe(event, handler)

Registers a handler for the given event. Returns an unsubscribe function to clean up the listener.

const unsubscribe = bus.subscribe('cart:update', (payload) => {
  console.log('Cart changed:', payload);
});

// Later — remove the listener
unsubscribe();

| Parameter | Type | Description | |---|---|---| | event | string | Event name to listen for | | handler | (payload: any) => void | Callback executed when the event is received | | Returns | () => void | Call this to unsubscribe |


bus.disconnect()

Tears down the bus — closes the transport, clears subscriptions, and releases all resources. Always call this on component/app unmount to avoid memory leaks.

bus.disconnect();

⚙️ Configuration Options

Pass an options object as the second argument to createBus(). The config is structured into three levels: top-level bus behaviour, SharedWorker transport tuning, and StorageEvent transport tuning.

const bus = createBus('my-app', {
  // Top-level bus options
  enableHealthChecks: true,
  enableMessageQueue: true,

  // SharedWorker-specific options
  worker: {
    connectTimeout: 3000,
    heartbeatInterval: 5000,
  },

  // StorageEvent (localStorage fallback) options
  storage: {
    ttl: 8000,
    evictionPolicy: 'oldest',
    onStorageFull: (err) => console.warn('Storage full:', err),
  },
});

FallbackChainOptions — Top-level

| Option | Type | Default | Description | |---|---|---|---| | enableHealthChecks | boolean | false | Periodically verify the transport is still alive | | healthCheckInterval | number | 10000 | Interval in ms between health checks | | enableMessageQueue | boolean | false | Queue and retry failed sends | | maxRetries | number | 3 | Max retry attempts per message | | retryDelay | number | 1000 | Initial retry delay in ms | | retryBackoff | number | 2 | Exponential backoff multiplier per retry | | worker | SharedWorkerTransportOptions | {} | SharedWorker transport settings (see below) | | storage | StorageEventTransportOptions | {} | StorageEvent transport settings (see below) |


SharedWorkerTransportOptionsconfig.worker

These options apply when Omnitab uses the SharedWorker transport (the default on Chrome, Firefox, and Edge).

| Option | Type | Default | Description | |---|---|---|---| | connectTimeout | number | - | Timeout in ms to establish a worker connection before falling back | | heartbeatInterval | number | - | Interval in ms for worker heartbeat pings to detect silent failures |


StorageEventTransportOptionsconfig.storage

These options apply only when the StorageEvent (localStorage) fallback is active — typically on Safari, mobile browsers, or IE11.

| Option | Type | Default | Description | |---|---|---|---| | ttl | number | 5000 | Message time-to-live in ms. Messages older than this are ignored and deleted. | | maxMessageSize | number | 102400 | Max message size in bytes (100 KB). Prevents large messages from filling storage. | | maxMessages | number | 100 | Max number of messages stored simultaneously. Prevents unbounded growth. | | evictionPolicy | 'none' \| 'oldest' \| 'error' | 'none' | Behaviour when localStorage is full (see details below) | | onStorageFull | (error: StorageFullError) => void | - | Callback fired when storage is full. Useful for custom warnings or handling. | | warnThreshold | number | 0.8 | Fraction of estimated quota (0-1) at which a storage usage warning is logged | | enableMonitoring | boolean | true | Periodically monitors storage usage against the warn threshold |

Eviction Policy Details

| Value | Behaviour | |---|---| | 'none' | (Safe default) Don't evict. Fires onStorageFull callback and fails silently. | | 'oldest' | Deletes the oldest messages to make room. ⚠️ May delete messages from other tabs sharing the same namespace prefix. | | 'error' | Throws an error immediately without attempting to write to storage. |


🌐 Browser Support

Omnitab automatically picks the best available transport per browser. Even in the worst case (IE11), the StorageEvent fallback ensures cross-tab messaging still works. | Browser | SharedWorker | BroadcastChannel | StorageEvent | |---|:---:|:---:|:---:| | Chrome (desktop) | ✅ | ✅ | ✅ | | Firefox (desktop) | ✅ | ✅ | ✅ | | Safari (desktop) | ✅ 16+ | ✅ | ✅ | | Safari on iOS | ✅ 16+ | ✅ | ✅ | | Edge | ✅ | ✅ | ✅ | | Chrome for Android | ❌ | ✅ | ✅ | | IE11 | ❌ | ❌ | ✅ | | Samsung Internet | ❌ | ✅ | ✅ |

On initialization, Omnitab logs a console report indicating which transport was selected and any browser-specific compatibility notes.


💡 Example Use Cases

🛒 Shopping Cart Sync

Keep cart state consistent across all open tabs - no more stale totals or conflicting item counts.

const bus = createBus('shop');

// Any tab that receives an update re-renders the cart
bus.subscribe('cart:update', (cart) => {
  renderCart(cart);
});

// Any tab that mutates the cart broadcasts the change
function updateCart(newCart) {
  saveCart(newCart);
  bus.publish('cart:update', newCart);
}

🌙 Theme Sync (Light / Dark Mode)

Switch themes in one tab and have all other tabs respond instantly.

const bus = createBus('ui');

bus.subscribe('theme:change', (theme) => {
  document.documentElement.dataset.theme = theme;
});

function setTheme(theme: 'light' | 'dark') {
  document.documentElement.dataset.theme = theme;
  bus.publish('theme:change', theme);
}

🔔 Real-time Notifications

Push notifications or alerts received in one tab (e.g. via WebSocket) out to all tabs.

const bus = createBus('notifications');

// The tab with the WebSocket connection broadcasts
websocket.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  bus.publish('notification:new', notification);
};

// All tabs receive and display the notification
bus.subscribe('notification:new', (notification) => {
  showToast(notification.message);
});

🔐 Session / Auth Sync

Log out in one tab and immediately invalidate all other open sessions.

const bus = createBus('auth');

bus.subscribe('auth:logout', () => {
  clearLocalSession();
  window.location.href = '/login';
});

function logout() {
  clearLocalSession();
  bus.publish('auth:logout');
  window.location.href = '/login';
}

💓 With Health Checks & Retry

For production apps where message delivery reliability matters:

const bus = createBus('my-app', {
  // Health checks + retry
  enableHealthChecks: true,
  healthCheckInterval: 15000,   // Check every 15s
  enableMessageQueue: true,
  maxRetries: 5,
  retryDelay: 500,
  retryBackoff: 2,              // 500ms -> 1s -> 2s -> 4s -> 8s

  // Tune the SharedWorker connection
  worker: {
    connectTimeout: 3000,       // Fall back if worker doesn't connect in 3s
    heartbeatInterval: 5000,    // Ping worker every 5s to detect silent crashes
  },

  // Tune the localStorage fallback (for Safari / IE11)
  storage: {
    ttl: 8000,
    evictionPolicy: 'oldest',
    onStorageFull: (err) => {
      console.warn('Omnitab: localStorage full', err);
    },
  },
});

📄 License

MIT - see LICENSE for details.


Made with ❤️ · npm · Report an Issue