Storage Cross-Tab Communication in JavaScript

Communicate between browser tabs using storage

AdvancedLocalStorage/SessionStorageExample 10 of 10
storage-cross-tab-communication.js
1// Method 1: Basic cross-tab messaging
2class CrossTabMessenger {
3 constructor(channel) {
4 this.channel = channel;
5 this.listeners = [];
6 this.setupListener();
7 }
8
9 setupListener() {
10 window.addEventListener('storage', (e) => {
11 if (e.key === this.channel && e.newValue) {
12 const message = JSON.parse(e.newValue);
13 this.listeners.forEach(listener => listener(message));
14 }
15 });
16 }
17
18 send(message) {
19 localStorage.setItem(this.channel, JSON.stringify({
20 ...message,
21 timestamp: Date.now(),
22 tabId: this.getTabId()
23 }));
24 // Clear immediately to allow same message again
25 setTimeout(() => localStorage.removeItem(this.channel), 100);
26 }
27
28 onMessage(callback) {
29 this.listeners.push(callback);
30 }
31
32 getTabId() {
33 let tabId = sessionStorage.getItem('tabId');
34 if (!tabId) {
35 tabId = 'tab_' + Date.now() + '_' + Math.random();
36 sessionStorage.setItem('tabId', tabId);
37 }
38 return tabId;
39 }
40}
41
42const messenger = new CrossTabMessenger('app-messages');
43messenger.onMessage((message) => {
44 console.log('Message received:', message);
45});
46
47messenger.send({ type: 'user-updated', data: { name: 'John' } });
48
49// Method 2: Broadcast channel API (modern)
50if ('BroadcastChannel' in window) {
51 const channel = new BroadcastChannel('app-channel');
52
53 channel.postMessage({ type: 'notification', message: 'Hello from tab 1' });
54
55 channel.onmessage = function(e) {
56 console.log('Broadcast received:', e.data);
57 };
58}
59
60// Method 3: Shared state manager
61class SharedState {
62 constructor(key) {
63 this.key = key;
64 this.state = this.loadState();
65 this.listeners = [];
66 this.setupListener();
67 }
68
69 loadState() {
70 const stored = localStorage.getItem(this.key);
71 return stored ? JSON.parse(stored) : {};
72 }
73
74 setupListener() {
75 window.addEventListener('storage', (e) => {
76 if (e.key === this.key) {
77 this.state = JSON.parse(e.newValue);
78 this.notifyListeners();
79 }
80 });
81 }
82
83 setState(updates) {
84 this.state = { ...this.state, ...updates };
85 localStorage.setItem(this.key, JSON.stringify(this.state));
86 this.notifyListeners();
87 }
88
89 getState() {
90 return this.state;
91 }
92
93 subscribe(callback) {
94 this.listeners.push(callback);
95 }
96
97 notifyListeners() {
98 this.listeners.forEach(listener => listener(this.state));
99 }
100}
101
102const sharedState = new SharedState('app-state');
103sharedState.subscribe((state) => {
104 console.log('State updated:', state);
105});
106
107sharedState.setState({ user: 'John', theme: 'dark' });
108
109// Method 4: Leader election
110class TabLeader {
111 constructor() {
112 this.leaderKey = 'tab-leader';
113 this.heartbeatInterval = null;
114 this.checkLeader();
115 }
116
117 checkLeader() {
118 const leader = localStorage.getItem(this.leaderKey);
119 const now = Date.now();
120
121 if (!leader) {
122 this.becomeLeader();
123 } else {
124 const [leaderTabId, timestamp] = leader.split(':');
125 const age = now - parseInt(timestamp);
126
127 // If leader is inactive for 5 seconds, take over
128 if (age > 5000) {
129 this.becomeLeader();
130 }
131 }
132
133 // Check every second
134 setTimeout(() => this.checkLeader(), 1000);
135 }
136
137 becomeLeader() {
138 const tabId = this.getTabId();
139 localStorage.setItem(this.leaderKey, tabId + ':' + Date.now());
140 this.startHeartbeat();
141 console.log('Became leader');
142 }
143
144 startHeartbeat() {
145 this.heartbeatInterval = setInterval(() => {
146 const tabId = this.getTabId();
147 localStorage.setItem(this.leaderKey, tabId + ':' + Date.now());
148 }, 2000);
149 }
150
151 isLeader() {
152 const leader = localStorage.getItem(this.leaderKey);
153 if (!leader) return false;
154 const [leaderTabId] = leader.split(':');
155 return leaderTabId === this.getTabId();
156 }
157
158 getTabId() {
159 let tabId = sessionStorage.getItem('tabId');
160 if (!tabId) {
161 tabId = 'tab_' + Date.now();
162 sessionStorage.setItem('tabId', tabId);
163 }
164 return tabId;
165 }
166}
167
168const 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 leader

What's going on

Cross-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