SharedWorker와 BroadcastChannel — 탭 간 통신 패턴
같은 사이트를 여러 탭에서 열었을 때, 한 탭에서 로그아웃하면 다른 탭에서도 반영되어야 합니다. SharedWorker와 BroadcastChannel은 이런 탭 간 통신 문제를 해결하는 API입니다.
BroadcastChannel — 가장 간단한 탭 간 통신
같은 출처(origin)의 모든 탭/윈도우/iframe 간에 메시지를 주고받을 수 있습니다.
// 채널 생성 (같은 이름이면 같은 채널)
const channel = new BroadcastChannel("auth");
// 메시지 전송
channel.postMessage({
type: "logout",
timestamp: Date.now(),
});
// 메시지 수신 (같은 탭에서 보낸 건 수신하지 않음)
channel.addEventListener("message", (event) => {
const { type } = event.data;
if (type === "logout") {
// 다른 탭에서 로그아웃됨 → 현재 탭도 로그아웃 처리
clearSession();
window.location.href = "/login";
}
});
// 채널 닫기
channel.close();
실전 활용 — 테마 동기화
const themeChannel = new BroadcastChannel("theme");
// 테마 변경 시 다른 탭에도 알림
function changeTheme(newTheme) {
document.documentElement.dataset.theme = newTheme;
localStorage.setItem("theme", newTheme);
themeChannel.postMessage({ theme: newTheme });
}
// 다른 탭에서 테마 변경 수신
themeChannel.addEventListener("message", (event) => {
document.documentElement.dataset.theme = event.data.theme;
});
실전 활용 — 장바구니 동기화
const cartChannel = new BroadcastChannel("cart");
function addToCart(item) {
cart.push(item);
updateCartUI();
cartChannel.postMessage({ type: "cartUpdate", cart });
}
cartChannel.addEventListener("message", (event) => {
if (event.data.type === "cartUpdate") {
cart = event.data.cart;
updateCartUI();
}
});
SharedWorker — 탭 간 공유 워커
여러 탭이 ** 하나의 워커 인스턴스 **를 공유합니다. WebSocket 연결을 하나만 유지하면서 여러 탭에 데이터를 분배하는 데 유용합니다.
워커 파일 (shared-worker.js)
// shared-worker.js
const ports = new Set();
// 새 탭이 연결될 때
self.addEventListener("connect", (event) => {
const port = event.ports[0];
ports.add(port);
port.addEventListener("message", (e) => {
const { type, data } = e.data;
if (type === "broadcast") {
// 모든 탭에 메시지 전달
ports.forEach((p) => {
if (p !== port) { // 보낸 탭 제외
p.postMessage({ type: "message", data });
}
});
}
});
port.start();
// 연결된 탭 수 알림
ports.forEach((p) => {
p.postMessage({ type: "tabCount", data: ports.size });
});
});
메인 스레드
// SharedWorker에 연결
const worker = new SharedWorker("shared-worker.js");
const port = worker.port;
port.start();
// 메시지 전송
port.postMessage({
type: "broadcast",
data: { user: "정훈", action: "commented" },
});
// 메시지 수신
port.addEventListener("message", (event) => {
const { type, data } = event.data;
if (type === "message") {
console.log("다른 탭에서:", data);
} else if (type === "tabCount") {
console.log(`현재 ${data}개 탭 연결 중`);
}
});
SharedWorker로 WebSocket 공유
// shared-worker.js
let ws = null;
const ports = new Set();
function connectWebSocket() {
ws = new WebSocket("wss://api.example.com/ws");
ws.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
// 모든 탭에 전달
ports.forEach((port) => {
port.postMessage({ type: "ws-message", data });
});
});
ws.addEventListener("close", () => {
setTimeout(connectWebSocket, 3000);
});
}
self.addEventListener("connect", (event) => {
const port = event.ports[0];
ports.add(port);
// 첫 연결 시 WebSocket 시작
if (!ws || ws.readyState === WebSocket.CLOSED) {
connectWebSocket();
}
port.addEventListener("message", (e) => {
if (e.data.type === "ws-send" && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(e.data.data));
}
});
port.start();
});
다른 탭 간 통신 방법들
localStorage 이벤트
// storage 이벤트는 다른 탭에서 변경 시에만 발생
window.addEventListener("storage", (event) => {
if (event.key === "auth-token" && !event.newValue) {
// 다른 탭에서 로그아웃됨
handleLogout();
}
});
// 이벤트 발생시키기
localStorage.setItem("auth-token", ""); // 다른 탭에서 감지됨
MessageChannel
// iframe과의 통신
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// iframe에 포트 전달
iframe.contentWindow.postMessage("init", "*", [port2]);
// 메시지 주고받기
port1.addEventListener("message", (e) => {
console.log("iframe에서:", e.data);
});
port1.start();
port1.postMessage("부모에서 보냄");
방법별 비교
| 방법 | 사용 편의성 | 데이터 타입 | 양방향 | 지속성 |
|---|---|---|---|---|
| BroadcastChannel | 매우 쉬움 | 구조화 복제 | O | X |
| SharedWorker | 중간 | 구조화 복제 | O | O |
| localStorage 이벤트 | 쉬움 | 문자열만 | X | O |
| postMessage | 쉬움 | 구조화 복제 | O | X |
주의사항
// 1. BroadcastChannel은 같은 출처에서만 동작
// http://a.com ↔ http://b.com 통신 불가
// 2. SharedWorker는 브라우저 지원 확인 필요
if (typeof SharedWorker !== "undefined") {
// SharedWorker 사용
} else {
// 폴백: BroadcastChannel 또는 localStorage
}
// 3. 탭 닫힐 때 정리
window.addEventListener("beforeunload", () => {
channel.close();
port.close();
});
**기억하기 **: 간단한 탭 간 메시지 전달에는 BroadcastChannel이 가장 쉽습니다. WebSocket을 탭 간에 공유하려면 SharedWorker를 사용합니다. 로그아웃 동기화 같은 간단한 경우에는 localStorage 이벤트도 충분합니다.
댓글 로딩 중...