Skip to content

Message contract

Messages between the content gate, player, picker, studio and service worker are defined in packages/messaging/src/index.ts. Every typed message has a Zod request schema and a response schema, keyed by message type. The worker parses each inbound message with parseMessage and replies { ok: true, data } or { ok: false, error }.

Typed messages

Type Request Response Notes
manifest.get origin Manifest entries Empty when no app owns the origin
tour.get id, version Content item Reads a published version
progress.get contentId Progress or null
progress.set Progress none
app.context origin App key, environment, theme override, help bubble, CSS variables, maxAnnouncements, or null
player.inject none none Honoured only from frame 0 of a tab
asset.get Asset reference mime, base64
events.push Up to 100 guidance events none The handler discards them
frame.register url none Records a frame of the tab
locator.resolve Optional frameId, locator found, rect, strategy Cross-frame resolution through the frame agent
picker.start tabId, mode none Modes: element, form, fields, condition-field, record
picker.stop Optional tabId none
picker.picked Locator, preview, optional action (click, input, select), optional orphanFrame none Sent from the picker to the studio
preview.play tabId, item, draft resources, optional fromStep none
preview.stop tabId none
auth.status none signedIn, roles Fixed values in the local MVP

Internal messages

These travel between the worker and injected scripts and are not in the typed contract. Each uses a guidepost. prefix and is checked by the receiver (the frame agent, for example, verifies sender.id equals its own extension id).

Type Between
guidepost.picker.start, guidepost.picker.stop Worker to picker
guidepost.picker.hop Picker frames
guidepost.studio.picked, guidepost.studio.picker-stopped Worker to studio
guidepost.preview.start, guidepost.preview.stop Worker to player
guidepost.frame.resolve Worker to frame agent

Sender checks

isTrustedSender(sender, extensionId, grantedOrigins) returns true only when sender.id equals the extension's id and one of these holds:

  • the sender has no origin (extension pages such as the side panel and options page);
  • the origin starts with chrome-extension://;
  • the origin matches a granted origin through originMatches.

originMatches follows Chrome match patterns: the port is ignored, a leading *. matches subdomains, and the scheme may be http, https or *. Any other pattern shape fails.

Gotchas

  • A new message type needs an entry in messageContracts and a handler. The handler table is typed with Record<MessageType, Handler>, so a missing handler is a compile error.
  • Responses are validated by parseResponse only where callers use it; treat the schema as the contract either way.