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.1 KiB

Dual Portal Demo Extension

A sample extension demonstrating how to build a single extension that works in both the MSP Portal and Client Portal. Uses the postMessage proxy pattern for WASM handler communication.

Note for external developers: To create a new extension project from scratch, install the CLI globally and use the scaffolding command:

npm install -g @alga-psa/cli
alga create extension my-extension

Features

  • Dual Portal Support: Registers in both MSP Portal (appMenu) and Client Portal (clientPortalMenu)
  • Context Detection: Automatically detects which portal it's running in
  • Different UIs: Shows different features and styling based on portal context
  • Proxy Pattern: Calls WASM handler via postMessage (not direct fetch)

How It Works

Dual Hook Registration

The manifest registers the extension in both portals using multiple hooks:

{
  "ui": {
    "hooks": {
      "appMenu": { "label": "Dual Portal Demo" },
      "clientPortalMenu": { "label": "Dual Portal Demo" }
    }
  }
}

Context Detection

The UI detects which portal it's running in by checking the referrer URL:

const referrer = document.referrer || '';
const isClientPortal = referrer.includes('/client-portal/');
const portalType = isClientPortal ? 'client' : 'msp';

Different UIs Per Portal

Based on the detected context, the extension:

  • Applies different CSS color schemes (purple for MSP, cyan for Client)
  • Shows different feature lists appropriate to each user type
  • Can pass the portal type to the WASM handler for server-side logic

Proxy Pattern

This sample demonstrates the recommended way for extension UIs to communicate with their WASM handlers:

  1. Iframe sends apiproxy message to the host via window.parent.postMessage()
  2. Host bridge receives the message and forwards to /api/ext-proxy/{extensionId}/{route}
  3. Runner executes the WASM handler and returns the response
  4. Host sends apiproxy_response message back to the iframe

This pattern ensures:

  • The iframe never makes direct HTTP requests to extension APIs
  • Authentication is handled by the host
  • Secrets never reach the browser

See ui/main.js for the implementation.

Structure

client-portal-test/
├── manifest.json      # Extension manifest with both portal hooks
├── package.json       # Build configuration
├── tsconfig.json      # TypeScript config for handler
├── src/
│   └── handler.ts     # WASM handler implementation
├── ui/
│   ├── index.html     # Extension UI with portal-aware styling
│   └── main.js        # UI JavaScript (context detection + proxy pattern)
└── wit/
    └── ext.wit        # WIT interface definition

Building

Using the Alga CLI (recommended):

npm install
npm run build    # runs: alga build

Or directly with the CLI:

alga build

This will:

  1. Compile src/handler.ts to JavaScript (via esbuild)
  2. Use jco componentize to create a WASM component at dist/main.wasm

Bundling

Using the Alga CLI:

npm run pack     # runs: alga pack

Manual bundling:

tar --zstd -cf bundle.tar.zst manifest.json ui/ dist/main.wasm
shasum -a 256 bundle.tar.zst

Manifest

{
  "name": "com.alga.sample.client-portal-test",
  "version": "1.3.0",
  "runtime": "wasm-js@1",
  "capabilities": ["cap:context.read", "cap:log.emit", "cap:ui.proxy"],
  "ui": {
    "type": "iframe",
    "entry": "ui/index.html",
    "hooks": {
      "appMenu": { "label": "Dual Portal Demo" },
      "clientPortalMenu": { "label": "Dual Portal Demo" }
    }
  },
  "assets": ["ui/**/*"]
}

Note: The cap:ui.proxy capability is required for the proxy pattern.

Handler Response

The WASM handler returns JSON with:

{
  "ok": true,
  "message": "Hello from the Dual Portal Demo WASM handler!",
  "portalType": "client",
  "context": {
    "tenantId": "...",
    "extensionId": "...",
    "installId": "...",
    "requestId": "..."
  },
  "timestamp": "2025-11-27T04:08:15.123Z"
}