Skip to main content
An embed sits on a different origin from your page, so none of it is reachable from your JavaScript: no DOM, no sandbox handle, no terminal. The bridge is the one seam through it; a small versioned postMessage protocol you can drive the preview with and hear back on. Every message carries vpod: 1. That is the protocol version, and it is also what tells a vpod message apart from everything else posting into a page; several frameworks post bare strings, so a message without it is ignored in both directions.

In one page

Wait for ready before sending anything you care about. A message posted before the preview has attached its listener is not queued, it is lost — and ready is also the first point at which there is a machine for run to run on.

What the preview sends

Two fields on ready are worth reading rather than ignoring:
  • network is "sab" or "none", which is the honest answer to “can the guest reach the internet from here” (see networking inside an iframe). It says nothing about which backend the SDK chose, on purpose.
  • warm is whether the snapshot came from cache rather than the network, so a page timing its own demo is not quoting a cold number as a warm one.
error distinguishes a snapshot that could not be resolved ("snapshot", raised before any bytes are downloaded) from a machine that would not start ("boot").
An embed refused for its origin sends nothing. It is a different document with no sandbox in it, so the absence of messages is what you get, not an error. Do not wait on a message to detect a bad origin — if ready never arrives, check the key’s allowlist.

What the preview accepts

An unknown theme name is ignored rather than falling back to the default. That is the opposite of the URL’s behaviour, and deliberately: a ?theme= typo is a page you can only fix by reloading it, where a message you sent is one you can send again.
clearCache clears origin-private storage for the preview origin, which is every snapshot cached there — not only the one in your embed. It is meant for a “download it again” control, not for tidying up after a demo.

run needs a key

run is honoured for a keyed preview and refused for a keyless one. By the time the message arrives, the browser has already enforced frame-ancestors from the key’s allowlist, so “framed at all” is “on the list” — there is no second check to do and no allowlist to ship to the client. A keyless preview is served frame-ancestors *, so being framed vouches for nothing, and an embed that any page could execute commands in is a different product. Everything else works either way. A keyless embed still reports ready, streams output, and takes reset and theme.

Origins, in both directions

Three rules, and each closes a real hole:
  • The preview always posts to an explicit target origin, never "*". A preview that broadcast guest output to whoever happened to be framing it would leak the contents of a private snapshot to a page that only managed to load it.
  • Inbound messages whose event.origin is not the embedder are ignored. The embedder is not the only thing that can reach that window.
  • Anything without the vpod tag is ignored, in both directions.
The preview works out who is framing it from location.ancestorOrigins, which is exact and unaffected by Referrer-Policy, and falls back to document.referrer where that does not exist (Firefox).
An embedder serving Referrer-Policy: no-referrer in Firefox leaves the preview with no origin it can address, so it sends no messages at all. The embed still loads and still works — it is the bridge that goes quiet. Chromium and Safari have ancestorOrigins and are unaffected.

A preview you drive and nobody types in

readonly=1 disables the keyboard at the terminal, but not run: the cmd parameter and an embedder’s run message both call the line editor directly.
That combination is a real machine running real commands, driven only by your page — a guided tour where the buttons are yours, rather than a terminal a visitor can wander off in.