# @alga/extension-iframe-sdk Client SDK for building UI extensions delivered via iframes. Provides a stable, versioned postMessage bridge, theme token injection, short‑lived auth session handling, and ergonomic React hooks. Protocol hardening: - Versioned message envelope with top-level fields: `alga: true`, `version: "1"`, `type`, optional `request_id`, and `payload` - Origin validation on both sides; child ignores events from unexpected origins - No usage of `targetOrigin="*"` except in explicit dev/test guard - Sandbox guidance: default `sandbox="allow-scripts"`; do not include `allow-same-origin` by default ## Quick start A complete Vite + React + TS example is provided under: - `packages/extension-iframe-sdk/examples/vite-react` Build and preview (from the example directory): - `pnpm dev` or `yarn dev` - `pnpm build` or `yarn build` (outputs static `index.html + assets/*`) Embed the built app via iframe with a src of the form: - Relative (Rust host path): `/ext-ui/{extensionId}/{content_hash}/index.html?path=/desired/route` - Absolute (when `RUNNER_PUBLIC_BASE` is absolute): `${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/index.html?path=/desired/route` Use the host helper to construct the URL in the host: - `buildExtUiSrc()` (see host implementation in `ee/server/src/lib/extensions/ui/iframeBridge.ts`) ## Parent-side (host) bootstrap usage ```ts // Host-side (not part of this package): see ee/server/src/lib/extensions/ui/iframeBridge.ts const iframe = document.querySelector('iframe#my-ext')!; iframe.src = buildExtUiSrc(extensionId, contentHash, '/'); bootstrapIframe({ iframe, extensionId, contentHash, // must match /^sha256:[0-9a-f]{64}$/i initialPath: '/', session: { token: shortLivedToken, expiresAt: '2025-01-01T00:00:00Z' }, themeTokens: { '--alga-primary': '#2266ff', '--alga-bg': '#fff', '--alga-fg': '#111', }, allowedOrigin: 'https://runner.example.com', requestId: 'req-123', }); ``` ## Child-side (iframe app) usage with React hooks ```tsx import React, { useEffect } from 'react'; // If you have @alga/ui-kit available locally, include tokens // import '@alga/ui-kit/tokens.css'; import { useBridge, useTheme, useAuthToken, useResize } from '@alga/extension-iframe-sdk'; export default function App() { const bridge = useBridge(); const tokens = useTheme(bridge); const token = useAuthToken(bridge); const reportResize = useResize(bridge); useEffect(() => { reportResize(document.documentElement.scrollHeight); }, [tokens, token, reportResize]); return
My Extension UI
; } ``` ## Canonical Handler Calls (UI -> own handler) Use `callHandlerJson` for extension UI calls instead of direct `fetch()` or manual postMessage wiring: ```ts import { IframeBridge, callHandlerJson } from '@alga-psa/extension-iframe-sdk'; const bridge = new IframeBridge({ devAllowWildcard: true }); bridge.ready(); const status = await callHandlerJson(bridge, '/api/status'); const created = await callHandlerJson(bridge, '/api/items', { method: 'POST', body: { name: 'Sample' }, }); await callHandlerJson(bridge, '/api/items/123', { method: 'DELETE' }); ``` `callHandlerJson` forwards the requested HTTP method through the iframe bridge transport, so handlers can receive the intended method without embedding transport-specific override fields in route/body data. ## Protocol - Parent → Child: `bootstrap` with `{ session, theme_tokens, navigation }` - Child → Parent: `ready`, `resize`, `navigate` - Envelope: `{ alga: true, version: "1", type, request_id?, payload }` ## Security notes - Iframes should use `sandbox="allow-scripts"` by default - Avoid `allow-same-origin` unless strictly necessary with additional CSP - Parent uses strict `targetOrigin`; child validates expected parent origin ## License BSD-3-Clause — see `LICENSE`.