API Reference

postMessage Protocol

Communication protocol between Universe Shell and modules via window.postMessage.

postMessage Protocol

Modules communicate with the Universe Shell via the browser's window.postMessage API. This is the bridge between the parent window (Shell) and the iframe (your module).

Message Types

Module β†’ Shell

Message TypeDescriptionPayload
REQUEST_AUTH_SESSIONRequest JWT tokens from Shellnone
navigateNavigate Shell to a path{ path: string }

Shell β†’ Module

Message TypeDescriptionPayload
AUTH_SESSIONJWT tokens for Supabase{ access_token, refresh_token }
SESSION_RESETWipe all local caches and sign outnone
SESSION_USER_CHANGEDNew user signed in β€” wipe before re-auth{ userId: string | null }

Flow: Session reset (sign-out / user change)

When the Shell signs out, or detects that a different user has signed in, it broadcasts a session-reset message to every active iframe. Modules must:

  1. queryClient.clear() β€” drop all cached server data
  2. supabase.auth.signOut() β€” invalidate the local Supabase session
  3. Clear any module-specific localStorage caches (e.g. simulator drafts, waste records, tenant IDs)
  4. Reset UI to the loading / unauthenticated state
window.addEventListener('message', async (event) => {
  if (event.data?.type === 'SESSION_RESET' || event.data?.type === 'SESSION_USER_CHANGED') {
    getQueryClient().clear();
    await getSupabase().auth.signOut();
    setIsAuthenticated(false);
  }
});

Modules that ignore these messages will leak data from a previous user into the next session β€” incognito would be the only escape hatch.

Flow: Authentication

// Module sends (on mount):
window.parent.postMessage(
  { type: 'REQUEST_AUTH_SESSION' },
  '*'
);
 
// Shell responds β€” tokens are posted ONLY to the module's exact origin,
// never '*', so the browser drops them if the iframe was redirected elsewhere:
iframe.contentWindow.postMessage(
  {
    type: 'AUTH_SESSION',
    payload: {
      access_token: 'eyJhbGciOiJIUzI1NiIs...',
      refresh_token: 'v1.abc123...',
    },
  },
  'https://my-module.example.com' // = new URL(moduleBaseUrl).origin
);

Flow: Navigation

// Module triggers Shell navigation:
window.parent.postMessage(
  { type: 'navigate', path: '/admin/modules' },
  '*'
);
 
// Shell handles:
window.addEventListener('message', (event) => {
  if (event.data?.type === 'navigate') {
    router.push(event.data.path);
  }
});

URL Parameters

The Shell passes context via iframe URL query parameters:

ParameterTypeDescription
org_idUUIDOrganization ID for the current tenant

Example iframe URL:

https://my-module.vercel.app?org_id=550e8400-e29b-41d4-a716-446655440000

Security Notes

  • Shell β†’ Module: the Shell posts AUTH_SESSION (and SESSION_RESET / SESSION_USER_CHANGED) to your module's exact origin β€” never '*'. The origin is derived from the module's configured base/deploy URL (new URL(baseUrl).origin). If your module is redirected to a different origin after load, the browser will silently drop these messages and tokens will not be delivered β€” keep the module on its configured origin.
  • Shell inbound validation: the Shell accepts navigate / REQUEST_AUTH_SESSION only from the exact origin it embedded. It does not substring-match (origin.includes('insolitum.ai')) β€” a lookalike such as https://insolitum.ai.evil.com is rejected.
  • Module β†’ Shell: your module should likewise validate event.origin on the AUTH_SESSION it receives against the expected Shell origin before trusting the tokens. Never trust message data without validation.
  • JWT tokens are short-lived; Supabase handles refresh automatically.

On this page