Service worker and messaging
Design message contracts
Use named message types, narrow payloads, clear responses, and error handling across popup, worker, and content script.
8 minute lesson
As an extension grows, ad hoc messages become an undocumented internal API.
Define the allowed message shapes in one module or type declaration. Include only serializable data, validate senders where trust differs, return explicit results, and handle runtime.lastError or rejected promises.
type PageNoteMessage =
| { type: 'SHOW_PAGE_NOTE'; note: string }
| { type: 'HIDE_PAGE_NOTE' }
| { type: 'GET_ACTIVE_NOTE'; url: string }
type MessageResult =
| { ok: true }
| { ok: false; code: 'INVALID_MESSAGE' | 'UNAVAILABLE' }
Validate at runtime as well as with TypeScript because a sender can be old, compromised, or plain JavaScript. Check sender.id for external or cross-extension surfaces and sender.tab or URL when the receiver’s authority depends on the source. Do not echo exception stacks or storage contents in errors.
Chrome messaging uses JSON serialization. For broadly compatible asynchronous listeners, call sendResponse and return the literal true so the channel stays open. Current Chrome is rolling out returned-promise support, but choosing it without a minimum-version decision can produce inconsistent behavior across installed clients.
Document the three message types with sender, receiver, payload, response, authorization rule, and failure codes. Exercise an unknown type, malformed data, no receiver, two listeners attempting to respond, and a response that cannot be serialized.
Lesson completed