Skip to content

AnishBhandarkar/omnitab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🗂️ 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

About

Cross-tab messaging for browsers. Pub/Sub with automatic SharedWorker → BroadcastChannel → localStorage fallback. Zero dependencies. TypeScript ready.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors