Có ba cơ chế phổ biến, chọn theo nhu cầu.
1. BroadcastChannel — kênh pub/sub giữa mọi context cùng origin, API sạch nhất.
const channel = new BroadcastChannel('auth')
channel.postMessage({ type: 'logout' }) // tab A
channel.onmessage = (e) => { // other tabs
if (e.data.type === 'logout') resetSessionUi()
}Tab gửi không nhận lại message của chính nó, nên vẫn phải tự xử lý cục bộ ở tab gửi.
2. storage event — bắn ở các tab khác khi localStorage thay đổi. Ưu điểm là hỗ trợ rộng và dữ liệu tự bền vững; nhược điểm là chỉ truyền được chuỗi và không bắn ở tab gây ra thay đổi. Ghi cùng một giá trị hai lần sẽ không bắn event, nên hay kèm timestamp: localStorage.setItem('logout_at', Date.now()).
3. SharedWorker / service worker — khi cần một nguồn sự thật duy nhất, ví dụ chỉ một kết nối WebSocket dùng chung cho mọi tab, hoặc tránh nhiều tab cùng gọi refresh token.
Thực chiến với luồng đăng xuất: nếu session nằm ở cookie HttpOnly thì tab khác không tự biết cookie đã bị xóa; broadcast chỉ để cập nhật UI ngay, còn nguồn sự thật vẫn là lần gọi API kế tiếp trả 401. Ngược lại, với refresh token, nên dùng khóa chung (Web Locks hoặc cờ trong localStorage) để chỉ một tab thực hiện refresh, tránh nhiều tab cùng gọi làm token bị xoay vòng và văng hết.