Skip to content

Cross-Tab Communication ​

The Cross-Tab adapter enables seamless message synchronization across multiple browser tabs and windows. Perfect for keeping UI state consistent across your application instances.

Overview ​

When a user opens your application in multiple tabs, the Cross-Tab adapter ensures they all stay synchronized by relaying messages between tabs using browser APIs.

Diagram showing multiple browser tabs communicating through cross-tab adapter with message relay

Cross-tab communication flow

Quick Start ​

typescript
import { createPubSub } from '@belyas/pubsub-mfe';
import {
  createCrossTabAdapter,
  BroadcastChannelTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab';

// Create bus
const bus = createPubSub({ app: 'my-app' });
// Create a transport
const transport = new BroadcastChannelTransport({
  channelName: 'my-app-sync',
});
// Attach cross-tab adapter
const crossTab = createCrossTabAdapter({
  channelName: 'my-app-sync',
  transport,
});

crossTab.attach(bus);

// Now messages are automatically synced across tabs!
bus.publish('cart.item.add', { sku: 'ABC123' });
// ☝️ This message appears in ALL open tabs

Transport Options ​

The Cross-Tab adapter supports multiple transport mechanisms with automatic fallback:

1. BroadcastChannel ​

Best performance, modern browser support:

typescript
import {
  createBroadcastChannelTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab/transports/broadcast-channel';

const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  transport: createBroadcastChannelTransport({
    channelName: 'my-app'
  })
});

Pros:

  • ✅ Fastest performance
  • ✅ Native browser API
  • ✅ Low latency
  • ✅ No storage overhead

Cons:

  • ❌ Same-origin only
  • ❌ Tabs must be open simultaneously

Browser Support: Chrome 54+, Firefox 38+, Edge 79+, Safari 15.4+

2. SharedWorker ​

Centralized broker pattern:

typescript
import {
  createSharedWorkerTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab/transports/shared-worker';

const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  transport: createSharedWorkerTransport({
    channelName: 'my-app',
    workerUrl: new URL(
      './node_modules/@belyas/pubsub-mfe/dist/workers/cross-tab-shared-worker-broker.js',
      import.meta.url
    ).href
  })
});

Pros:

  • ✅ Centralized message broker
  • ✅ Connection management
  • ✅ Heartbeat/reconnection
  • ✅ Better for many tabs

Cons:

  • ❌ Requires worker file
  • ❌ More complex setup
  • ❌ Limited browser support

Browser Support: Chrome 4+, Firefox 29+, Edge 79+, Safari 16+ (with limitations)

3. localStorage/Storage Events ​

Maximum compatibility fallback:

typescript
import {
  createStorageTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab/transports/storage';

const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  transport: createStorageTransport({
    channelName: 'my-app',
    ttlMs: 30 * 1000, // Message TTL
    maxMessages: 100 // Storage limit
  })
});

Pros:

  • ✅ Universal browser support
  • ✅ Persistent messages
  • ✅ Works across sessions
  • ✅ Simple setup

Cons:

  • ❌ Slower than other transports
  • ❌ Storage quota limits
  • ❌ Requires cleanup (GC)

Browser Support: All modern browsers with localStorage

Smart fallback with best available transport:

typescript
import {
  createAutoTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab/transports/auto';

const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  transport: createAutoTransport({
    channelName: 'my-app',
    // Optional: prefer specific transport
    preferredMode: 'broadcast-channel',
    // Optional: SharedWorker config
    sharedWorkerUrl: './node_modules/@belyas/pubsub-mfe/dist/workers/cross-tab-shared-worker-broker.js',
    // Callback when fallback occurs
    onFallback: (from, to, reason) => {
      console.log(`Fallback: ${from} → ${to}`, reason);
    }
  })
});

Fallback Order:

  1. SharedWorker (if workerUrl provided)
  2. BroadcastChannel
  3. Storage (localStorage)

Transport Comparison ​

Comparison table showing performance, browser support, and features of BroadcastChannel, SharedWorker, and Storage transports

Transport feature comparison

Configuration Options ​

Channel Name ​

Unique identifier for the communication channel:

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app-v1' // Use versioning for breaking changes
});

💡 Channel Isolation

Different channel names are completely isolated. Use separate channels for:

  • Different app versions
  • Development vs production
  • Separate tenants/workspaces

Batching ​

Optimize performance by batching multiple messages:

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  maxBatchSize: 100,    // Max messages per batch
  batchIntervalMs: 50   // Max wait time
});

Benefits:

  • Reduces overhead for high-frequency publishing
  • Better performance with many small messages
  • Configurable trade-off between latency and throughput

Deduplication ​

Prevent duplicate message delivery:

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  dedupeWindowMs: 5000,   // Dedup window
  dedupeCacheSize: 1000,  // Message cache size
});

Rate Limiting ​

Control the sent messages and its frequency:

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  rateLimit: {
    maxPerSecond: 10, // Maximum messages per second from other tabs.
    maxBurst: 200,    // Maximum burst size (token bucket capacity).
  }
});

Usage Examples ​

Shopping Cart Sync ​

Keep cart items synchronized across tabs:

typescript
// Tab 1: Add item to cart
bus.publish('cart.item.add', {
  sku: 'ABC123',
  name: 'Widget',
  price: 29.99,
  qty: 1
});

