PSA/sdk/extension-iframe-sdk
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00
..

@alga/extension-iframe-sdk

Client SDK for building UI extensions delivered via iframes. Provides a stable, versioned postMessage bridge, theme token injection, shortlived 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

// 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: 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.