HyperGen

postMessage Protocol

All postMessage types exchanged between the HyperGen host application and the sandboxed iframe, with exact JSON shapes and origin validation rules.

The host application and the sandboxed iframe communicate via the browser's postMessage API. This section defines all message types, their exact JSON shapes, and the origin validation requirements.

postMessage is used only for host-iframe integration: theming, resize notifications, navigation intents, data exchange, and lifecycle management. Agent communication (SSE streaming, user action submission) flows directly between the iframe and the agent server via HTTP — see SSE Message Format.

Message Envelope

Every HyperGen postMessage MUST be a JSON object with a type field:

interface HyperGenMessage {
  type: string;
  [key: string]: unknown;
}

The type field MUST be a string prefixed with hg:. Messages with unrecognized types SHOULD be silently ignored.

Origin Validation

Host receiving messages from iframe

The host MUST verify that incoming messages originate from the expected iframe:

  1. Source check: event.source MUST equal the iframe's contentWindow. This confirms the message came from the correct iframe (critical when multiple iframes exist).
  2. Origin check: If the iframe origin is known, event.origin SHOULD be validated against it. When iframeOrigin is "*", origin checking is skipped.
window.addEventListener("message", function(event) {
  // MUST: verify the message source is our iframe
  if (event.source !== iframe.contentWindow) return;

  // SHOULD: verify origin when known
  if (expectedOrigin !== "*" && event.origin !== expectedOrigin) return;

  // Process the message...
});

Iframe receiving messages from host

The bootstrap document's postMessage handler MUST validate the origin when a specific hostOrigin is configured:

window.addEventListener("message", function(e) {
  if (hostOrigin !== "*" && e.origin !== hostOrigin) return;
  // Process the message...
});

Production deployments SHOULD set explicit origins rather than "*".

Host to Iframe Messages

hg:theme — CSS Variable Injection

Injects or updates CSS custom properties on the iframe's :root element. Used to synchronize the host's design system with the iframe.

interface HgThemeMessage {
  type: "hg:theme";
  /** CSS custom property key-value pairs. Keys MUST be valid CSS custom property names. */
  vars: Record<string, string>;
}

Example:

{
  "type": "hg:theme",
  "vars": {
    "--hg-surface": "#1a1a2e",
    "--hg-text": "#e0e0e0",
    "--hg-accent": "#7c3aed",
    "--hg-accent-fg": "#ffffff",
    "--hg-border": "#374151",
    "--hg-radius": "8px"
  }
}

Behavior:

  • The iframe MUST iterate over vars and call document.documentElement.style.setProperty(key, value) for each entry.
  • The message is additive — it updates the specified variables without removing previously set ones.
  • The host SHOULD send a hg:theme message immediately after the iframe loads and whenever the host theme changes (e.g., dark mode toggle).

hg:destroy — Cleanup Signal

Instructs the iframe to shut down: close SSE connections, stop observers, and release resources.

interface HgDestroyMessage {
  type: "hg:destroy";
}

Example:

{
  "type": "hg:destroy"
}

Behavior:

  • The iframe MUST remove the sse-connect attribute from the root element to close the SSE connection.
  • The iframe SHOULD trigger htmx:sseClose to clean up HTMX SSE state.
  • After processing hg:destroy, the host typically removes the iframe from the DOM.

Iframe to Host Messages

hg:ready — Iframe Ready Signal

Notifies the host that the iframe's bootstrap document has loaded and the postMessage bridge is operational.

interface HgReadyMessage {
  type: "hg:ready";
}

Example:

{
  "type": "hg:ready"
}

