@alga-psa/extension-iframe-sdk
v0.1.1
Published
Client SDK for building Alga iframe-delivered extensions (postMessage bridge + React hooks).
Readme
@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, optionalrequest_id, andpayload - 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 includeallow-same-originby 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 devoryarn devpnpm buildoryarn build(outputs staticindex.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_BASEis 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 inee/server/src/lib/extensions/ui/iframeBridge.ts)
Parent-side (host) bootstrap usage
// 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
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 <div>My Extension UI</div>;
}Canonical Handler Calls (UI -> own handler)
Use callHandlerJson for extension UI calls instead of direct fetch() or manual postMessage wiring:
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:
bootstrapwith{ 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-originunless strictly necessary with additional CSP - Parent uses strict
targetOrigin; child validates expected parent origin
License
BSD-3-Clause — see LICENSE.
