Skip to content

Inter-process communication (browser)

Audience: Agent

This document describes the postMessage-based protocol implemented by @howfe/inter-frame-messenger and used between the surface map (parent window) and the remote journal reader (popup). Source of truth: inter-frame-messenger/src/index.ts.

Related: URL query bootstrap, embedding profiles, integration manifests (/.well-known/edos-integration.json), and optional non-IFM embed handshakes are specified in HOSTED_APP_CONTRACT.md. They are intentionally separate from MessageTypeEnum unless a maintainer adds a new enum value and coordinates all consumers (AGENTS.md).

Not IFM: Request/response access to another app’s origin-local data from a sibling iframe (for example commander bookmarks) is not a MessageTypeEnum flow. Use the composition-host proxy in HOSTED_APP_CONTRACT.md — Composition host and sibling iframes. Do not use hidden iframes to the provider origin.

Message envelope

All messages use the browser’s structured-clone payload shape:

Field Type Meaning
event string One of MessageTypeEnum values (e.g. 'elite-event', 'open-file:journal').
payload unknown Event-specific data; for elite-event / elite-status use isEliteEvent / isStatus; for edos-file-event use isEdosFileEventEnvelope (full BridgeEnvelope, same shape as desktop edos:file:event).

Receive path: window.addEventListener('message', …) filters by event.origin (see below), then compares event.data.event to the subscribed name, then passes event.data.payload to callbacks.

Send path: target.postMessage({ event, payload }, origin) — for each origin in allowedListenerOrigins (see inter-frame-messenger/src/index.ts send implementation).

MessageTypeEnum

Defined in inter-frame-messenger/src/index.ts:

Value Direction (typical) Payload
elite-event Popup → opener EliteEvent
elite-status Popup → opener Status (legacy bare snapshot; no BridgeEnvelope wrapper)
edos-file-event Desktop shell → webview (today); future: remote reader parity EdosFileEventEnvelope (BridgeEnvelope: all sidecars + journal + status, discriminated by sourceType / eventKind)
message-types-info Either { sendMessageTypes, listenMessageTypes }
get-message-types Either (triggers info reply)
open-file:journal Opener → popup (no structured payload in handlers)
refetch-file:journal Opener → popup
poll-file:journal Opener → popup
stop-polling:journal Opener → popup
open-file:status Opener → popup
refetch-file:status Opener → popup
poll-file:status Opener → popup
stop-polling:status Opener → popup

Note: JournalWatcher / StatusWatcher call this.messenger.send(MessageTypeEnum.EliteEvent, event) for journal lines (line 93 in JournalWatcher.ts) — equivalent to sendEliteEvent when the type is allowed.

Same-window listener (Stellar Scan, Credit Account)

Files: elite-dangerous-stellar-scan/src/composables/useJournalConnection.ts, elite-dangerous-credit-account/src/composables/useJournalConnection.ts (same IFM pattern; Credit Account ingests ledger events from onEliteEvent / onEdosFileEvent only).

  • target = window (not window.opener or a popup reference).
  • Listens (IFM allowlist): EliteEvent, Status, EdosFileEvent. Handlers registered today: onEliteEvent and onEdosFileEvent only (useJournalConnection.ts — no onStatusEvent; bare elite-status is not ingested until a handler is added).
  • Sends: none (sendMessageTypes is []).
  • Origins: allowedSenderOrigins from VITE_ALLOWED_SENDER_ORIGINS (comma-separated) or defaults including https://edjr.howfe.org, https://edss.howfe.org, https://edjev.howfe.org, and http://localhost:5173; allowedListenerOrigins: ['*'].
  • Popup: openRemoteReader() uses window.open(VITE_REMOTE_JOURNAL_READER_URL ?? 'https://edjr.howfe.org', …); events are expected to arrive on the same window via postMessage from the popup or from the desktop shell’s injected bridge (same-tab / same-window pattern). TODO: Verify EDJR popup → parent routing when opener and listener are not the classic surface-map parent/child pair — compare with full elite-dangerous-remote-journal-reader checkout.

Embedded mode hides the “Open journal reader” control; use the local journal reader webview or host-driven IFM instead (apps/elite-dangerous-stellar-scan.md).

Parent vs popup construction

Files: elite-dangerous-remote-journal-reader/ui/src/components/JournalWatchButton.vue, StatusWatchButton.vue.

  • target = window.opener
  • Sends: EliteEvent (journal) or Status (status file)
  • Listens: Open* / Refetch* / Poll* / Stop* for journal or status
  • Origins: allowedSenderOrigins: ['*'], allowedListenerOrigins: ['*'] in current code — permissive so any opener can control the popup if it sends allowed message types.

Parent (surface map)

File: elite-dangerous-surface-map/ui/src/composables/useJournalReader.ts.

  • target = reference to the popup Window from window.open('https://edjr.howfe.org', …)
  • Sends: none of the journal/status command types are registered (sendMessageTypes is []); only implicit message-types-info handling from the library constructor
  • Listens: EliteEvent, Status
  • Origins: allowedSenderOrigins: ['*'], allowedListenerOrigins: ['https://edjr.howfe.org'] — incoming messages from any origin are accepted (*); outgoing postMessage to the popup uses target origin https://edjr.howfe.org

Security implication: inbound filtering on the parent uses '*' for senders — rely on child origin in practice being the known popup. Tightening to the EDJR origin is a possible hardening step (TODO: Verify if you support multiple embedders).

Sequence: user starts journal feed from map

sequenceDiagram participant User participant Map as Surface map participant Pop as Journal reader popup participant FS as File System Access API User->>Map: Click start journal reader Map->>Pop: window.open(edjr) Map->>Map: new InterFrameMessenger(popup, listen=elite-event,status) User->>Pop: Open journal / status file Pop->>FS: read file loop poll ~1s Pop->>Pop: parse lines / JSON Pop->>Map: postMessage elite-event / elite-status Map->>Map: onEliteEvent / onStatus callbacks end

Sequence: journal line parsing (popup)

sequenceDiagram participant JW as JournalWatcher participant FS as File handle participant IFM as InterFrameMessenger JW->>FS: updateData / read text JW->>JW: split lines, JSON.parse JW->>JW: isEliteEvent + dedupe by line JW->>IFM: send(elite-event, payload) IFM->>IFM: opener.postMessage({event, payload})

Extending the protocol

  1. Add a new enum value in inter-frame-messenger/src/index.ts (avoid reusing strings).
  2. Rebuild and publish @howfe/inter-frame-messenger (or link workspace package).
  3. Update both sender and receiver allowlists (sendMessageTypes / listenMessageTypes) and handlers.
  4. If payload types are new, extend @howfe/elite-dangerous-event-types (sidecar blobs + EdosFileEventEnvelope, or journal events) — do not invent ad-hoc shapes only in one app. Do not add per-sidecar IFM outer names such as elite-market; use edos-file-event and discriminate inside the envelope.

Compatibility

  • Requires modern postMessage and (for file picking) File System Access API — Chromium-based browsers / WebView2 align with the assumptions in original-concept.md.