JavaScript
// Method 1: Basic cross-tab messaging
class CrossTabMessenger {
constructor(channel) {
this.channel = channel;
this.listeners = [];
this.setupListener();
}
setupListener() {
window.addEventListener('storage', (e) => {
if (e.key === this.channel && e.newValue) {
const message = JSON.parse(e.newValue);
this.listeners.forEach(listener => listener(message));
}
});
}
send(message) {
localStorage.setItem(this.channel, JSON.stringify({
...message,
timestamp: Date.now(),
tabId: this.getTabId()
}));
// Clear immediately to allow same message again
setTimeout(() => localStorage.removeItem(this.channel), 100);
}
onMessage(callback) {
this.listeners.push(callback);
}
getTabId() {
let tabId = sessionStorage.getItem('tabId');
if (!tabId) {
tabId = 'tab_' + Date.now() + '_' + Math.random();
sessionStorage.setItem('tabId', tabId);
}
return tabId;
}
}
const messenger = new CrossTabMessenger('app-messages');
messenger.onMessage((message) => {
console.log('Message received:', message);
});
messenger.send({ type: 'user-updated', data: { name: 'John' } });
// Method 2: Broadcast channel API (modern)
if ('BroadcastChannel' in window) {
const channel = new BroadcastChannel('app-channel');
channel.postMessage({ type: 'notification', message: 'Hello from tab 1' });
channel.onmessage = function(e) {
console.log('Broadcast received:', e.data);
};
}
// Method 3: Shared state manager
class SharedState {
constructor(key) {
this.key = key;
this.state = this.loadState();
this.listeners = [];
this.setupListener();
}
loadState() {
const stored = localStorage.getItem(this.key);
return stored ? JSON.parse(stored) : {};
}
setupListener() {
window.addEventListener('storage', (e) => {
if (e.key === this.key) {
this.state = JSON.parse(e.newValue);
this.notifyListeners();
}
});
}
setState(updates) {
this.state = { ...this.state, ...updates };
localStorage.setItem(this.key, JSON.stringify(this.state));
this.notifyListeners();
}
getState() {
return this.state;
}
subscribe(callback) {
this.listeners.push(callback);
}
notifyListeners() {
this.listeners.forEach(listener => listener(this.state));
}
}
const sharedState = new SharedState('app-state');
sharedState.subscribe((state) => {
console.log('State updated:', state);
});
sharedState.setState({ user: 'John', theme: 'dark' });
// Method 4: Leader election
class TabLeader {
constructor() {
this.leaderKey = 'tab-leader';
this.heartbeatInterval = null;
this.checkLeader();
}
checkLeader() {
const leader = localStorage.getItem(this.leaderKey);
const now = Date.now();
if (!leader) {
this.becomeLeader();
} else {
const [leaderTabId, timestamp] = leader.split(':');
const age = now - parseInt(timestamp);
// If leader is inactive for 5 seconds, take over
if (age > 5000) {
this.becomeLeader();
}
}
// Check every second
setTimeout(() => this.checkLeader(), 1000);
}
becomeLeader() {
const tabId = this.getTabId();
localStorage.setItem(this.leaderKey, tabId + ':' + Date.now());
this.startHeartbeat();
console.log('Became leader');
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
const tabId = this.getTabId();
localStorage.setItem(this.leaderKey, tabId + ':' + Date.now());
}, 2000);
}
isLeader() {
const leader = localStorage.getItem(this.leaderKey);
if (!leader) return false;
const [leaderTabId] = leader.split(':');
return leaderTabId === this.getTabId();
}
getTabId() {
let tabId = sessionStorage.getItem('tabId');
if (!tabId) {
tabId = 'tab_' + Date.now();
sessionStorage.setItem('tabId', tabId);
}
return tabId;
}
}
const tabLeader = new TabLeader();Output
Message received: { type: 'user-updated', data: { name: 'John' }, timestamp: 1234567890, tabId: 'tab_1234567890' }
Broadcast received: { type: 'notification', message: 'Hello from tab 1' }
State updated: { user: 'John', theme: 'dark' }
Became leaderCross-tab communication syncs data.
Methods
- Storage events: Cross-tab
- BroadcastChannel: Modern API
- Shared state: Common pattern
- Leader election: Coordination
Storage Events
- Fires in other tabs
- Not in same tab
- Use for messaging
BroadcastChannel
- Modern API
- Better performance
- Easier to use
- Browser support varies
Use Cases
- Sync user state
- Notifications
- Real-time updates
- Coordination
Best Practices
- Use BroadcastChannel if available
- Fallback to storage events
- Handle errors
- Clean up listeners