Behavior:

  • The iframe MAY send this message after the bootstrap document's scripts have executed.
  • The host MAY use this signal to delay sending hg:theme until the iframe is ready to receive it.
  • This message is OPTIONAL. The host SHOULD also handle the case where hg:theme is sent before hg:ready (e.g., via the iframe's load event).

hg:resize — Content Height Notification

Notifies the host that the iframe content size has changed, enabling the host to resize the iframe element accordingly.

interface HgResizeMessage {
  type: "hg:resize";
  /** The content height in pixels (document.documentElement.scrollHeight). */
  height: number;
  /** The content width in pixels (optional). */
  width?: number;
}

Example:

{
  "type": "hg:resize",
  "height": 542,
  "width": 800
}

Behavior:

  • The iframe MUST use a ResizeObserver on document.body to detect size changes.
  • On each size change, the iframe MUST post an hg:resize message with the current scrollHeight.
  • The width field is OPTIONAL. It is included for layouts where width information is useful.
  • The host SHOULD auto-resize the iframe element by setting its height style property to the received value, clamped between configured minimum and maximum heights.

hg:navigate — Navigation Intent

Requests that the host application navigate to a URL. The iframe cannot navigate the host directly (the allow-top-navigation sandbox permission is denied).

interface HgNavigateMessage {
  type: "hg:navigate";
  /** The target URL. May be absolute or relative to the host. */
  url: string;
}

Example:

{
  "type": "hg:navigate",
  "url": "/dashboard/reports"
}

Behavior:

  • Agent-generated HTML can trigger navigation by including elements with a data-hg-navigate attribute. The bootstrap document's click handler intercepts these clicks and posts the hg:navigate message.
  • Alternatively, agent-generated inline scripts can post hg:navigate directly.
  • The host MUST validate the URL before navigating (e.g., reject absolute URLs to untrusted domains).
  • How the host performs the navigation is implementation-specific (e.g., router.push(), window.location.href).

Agent HTML triggering navigation:

<a data-hg-navigate="/settings" href="#">Open Settings</a>

hg:data — Arbitrary Data Payload

Sends structured data from the iframe to the host. This is the general-purpose channel for agent-generated UI to communicate results, selections, or state to the host application.

interface HgDataMessage {
  type: "hg:data";
  /** Any JSON-serializable payload. */
  payload: unknown;
}

Example:

{
  "type": "hg:data",
  "payload": {
    "action": "item_selected",
    "itemId": 42,
    "label": "Quarterly Report"
  }
}

Behavior:

  • Agent-generated scripts can dispatch a CustomEvent named hg:data on document. The bootstrap's event listener catches it and forwards event.detail to the host as the payload.
  • The host application registers a callback to handle hg:data messages.
  • The payload may be any JSON-serializable value: object, array, string, number, boolean, or null.

Agent HTML sending data:

<button onclick="document.dispatchEvent(new CustomEvent('hg:data', {
  detail: { action: 'confirm', orderId: 123 }
}))">
  Confirm Order
</button>

Complete Type Definition

For implementers using TypeScript, the complete set of message types:

// Host → Iframe
type HostToIframeMessage =
  | { type: "hg:theme"; vars: Record<string, string> }
  | { type: "hg:destroy" };

// Iframe → Host
type IframeToHostMessage =
  | { type: "hg:ready" }
  | { type: "hg:resize"; height: number; width?: number }
  | { type: "hg:navigate"; url: string }
  | { type: "hg:data"; payload: unknown };

// Union of all messages
type HyperGenMessage = HostToIframeMessage | IframeToHostMessage;

Message Flow Diagram

Host Application                    Sandboxed Iframe
     │                                    │
     │◄──── hg:ready ────────────────────│  (iframe loaded)
     │                                    │
     │──── hg:theme ─────────────────────►│  (inject CSS vars)
     │                                    │
     │                                    │  (SSE streams HTML, user interacts)
     │                                    │
     │◄──── hg:resize ──────────────────│  (content grew)
     │                                    │
     │◄──── hg:navigate ────────────────│  (user clicked nav link)
     │                                    │
     │◄──── hg:data ────────────────────│  (user confirmed action)
     │                                    │
     │──── hg:destroy ───────────────────►│  (host removing iframe)
     │                                    │

Extensibility

Implementations MAY define additional message types beyond those specified here. Custom message types MUST use the hg: prefix followed by a namespace to avoid collisions:

hg:x-myapp:custom-event

Receivers MUST silently ignore message types they do not recognize.

On this page