[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user