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

199 lines
5.8 KiB
JavaScript

const ENVELOPE_VERSION = '1';
const output = document.getElementById('output');
/**
* Resolve the main application's origin (host domain).
* Since the extension UI runs in an iframe on a separate domain (e.g., ext-abc.apps.algapsa.com),
* we need to detect the parent frame's domain to make API calls to the main app.
*/
function resolveHostOrigin() {
// Try to get parent frame's origin from referrer header
const referrer = document.referrer;
if (referrer) {
try {
return new URL(referrer).origin;
} catch {
// ignore invalid referrer
}
}
// Try to access parent frame's location (works if same-origin)
try {
if (window.parent && window.parent !== window && window.parent.location) {
return window.parent.location.origin;
}
} catch {
// cross-origin access throws; swallow and fallback
}
// Fallback to current window's origin (last resort)
return window.location.origin;
}
function resolveExtensionId(searchParams) {
const fromQuery = searchParams.get('extensionId');
if (fromQuery && fromQuery !== 'unknown') {
return fromQuery;
}
const segments = window.location.pathname.split('/').filter(Boolean);
const extUiIndex = segments.indexOf('ext-ui');
if (extUiIndex >= 0 && segments[extUiIndex + 1]) {
try {
return decodeURIComponent(segments[extUiIndex + 1]);
} catch {
return segments[extUiIndex + 1];
}
}
return null;
}
function renderSecret(secretName, secretValue) {
const div = document.createElement('div');
div.innerHTML = `
<p><strong>${secretName}</strong></p>
<div class="secret">${secretValue}</div>
`;
output.appendChild(div);
}
function renderError(message) {
const div = document.createElement('div');
div.className = 'error';
div.textContent = `Error: ${message}`;
output.appendChild(div);
}
function signalReady(requestId = '') {
// Signal ready to host using Alga envelope protocol
if (window.parent && window.parent !== window) {
window.parent.postMessage(
{
alga: true,
version: ENVELOPE_VERSION,
type: 'ready',
request_id: requestId,
payload: {}
},
'*' // In production, this should be the specific parent origin
);
}
}
function reportResize() {
const height = output.getBoundingClientRect().height;
if (window.parent && window.parent !== window) {
window.parent.postMessage(
{
alga: true,
version: ENVELOPE_VERSION,
type: 'resize',
payload: { height }
},
'*'
);
}
}
async function fetchAndDisplaySecret() {
try {
// Get extension context from URL parameters
const params = new URLSearchParams(window.location.search);
const extensionId = resolveExtensionId(params);
const tenantId = params.get('tenant') || 'Not available';
if (!extensionId) {
renderError(
'Missing extension context. Ensure the iframe URL includes ?extensionId=<registry-id>.'
);
reportResize();
return;
}
// Display context info
renderSecret('Tenant ID', tenantId);
renderSecret('Extension ID', extensionId);
// Make a request to the API gateway which triggers /v1/execute
// This calls the handler via the runner
const hostOrigin = resolveHostOrigin();
const apiUrl = new URL(`/api/ext/${extensionId}/`, hostOrigin).toString();
console.log(`Making request to ${apiUrl} to trigger handler execution...`);
const response = await fetch(apiUrl, {
method: 'GET',
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
});
let data = null;
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
data = await response.json();
} else {
const text = await response.text();
renderError(
`API Gateway returned ${response.status} with non-JSON payload: ${text.slice(0, 160)}`
);
reportResize();
return;
}
if (!response.ok) {
const detail = data?.error || data?.message || data?.detail || response.statusText;
renderError(`API Gateway returned status ${response.status}: ${detail}`);
console.warn('Secrets demo gateway error', { status: response.status, detail, data });
reportResize();
return;
}
// Display the results from the handler
renderSecret('Message (from secret)', data.message || 'No message');
renderSecret('Handler Response Path', data.path || '/');
// Show config if present
if (data.config && Object.keys(data.config).length > 0) {
renderSecret('Install Config', JSON.stringify(data.config, null, 2));
}
renderSecret('Handler Status', '✓ Successfully executed via /v1/execute');
} catch (err) {
const message =
err instanceof Error ? err.message : typeof err === 'string' ? err : 'Unknown failure';
renderError(message || 'Failed to trigger handler execution');
console.error('Secrets demo request error', err);
}
reportResize();
}
// Listen for bootstrap message from parent (optional, for session token if needed)
window.addEventListener('message', (event) => {
const data = event.data;
if (!data || typeof data !== 'object') return;
if (data.alga !== true || data.version !== ENVELOPE_VERSION) return;
if (data.type === 'bootstrap') {
// Store session token if provided
if (data.payload?.session?.token) {
window.sessionStorage.setItem('alga-ext-session-token', data.payload.session.token);
}
// Optionally fetch data after bootstrap
// fetchAndDisplaySecret();
}
});
// Signal ready when page loads
window.addEventListener('load', () => {
signalReady();
fetchAndDisplaySecret();
});
// Periodically report size changes (for dynamic content)
const resizeObserver = new ResizeObserver(() => reportResize());
resizeObserver.observe(output);