--- id: web-broadcastchannel-sharedworker title: BroadcastChannel / SharedWorker — tab 간 통신 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, worker, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Frontend"] } applied_in: [] aliases: [BroadcastChannel, SharedWorker, MessageChannel, postMessage, tab sync, cross-tab] --- # BroadcastChannel / SharedWorker > Tab / iframe / worker 간 message passing. **BroadcastChannel (simple), SharedWorker (logic 공유), MessageChannel (port pair)**. Same-origin 만. ## 📖 핵심 개념 - 같은 origin 의 다른 tab. - BroadcastChannel: pub/sub. - SharedWorker: 공유 worker. - MessageChannel: 1-1 port. ## 💻 코드 패턴 ### BroadcastChannel ```ts const channel = new BroadcastChannel('updates'); // Sender (tab A) channel.postMessage({ type: 'config-changed', value: 'dark' }); // Receiver (tab B) channel.onmessage = (e) => { console.log(e.data); // { type: 'config-changed', value: 'dark' } }; // Cleanup channel.close(); ``` → Same-origin 의 모든 tab 가 receive. ### Use case ``` - Theme toggle: 1 tab 에서 바꾸면 모든 tab. - Logout: 1 tab 에서 logout = 모두. - Cache invalidation: data 변경 알림. - Real-time sync: collaborative edit. ``` ### SharedWorker ```ts // shared-worker.ts (worker file) const ports: MessagePort[] = []; self.onconnect = (e) => { const port = e.ports[0]; ports.push(port); port.onmessage = (e) => { // 모든 tab 에 broadcast ports.forEach(p => p.postMessage(e.data)); }; }; ``` ```ts // main thread const worker = new SharedWorker('shared-worker.ts', { type: 'module' }); worker.port.start(); worker.port.postMessage({ type: 'hello' }); worker.port.onmessage = (e) => console.log(e.data); ``` → 모든 tab 가 1 worker instance 공유. ### vs BroadcastChannel ``` BroadcastChannel: - Simple. - 매 tab 가 자체 logic. SharedWorker: - 공유 state (1 instance). - Logic 가 worker 안. - WebSocket / DB connection 가 1 곳. ``` ### MessageChannel (port pair) ```ts const channel = new MessageChannel(); const { port1, port2 } = channel; port1.onmessage = (e) => console.log('p1:', e.data); port2.onmessage = (e) => console.log('p2:', e.data); port1.postMessage('hello'); // → p2: hello port2.postMessage('world'); // → p1: world port1.start(); port2.start(); ``` → 1-1 통신. iframe / worker 친화. ### iframe 와 통신 ```ts // Parent const iframe = document.querySelector('iframe')!; const channel = new MessageChannel(); iframe.contentWindow!.postMessage({ port: channel.port2 }, '*', [channel.port2]); channel.port1.postMessage('hello'); channel.port1.onmessage = (e) => console.log(e.data); ``` ```ts // iframe window.addEventListener('message', (e) => { const port = e.data.port; port.onmessage = (e) => console.log('parent:', e.data); port.postMessage('hi from iframe'); }); ``` → Parent ↔ iframe 의 secure channel. ### Use case 비교 ``` BroadcastChannel: - 매 tab 가 같은 logic. - 알림 / sync. SharedWorker: - 공유 connection (WebSocket). - 공유 cache. - 공유 computation. MessageChannel: - iframe 와 통신. - Worker 와 sub-channel. Web Locks: - Tab 간 mutex. ``` ### SharedWorker 의 함정 ``` - iOS Safari 가 안 (no support). - Chrome incognito 가 안 (privacy). - Firefox 가 OK. → 가벼운 use case 만. 대부분 = BroadcastChannel + Web Locks. ``` → [[Web_Web_Locks_API]]. ### Single WebSocket pattern ```ts // SharedWorker 가 1 WS. self.onconnect = (e) => { const port = e.ports[0]; if (!ws) { ws = new WebSocket('wss://...'); ws.onmessage = (msg) => { // 모든 connected tab 에 broadcast ports.forEach(p => p.postMessage({ type: 'message', data: msg.data })); }; } ports.push(port); port.onmessage = (e) => ws.send(e.data.payload); }; ``` → N tab → 1 WS connection. ### postMessage (window) ```ts // Parent → iframe iframe.contentWindow.postMessage({ type: 'init' }, 'https://child.com'); // iframe window.addEventListener('message', (e) => { if (e.origin !== 'https://parent.com') return; // 검증 console.log(e.data); }); ``` → Cross-origin (BroadcastChannel 가 안 됨). ### postMessage 의 함정 ``` - Origin 검증 필수 (모든 origin = 위험). - Object 가 structured clone (function 안 됨). - 큰 object = 느림 → transferable. ``` ### Transferable ```ts const buf = new ArrayBuffer(1_000_000); worker.postMessage({ buf }, [buf]); // → buf 가 worker 로 transfer (main thread 에서 사용 X). ``` → Zero-copy. ### MessageChannel 의 transferable port ```ts const { port1, port2 } = new MessageChannel(); worker.postMessage({ port: port2 }, [port2]); // → port2 가 worker 로 transfer. ``` ### React 통합 ```tsx // Theme sync across tab useEffect(() => { const ch = new BroadcastChannel('theme'); ch.onmessage = (e) => setTheme(e.data); return () => ch.close(); }, []); const updateTheme = (t: Theme) => { setTheme(t); new BroadcastChannel('theme').postMessage(t); }; ``` ### Service Worker + BroadcastChannel ```ts // Service worker self.addEventListener('fetch', async (event) => { // ... const ch = new BroadcastChannel('cache'); ch.postMessage({ type: 'fetched', url: event.request.url }); }); // Page const ch = new BroadcastChannel('cache'); ch.onmessage = (e) => console.log(e.data); ``` ### Browser support ``` BroadcastChannel: 모든 modern (Safari 15.4+). SharedWorker: Chrome / FF / Edge. Safari iOS 안. MessageChannel: 모든. postMessage: 모든. ``` ### Polyfill (BroadcastChannel) ```ts // localStorage 식 (옛 browser) window.addEventListener('storage', (e) => { if (e.key === 'channel:updates') { const data = JSON.parse(e.newValue!); // ... } }); localStorage.setItem('channel:updates', JSON.stringify({ ... })); localStorage.removeItem('channel:updates'); ``` → 옛 hack. Modern = native. ### When 안 사용? ``` - Cross-origin: postMessage + origin check. - Same-tab worker: regular worker. - Server → multi-tab: SSE / WebSocket + BroadcastChannel. - Persistent: IndexedDB. ``` ### 함정 ``` - BroadcastChannel 가 same origin 만. - Worker close 시 message lost. - iOS Safari 가 SharedWorker X. - 큰 object = serialize cost. - Origin 검증 안 함: XSS. ``` ### Use case 패턴 ``` 1. Tab sync: BroadcastChannel. 2. Single WS: SharedWorker (Chrome/FF) + BroadcastChannel fallback. 3. iframe widget: MessageChannel. 4. Service worker: BroadcastChannel. 5. Cross-origin: postMessage + origin. ``` ### Performance ``` BroadcastChannel: ms latency. SharedWorker: 비슷. MessageChannel: 가장 빠름 (port). postMessage cross-origin: 비슷. → 모두 빠름. 큰 object 가 cost. ``` ### Tab 의 visibility ```ts document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { // Refetch state via BroadcastChannel bc.postMessage({ type: 'request-state' }); } }); ``` → Tab 가 hidden = sync 가 throttle. ## 🤔 의사결정 기준 | 작업 | 추천 | |---|---| | Tab sync | BroadcastChannel | | Shared logic | SharedWorker | | iframe 통신 | MessageChannel + postMessage | | Cross-origin | postMessage + origin check | | Mutex | Web Locks | | 1 WS / N tab | SharedWorker | | Service worker | BroadcastChannel | ## ❌ 안티패턴 - **Cross-origin 가 BroadcastChannel**: 안 됨. - **Origin 검증 X**: XSS. - **iOS Safari + SharedWorker**: 깨짐. - **큰 object 자주 broadcast**: 느림. - **Channel close X**: leak. - **모든 tab 가 자체 WS**: 비효율. ## 🤖 LLM 활용 힌트 - BroadcastChannel = simple cross-tab. - SharedWorker = 공유 logic (Safari iOS X). - MessageChannel = iframe / worker. - Origin check 가 cross-origin 의 보안. ## 🔗 관련 문서 - [[Web_OffMain_WebWorker]] - [[Web_Web_Locks_API]] - [[Web_Service_Worker_Patterns]]