Skip to main content

Handshake

The map announces itself, and a host may also ask. Either is sufficient.

1. Host loads the frame with ?embed=1&host={origin}
2. Map sends map.ready { protocol, campus, capabilities[], build? }
3. Host sends host.hello { sdk?, protocol? } optional
Map answers { protocol, campus, capabilities[], build? }
4. Map sends map.loaded { ...state } once campus data resolves

Steps 2 and 3 carry the same payload. A host may use whichever arrives first.

Why there are two paths

map.ready is a single message with no replay. A host that creates the frame, awaits anything at all, and only then attaches its listener misses it entirely and would wait forever for an announcement that already happened.

Whether a host wins that race depends on incidental ordering in its own framework, which is not something an integration should have to depend on. host.hello makes readiness pollable instead, so a listener attached late still connects.

The command earns its place twice more. It reports the host's SDK build, which is what makes the deployed install base measurable and therefore makes it safe to keep old versions working rather than guessing. And it lets a host learn the protocol version and capability list before sending anything that depends on them.

Maps built before this command existed answer UNSUPPORTED. A host should treat that as a signal to fall back to waiting for map.ready, which is what the SDK does.

The readiness payload

interface MapReady {
protocol: number;
campus: string | null;
capabilities: string[];
build?: string;
}

campus may be null in map.ready. The bridge installs at boot, before routing has resolved, and reads the slug from the path as a best effort. map.loaded carries the authoritative state, and a host.hello answer is built fresh at call time, so a host that asks later gets a better answer than one that takes the announcement.

capabilities lists the command names this build accepts. Checking it before sending is what turns an unsupported command into an immediate error rather than a timeout.

build is present only on deployments built with a build identifier. Treat it as opaque; it exists so that a bug report can be tied to a specific deploy.

Command queueing

Commands sent between map.ready and map.loaded are queued and drained in order, each still receiving exactly one result. The queue holds fifty entries for thirty seconds; on overflow or expiry the host receives NOT_READY rather than silence.

Three commands skip the queue, because they concern the frame rather than campus state and would otherwise be answered only after the condition they are about had already passed: ui.setChrome, events.subscribe, and events.unsubscribe. host.hello skips it for the same reason.

Malformed and unrecognised commands are also answered immediately. Waiting thirty seconds to be told that a reference was a number helps nobody.

Commands sent before map.ready are lost, since no listener exists yet. Send setup in response to map.ready, or ask with host.hello.

Origin

The listener is attached only when three conditions hold: embed=1 is set, the page is actually framed, and host parses as a valid origin. A listener that answers nobody is a larger attack surface for no benefit, so it is not attached at all rather than attached and rejecting.

Inbound messages are compared against host by exact string equality. Outbound messages are posted to that exact origin, never to a wildcard.

Messages from a disallowed origin are dropped without a reply. Replying would mean posting to an origin already judged untrusted, which confirms the map is present and hands the sender a channel. ORIGIN_REJECTED therefore exists in the error type but never reaches a host; it appears only in logs.

Next

  • Commands for what can be sent after connecting.
  • Events for what arrives unprompted.