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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
158 lines
4.1 KiB
Markdown
158 lines
4.1 KiB
Markdown
# 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:
|
|
> ```bash
|
|
> 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```javascript
|
|
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):**
|
|
|
|
```bash
|
|
npm install
|
|
npm run build # runs: alga build
|
|
```
|
|
|
|
Or directly with the CLI:
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```bash
|
|
npm run pack # runs: alga pack
|
|
```
|
|
|
|
**Manual bundling:**
|
|
|
|
```bash
|
|
tar --zstd -cf bundle.tar.zst manifest.json ui/ dist/main.wasm
|
|
shasum -a 256 bundle.tar.zst
|
|
```
|
|
|
|
## Manifest
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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"
|
|
}
|
|
```
|