# Sample Extension Template (Runner + Iframe UI) This template outlines a minimal v2 extension composed of: - Server handlers compiled to WASM and executed by the Runner - An iframe UI built with React using the Alga UI kit and SDK - A Manifest v2 that declares endpoints and the UI entry Core rules: - All server calls go through the Gateway: `/api/ext/[extensionId]/[[...path]]` → Runner `POST /v1/execute` - UI assets are served by the Runner at `${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/[...]` - Iframe src is constructed via [buildExtUiSrc()](../../../server/src/lib/extensions/ui/iframeBridge.ts:38) and initialized with [bootstrapIframe()](../../../server/src/lib/extensions/ui/iframeBridge.ts:45) - Gateway scaffold reference: [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts) ## File Structure ``` my-extension/ ├── src/ │ ├── http/ │ │ ├── list_agreements.ts │ │ └── sync.ts │ └── ui/ │ ├── index.html │ └── src/ │ ├── main.tsx │ └── components/ ├── dist/ │ ├── handlers/http/list_agreements.wasm # built artifact(s) │ └── ui/ # built iframe assets ├── manifest.json ├── SIGNATURE # generated by CI ├── package.json └── README.md ``` ## Manifest v2 ```json { "name": "com.example.agreements", "publisher": "Example, Inc.", "version": "1.0.0", "runtime": "wasm-js@1", "capabilities": ["http.fetch", "storage.kv"], "ui": { "type": "iframe", "entry": "ui/index.html" }, "api": { "endpoints": [ { "method": "GET", "path": "/agreements", "handler": "dist/handlers/http/list_agreements" }, { "method": "POST", "path": "/agreements/sync", "handler": "dist/handlers/http/sync" } ] }, "assets": ["ui/**/*"] } ``` ## WASM Handler (conceptual) ```ts // src/http/list_agreements.ts export async function list_agreements(ctx) { const items = await ctx.storage.list({ namespace: 'agreements' }); return { status: 200, headers: { 'content-type': 'application/json' }, body: { data: items } }; } ``` Build to `dist/handlers/http/list_agreements.wasm` via your language toolchain. ## Iframe UI (React) ```tsx // ui/src/main.tsx import React, { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { useExtension, BridgeProvider } from '@alga/extension-iframe-sdk'; import { DataTable } from '@alga/ui-kit'; function App() { const { context } = useExtension(); const [rows, setRows] = useState([]); useEffect(() => { async function load() { const res = await fetch(`/api/ext/${context.extensionId}/agreements`, { headers: context.authHeaders }); const data = await res.json(); setRows(Array.isArray(data) ? data : data.data ?? []); } load(); }, [context.extensionId, context.authHeaders]); return ; } createRoot(document.getElementById('root')!).render( ); ``` ## Packaging & Signing - Produce bundle with `manifest.json`, WASM artifacts under `dist/`, UI assets under `ui/` - Compute SHA256 over the canonical bundle - Sign using your publisher certificate; write `SIGNATURE` - Publish to the Registry (CI step) See: [security_signing.md](security_signing.md) ## Install & Use - Tenant admin installs a specific version and grants capabilities - UI entry is served by the Runner at `${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/index.html` - Client/UI code calls handlers via `/api/ext/{extensionId}/...` (Gateway proxies to Runner) ## Notes - Do not rely on server filesystem paths; artifacts are fetched by content hash - Keep responses small; paginate lists - Use SDK‑provided headers for authentication; do not forward end‑user tokens - Validate inputs; return structured errors Related references: - Gateway route scaffold: [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts) - Iframe bridge: [server/src/lib/extensions/ui/iframeBridge.ts](../../../server/src/lib/extensions/ui/iframeBridge.ts:38) - Runner overview: [runner.md](runner.md) - Manifest schema: [manifest_schema.md](manifest_schema.md)