PSA/ee/docs/extension-system/sample_template.md
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

4.4 KiB
Raw Blame History

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:

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

{
  "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)

// 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)

// 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<any[]>([]);

  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 <DataTable columns={[{ key: 'name', header: 'Name' }]} rows={rows} />;
}

createRoot(document.getElementById('root')!).render(
  <BridgeProvider>
    <App />
  </BridgeProvider>
);

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

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 SDKprovided headers for authentication; do not forward enduser tokens
  • Validate inputs; return structured errors

Related references: