"Lesson 4 의 storage 는 worker 가 적어 두는 수동적 memory 였어. Message 는 context 간 능동적 wake-up signal. Lesson 5 는 popup 과 service worker 사이의 줄 — 짧고 팽팽하고 한 turn 에 정확히 한 번만 당겨지는."
같은 channel 의 두 반쪽
모든 Chrome extension 이 모든 context 에서 같은 runtime channel 노출. 보내려면: chrome.runtime.sendMessage. 받으려면: chrome.runtime.onMessage.addListener. "popup messaging" 따로, "SW messaging" 따로 — 그런 API 없어. 두 context 가 같은 두 반쪽 사용.
sendMessage(message, callback?)— runtime 에 message 발사. MV3 에서 callback 안 주면 Promise 반환. extension 안 등록된 다른 listener 전부 받음.onMessage.addListener((message, sender, sendResponse) => boolean | undefined)— handler 등록, 들어오는 모든 message 에 호출.sendResponse를 async 로 부를 거면true반환; 동기 응답 (또는 무응답) 이면undefined.
sender argument 가 message 출처 — popup / content script / side panel / options page — 를 알려줘. Routing, 그리고 보내면 안 되는 context 의 요청 거절에 유용.
return true 불변식
listener 안에서 응답 전에 async 작업 — 예: chrome.storage read — 하면 listener body 에서 await 가 suspend 되기 전에 동기적으로 true 반환 필수:
Chrome runtime 은 listener 가 stack 에 있는 동안만 message channel 열어 둠. return true 가 runtime 한테 "sendResponse 나중에 부를게, channel 살려 둬" 라고 말해 줘. 잊으면 async path 의 sendResponse 는 silent no-op — caller 가 영원히 기다리다 한 5 분 뒤 timeout. MV3 message-passing 의 가장 흔한 버그 한 가지.
두 방향, 두 패턴
Popup → SW. popup 은 짧게 살아. SW 한테 뭔가 해 달라고 부탁하고 답 기다림. 흔한 예: "현재 clip list 줘", "이 clip 저장해 줘", "이 clip id 지워 줘". SW 가 storage read/write 하고 응답.
SW → popup. 덜 흔해. popup 이 열려 있다는 보장이 안 됨. 보냈는데 listening 하는 popup 없으면 SDK 가 "Receiving end does not exist" 비슷한 에러 반환. SW→popup update 는 chrome.storage.onChanged (Lesson 4) 가 자연. popup 이 열릴 때 구독, SW 가 storage write, 두 context 모두 반응. "popup 열려 있나" 가드 필요 없음.
Message 와 storage 결합
등장하는 실용 ClipDeck 패턴:
- Action 은 message 로. "clip 저장", "clip 삭제", "전체 비우기". popup 이 보내고, SW 가 storage mutate, 새 state 와 함께 응답.
- State update 는 storage 로. popup mount 시 storage 한 번 read, 이후
onChanged구독. 이후 SW 쪽 mutation 이 자동으로 re-render trigger.
이 분리가 codebase 를 정직하게 유지: message 는 동사, storage 는 명사. 섞으면 — message 로 전체 state 전송, message 로 변경 broadcast — 작은 extension 에선 돌아가지만 clip 쌓이는 순간 spaghetti.
ClipDeck preview: popup 이 SW ping
아래 exercise 가 ClipDeck popup 에 "Ping SW" 버튼 추가. { type: "ping" } 보내고, SW 가 { ok: true, at: Date.now() } 응답, popup 이 timestamp 표시. Lesson 6 가 이 scaffolding 을 진짜 방문 카운터 feature 로 발전. Track 3 부터는 같은 channel 을 ClipDeck 의 실제 CRUD-C 작업 — "선택 텍스트를 clip 으로 저장" 에 사용.
storage.onChanged 구독해서 무슨 일이 생겼는지 봄.sendResponse 전에 async 작업 하면 동기적으로 true 반환. 잊으면 listener return 순간 message channel 닫혀. async path 의 sendResponse 는 no-op; caller 영원히 대기, 결국 timeout. 증상: popup spinner 가 안 풀려, 양쪽 DevTools console 에 에러 안 보여.