// Tab 2: Automatically updates
bus.subscribe('cart.item.add', (msg) => {
  const { sku, name, price, qty } = msg.payload;
  updateCartUI({ sku, name, price, qty });
  showNotification(`${name} added to cart`);
});

User Authentication Sync ​

Log out user from all tabs:

typescript
// Tab 1: User logs out
bus.publish('user.logout', {
  userId: '12345',
  timestamp: Date.now()
});

// All tabs: Handle logout
bus.subscribe('user.logout', (msg) => {
  clearLocalStorage();
  redirectToLogin();
});

Real-time Notifications ​

Show notifications across all tabs:

typescript
// Tab 1: New order received
bus.publish('notification.order.new', {
  orderId: 'ORD-001',
  total: 149.99,
  customer: 'John Doe'
});

// All tabs: Show notification
bus.subscribe('notification.#', (msg) => {
  showToast({
    title: 'New Order',
    message: `Order ${msg.payload.orderId} received`,
    type: 'success'
  });
});

Theme Sync ​

Synchronize dark/light mode:

typescript
// Tab 1: User toggles theme
bus.publish('ui.theme.change', {
  theme: 'dark'
});

// All tabs: Apply theme
bus.subscribe('ui.theme.change', (msg) => {
  document.documentElement.setAttribute('data-theme', msg.payload.theme);
  localStorage.setItem('theme', msg.payload.theme);
});

Lifecycle Management ​

Attach/Detach ​

typescript
const crossTab = createCrossTabAdapter(config);

// Attach to bus
crossTab.attach(bus);

// Later: detach
crossTab.detach();

Error Handling ​

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  onError: (error) => {
    console.error('Cross-tab error:', error);
    // Send to error tracking
    Sentry.captureException(error);
  }
});

Fallback Handling ​

typescript
import {
  createAutoTransport
} from '@belyas/pubsub-mfe/adapters/cross-tab/transports/auto';

const transport = createAutoTransport({
  channelName: 'my-app',
  onFallback: (from, to, reason) => {
    console.warn(`Transport fallback: ${from} → ${to}`);
    console.log('Reason:', reason);
    
    // Track in analytics
    analytics.track('transport_fallback', { from, to, reason });
  }
});

Performance Optimization ​

Message Throttling ​

Prevent overwhelming tabs with high-frequency updates:

typescript
import { throttle } from 'lodash-es';

const throttledHandler = throttle((msg) => {
  updateUI(msg.payload);
}, 100); // Max once per 100ms

bus.subscribe('realtime.price.#', throttledHandler);

Lazy Attachment ​

Only attach cross-tab when needed:

typescript
let crossTab: CrossTabAdapter | null = null;

function enableCrossTabSync() {
  if (!crossTab) {
    crossTab = createCrossTabAdapter(config);
    crossTab.attach(bus);
  }
}

// Later, when user enables sync feature
enableCrossTabSync();

Debugging ​

Enable Debug Logging ​

typescript
const crossTab = createCrossTabAdapter({
  channelName: 'my-app',
  debug: true // Enables console logging
});

Monitor Message Flow ​

typescript
bus.subscribe('#', (msg) => {
  console.log('[CrossTab]', msg.topic, msg);
}, {
  sourceFilter: {
    include: ['cross-tab-adapter'] // Only log cross-tab messages
  }
});

Transport Stats ​

typescript
// Get transport statistics
const stats = crossTab.getStats();

console.log('Stats:', stats);

Browser Support ​

TransportChromeFirefoxSafariEdge
BroadcastChannel54+38+15.4+79+
SharedWorker4+29+16+79+
StorageAllAllAllAll

🌐 Progressive Enhancement

Use Auto-transport for automatic fallback to the best available transport.

Best Practices ​

1. Use Auto-Transport

Let the library choose the best transport for each browser:

typescript
const transport = createAutoTransport({ channelName: 'my-app' });

2. Version Your Channels

Avoid compatibility issues when deploying:

typescript
const channelName = `my-app-${APP_VERSION}`;

3. Handle Offline/Online

Gracefully handle network changes:

typescript
window.addEventListener('online', () => {
  crossTab.reconnect();
});

4. Clean Up

Always detach when done:

typescript
// This is not reliable, it's just for demo's sake
window.addEventListener('beforeunload', () => {
  crossTab.detach();
});

Troubleshooting ​

Messages Not Syncing ​

  1. Check channel names match across all tabs
  2. Verify origin restrictions (same-origin policy)
  3. Check browser console for errors
  4. Enable debug mode to see message flow

High Memory Usage ​

  1. Enable deduplication to reduce cache size
  2. Use topic filtering to sync only necessary messages
  3. Implement message throttling for high-frequency updates
  4. Monitor storage quota (for Storage transport)

Slow Performance ​

  1. Switch to BroadcastChannel if using Storage
  2. Enable message batching for bulk updates
  3. Use topic filtering to reduce message volume
  4. Consider SharedWorker for many tabs

Next Steps ​

⚠️ Important

Cross-tab communication only works within the same origin (protocol + domain + port). For cross-origin communication, use the Iframe Adapter.

Released under the Apache-2.0 License.