// src/browser/MatrixDSLHost.ts // // FP-36: Declarative DSL Host — Zero-Ceremony Browser Bootstrap // // creates its own NATS transport, // MatrixRuntime, and context hierarchy from HTML attributes — so each // browser app can compose Matrix actors and CQRS views with zero init.ts. // // Two modes: // LOCAL: // FEDERATED: // // Children receive contexts via explicit mounting (not AUTO-PULL, // because this element is not a MatrixActorHtmlElement). import { MatrixRuntime } from '@open-matrix/core'; import { BrowserDomStrategy } from '@open-matrix/core'; import { TopicRouter } from '@open-matrix/core'; import { RequestReply } from '@open-matrix/core'; import { createBrowserNatsTransport } from '@open-matrix/core/transport/createBrowserNatsTransport'; import { ACTOR_CALL_CLIENT, ACTOR_SERVICES, ActorCallClient, ActorServices, RUNTIME_ADDRESS_CONTEXT, SCOPED_INTENT_RESOLVER, ScopedIntentResolver, WELL_KNOWN_SERVICE_BINDINGS, } from '@open-matrix/core'; import type { IMatrixContext } from '@open-matrix/core'; import type { IMatrixRuntime } from '@open-matrix/core'; import type { MatrixAuthorityContext } from '@open-matrix/core'; import type { RuntimeAddressContext } from '@open-matrix/core'; import type { ITransportAdapter } from '@open-matrix/core'; import type { MxEnvelope } from '@open-matrix/core'; import { SESSION_INFO_KEY, FEDERATION_PEER_KEY, type ISessionInfo } from '@open-matrix/core/services/SessionInfoService'; import { HOSTED_RUNTIME_TARGET_CHANGED_EVENT, hostedRuntimeTargetInfoEquals, resolveHostedRuntimeTargetInfo, type IHostedRuntimeTargetInfo, } from './HostedRuntimeTarget.js'; import { resolveHostedRuntimeRecoveryPlan } from './HostedRuntimeRecovery.js'; import { createBrowserProfileSpan, logBrowserProfile, profileBrowserSpan, } from './BrowserProfileTrace.js'; import { mountBrowserRuntimeControl, type BrowserRuntimeLifecycleState, } from './BrowserRuntimeControlActor.js'; import { getOrCreateTabRealm, getOrCreateIdentityTabRoot, isValidRealm, getBrowserRouter, type FederationPeer, type BrowserRouterResult, } from '@open-matrix/federation'; import type { RootContext, FederationMode } from '@open-matrix/core/core/context/RealmContext'; import { createRootContext } from '@open-matrix/core/core/context/RealmContext'; import { COMPONENT_RUNTIME_METADATA, type ComponentRegistrationSource, type ComponentSourceKind, type IComponentRuntimeMetadata, } from '@open-matrix/core'; import type { ComponentArtifactFreshness } from '@open-matrix/core/core/composite/IComponentRuntimeMetadata'; import type { ServiceRegistryEntryKindV1, ServiceRegistryEntryV1, } from '@open-matrix/contracts/service-registry'; import type { RuntimeInventoryEntryV1, RuntimePresenceEventV1, RuntimePresenceRecordV1, } from '@open-matrix/contracts/runtime-presence'; interface IBootstrapTransportPlan { readonly protocolVersion?: 1; readonly kind?: 'nats'; readonly wsMode?: 'same-origin-proxy'; readonly wsPath?: string | null; readonly authMode?: 'jwt' | 'static' | 'anonymous' | 'login-required'; readonly authEndpoint?: string | null; readonly busAuthMode?: 'bus-token' | 'none'; readonly sessionMode?: 'session-cookie' | 'local-client' | 'anonymous' | 'device-login-required' | 'device-session'; } interface IBootstrapValueSources { readonly root?: string; readonly authorityRoot?: string; readonly assetHostRoot?: string; readonly platformServiceRoot?: string; readonly sessionAuthorityRoot?: string; readonly selectedAuthorityRoot?: string; readonly runtimeInstanceRoot?: string; readonly platformRoot?: string; readonly addressRoot?: string; readonly routeKey?: string; readonly publicNamespace?: string; readonly workspaceRealm?: string; readonly browserWsPath?: string; readonly transportAuthMode?: string; readonly busToken?: string; readonly sessionIdentity?: string; readonly federation?: string; } interface IBootstrapFailureDiagnostics { readonly classification: string; readonly hint: string; readonly lines: string[]; } interface INatsWsBootstrapPayload { readonly natsWsUrl?: string; readonly authenticated?: boolean; readonly busToken?: string; readonly authorityContext?: unknown; } function readBootstrapRecord(value: unknown, label: string): Record { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`MatrixDSLHost: /api/bootstrap missing ${label}`); } return value as Record; } function readBootstrapString(value: unknown, field: string): string { if (typeof value !== 'string') { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must be a string`); } const trimmed = value.trim(); if (!trimmed) { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must not be empty`); } return trimmed; } function readBootstrapNullableString(value: unknown, field: string): string | null { if (value === null || value === undefined) { return null; } if (typeof value !== 'string') { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must be a string or null`); } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function readBootstrapBoolean(value: unknown, field: string): boolean { if (typeof value !== 'boolean') { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must be a boolean`); } return value; } function readBootstrapStringArray(value: unknown, field: string): readonly string[] { if (!Array.isArray(value)) { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must be a string array`); } return value.map((item) => { if (typeof item !== 'string') { throw new Error(`MatrixDSLHost: /api/bootstrap authorityContext.${field} must contain only strings`); } return item.trim(); }).filter((item) => item.length > 0); } function parseBootstrapAuthorityContext(value: unknown): MatrixAuthorityContext { const record = readBootstrapRecord(value, 'authorityContext'); return { assetHostRoot: readBootstrapString(record.assetHostRoot, 'assetHostRoot'), platformServiceRoot: readBootstrapString(record.platformServiceRoot, 'platformServiceRoot'), sessionAuthorityRoot: readBootstrapNullableString(record.sessionAuthorityRoot, 'sessionAuthorityRoot'), selectedAuthorityRoot: readBootstrapNullableString(record.selectedAuthorityRoot, 'selectedAuthorityRoot'), runtimeInstanceRoot: readBootstrapNullableString(record.runtimeInstanceRoot, 'runtimeInstanceRoot'), isAuthenticated: readBootstrapBoolean(record.isAuthenticated, 'isAuthenticated'), isPlatformAdmin: readBootstrapBoolean(record.isPlatformAdmin, 'isPlatformAdmin'), selectableAuthorityRoots: readBootstrapStringArray(record.selectableAuthorityRoots, 'selectableAuthorityRoots'), }; } interface IRegistryQueryResponse { readonly entries?: unknown; } interface IRouteKeyWebappRoute { readonly appName: string; readonly routeKey: string; } interface IBrowserRuntimeInventoryEntry { mount: string; type: string; surface: string; runtimeId: string; claimKind: string; description?: string; hasChildren?: boolean; } interface IBrowserRuntimeRegistryState { readonly registryTarget: string; readonly namespaceRoot: string; readonly runtimeId: string; readonly runtimeRoot: string; readonly app: string; readonly contextMount: string; readonly hostBootstrapMode: boolean; readonly sessionMount: string; readonly relativeMount: string; readonly runtimeMount: string; readonly controlMount: string; readonly authorityRoot: string; readonly sessionAuthorityRoot: string; readonly selectedAuthorityRoot: string; readonly runtimeInstanceRoot: string; readonly runtimeHref: string; readonly registeredAt: number; } interface IRuntimeSummaryPayload { readonly authority?: { readonly addressRoot?: string | null; } | null; readonly attachedLeafRoots?: { readonly items?: Array<{ readonly root?: string; readonly connectionKind?: string | null; }>; } | null; } interface IDevicesListResponse { readonly ok?: unknown; readonly devices?: unknown; } interface IDeviceInventoryRecord { readonly authorityRoot?: unknown; readonly status?: unknown; readonly updatedAt?: unknown; readonly lastHeartbeatAt?: unknown; } const HOSTED_SELECTED_LEAF_STORAGE_KEY = 'matrix-web.selected-leaf-root'; const HOSTED_RUNTIME_RECOVERY_NOTICE_ATTR = 'data-hosted-runtime-recovery'; const BROWSER_RUNTIME_ID_PREFIX = 'RUNTIME-BROWSER-'; const HOST_PROFILE_SCOPE = 'host'; type HostedRuntimeTargetSource = 'explicit' | 'auto' | 'none'; type HostedRuntimeTargetKind = 'leaf' | 'authority' | 'unknown'; interface IHostedRuntimeTarget { readonly root: string | null; readonly source: HostedRuntimeTargetSource; readonly kind: HostedRuntimeTargetKind; readonly requestedRoot: string | null; readonly unavailableReason: 'missing' | 'not-live' | 'unauthorized' | 'none'; } interface IHostedRuntimeSummaryState { readonly authorityRoot: string | null; readonly liveLeafRoots: string[]; readonly knownLeafRoots: string[]; } const HOSTED_RUNTIME_TARGET_REFRESH_INTERVAL_MS = 3_000; interface IHostRoutingPeer { readonly hasBackbone: boolean; readonly localRoot: string; readonly localRealm?: string; invoke( target: string, opOrRequest: string | { op: string; payload?: unknown }, payloadOrTimeout?: unknown, timeoutMs?: number, ): Promise; route(topic: string, payload: unknown): Promise; } interface IMatrixChildElementHooks { _receiveContext?: (ctx: IMatrixContext) => Promise; mount?: (ctx: IMatrixContext) => Promise; setContext?: (ctx: IMatrixContext) => void; } interface IMatrixRootContextReceiver { _receiveContext?: (ctx: { rootContext: RootContext; realmContext: RootContext }) => void; } interface IMatrixActorElementVersionSource { _actor?: { children?: { version?: number; }; }; } /** * Options for programmatic bootstrap via `host.bootstrap()`. */ export interface BootstrapOptions { /** Wrap the transport after creation but before runtime/children. */ wrapTransport?: (transport: ITransportAdapter) => ITransportAdapter; /** Override bus root (alternative to setting attribute). */ busRoot?: string; /** Override backbone URL. */ backbone?: string; /** Override mount path. */ mount?: string; } /** * - Self-contained declarative browser bootstrap. * * Creates MatrixRuntime, FederationPeer, and context hierarchy from * HTML attributes. Children (MatrixActorHtmlElement instances) receive * derived contexts and can communicate immediately. * * @example Local-only (in-memory transport, no NATS) * ```html * * * * * ``` * * @example Federated (NATS backbone for cross-root) * ```html * * * * * ``` * * @example Existing init.ts auto-init control * ```html * * * * * ``` */ export class MatrixDSLHost extends HTMLElement { /** Matrix context for this host's children */ private _context: IMatrixContext | null = null; /** Runtime instance (created internally or injected) */ private _runtime: IMatrixRuntime | null = null; /** FederationPeer (only in federated mode) */ private _peer: FederationPeer | null = null; /** Wall-clock time the splash was first shown; used to enforce a minimum * visible duration before fade-out so the brand frame is perceivable * even when bootstrap is sub-100ms (warm cache). */ private _splashShownAt: number | null = null; private _splashHideScheduled = false; /** Router-shaped service exposed to app code. */ private _routingPeer: IHostRoutingPeer | null = null; /** Transport adapter used by the runtime */ private _transport: ITransportAdapter | null = null; /** Initialization flag */ private _initialized = false; /** Mutation observer for light DOM changes */ private _observer: MutationObserver | null = null; /** Root context for federation metadata */ private _rootContext: RootContext | null = null; /** $join subscription cleanup */ private _joinUnsub: (() => void) | null = null; /** Transport wrapper callback (set via bootstrap() or public property) */ private _wrapTransport: ((t: ITransportAdapter) => ITransportAdapter) | null = null; /** Bus token refresh timer (INV-ID-7: 4-min interval, token has 5-min TTL) */ private _busTokenRefreshTimer: ReturnType | null = null; /** Background runtime registry discovery retry timer after initial discovery timeouts. */ private _daemonDiscoveryRetryTimer: ReturnType | null = null; /** Exponential backoff for background runtime registry rediscovery. */ private _daemonDiscoveryRetryDelayMs = 1_000; /** Prevent overlapping runtime registry discovery loops. */ private _daemonDiscoveryInFlight = false; /** Background refresh loop for hosted runtime liveness metadata. */ private _hostedRuntimeTargetRefreshTimer: ReturnType | null = null; /** Prevent overlapping hosted runtime summary polls. */ private _hostedRuntimeTargetRefreshInFlight = false; /** Service Registry lease renewal timer for this browser runtime. */ private _browserRuntimeRenewTimer: ReturnType | null = null; /** Bound beforeunload handler for releasing this browser runtime claim. */ private _browserRuntimeReleaseHandler: (() => void) | null = null; /** Current Service Registry claim state for this browser runtime. */ private _browserRuntimeRegistryState: IBrowserRuntimeRegistryState | null = null; /** Component IDs currently claimed for this browser runtime in system.registry. */ private _browserRuntimeClaimedComponentIds = new Set(); /** Current browser runtime id, used to refresh inventory after dynamic child changes. */ private _runtimeRegistryId = ''; /** Browser runtime control actor start time for stable runtime status. */ private _runtimeControlStartedAt = ''; /** Debounce timer for child-driven registry claim refreshes. */ private _runtimeInventoryClaimRefreshTimer: ReturnType | null = null; /** Increments whenever cleanup invalidates async registration continuations. */ private _lifecycleGeneration = 0; /** Last reported host child version for runtime inventory refreshes. */ private _runtimeInventoryVersion = -1; /** URL param keys to read (when url-params attribute is present) */ private static readonly URL_PARAM_MAP: Record = { root: 'root', realm: 'root', // ?realm= maps to root attr backbone: 'backbone', backboneUrl: 'backbone', mount: 'mount', daemonRoot: 'daemon-root', daemonRealm: 'daemon-root', addressRoot: 'data-address-root', }; /** Raw adapter before wrapping (for getWireTopicInfo etc.) */ private _rawAdapter: ITransportAdapter | null = null; /** Identity state from bootstrap */ private _principal: { principalId: string; displayName?: string; email?: string; tier?: string } | null = null; private _activeOrg: { orgId: string; displayName: string; role: string } | null = null; private _isAuthenticated = false; /** Promise that resolves when initialization is complete */ readonly ready: Promise; private _resolveReady!: () => void; private _bootstrapTransportPlan: IBootstrapTransportPlan | null = null; static get observedAttributes(): string[] { return ['mount', 'auto-init', 'root', 'realm', 'federation', 'backbone', 'identity-provider', 'client-id', 'auto-attach']; } constructor() { super(); this.ready = new Promise(resolve => { this._resolveReady = resolve; }); } private _deriveNatsWsTransportPlan(bootstrap: INatsWsBootstrapPayload): IBootstrapTransportPlan | null { const rawWsUrl = typeof bootstrap.natsWsUrl === 'string' ? bootstrap.natsWsUrl.trim() : ''; if (!rawWsUrl) { return null; } let wsPath = '/nats-ws'; try { const parsed = new URL(rawWsUrl, window.location.href); if (parsed.pathname && parsed.pathname !== '/') { wsPath = parsed.pathname; } } catch { const match = rawWsUrl.match(/^wss?:\/\/[^/]+(\/.*)$/i); if (match?.[1]) { wsPath = match[1]; } } return { kind: 'nats', wsMode: 'same-origin-proxy', wsPath, busAuthMode: bootstrap.busToken ? 'bus-token' : 'none', sessionMode: bootstrap.authenticated ? 'session-cookie' : 'anonymous', }; } // ═══════════════════════════════════════════════════════════════════════════ // DOM Lifecycle // ═══════════════════════════════════════════════════════════════════════════ connectedCallback(): void { const autoInit = this.getAttribute('auto-init'); if (autoInit !== 'false') { this._bootstrapAsync(); } } disconnectedCallback(): void { this._cleanup(); } attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void { if (oldValue === newValue) return; if (name === 'mount' && this._initialized) { this._cleanup(); this._bootstrapAsync(); } } // ═══════════════════════════════════════════════════════════════════════════ // Public API // ═══════════════════════════════════════════════════════════════════════════ /** The FederationPeer (null in local-only mode). */ get peer(): FederationPeer | null { return this._peer; } /** The MatrixRuntime instance. */ get runtime(): IMatrixRuntime | null { return this._runtime; } /** The host's Matrix context (children derive from this). */ get context(): IMatrixContext | null { return this._context; } /** The root context for federation metadata. */ get rootContext(): RootContext | null { return this._rootContext; } get realmContext(): RootContext | null { return this._rootContext; } /** The transport adapter (possibly wrapped). */ get transport(): ITransportAdapter | null { return this._transport; } /** The raw (unwrapped) transport adapter (for getWireTopicInfo etc.). */ get rawAdapter(): ITransportAdapter | null { return this._rawAdapter; } /** Whether backbone is connected (false in local-only or before bootstrap). */ get hasBackbone(): boolean { return this._routingPeer?.hasBackbone ?? false; } /** Authenticated principal from bootstrap (null if anonymous). */ get principal(): { principalId: string; displayName?: string; email?: string; tier?: string } | null { return this._principal; } /** Active org context (null if not in an org). */ get activeOrg(): { orgId: string; displayName: string; role: string } | null { return this._activeOrg; } /** Subscription tier (from principal or 'anonymous'). */ get tier(): string { return this._principal?.tier ?? 'anonymous'; } /** Whether the session is authenticated. */ get isAuthenticated(): boolean { return this._isAuthenticated; } /** * Set a transport wrapper BEFORE bootstrap. * The wrapper is applied after transport creation but before runtime/children. * This is how apps intercept all pub/sub (e.g., for logging panels). */ set wrapTransport(fn: ((t: ITransportAdapter) => ITransportAdapter) | null) { this._wrapTransport = fn; } /** * Programmatic bootstrap with options. * Use when auto-init="false" and you need to configure before bootstrap. */ async bootstrap(options?: BootstrapOptions): Promise { if (this._initialized) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn('[MatrixDSLHost] Already initialized'); return; } // Apply option overrides to attributes. if (options?.busRoot) this.setAttribute('bus-root', options.busRoot); if (options?.backbone) this.setAttribute('backbone', options.backbone); if (options?.mount !== undefined) this.setAttribute('mount', options.mount); // Store transport wrapper (option overrides property setter) if (options?.wrapTransport) { this._wrapTransport = options.wrapTransport; } await this._bootstrapAsync(); } /** * Initialize with an externally-created runtime. * Use this when auto-init="false" and you have an existing runtime. */ async initialize(runtime: IMatrixRuntime, context?: IMatrixContext): Promise { if (this._initialized) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn('[MatrixDSLHost] Already initialized'); return; } this._runtime = runtime; const mountPath = this.getAttribute('mount') ?? this.id ?? ''; const rootContext = runtime.getRootContext?.(); if (!rootContext && !context) { throw new Error('[MatrixDSLHost] Runtime has no getRootContext() and no context provided'); } this._context = context ?? (mountPath ? rootContext!.derive(mountPath) : rootContext!); // Install $join responder and mount children this._installJoinResponder(); await this._mountChildren(); this._setupMutationObserver(); this._parseRootAttributes(); this._propagateRootToChildren(); this._initialized = true; this._resolveReady(); this._fireReadyEvent(); } // ═══════════════════════════════════════════════════════════════════════════ // Self-Bootstrap (FP-36 Phase 1) // ═══════════════════════════════════════════════════════════════════════════ /** * Async self-bootstrap: creates runtime + transport from attributes. */ private async _bootstrapAsync(): Promise { const explicitBusRootAttr = this.getAttribute('bus-root'); let hostBootstrapMode = this._isHostBootstrapMode(); const routeKeyRoute = this._parseRouteKeyWebappRouteFromLocation(); const requireDaemonBootstrap = this._shouldRequireDaemonBootstrap() || routeKeyRoute !== null; delete this.dataset.explicitAuthorityRoot; const hadExplicitDaemonTarget = this.hasAttribute('daemon-root') || this.hasAttribute('daemon-realm') || routeKeyRoute !== null; const initialUrlParams = this.hasAttribute('url-params') ? new URLSearchParams(window.location.search) : null; const hasExplicitDaemonTargetParam = initialUrlParams?.has('daemonRoot') || initialUrlParams?.has('daemonRealm') || false; const hostedAppName = this._deriveHostedAppName(); const finishBootstrapProfile = createBrowserProfileSpan(HOST_PROFILE_SCOPE, 'bootstrapAsync', { hostedApp: hostedAppName ?? '', requireDaemonBootstrap, hasExplicitDaemonTarget: hadExplicitDaemonTarget || hasExplicitDaemonTargetParam, }); try { this.dataset.status = 'initializing'; this._manageLoadingElement('initializing'); let discoveredNats = false; let bootstrapAuthorityRoot = ''; let bootstrapPublicNamespace = ''; let bootstrapRouteKey = ''; let renderNamespaceSetupLocally = false; let loginRequiredBootstrap = false; // Phase A: Transport coordinate discovery. // Daemon-served apps must declare their path through /api/bootstrap. let usedBootstrap = false; try { const bootstrapEndpoint = this._bootstrapEndpointForRoute(routeKeyRoute, hostedAppName); const bootstrapResp = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'api.bootstrap', async () => fetch(bootstrapEndpoint, { credentials: 'same-origin' }), { hostedApp: hostedAppName ?? '', required: requireDaemonBootstrap, }, ); if (!bootstrapResp.ok) { if (requireDaemonBootstrap) { throw new Error(`Hosted app requires /api/bootstrap (HTTP ${bootstrapResp.status})`); } } else { const bootstrap = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'api.bootstrap.parse', async () => bootstrapResp.json() as Promise<{ natsWsUrl?: string; root?: string; natsToken?: string; busToken?: string; authenticated?: boolean; principal?: { principalId: string; displayName?: string; email?: string; tier?: string }; activeOrg?: { orgId: string; displayName: string; role: string }; addressRoot?: string; authorityRoot?: string; assetHostRoot?: string; platformServiceRoot?: string; sessionAuthorityRoot?: string; selectedAuthorityRoot?: string; runtimeInstanceRoot?: string; authorityContext?: unknown; selectableAuthorityRoots?: string[]; isPlatformAdmin?: boolean; routeKey?: string; publicNamespace?: string; spaceId?: string; platformRoot?: string; busRoot?: string; appName?: string; role?: string; workspaceRealm?: string; routeKind?: string; login?: { provider?: string; startUrl?: string }; sources?: IBootstrapValueSources; transportPlan?: IBootstrapTransportPlan; topologyKind?: string; nodeRoles?: unknown; federation?: { attached?: unknown; hubUrl?: unknown; hubKind?: unknown; }; shellAccessMode?: string; appLinkMode?: string; }>, { hostedApp: hostedAppName ?? '', }, ); const authorityContext = parseBootstrapAuthorityContext(bootstrap.authorityContext); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info(`[MatrixDSLHost] /api/bootstrap → selectedAuthorityRoot="${authorityContext.selectedAuthorityRoot ?? ''}", runtimeInstanceRoot="${authorityContext.runtimeInstanceRoot ?? ''}", wsUrl="${bootstrap.natsWsUrl}", authenticated=${authorityContext.isAuthenticated}`); // Store identity state from bootstrap this._isAuthenticated = authorityContext.isAuthenticated; this.dataset.authenticated = String(this._isAuthenticated); if (bootstrap.principal) { this._principal = bootstrap.principal; this.dataset.principalId = bootstrap.principal.principalId; if (bootstrap.principal.displayName) { this.dataset.displayName = bootstrap.principal.displayName; } } if (bootstrap.activeOrg) { this._activeOrg = bootstrap.activeOrg; } const platformServiceRoot = authorityContext.platformServiceRoot; const assetHostRoot = authorityContext.assetHostRoot; const sessionAuthorityRoot = authorityContext.sessionAuthorityRoot ?? ''; const selectedAuthorityRoot = authorityContext.selectedAuthorityRoot ?? ''; const runtimeInstanceRoot = authorityContext.runtimeInstanceRoot ?? ''; this._setOptionalDatasetValue('assetHostRoot', assetHostRoot); this._setOptionalDatasetValue('platformServiceRoot', platformServiceRoot); this._setOptionalDatasetValue('sessionAuthorityRoot', sessionAuthorityRoot); this._setOptionalDatasetValue('selectedAuthorityRoot', selectedAuthorityRoot); this._setOptionalDatasetValue('runtimeInstanceRoot', runtimeInstanceRoot); delete this.dataset.addressRoot; const selectableAuthorityRoots = authorityContext.selectableAuthorityRoots; this._setOptionalDatasetValue('selectableAuthorityRoots', selectableAuthorityRoots.join(',')); this.dataset.isPlatformAdmin = authorityContext.isPlatformAdmin ? 'true' : 'false'; if (authorityContext.isAuthenticated && sessionAuthorityRoot) { this.dataset.principalAuthorityRoot = sessionAuthorityRoot; } else { delete this.dataset.principalAuthorityRoot; } bootstrapAuthorityRoot = selectedAuthorityRoot; bootstrapPublicNamespace = (bootstrap.publicNamespace || '').trim(); bootstrapRouteKey = (bootstrap.routeKey || routeKeyFromPublicNamespace(bootstrapPublicNamespace) || '').trim(); const bootstrapSpaceId = (bootstrap.spaceId || '').trim(); this._setOptionalDatasetValue('routeKey', bootstrapRouteKey); this._setOptionalDatasetValue('publicNamespace', bootstrapPublicNamespace); this._setOptionalDatasetValue('spaceId', bootstrapSpaceId); const bootstrapAppName = typeof bootstrap.appName === 'string' ? bootstrap.appName.trim() : ''; if (bootstrapAppName) { this.setAttribute('app-name', bootstrapAppName); } const bootstrapAttributeAuthorityRoot = sessionAuthorityRoot || selectedAuthorityRoot; if (bootstrapAttributeAuthorityRoot) { this.setAttribute('authority-root', bootstrapAttributeAuthorityRoot); } const bootstrapPlatformRoot = platformServiceRoot; renderNamespaceSetupLocally = hostedAppName === 'web' && authorityContext.isAuthenticated && !bootstrapPublicNamespace && !bootstrapRouteKey && bootstrapAuthorityRoot === bootstrapPlatformRoot; if (renderNamespaceSetupLocally) { this.dataset.namespaceSetupMode = 'local'; } else { delete this.dataset.namespaceSetupMode; } if (bootstrap.workspaceRealm) { this.dataset.workspaceRealm = bootstrap.workspaceRealm; } this._setOptionalDatasetValue('routeKind', typeof bootstrap.routeKind === 'string' ? bootstrap.routeKind : ''); this._setOptionalDatasetValue('topologyKind', typeof bootstrap.topologyKind === 'string' ? bootstrap.topologyKind : ''); const authorityCoordinatorActive = bootstrapNodeRoleActive(bootstrap.nodeRoles, 'authorityCoordinator'); if (typeof authorityCoordinatorActive === 'boolean') { this.dataset.authorityCoordinatorActive = String(authorityCoordinatorActive); } else { delete this.dataset.authorityCoordinatorActive; } this._setOptionalDatasetValue('shellAccessMode', typeof bootstrap.shellAccessMode === 'string' ? bootstrap.shellAccessMode : ''); this._setOptionalDatasetValue('appLinkMode', typeof bootstrap.appLinkMode === 'string' ? bootstrap.appLinkMode : ''); if (bootstrap.federation && typeof bootstrap.federation === 'object') { this.dataset.federationAttached = bootstrap.federation.attached === true ? 'true' : 'false'; this._setOptionalDatasetValue( 'federationHubUrl', typeof bootstrap.federation.hubUrl === 'string' ? bootstrap.federation.hubUrl : '', ); this._setOptionalDatasetValue( 'federationHubKind', typeof bootstrap.federation.hubKind === 'string' ? bootstrap.federation.hubKind : '', ); } else { delete this.dataset.federationAttached; delete this.dataset.federationHubUrl; delete this.dataset.federationHubKind; } this._setOptionalDatasetValue('loginProvider', bootstrap.login?.provider ?? ''); this._setOptionalDatasetValue('loginStartUrl', bootstrap.login?.startUrl ?? ''); const transportPlan = bootstrap.transportPlan ?? this._deriveNatsWsTransportPlan(bootstrap); this._bootstrapTransportPlan = transportPlan ?? null; loginRequiredBootstrap = transportPlan?.authMode === 'login-required'; if (loginRequiredBootstrap) { this.dataset.status = 'login-required'; this.dataset.daemonStatus = 'skipped'; } usedBootstrap = true; const bootstrapRole = typeof bootstrap.role === 'string' ? bootstrap.role.trim().toLowerCase() : ''; const isLocalClientSession = transportPlan?.sessionMode === 'local-client' || bootstrap.sources?.sessionIdentity === 'local-client'; if (isLocalClientSession && !hostBootstrapMode) { hostBootstrapMode = true; this.setAttribute('bootstrap-mode', 'host'); } if (hostBootstrapMode && bootstrapRole === 'platform' && hostedAppName && hostedAppName !== 'web') { hostBootstrapMode = false; this.setAttribute('bootstrap-mode', 'hosted'); } // NATS subject scope = busRoot (Host's transport.root). // Ambiguous bootstrap fields are intentionally not read here. const busRoot = (bootstrap.busRoot || platformServiceRoot).trim(); if (busRoot) { this.setAttribute('bus-root', busRoot); if (renderNamespaceSetupLocally) { this.removeAttribute('daemon-root'); this.removeAttribute('daemon-realm'); } else { this.setAttribute('daemon-realm', busRoot); } if (hostBootstrapMode) { this._ensureHostRuntimeMount(busRoot, hostedAppName); } if (!renderNamespaceSetupLocally && !hadExplicitDaemonTarget && !hasExplicitDaemonTargetParam) { this.setAttribute('daemon-root', busRoot); } this.dataset.platformRoot = platformServiceRoot || busRoot; } discoveredNats = true; this.setAttribute('transport', 'nats'); if (transportPlan) { const wsPath = transportPlan.wsPath ?? '/nats-ws'; const localOnlyAnonymousSession = transportPlan.authMode === 'anonymous' && transportPlan.sessionMode === 'anonymous'; if (transportPlan.authMode === 'login-required' || localOnlyAnonymousSession) { this.removeAttribute('nats-ws-url'); this.removeAttribute('backbone'); delete this.dataset.natsWsUrl; delete this.dataset.natsAuthEndpoint; delete this.dataset.busToken; } else { const sameOriginWsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}${wsPath}`; this.setAttribute('nats-ws-url', sameOriginWsUrl); this.setAttribute('backbone', sameOriginWsUrl); } if (transportPlan.authMode) { this.dataset.natsAuthMode = transportPlan.authMode; } if (transportPlan.authEndpoint && transportPlan.authMode !== 'login-required') { this.dataset.natsAuthEndpoint = transportPlan.authEndpoint; } else { delete this.dataset.natsAuthEndpoint; } if (transportPlan.busAuthMode) { this.dataset.busAuthMode = transportPlan.busAuthMode; } if (transportPlan.sessionMode) { this.dataset.sessionMode = transportPlan.sessionMode; } // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info( `[MatrixDSLHost] transport plan → wsMode="${transportPlan.wsMode ?? 'same-origin-proxy'}", wsPath="${wsPath ?? 'none'}", authMode="${transportPlan.authMode ?? 'unknown'}", busAuthMode="${transportPlan.busAuthMode ?? 'unknown'}", sessionMode="${transportPlan.sessionMode ?? 'unknown'}"`, ); } else { throw new Error('Hosted bootstrap did not declare transportPlan'); } if (bootstrap.sources) { this.dataset.rootSource = bootstrap.sources.root ?? ''; this.dataset.addressRootSource = bootstrap.sources.addressRoot ?? ''; this.dataset.selectedAuthorityRootSource = bootstrap.sources.selectedAuthorityRoot ?? ''; this.dataset.runtimeInstanceRootSource = bootstrap.sources.runtimeInstanceRoot ?? ''; this.dataset.transportAuthSource = bootstrap.sources.transportAuthMode ?? ''; this.dataset.busTokenSource = bootstrap.sources.busToken ?? ''; // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info( `[MatrixDSLHost] bootstrap sources → root="${bootstrap.sources.root ?? 'unknown'}", addressRoot="${bootstrap.sources.addressRoot ?? 'unknown'}", wsPath="${bootstrap.sources.browserWsPath ?? 'unknown'}", auth="${bootstrap.sources.transportAuthMode ?? 'unknown'}", busToken="${bootstrap.sources.busToken ?? 'unknown'}", identity="${bootstrap.sources.sessionIdentity ?? 'unknown'}"`, ); } if (bootstrap.natsToken) { this.dataset.natsToken = bootstrap.natsToken; } // Store bus token in memory only (INV-ID-7: never localStorage/cookies) if (bootstrap.busToken && !renderNamespaceSetupLocally) { this.dataset.busToken = bootstrap.busToken; } } } catch (error) { if (requireDaemonBootstrap) { throw error; } } // 0b. URL param overrides (when url-params attribute is present) // URL params take highest priority, overriding even Host bootstrap config. if (this.hasAttribute('url-params')) { const urlParams = new URLSearchParams(window.location.search); for (const [paramKey, attrName] of Object.entries(MatrixDSLHost.URL_PARAM_MAP)) { const value = urlParams.get(paramKey); if (value) this.setAttribute(attrName, value); } } if ( usedBootstrap && hostedAppName && hostedAppName !== 'web' && routeKeyRoute !== null && !this._isAuthenticated && !loginRequiredBootstrap ) { this._redirectHostedAppToLogin(); return; } if ( usedBootstrap && hostedAppName && hostedAppName !== 'web' && !hostBootstrapMode && hasExplicitDaemonTargetParam ) { await this._applyExplicitHostedRuntimeTarget('bootstrap-explicit'); } if (routeKeyRoute && !hasExplicitDaemonTargetParam) { const routeKeyMatchesBootstrap = !bootstrapRouteKey || bootstrapRouteKey === routeKeyRoute.routeKey; const authorityRoot = routeKeyMatchesBootstrap ? bootstrapAuthorityRoot || (this.dataset.selectedAuthorityRoot ?? '').trim() : ''; if (authorityRoot) { this.setAttribute('daemon-root', authorityRoot); this.setAttribute('daemon-realm', authorityRoot); this.dataset.selectedAuthorityRoot = authorityRoot; this.dataset.explicitAuthorityRoot = authorityRoot; this._applyHostedRuntimeTargetMetadata( authorityRoot, 'explicit', 'authority', authorityRoot, ); } } if ( usedBootstrap && hostedAppName && hostedAppName !== 'web' && !hostBootstrapMode && routeKeyRoute === null ) { if (!this._isAuthenticated && !loginRequiredBootstrap) { this._redirectHostedAppToLogin(); return; } if (hadExplicitDaemonTarget && !hasExplicitDaemonTargetParam) { await this._applyExplicitHostedRuntimeTarget('bootstrap-explicit'); } } if ( usedBootstrap && hostedAppName && hostedAppName !== 'web' && !hostBootstrapMode && !hadExplicitDaemonTarget && !hasExplicitDaemonTargetParam ) { const runtimeTarget = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'runtimeTarget.auto', async () => this._resolveHostedUserRuntimeTarget(), { hostedApp: hostedAppName ?? '', }, ); this._applyHostedRuntimeTargetMetadata( runtimeTarget.root, runtimeTarget.source, runtimeTarget.kind, runtimeTarget.requestedRoot, runtimeTarget.unavailableReason, ); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info( `[MatrixDSLHost] hosted app runtime target → ${runtimeTarget.root || '(none)'} ` + `(source=${runtimeTarget.source}, kind=${runtimeTarget.kind}, unavailable=${runtimeTarget.unavailableReason})`, ); } // 1. Determine mode from explicit four-roots attributes. const busRootAttr = this.getAttribute('bus-root'); const backboneUrl = this.getAttribute('backbone'); const natsWsAttr = this.getAttribute('nats-ws-url'); const natsWsUrl = natsWsAttr ?? (!discoveredNats ? backboneUrl : null); const mountPath = this.getAttribute('mount') ?? this.id ?? ''; const hasNatsBackbone = Boolean(busRootAttr && natsWsUrl); const anonymousLocalBootstrap = this._bootstrapTransportPlan?.authMode === 'anonymous' && this._bootstrapTransportPlan.sessionMode === 'anonymous'; if (requireDaemonBootstrap && !renderNamespaceSetupLocally && !loginRequiredBootstrap && !anonymousLocalBootstrap && (!usedBootstrap || !hasNatsBackbone)) { throw new Error('Hosted app bootstrap did not provide complete transport coordinates'); } let selectedBackboneUrl: string | undefined; // 2. Create transport + optional peer if (busRootAttr && natsWsUrl) { // FEDERATED MODE: Connect to the NATS backbone when transport coordinates exist. // _bootstrapFederated() already handles both authenticated JWT flows and // unauthenticated direct websocket connections. await profileBrowserSpan( HOST_PROFILE_SCOPE, 'bootstrapFederated', async () => this._bootstrapFederated(busRootAttr, natsWsUrl, mountPath), { root: busRootAttr, mountPath, }, ); selectedBackboneUrl = natsWsUrl; } else if (busRootAttr) { // ROOT-LOCAL MODE: Anonymous users or no backbone URL. // Uses in-memory transport — actor lifecycle works normally, // onReady fires, pages render. No NATS permission errors. this._bootstrapRootLocal(busRootAttr, mountPath); } else { // LOCAL MODE: plain InMemoryBroker, no federation this._bootstrapLocal(mountPath); } const rootCtx = this._runtime!.getRootContext!(); // 2b. Register context services for child components const routingPeer = this._routingPeer; const sessionRoot = (this._transport?.root ?? this._transport?.realm) ?? busRootAttr ?? ''; const hasBackbone = routingPeer?.hasBackbone ?? false; this.dataset.sessionRoot = sessionRoot; this.dataset.runtimeRoot = sessionRoot; this.dataset.runtimeInstanceRoot = sessionRoot; rootCtx.setService('runtime-instance-root', sessionRoot); if (routingPeer) { rootCtx.setService(SESSION_INFO_KEY, { mode: 'router', root: sessionRoot, realm: sessionRoot, hasBackbone, backboneUrl: selectedBackboneUrl, backboneError: hasBackbone ? null : 'Connection failed or not attempted', } as ISessionInfo); rootCtx.setService(FEDERATION_PEER_KEY, routingPeer); } else { rootCtx.setService(SESSION_INFO_KEY, { mode: 'local', root: sessionRoot, realm: sessionRoot, hasBackbone: false, } as ISessionInfo); } // 2c. Register identity services on root context for child actors const daemonRoot = this.getAttribute('daemon-root') ?? this.getAttribute('daemon-realm'); if (daemonRoot) { rootCtx.setService('daemon-root', daemonRoot); } if (this.dataset.platformRoot) { rootCtx.setService('platform-root', this.dataset.platformRoot); } if (this.dataset.assetHostRoot) { rootCtx.setService('asset-host-root', this.dataset.assetHostRoot); } if (this.dataset.platformServiceRoot) { rootCtx.setService('platform-service-root', this.dataset.platformServiceRoot); } if (this.dataset.sessionAuthorityRoot) { rootCtx.setService('session-authority-root', this.dataset.sessionAuthorityRoot); } if (this.dataset.selectedAuthorityRoot) { rootCtx.setService('selected-authority-root', this.dataset.selectedAuthorityRoot); } rootCtx.setService('is-platform-admin', this.dataset.isPlatformAdmin === 'true'); const authorityRootService = this.dataset.selectedAuthorityRoot || this.dataset.sessionAuthorityRoot; if (authorityRootService) { rootCtx.setService('authority-root', authorityRootService); } if (this.dataset.principalId) { rootCtx.setService('principal-id', this.dataset.principalId); } if (this.dataset.busToken) { rootCtx.setService('bus-token', this.dataset.busToken); } this._installScopedActorServices(rootCtx, sessionRoot); if (this.dataset.orgId) { rootCtx.setService('org-id', this.dataset.orgId); } if (this.dataset.workspaceId) { rootCtx.setService('workspace-id', this.dataset.workspaceId); } // 3. Install $join responder so children can join this._installJoinResponder(); // 4. Mount all light DOM children with derived contexts await profileBrowserSpan( HOST_PROFILE_SCOPE, 'mountChildren', async () => this._mountChildren(), { childCount: this.children.length, }, ); // 5. Set up mutation observer for dynamic children this._setupMutationObserver(); // 6. Parse root attributes for RootContext propagation this._parseRootAttributes(); this._propagateRootToChildren(); // 7. Done this._initialized = true; const status = loginRequiredBootstrap ? 'login-required' : (routingPeer?.hasBackbone || hasNatsBackbone) ? 'connected' : 'local'; this.dataset.status = status; this.dataset.daemonStatus = loginRequiredBootstrap ? 'skipped' : this.getAttribute('daemon-root') ? 'discovering' : 'skipped'; this._manageLoadingElement(status); this._resolveReady(); this._fireReadyEvent(); const skipHostedShellDaemonDiscovery = this._shouldSkipHostedShellDaemonDiscovery( hadExplicitDaemonTarget || hasExplicitDaemonTargetParam, ); // Dispatch auth state event for app-level code this.dispatchEvent(new CustomEvent( this._isAuthenticated ? 'matrix-dsl-authenticated' : 'matrix-dsl-anonymous', { bubbles: true, detail: { principal: this._principal, activeOrg: this._activeOrg } }, )); if (loginRequiredBootstrap) { this.dispatchEvent(new CustomEvent('matrix-host-login-required', { bubbles: true, composed: true, detail: { routeKind: this.dataset.routeKind ?? '', sessionMode: this.dataset.sessionMode ?? '', provider: this.dataset.loginProvider ?? '', startUrl: this.dataset.loginStartUrl ?? '', }, })); if (this._shouldRedirectToDeviceLogin(hostedAppName)) { this._redirectToDeviceLogin(); } else if (hostedAppName !== 'web') { this._redirectToPlatformLogin(); } return; } // Non-blocking — children are already mounted and can observe late identity resolution. logBrowserProfile(HOST_PROFILE_SCOPE, 'phaseB.scheduled', { usedBootstrap, skipped: renderNamespaceSetupLocally, }); if (!renderNamespaceSetupLocally) { void this._runPhaseBBusDiscovery(usedBootstrap); } else { console.info('[MatrixDSLHost] Phase B service probe skipped for namespace setup — awaiting route-key claim'); } // Non-blocking — must run after _fireReadyEvent() so child app subscriptions are live logBrowserProfile(HOST_PROFILE_SCOPE, 'daemonDiscovery.scheduled', { daemonRoot: this.getAttribute('daemon-root') ?? '', skipped: renderNamespaceSetupLocally || skipHostedShellDaemonDiscovery, }); if (!renderNamespaceSetupLocally && !skipHostedShellDaemonDiscovery) { void this._discoverDaemon(); } else { this.dataset.daemonStatus = 'skipped'; const skippedDaemonRoot = this.getAttribute('daemon-root') ?? this.getAttribute('daemon-realm') ?? ''; rootCtx.transport.publish(TopicRouter.events(''), { op: 'daemonReady', payload: { root: skippedDaemonRoot, realm: skippedDaemonRoot, skipped: true, namespaceSetup: renderNamespaceSetupLocally, platformShell: skipHostedShellDaemonDiscovery, }, }); } if (this._shouldRefreshHostedRuntimeTarget()) { this._startHostedRuntimeTargetRefresh(); void this._refreshHostedRuntimeTargetMetadata(); } } catch (err) { const diagnostics = await this._diagnoseBootstrapFailure(err); const error = err instanceof Error ? err : new Error(String(err)); error.message = [error.message, '', ...diagnostics.lines].join('\n'); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.error( requireDaemonBootstrap ? '[MatrixDSLHost] Hosted bootstrap failed — refusing local substitution:' : '[MatrixDSLHost] Bootstrap failed — offline local substitution disabled:', error, ); this.dataset.errorClass = diagnostics.classification; this.dataset.status = 'disconnected'; this.dataset.daemonStatus = 'error'; this.dataset.status = 'error'; this._manageLoadingElement('error', error); this.dispatchEvent(new CustomEvent('matrix-dsl-error', { bubbles: true, detail: { error, classification: diagnostics.classification, hint: diagnostics.hint }, })); } finally { finishBootstrapProfile({ status: this.dataset.status ?? '', daemonStatus: this.dataset.daemonStatus ?? '', }); } } private _readHostedSelectedLeafRoot(): string | null { if (typeof window === 'undefined') { return null; } const stores: Array = [window.sessionStorage, window.localStorage]; for (const store of stores) { const raw = store?.getItem(HOSTED_SELECTED_LEAF_STORAGE_KEY); if (typeof raw !== 'string') { continue; } const normalized = raw.trim(); if (normalized.length > 0) { return normalized; } } return null; } private _sortRuntimeRootsForDefault(authorityRoot: string | null, roots: string[]): string[] { const normalizedAuthority = typeof authorityRoot === 'string' && authorityRoot.trim().length > 0 ? authorityRoot.trim() : null; const filtered = normalizedAuthority ? roots.filter((root) => root === normalizedAuthority || root.startsWith(`${normalizedAuthority}.`)) : roots.slice(); const candidates = normalizedAuthority ? filtered : roots.slice(); return candidates.sort((left, right) => { const leftSegments = left.split('.').length; const rightSegments = right.split('.').length; if (leftSegments !== rightSegments) { return leftSegments - rightSegments; } return left.localeCompare(right); }); } private _classifyHostedRuntimeTarget( root: string | null, authorityRoot: string | null, attachedLeafRoots: string[], ): HostedRuntimeTargetKind { if (!root) { return 'unknown'; } if (authorityRoot && root === authorityRoot) { return 'authority'; } if (attachedLeafRoots.includes(root)) { return 'leaf'; } if (authorityRoot && root.startsWith(`${authorityRoot}.`)) { return 'leaf'; } return 'unknown'; } private _authenticatedPrincipalAuthorityRoot(): string | null { if (!this._isAuthenticated) { return null; } const principalRoot = (this.dataset.principalAuthorityRoot ?? '').trim(); if (principalRoot) { return principalRoot; } const sessionRoot = (this.dataset.sessionAuthorityRoot ?? '').trim(); return sessionRoot || null; } private _hostedAuthorityRootFromBootstrap(): string { const selectedRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const principalRoot = this._authenticatedPrincipalAuthorityRoot(); return selectedRoot || principalRoot || ''; } private _parseHostedRuntimeSummary(summary: IRuntimeSummaryPayload): IHostedRuntimeSummaryState { const authorityRoot = this._hostedAuthorityRootFromBootstrap(); const items = Array.isArray(summary.attachedLeafRoots?.items) ? summary.attachedLeafRoots.items : []; const knownLeafRoots = [...new Set( items .map((item) => typeof item?.root === 'string' ? item.root.trim() : '') .filter((root) => root.length > 0), )]; const liveLeafRoots = [...new Set( items .filter((item) => { const connectionKind = typeof item?.connectionKind === 'string' ? item.connectionKind.trim() : ''; return connectionKind.length === 0 || connectionKind !== 'registered'; }) .map((item) => typeof item?.root === 'string' ? item.root.trim() : '') .filter((root) => root.length > 0), )]; return { authorityRoot: authorityRoot || null, liveLeafRoots, knownLeafRoots, }; } private _readHostedRuntimeTargetInfo(): IHostedRuntimeTargetInfo { return resolveHostedRuntimeTargetInfo(this); } private _setOptionalDatasetValue(name: string, value: string): void { if (value) { this.dataset[name] = value; } else { delete this.dataset[name]; } } private _initialRuntimeSummaryState(): IHostedRuntimeSummaryState { return { authorityRoot: typeof this.dataset.selectedAuthorityRoot === 'string' ? this.dataset.selectedAuthorityRoot.trim() || null : null, liveLeafRoots: [], knownLeafRoots: [], }; } private async _applyExplicitHostedRuntimeTarget(reason: string): Promise { const explicitRuntimeTarget = ( this.getAttribute('daemon-root') ?? this.getAttribute('daemon-realm') ?? '' ).trim(); if (!explicitRuntimeTarget) { return; } if (!this._context) { const summaryState = this._initialRuntimeSummaryState(); const runtimeTarget = this._resolveHostedRequestedRuntimeTarget( explicitRuntimeTarget, summaryState, 'explicit', ); const authorizedInitialRoot = runtimeTarget.root ?? this._authorizedExplicitRootBeforeRuntimeSummary( explicitRuntimeTarget, summaryState.authorityRoot, runtimeTarget.unavailableReason, ); this._applyHostedRuntimeTargetMetadata( authorizedInitialRoot, runtimeTarget.source, authorizedInitialRoot ? this._classifyHostedRuntimeTarget( authorizedInitialRoot, summaryState.authorityRoot, summaryState.liveLeafRoots, ) : runtimeTarget.kind, runtimeTarget.requestedRoot, authorizedInitialRoot ? 'none' : runtimeTarget.unavailableReason, ); return; } let summaryState = this._initialRuntimeSummaryState(); try { const summary = await this._loadHostedRuntimeSummaryFromBus(reason); if (summary) { summaryState = this._parseHostedRuntimeSummary(summary); } } catch { // Preserve the explicit requested target in metadata, but do not promote // it to the actual runtime root when live runtime inventory is missing. } const runtimeTarget = this._resolveHostedRequestedRuntimeTarget( explicitRuntimeTarget, summaryState, 'explicit', ); this._applyHostedRuntimeTargetMetadata( runtimeTarget.root, runtimeTarget.source, runtimeTarget.kind, runtimeTarget.requestedRoot, runtimeTarget.unavailableReason, ); } private _applyHostedRuntimeTargetMetadata( root: string | null, source: HostedRuntimeTargetSource, kind: HostedRuntimeTargetKind, requestedRoot: string | null = null, unavailableReason: 'missing' | 'not-live' | 'unauthorized' | 'none' = 'none', ): void { if (root) { this.setAttribute('daemon-root', root); this.setAttribute('daemon-realm', root); } else { this.removeAttribute('daemon-root'); this.removeAttribute('daemon-realm'); } if (root) { this.dataset.runtimeTargetRoot = root; this.dataset.runtimeTargetSource = source; this.dataset.runtimeTargetKind = kind; } else { delete this.dataset.runtimeTargetRoot; this.dataset.runtimeTargetSource = source; this.dataset.runtimeTargetKind = kind; } if (requestedRoot) { this.dataset.runtimeTargetRequestedRoot = requestedRoot; } else { delete this.dataset.runtimeTargetRequestedRoot; } if (unavailableReason !== 'none') { this.dataset.runtimeTargetUnavailableReason = unavailableReason; } else { delete this.dataset.runtimeTargetUnavailableReason; } this._renderHostedRuntimeRecoveryNotice(root, requestedRoot, unavailableReason); } private _renderHostedRuntimeRecoveryNotice( root: string | null, requestedRoot: string | null, unavailableReason: 'missing' | 'not-live' | 'unauthorized' | 'none', ): void { const existing = this._hostedRuntimeRecoveryNotice(); const plan = resolveHostedRuntimeRecoveryPlan({ runtimeRoot: root, authorityRoot: this._initialRuntimeSummaryState().authorityRoot, requestedRoot, unavailableReason, currentHref: typeof window !== 'undefined' ? window.location.href : null, }); if (!plan) { existing?.remove(); return; } const notice = existing ?? document.createElement('div'); notice.setAttribute(HOSTED_RUNTIME_RECOVERY_NOTICE_ATTR, 'true'); notice.setAttribute('role', 'status'); notice.style.display = 'flex'; notice.style.gap = '8px'; notice.style.alignItems = 'center'; notice.style.padding = '8px 12px'; notice.style.background = '#fff4e5'; notice.style.color = '#4a2a00'; notice.style.border = '1px solid #ffcc80'; notice.style.font = '13px system-ui, sans-serif'; notice.textContent = ''; const summary = document.createElement('span'); summary.textContent = plan.summary; notice.appendChild(summary); for (const action of plan.actions) { const link = document.createElement('a'); link.href = action.href; link.textContent = action.label; link.title = action.title; link.style.color = '#6b3900'; link.style.fontWeight = '600'; notice.appendChild(link); } if (!existing) { this.insertBefore(notice, this.firstChild); } } private _hostedRuntimeRecoveryNotice(): HTMLElement | null { for (const child of Array.from(this.children)) { if ( child instanceof HTMLElement && child.getAttribute(HOSTED_RUNTIME_RECOVERY_NOTICE_ATTR) === 'true' ) { return child; } } return null; } private _resolveHostedRequestedRuntimeTarget( requestedRoot: string | null, summary: IHostedRuntimeSummaryState, preferredSource: HostedRuntimeTargetSource, ): IHostedRuntimeTarget { const target = typeof requestedRoot === 'string' && requestedRoot.trim().length > 0 ? requestedRoot.trim() : null; const authorityRoot = summary.authorityRoot; const liveLeafRoots = summary.liveLeafRoots; const knownLeafRoots = summary.knownLeafRoots; const chooseDefault = (): string | null => { const candidates = this._sortRuntimeRootsForDefault(authorityRoot, liveLeafRoots); return candidates[0] ?? null; }; if (target && liveLeafRoots.includes(target)) { return { root: target, source: preferredSource, kind: this._classifyHostedRuntimeTarget(target, authorityRoot, liveLeafRoots), requestedRoot: target, unavailableReason: 'none', }; } if (target && authorityRoot && target === authorityRoot) { return { root: authorityRoot, source: preferredSource, kind: this._classifyHostedRuntimeTarget(authorityRoot, authorityRoot, liveLeafRoots), requestedRoot: target, unavailableReason: 'none', }; } if (target) { if (this._isUnauthorizedHostedRuntimeTarget(target, authorityRoot, liveLeafRoots, knownLeafRoots)) { return { root: null, source: preferredSource, kind: 'unknown', requestedRoot: target, unavailableReason: 'unauthorized', }; } const unavailableReason = knownLeafRoots.includes(target) ? 'not-live' : 'missing'; return { root: null, source: preferredSource, kind: this._classifyHostedRuntimeTarget(target, authorityRoot, liveLeafRoots), requestedRoot: target, unavailableReason, }; } const automaticRoot = chooseDefault(); if (automaticRoot) { return { root: automaticRoot, source: 'auto', kind: this._classifyHostedRuntimeTarget(automaticRoot, authorityRoot, liveLeafRoots), requestedRoot: null, unavailableReason: 'none', }; } if (authorityRoot) { return { root: authorityRoot, source: 'auto', kind: 'authority', requestedRoot: null, unavailableReason: 'none', }; } return { root: null, source: 'none', kind: 'unknown', requestedRoot: target, unavailableReason: 'none', }; } private _isUnauthorizedHostedRuntimeTarget( target: string, authorityRoot: string | null, liveLeafRoots: readonly string[], knownLeafRoots: readonly string[], ): boolean { if (!authorityRoot || target === authorityRoot) { return false; } if (liveLeafRoots.includes(target) || knownLeafRoots.includes(target)) { return false; } if (target.startsWith(`${authorityRoot}.`)) { return false; } return this._looksLikeHostedAuthorityRoot(target); } private _authorizedExplicitRootBeforeRuntimeSummary( target: string, authorityRoot: string | null, unavailableReason: 'missing' | 'not-live' | 'unauthorized' | 'none', ): string | null { if (unavailableReason === 'unauthorized' || !authorityRoot) { return null; } if (target === authorityRoot || target.startsWith(`${authorityRoot}.`)) { return target; } return null; } private _looksLikeHostedAuthorityRoot(root: string): boolean { return /^SPACE-[A-Z0-9][A-Z0-9_-]*$/.test(root) || /^[A-Z][A-Z0-9_-]*(?:\.[A-Z0-9_-]+)+$/.test(root); } private _shouldRefreshHostedRuntimeTarget(): boolean { if (!this._isAuthenticated) { return false; } const pathname = typeof window !== 'undefined' && typeof window.location?.pathname === 'string' ? window.location.pathname : ''; if (!pathname.startsWith('/apps/') && this._parseRouteKeyWebappRouteFromLocation() === null) { return false; } const hostedAppName = this._deriveHostedAppName()?.trim().toLowerCase(); return Boolean(hostedAppName && hostedAppName !== 'web'); } private _resolveHostedRuntimeTargetForActiveSession( summary: IHostedRuntimeSummaryState, ): IHostedRuntimeTarget { const current = this._readHostedRuntimeTargetInfo(); const preferredSource = current.targetSource === 'explicit' ? 'explicit' : 'auto'; const requestedRoot = preferredSource === 'explicit' ? current.requestedRoot || current.runtimeRoot || null : null; return this._resolveHostedRequestedRuntimeTarget(requestedRoot, { authorityRoot: summary.authorityRoot ?? (current.authorityRoot || null), liveLeafRoots: summary.liveLeafRoots, knownLeafRoots: summary.knownLeafRoots, }, preferredSource); } private async _refreshHostedRuntimeTargetMetadata(): Promise { if (!this._shouldRefreshHostedRuntimeTarget() || this._hostedRuntimeTargetRefreshInFlight) { return; } this._hostedRuntimeTargetRefreshInFlight = true; const previous = this._readHostedRuntimeTargetInfo(); try { const summary = await this._loadHostedRuntimeSummaryFromBus('refresh'); if (!summary) { return; } const parsed = this._parseHostedRuntimeSummary(summary); const current = this._resolveHostedRuntimeTargetForActiveSession(parsed); this._applyHostedRuntimeTargetMetadata( current.root, current.source, current.kind, current.requestedRoot, current.unavailableReason, ); const next = this._readHostedRuntimeTargetInfo(); if (!hostedRuntimeTargetInfoEquals(previous, next)) { this.dispatchEvent(new CustomEvent( HOSTED_RUNTIME_TARGET_CHANGED_EVENT, { bubbles: true, detail: next, }, )); } } catch { // Keep current metadata when runtime summary is temporarily unavailable. } finally { this._hostedRuntimeTargetRefreshInFlight = false; } } private async _resolveHostedUserRuntimeTarget(): Promise { const selectedLeafRoot = this._readHostedSelectedLeafRoot(); if (!this._isAuthenticated) { return { root: null, source: 'none', kind: 'unknown', requestedRoot: selectedLeafRoot, unavailableReason: 'none', }; } if (!this._context) { const summaryState = this._initialRuntimeSummaryState(); if (selectedLeafRoot) { return { root: selectedLeafRoot, source: 'explicit', kind: this._classifyHostedRuntimeTarget( selectedLeafRoot, summaryState.authorityRoot, summaryState.liveLeafRoots, ), requestedRoot: selectedLeafRoot, unavailableReason: 'none', }; } const authorityRoot = summaryState.authorityRoot; return { root: authorityRoot, source: authorityRoot ? 'auto' : 'none', kind: authorityRoot ? 'authority' : 'unknown', requestedRoot: selectedLeafRoot, unavailableReason: 'none', }; } try { const summary = await this._loadHostedRuntimeSummaryFromBus('bootstrap-auto'); if (!summary) { return { root: null, source: 'none', kind: 'unknown', requestedRoot: selectedLeafRoot, unavailableReason: 'none', }; } const parsed = this._parseHostedRuntimeSummary(summary); return this._resolveHostedRequestedRuntimeTarget(selectedLeafRoot, parsed, 'explicit'); } catch { return { root: null, source: 'none', kind: 'unknown', requestedRoot: selectedLeafRoot, unavailableReason: 'none', }; } } private async _loadHostedRuntimeSummaryFromBus(reason: string): Promise { if (!this._context) { return null; } const authorityRoot = this._authenticatedPrincipalAuthorityRoot() ?? this._initialRuntimeSummaryState().authorityRoot; if (!authorityRoot) { return null; } const principalId = typeof this._principal?.principalId === 'string' ? this._principal.principalId.trim() : ''; const busToken = typeof this.dataset.busToken === 'string' ? this.dataset.busToken.trim() : ''; if (!busToken) { return null; } const payload: Record = { busToken, ...(principalId ? { principalId } : {}), authorityRoot, includeOffline: true, }; const controlPlaneRoot = this._controlPlaneRootForSingletons(authorityRoot); const response = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'runtimeSummary.devices.list', async () => RequestReply.execute, IDevicesListResponse>( this._context!, `${controlPlaneRoot}/system.devices`, 'devices.list', payload, { timeoutMs: 5000 }, ), { reason }, ); if (response.ok !== true) { return null; } return this._runtimeSummaryFromDeviceInventory(authorityRoot, response.devices); } private _runtimeSummaryFromDeviceInventory(authorityRoot: string, value: unknown): IRuntimeSummaryPayload { const devices = Array.isArray(value) ? value .map((entry) => this._deviceInventoryRecord(entry)) .filter((entry): entry is IDeviceInventoryRecord => entry !== null) : []; return { authority: { addressRoot: authorityRoot, }, attachedLeafRoots: { items: devices .map((device) => { const root = typeof device.authorityRoot === 'string' ? device.authorityRoot.trim() : ''; if (!root) { return null; } const status = typeof device.status === 'string' ? device.status.trim().toLowerCase() : ''; return { root, connectionKind: status === 'online' ? 'Leafnode' : 'registered', }; }) .filter((entry): entry is { readonly root: string; readonly connectionKind: string } => entry !== null), }, }; } private _deviceInventoryRecord(value: unknown): IDeviceInventoryRecord | null { return value && typeof value === 'object' && !Array.isArray(value) ? value as IDeviceInventoryRecord : null; } private _shouldRequireDaemonBootstrap(): boolean { const bootstrapMode = this.getAttribute('bootstrap-mode')?.trim().toLowerCase(); const pathname = typeof window !== 'undefined' && typeof window.location?.pathname === 'string' ? window.location.pathname : ''; if (bootstrapMode === 'local' || bootstrapMode === 'standalone') { return false; } if (pathname.startsWith('/apps/')) { return true; } if (this._isHostBootstrapMode() || this._isHostServiceBootstrap()) { return true; } if (bootstrapMode === 'daemon' || bootstrapMode === 'hosted') { return true; } return false; } private _redirectHostedAppToLogin(): void { const redirectTarget = this._currentRelativeLocation(); const location = window.location; const nextUrl = `/login?redirect=${encodeURIComponent(redirectTarget)}`; this.dataset.status = 'redirecting-auth'; this._manageLoadingElement('redirecting-auth'); if (typeof location.assign === 'function') { location.assign(nextUrl); return; } if (typeof location.replace === 'function') { location.replace(nextUrl); return; } try { location.href = nextUrl; } catch { // no-op: test doubles may expose a read-only href } } private _currentRelativeLocation(): string { const pathname = typeof window !== 'undefined' && typeof window.location?.pathname === 'string' ? window.location.pathname : '/'; const search = typeof window !== 'undefined' && typeof window.location?.search === 'string' ? window.location.search : ''; const hash = typeof window !== 'undefined' && typeof window.location?.hash === 'string' ? window.location.hash : ''; return `${pathname || '/'}${search}${hash}`; } private _bootstrapEndpointForRoute(routeKeyRoute: IRouteKeyWebappRoute | null, appName: string | null): string { const params = new URLSearchParams(); if (routeKeyRoute) { params.set('routeKey', routeKeyRoute.routeKey); } if (appName) { params.set('appName', appName); } const query = params.toString(); return query ? `/api/bootstrap?${query}` : '/api/bootstrap'; } private _isHostBootstrapMode(): boolean { const mode = this.getAttribute('bootstrap-mode')?.trim().toLowerCase(); return mode === 'host' || mode === 'host-service' || mode === 'matrix-host'; } private _isHostServiceBootstrap(): boolean { return this.dataset.rootSource === 'host-service:transport.root'; } private _isAuthoritativeBootstrapIdentitySource(source: string): boolean { return source === 'system-auth:principal.addressRoots' || source === 'system-auth:publicNamespaces'; } private _shouldUseRuntimeRegistryDiscovery(authorityRoot: string): boolean { const root = authorityRoot.trim(); if (!root) { return false; } const hostedAppName = this._deriveHostedAppName(); if (!hostedAppName) { return false; } const runtimeTargetKind = this.dataset.runtimeTargetKind ?? ''; const runtimeTargetRoot = this.dataset.runtimeTargetRoot ?? ''; if (runtimeTargetKind === 'authority' && runtimeTargetRoot === root) { return true; } const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const rootSource = this.dataset.rootSource ?? ''; const selectedAuthorityRootSource = this.dataset.selectedAuthorityRootSource ?? ''; const rootCameFromAuthoritativeBootstrap = this._isAuthoritativeBootstrapIdentitySource(rootSource) || this._isAuthoritativeBootstrapIdentitySource(selectedAuthorityRootSource); const authenticated = this.dataset.authenticated === 'true' || this._isAuthenticated === true; // Host Service deployments discover authority runtime state through // system.runtimes. Retired actor paths are not a product path. return authenticated && rootCameFromAuthoritativeBootstrap && selectedAuthorityRoot === root; } private _shouldSkipHostedShellDaemonDiscovery(hasExplicitDaemonTarget: boolean): boolean { if (hasExplicitDaemonTarget || this._isHostBootstrapMode() || this._isHostServiceBootstrap()) { return false; } return this._deriveHostedAppName() === 'web'; } private _shouldRetainBootstrapIdentity(authorityRoot: string): boolean { const root = authorityRoot.trim(); const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const rootSource = this.dataset.rootSource ?? ''; const selectedAuthorityRootSource = this.dataset.selectedAuthorityRootSource ?? ''; const rootCameFromAuthoritativeBootstrap = this._isAuthoritativeBootstrapIdentitySource(rootSource) || this._isAuthoritativeBootstrapIdentitySource(selectedAuthorityRootSource); const authenticated = this.dataset.authenticated === 'true' || this._isAuthenticated === true; return authenticated && root.length > 0 && rootCameFromAuthoritativeBootstrap && (!selectedAuthorityRoot || selectedAuthorityRoot === root); } private _ensureHostRuntimeMount(authorityRoot: string, appName: string | null): void { const existingMount = this.getAttribute('mount')?.trim() ?? ''; if (existingMount.length > 0) { return; } const runtimeId = this._getOrCreateBrowserRuntimeId(authorityRoot, appName); this.dataset.hostRuntimeId = runtimeId; this.setAttribute('mount', `system.runtimes.${runtimeId}`); } private _getOrCreateBrowserRuntimeId(authorityRoot: string, appName: string | null): string { const normalizedAuthority = authorityRoot.trim() || 'LOCAL'; const normalizedApp = (appName ?? 'browser').trim().toUpperCase().replace(/[^A-Z0-9_-]/g, '-'); const storageKey = `matrix-browser-runtime-id:${normalizedAuthority}:${normalizedApp}`; const existing = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem(storageKey) : null; if (existing && /^RUNTIME-BROWSER-[A-Z0-9_-]+$/.test(existing)) { return existing; } const randomId = this._randomRuntimeSuffix(); const runtimeId = `${BROWSER_RUNTIME_ID_PREFIX}${randomId}`; if (typeof sessionStorage !== 'undefined') { sessionStorage.setItem(storageKey, runtimeId); } return runtimeId; } private _randomRuntimeSuffix(): string { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID().replace(/-/g, '').slice(0, 8).toUpperCase(); } return Math.random().toString(36).slice(2, 10).toUpperCase(); } private async _diagnoseBootstrapFailure(err: unknown): Promise { const rawMessage = err instanceof Error ? err.message : String(err); const lines = [`Cause: ${rawMessage}`]; const classify = (classification: string, hint: string): IBootstrapFailureDiagnostics => ({ classification, hint, lines: [...lines, `Classification: ${classification}`, `Hint: ${hint}`], }); if (/NATS JWT bootstrap failed/i.test(rawMessage)) { let healthLine = 'HTTP /healthz: not checked'; let bootstrapLine = 'HTTP /api/bootstrap: not checked'; try { const healthResp = await fetch('/healthz', { credentials: 'same-origin' }); healthLine = `HTTP /healthz: ${healthResp.status} ${healthResp.ok ? 'ok' : 'not ok'}`; } catch (error) { healthLine = `HTTP /healthz: unreachable (${error instanceof Error ? error.message : String(error)})`; } try { const bootstrapResp = await fetch('/api/bootstrap', { credentials: 'same-origin' }); bootstrapLine = `HTTP /api/bootstrap: ${bootstrapResp.status} ${bootstrapResp.ok ? 'ok' : 'not ok'}`; } catch (error) { bootstrapLine = `HTTP /api/bootstrap: unreachable (${error instanceof Error ? error.message : String(error)})`; } lines.push(healthLine, bootstrapLine); return classify( 'nats-jwt-bootstrap', 'The HiveCast HTTP surface may still be up. This usually means session-to-bus auth or first-boot provisioning failed, not that the whole Host is down.', ); } if (/Failed to fetch|NetworkError|fetch/i.test(rawMessage)) { try { const healthResp = await fetch('/healthz', { credentials: 'same-origin' }); lines.push(`HTTP /healthz: ${healthResp.status} ${healthResp.ok ? 'ok' : 'not ok'}`); return classify( 'http-bootstrap', 'The browser reached the HiveCast origin but a bootstrap HTTP request failed. Check /api/bootstrap and auth/session routes before assuming the Host is down.', ); } catch (error) { lines.push(`HTTP /healthz: unreachable (${error instanceof Error ? error.message : String(error)})`); return classify( 'host-gateway-unreachable', 'The browser cannot reach the HiveCast HTTP gateway at all. This is the closest thing to “Host is down” in the bootstrap path.', ); } } if (/websocket|nats|backbone|ECONNREFUSED|timeout/i.test(rawMessage)) { return classify( 'backbone-connect', 'HTTP bootstrap likely succeeded, but the browser failed while connecting to the NATS/websocket backbone or the routed Host path.', ); } return classify( 'bootstrap-runtime', 'The Host may still be running. This failure happened inside browser bootstrap logic after initial HTTP discovery.', ); } /** * FEDERATED MODE: Browser NATS WebSocket transport. */ private async _bootstrapFederated( busRootInput: string, wsUrl: string, mountPath: string, ): Promise { const hostBootstrapMode = this._isHostBootstrapMode(); const busRoot = hostBootstrapMode ? busRootInput.trim() : busRootInput.toLowerCase().trim(); const runtimeInstanceRoot = (this.dataset.runtimeInstanceRoot ?? '').trim(); const sessionAuthorityRoot = (this.dataset.sessionAuthorityRoot ?? '').trim(); const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const identityRoot = sessionAuthorityRoot || selectedAuthorityRoot; const appName = this.getAttribute('app-name')?.trim() ?? ''; if (!hostBootstrapMode && !runtimeInstanceRoot && identityRoot && !appName) { throw new Error( 'MatrixDSLHost: bootstrap did not provide app-name. ' + 'Identity tab root requires appName; see four-roots.md.', ); } const tabRoot = hostBootstrapMode ? busRoot : runtimeInstanceRoot ? runtimeInstanceRoot : identityRoot ? getOrCreateIdentityTabRoot(appName, identityRoot) : getOrCreateTabRealm(busRoot); this._setOptionalDatasetValue('runtimeInstanceRoot', tabRoot); const declaredAuthMode = this._bootstrapTransportPlan?.authMode; // Prefer the explicit transport plan from /api/bootstrap. // If absent, probe the NATS auth endpoint to identify the configured mode. let jwtProvider: (() => Promise<{ jwt: string; seed: string }>) | undefined; let cachedJwt: { jwt: string; seed: string } | null = null; const buildNatsAuthEndpoint = (): string => { const base = this.dataset.natsAuthEndpoint || '/api/nats-jwt'; const url = new URL(base, window.location.origin); const platformRoot = (this.dataset.platformRoot ?? '').trim(); const requestedDaemonRoot = (this.getAttribute('daemon-root') ?? this.getAttribute('daemon-realm') ?? '').trim(); const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const anonymousSession = !this._isAuthenticated && (this._bootstrapTransportPlan?.sessionMode === 'anonymous' || this.dataset.sessionMode === 'anonymous'); const daemonRoot = anonymousSession ? (platformRoot && requestedDaemonRoot === platformRoot ? platformRoot : '') : (selectedAuthorityRoot || requestedDaemonRoot); if (daemonRoot) { url.searchParams.set('daemonRoot', daemonRoot); } else if (anonymousSession) { url.searchParams.delete('daemonRoot'); url.searchParams.delete('root'); } if (tabRoot) { url.searchParams.set('clientRoot', tabRoot); } return url.pathname + url.search; }; const fetchNatsAuthPayload = async (): Promise<{ jwt?: string; seed?: string; user?: string; pass?: string; mode?: string }> => { const endpoint = buildNatsAuthEndpoint(); const response = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsAuth.fetch', async () => fetch(endpoint, { method: 'POST', credentials: 'same-origin' }), { authMode: declaredAuthMode ?? 'auto', endpoint, }, ); const payload = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsAuth.parse', async () => response.json().catch(() => ({})) as Promise<{ jwt?: string; seed?: string; user?: string; pass?: string; mode?: string; error?: string }>, { authMode: declaredAuthMode ?? 'auto', endpoint, }, ); if (!response.ok) { const detail = typeof payload.error === 'string' && payload.error.length > 0 ? payload.error : `HTTP ${response.status}`; throw new Error(`NATS auth request failed (${response.status}): ${detail}`); } return payload; }; const fetchJwtCredentials = async (): Promise<{ jwt: string; seed: string } | null> => { if (!this._isAuthenticated) { return null; } let lastError: Error | null = null; for (let attempt = 0; attempt < 8; attempt++) { const endpoint = buildNatsAuthEndpoint(); const response = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsJwt.fetch', async () => fetch(endpoint, { method: 'POST', credentials: 'same-origin' }), { attempt: attempt + 1, authMode: declaredAuthMode ?? 'auto', }, ); if (response.ok) { const payload = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsJwt.parse', async () => response.json() as Promise<{ jwt?: string; seed?: string; user?: string; pass?: string; mode?: string }>, { attempt: attempt + 1, authMode: declaredAuthMode ?? 'auto', }, ); if (payload.jwt && payload.seed) { return { jwt: payload.jwt, seed: payload.seed }; } // Static NATS mode — store user/pass for transport auth if (payload.mode === 'static' && payload.user && payload.pass) { this.dataset.natsUser = payload.user; this.dataset.natsPass = payload.pass; return null; // No JWT, use user/pass auth instead } // Anonymous mode — no credentials needed if (payload.mode === 'anonymous') { return null; } lastError = new Error('NATS JWT response missing jwt or seed'); } else { const bodyText = await response.text().catch(() => ''); const detail = bodyText.trim(); // This deployment does not expose operator/JWT auth, so plain WS token mode is expected. if ( response.status === 401 || response.status === 403 || response.status === 404 || (response.status === 503 && /not configured|no operatorseed|no account selected|failed to create nats jwt/i.test(detail)) ) { return null; } // Provisioning and rate-limit windows are transient immediately after login/startup. if (response.status === 429 || response.status >= 500 || /provisioning operation is in progress/i.test(detail)) { lastError = new Error(`NATS JWT temporary failure (${response.status}): ${detail || 'no body'}`); await new Promise((resolve) => setTimeout(resolve, Math.min(250 * (attempt + 1), 1000))); continue; } throw new Error(`NATS JWT request failed (${response.status}): ${detail || 'no body'}`); } } if (lastError) { throw lastError; } throw new Error('NATS JWT request failed without a response'); }; if (declaredAuthMode === 'jwt') { const payload = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsAuth.declared', async () => fetchNatsAuthPayload(), { authMode: declaredAuthMode }, ); if (!payload.jwt || !payload.seed) { throw new Error('Declared NATS authMode "jwt" but /api/nats-jwt did not return jwt+seed'); } cachedJwt = { jwt: payload.jwt, seed: payload.seed }; jwtProvider = async () => { if (cachedJwt) { const next = cachedJwt; cachedJwt = null; return next; } const refreshed = await fetchNatsAuthPayload(); if (!refreshed.jwt || !refreshed.seed) { throw new Error('Declared NATS authMode "jwt" but refresh did not return jwt+seed'); } return { jwt: refreshed.jwt, seed: refreshed.seed }; }; } else if (declaredAuthMode === 'static') { const payload = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsAuth.declared', async () => fetchNatsAuthPayload(), { authMode: declaredAuthMode }, ); if (payload.mode !== 'static' || !payload.user || !payload.pass) { throw new Error('Declared NATS authMode "static" but /api/nats-jwt did not return user/pass'); } this.dataset.natsUser = payload.user; this.dataset.natsPass = payload.pass; } else if (declaredAuthMode === 'anonymous') { // No extra auth bootstrap required. } else { try { const initial = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'natsJwt.bootstrap', async () => fetchJwtCredentials(), { authMode: declaredAuthMode ?? 'auto' }, ); if (initial) { cachedJwt = initial; jwtProvider = async () => { if (cachedJwt) { const next = cachedJwt; cachedJwt = null; return next; } const refreshed = await fetchJwtCredentials(); if (!refreshed) { throw new Error('NATS JWT refresh unavailable: operator mode no longer configured'); } return refreshed; }; } } catch (error) { throw new Error(`NATS JWT bootstrap failed: ${error instanceof Error ? error.message : String(error)}`); } if (!jwtProvider) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info('[MatrixDSLHost] NATS JWT unavailable — connecting with configured non-JWT transport'); } } if (declaredAuthMode === 'anonymous') { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info('[MatrixDSLHost] NATS auth plan → anonymous'); } else if (declaredAuthMode === 'static') { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info('[MatrixDSLHost] NATS auth plan → static credentials via /api/nats-jwt'); } else if (declaredAuthMode === 'jwt') { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info('[MatrixDSLHost] NATS auth plan → JWT via /api/nats-jwt'); } else if (!jwtProvider) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info('[MatrixDSLHost] NATS operator mode not available — connecting without JWT auth'); } const connectBackbone = async () => createBrowserNatsTransport({ root: tabRoot, wsUrl, token: this.dataset.natsToken, user: this.dataset.natsUser, pass: this.dataset.natsPass, jwtProvider, }); const shouldRetryJwtReadiness = (error: unknown): boolean => { const message = error instanceof Error ? error.message : String(error); return declaredAuthMode === 'jwt' && Boolean(jwtProvider) && /Authorization Violation|authentication violation/i.test(message); }; let rawAdapter: Awaited> | null = null; let lastConnectError: unknown = null; for (let attempt = 0; attempt < 4; attempt++) { try { rawAdapter = await profileBrowserSpan( HOST_PROFILE_SCOPE, 'nats.connect', async () => connectBackbone(), { root: tabRoot, authMode: declaredAuthMode ?? 'auto', attempt: attempt + 1, }, ); lastConnectError = null; break; } catch (error) { lastConnectError = error; if (!shouldRetryJwtReadiness(error) || attempt === 3) { throw error; } // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn(`[MatrixDSLHost] NATS JWT account not ready yet; retrying backbone connect (${attempt + 2}/4)...`); await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1))); } } if (!this._rawAdapter && lastConnectError) { throw lastConnectError; } if (!rawAdapter) { throw new Error('NATS backbone connection failed before an adapter was created'); } this._rawAdapter = rawAdapter; this._transport = this._rawAdapter; this.dataset.status = 'connected'; // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info(`[MatrixDSLHost] NATS backbone connected: ${wsUrl} (${tabRoot})`); if (this._wrapTransport && this._transport) { this._transport = this._wrapTransport(this._transport); } this._runtime = new MatrixRuntime({ transport: this._transport ?? undefined, domStrategy: new BrowserDomStrategy(), }); const rootCtx = this._runtime.getRootContext!(); this._context = mountPath ? rootCtx.derive(mountPath) : rootCtx; await this._applyHostedWebappMetadata(); this._routingPeer = this._createTransportRoutingPeer(tabRoot); } private _deriveHostedAppName(): string | null { const routeKeyRoute = this._parseRouteKeyWebappRouteFromLocation(); if (routeKeyRoute) { return routeKeyRoute.appName; } const pathname = typeof window !== 'undefined' && typeof window.location?.pathname === 'string' ? window.location.pathname : ''; const match = /^\/apps\/([^/]+)/.exec(pathname); const rawAppName = match?.[1]; if (!rawAppName) return null; try { const decoded = decodeURIComponent(rawAppName).trim(); return decoded.length > 0 ? decoded : null; } catch { return null; } } private _parseRouteKeyWebappRouteFromLocation(): IRouteKeyWebappRoute | null { const pathname = typeof window !== 'undefined' && typeof window.location?.pathname === 'string' ? window.location.pathname : ''; if (!pathname.startsWith('/')) { return null; } const withoutLeadingSlash = pathname.slice(1); const firstSlash = withoutLeadingSlash.indexOf('/'); if (firstSlash <= 0) { return null; } let routeKey: string; try { routeKey = decodeURIComponent(withoutLeadingSlash.slice(0, firstSlash)).trim().toLowerCase(); } catch { return null; } if (!isValidRouteKey(routeKey)) { return null; } const afterRouteKey = withoutLeadingSlash.slice(firstSlash + 1); const appPath = afterRouteKey.startsWith('apps/') ? afterRouteKey.slice('apps/'.length) : afterRouteKey; const appSlash = appPath.indexOf('/'); const rawAppName = appSlash === -1 ? appPath : appPath.slice(0, appSlash); let appName: string; try { appName = decodeURIComponent(rawAppName).trim(); } catch { return null; } if (!/^[A-Za-z0-9._-]+$/.test(appName) || appName === 'web') { return null; } return { appName, routeKey, }; } private async _applyHostedWebappMetadata(): Promise { if (!this._context) return; const appName = this._deriveHostedAppName(); if (!appName) return; if (appName === 'web') return; if (this._parseRouteKeyWebappRouteFromLocation()) { return; } const assetHostRoot = (this.dataset.assetHostRoot ?? '').trim(); const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const platformServiceRoot = (this.dataset.platformServiceRoot ?? '').trim(); const isPlatformAdmin = this.dataset.isPlatformAdmin === 'true'; if (platformServiceRoot && selectedAuthorityRoot === platformServiceRoot && !isPlatformAdmin) { return; } if (assetHostRoot && selectedAuthorityRoot && assetHostRoot !== selectedAuthorityRoot && !isPlatformAdmin) { return; } const entry = await this._loadHostedWebappRegistryEntry(appName); const metadataRecord = registryRecord(entry.metadata); const artifact = registryArtifact(metadataRecord.artifact); const packageName = registryString(entry.placement.packageRef) ?? registryString(entry.placement.packageId); const packageVersion = registryString(metadataRecord.packageVersion); const sourceKind = registrySourceKind(metadataRecord.sourceKind) ?? (registryString(metadataRecord.origin) ? 'external-webapp' : 'installed-webapp'); const registrationSource = registryRegistrationSource(metadataRecord.registrationSource); const deploymentInstanceId = registryString(entry.placement.deploymentInstanceId) ?? registryString(entry.placement.runtimeId); const distPath = registryString(metadataRecord.distPath); const entryFile = registryString(metadataRecord.entryFile); const buildGitSha = registryString(artifact.gitSha); const artifactBuiltAt = registryString(artifact.builtAt); const artifactSignature = registryString(artifact.signature); const artifactFreshness = registryArtifactFreshness(artifact.freshness); const artifactFreshnessReason = registryString(artifact.reason); const metadata: IComponentRuntimeMetadata = { versionSource: packageVersion ? 'webapp-registry' : 'undeclared', sourceKind, appName, ...(packageName ? { packageName } : {}), ...(packageVersion ? { version: packageVersion } : {}), ...(registrationSource ? { registrationSource } : {}), ...(deploymentInstanceId ? { deploymentInstanceId } : {}), ...(distPath ? { distPath } : {}), ...(entryFile ? { entryFile } : {}), ...(buildGitSha ? { buildGitSha } : {}), ...(artifactBuiltAt ? { artifactBuiltAt } : {}), ...(artifactSignature ? { artifactSignature } : {}), ...(artifactFreshness ? { artifactFreshness } : {}), ...(artifactFreshnessReason ? { artifactFreshnessReason } : {}), }; if (metadata.artifactFreshness && metadata.artifactFreshness !== 'current') { // eslint-disable-next-line no-console -- browser bootstrap visibility console.warn( `[MatrixDSLHost] Hosted webapp artifact for "${appName}" is ${metadata.artifactFreshness}: ${metadata.artifactFreshnessReason ?? 'no reason provided'}` ); } const contextual = this._context as IMatrixContext & { setService?: (key: string | symbol, service: unknown) => void; }; contextual.setService?.(COMPONENT_RUNTIME_METADATA, metadata); } private async _loadHostedWebappRegistryEntry(appName: string): Promise { if (!this._context) { throw new Error(`system.registry lookup for hosted app '${appName}' requires a Matrix context`); } const namespaceRoot = this._hostedWebappRegistryNamespaceRoot(); const response = await RequestReply.execute, IRegistryQueryResponse>( this._context, `${namespaceRoot}/system.registry`, 'registry.query', { namespaceRoot, mount: appName, kind: 'app', openable: true, }, { timeoutMs: 5000 }, ); const entries = Array.isArray(response.entries) ? response.entries : []; const entry = entries .map((candidate) => serviceRegistryEntry(candidate)) .find((candidate) => candidate !== null && candidate.kind === 'app' && candidate.app?.openable === true && candidate.mount === appName); if (!entry) { throw new Error(`app '${appName}' not registered by ${namespaceRoot}/system.registry`); } return entry; } private _hostedWebappRegistryNamespaceRoot(): string { const assetHostRoot = (this.dataset.assetHostRoot ?? '').trim(); if (assetHostRoot) { return assetHostRoot; } const busRoot = (this.getAttribute('bus-root') ?? '').trim(); if (!busRoot) { throw new Error('Hosted webapp registry lookup requires assetHostRoot from /api/bootstrap'); } return busRoot; } /** * ROOT LOCAL MODE: FederationPeer without backbone (local pub/sub only). */ private _bootstrapRootLocal(busRootInput: string, mountPath: string): void { const baseRoot = busRootInput.toLowerCase().trim(); // Create browser router (FederationPeer in local-only mode) const result: BrowserRouterResult = getBrowserRouter({ baseRealm: baseRoot }); this._peer = result.router; this._routingPeer = result.router; this._rawAdapter = browserRouterTransportAdapter(result.adapter, result.router); this._transport = this._rawAdapter; // Apply transport wrapper if provided if (this._wrapTransport) { this._transport = this._wrapTransport(this._transport); } // Create runtime with federation adapter this._runtime = new MatrixRuntime({ transport: this._transport, domStrategy: new BrowserDomStrategy(), }); const rootCtx = this._runtime.getRootContext!(); this._context = mountPath ? rootCtx.derive(mountPath) : rootCtx; } private _createTransportRoutingPeer(localRoot: string): IHostRoutingPeer { return { hasBackbone: true, localRoot, localRealm: localRoot, invoke: async ( target: string, opOrRequest: string | { op: string; payload?: unknown }, payloadOrTimeout?: unknown, timeoutMs?: number, ): Promise => { if (!this._context) { throw new Error('MatrixDSLHost transport router is not initialized'); } if (typeof opOrRequest === 'string') { const op = opOrRequest; const payload = payloadOrTimeout; return await RequestReply.execute(this._context, target, op, payload, { timeoutMs, }); } const op = opOrRequest.op; const payload = opOrRequest.payload; const resolvedTimeoutMs = typeof payloadOrTimeout === 'number' ? payloadOrTimeout : timeoutMs; return await RequestReply.execute(this._context, target, op, payload, { timeoutMs: resolvedTimeoutMs, }); }, route: async (topic: string, payload: unknown): Promise => { if (!this._transport) { throw new Error('MatrixDSLHost transport router is not initialized'); } this._transport.publish(topic, payload); }, }; } /** * LOCAL MODE: Plain in-memory transport, no federation overhead. */ private _bootstrapLocal(mountPath: string): void { // No federation — just in-memory broker + transport this._runtime = new MatrixRuntime({ domStrategy: new BrowserDomStrategy(), }); const rootCtx = this._runtime.getRootContext!(); this._context = mountPath ? rootCtx.derive(mountPath) : rootCtx; } // ═══════════════════════════════════════════════════════════════════════════ // $join Responder // ═══════════════════════════════════════════════════════════════════════════ /** * Subscribe to this host's $inbox to respond to $join from children. * * When children call _receiveContext(derivedCtx), they send $join to * parent.mount/$inbox. This responder acks them so they don't timeout. */ private _installJoinResponder(): void { if (!this._context) return; const hostMount = this._context.mount; const hostInbox = TopicRouter.inbox(hostMount); this._joinUnsub = this._context.transport.subscribe( hostInbox, (rawPayload: unknown) => { const envelope = rawPayload as MxEnvelope; if (!envelope || envelope.op !== '$join') return; const joinPayload = envelope.payload as { id?: string; mount?: string } | undefined; const childMount = joinPayload?.mount; if (!childMount) return; const childInbox = TopicRouter.inbox(childMount); this._context!.transport.publish(childInbox, { op: '$join_ack', payload: { status: 'ok', supervisor: hostMount }, }); }, ); } // ═══════════════════════════════════════════════════════════════════════════ // Child Mounting // ═══════════════════════════════════════════════════════════════════════════ /** * Mount all light DOM children that are custom elements. * Uses explicit _receiveContext() — NOT AUTO-PULL (because this host * is not a MatrixActorHtmlElement and won't be found by _findParentShell). */ private async _mountChildren(): Promise { if (!this._context) return; const mountPromises: Promise[] = []; for (const child of Array.from(this.children)) { if (!(child instanceof HTMLElement)) continue; mountPromises.push(this._mountChild(child)); } await Promise.all(mountPromises); } /** * Mount a single child element with a derived context. */ private async _mountChild(element: HTMLElement): Promise { if (!this._context) return; const tagName = element.tagName.toLowerCase(); // Skip non-custom elements (no hyphen = not a CE) if (!tagName.includes('-')) return; // Skip script/style if (tagName === 'script' || tagName === 'style') return; // Wait for custom element definition (handles script loading order) await customElements.whenDefined(tagName); // Derive child ID from name, id, or tag const childId = ( element.getAttribute('name') || element.id || tagName ).toLowerCase(); const childContext = this._context.derive(childId); const shell = matrixChildElementHooks(element); if (typeof shell._receiveContext === 'function') { await shell._receiveContext(childContext); } else if (typeof shell.mount === 'function') { await shell.mount(childContext); } else if (typeof shell.setContext === 'function') { shell.setContext(childContext); } // Plain HTML elements without Matrix interface are silently skipped } private _browserRuntimeInventory(runtimeId: string, contextMount: string, hostBootstrapMode: boolean): IBrowserRuntimeInventoryEntry[] { const inventory: IBrowserRuntimeInventoryEntry[] = []; const runtimeMount = `system.runtimes.${runtimeId}`; inventory.push({ mount: runtimeMount, type: 'BrowserRuntimeControlActor', surface: 'runtime', runtimeId, claimKind: 'local-only', description: 'Browser runtime actor for this tab.', hasChildren: true, }); for (const child of Array.from(this.children)) { if (!(child instanceof HTMLElement)) continue; const tag = child.tagName.toLowerCase(); if (!tag.includes('-')) continue; const name = child.getAttribute('name') || child.getAttribute('id') || tag; const localMount = hostBootstrapMode && contextMount ? `${contextMount}.${name}` : name; inventory.push({ mount: localMount, type: child.constructor.name !== 'HTMLElement' ? child.constructor.name : tag, surface: 'dom', runtimeId, claimKind: 'local-only', hasChildren: true, }); } return inventory; } private async _ensureBrowserRuntimeControl( runtimeId: string, runtimeMount: string, controlMount: string, ): Promise { if (!(this._runtime instanceof MatrixRuntime)) { return; } if (!this._runtimeControlStartedAt) { this._runtimeControlStartedAt = new Date().toISOString(); } await mountBrowserRuntimeControl({ runtime: this._runtime, runtimeId, runtimeMount, controlMount, startedAt: this._runtimeControlStartedAt, getState: (): BrowserRuntimeLifecycleState => this._initialized ? 'live' : 'closing', getInventory: () => this._browserRuntimeInventory( runtimeId, this._context?.mount ?? '', this._isHostBootstrapMode(), ), }); } private _runtimeInventoryVersionForHost(): number { let version = 0; for (const child of Array.from(this.children)) { if (!(child instanceof HTMLElement)) continue; const shell = matrixActorElementVersionSource(child); const childVersion = shell._actor?.children?.version; if (typeof childVersion === 'number' && Number.isFinite(childVersion)) { version += childVersion; } } return version; } private _scheduleRuntimeInventoryRefresh(): void { if (!this._browserRuntimeRegistryState) { return; } if (this._runtimeInventoryClaimRefreshTimer) { clearTimeout(this._runtimeInventoryClaimRefreshTimer); } this._runtimeInventoryClaimRefreshTimer = setTimeout(() => { this._runtimeInventoryClaimRefreshTimer = null; void this._refreshRuntimeInventory(); }, 250); } private async _refreshRuntimeInventory(): Promise { const state = this._browserRuntimeRegistryState; if (!this._context || !state) { return; } const inventory = this._browserRuntimeInventory( state.runtimeId, state.contextMount, state.hostBootstrapMode, ); const inventoryVersion = this._runtimeInventoryVersionForHost(); try { this._runtimeInventoryVersion = inventoryVersion; await this._replaceBrowserRuntimeClaims(state, inventory, inventoryVersion); } catch { // Registry lease renewal keeps liveness; the next DOM mutation retries inventory claims. } } // ═══════════════════════════════════════════════════════════════════════════ // Root Context // ═══════════════════════════════════════════════════════════════════════════ /** * Parse root and federation attributes into RootContext. */ private _parseRootAttributes(): void { const rawRoot = this.getAttribute('bus-root'); if (!rawRoot) return; const baseRoot = rawRoot.toLowerCase().trim(); if (!isValidRealm(baseRoot)) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.error(`[MatrixDSLHost] Invalid root: "${rawRoot}"`); return; } const rawFederation = this.getAttribute('federation') || 'auto'; const federation = rawFederation.toLowerCase().trim() as FederationMode; const validModes: FederationMode[] = ['auto', 'local', 'public', 'disabled']; if (!validModes.includes(federation)) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.error(`[MatrixDSLHost] Invalid federation mode: "${rawFederation}"`); return; } let tabRoot: string | undefined; if (federation !== 'disabled') { tabRoot = getOrCreateTabRealm(baseRoot); } this._rootContext = createRootContext(baseRoot, federation, tabRoot); } /** * Propagate RootContext to child elements that support it. */ private _propagateRootToChildren(): void { const rootContext = this._rootContext; if (!rootContext) return; const children = this.querySelectorAll('*'); children.forEach((child: Element) => { const contextReceiver = matrixRootContextReceiver(child); if (typeof contextReceiver._receiveContext === 'function') { // Only propagate root context (not a full IMatrixContext) // MatrixActorHtmlElement._receiveContext handles this special case contextReceiver._receiveContext({ rootContext, realmContext: rootContext }); } }); } // ═══════════════════════════════════════════════════════════════════════════ // Mutation Observation // ═══════════════════════════════════════════════════════════════════════════ private _setupMutationObserver(): void { this._observer = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of Array.from(mutation.addedNodes)) { if (node instanceof HTMLElement) { void this._mountChild(node).then(() => this._scheduleRuntimeInventoryRefresh()); } } if (mutation.removedNodes.length > 0) { this._scheduleRuntimeInventoryRefresh(); } } } }); this._observer.observe(this, { childList: true }); } // ═══════════════════════════════════════════════════════════════════════════ // Loading Element Management // ═══════════════════════════════════════════════════════════════════════════ /** * Manage a sibling loading element and this host's visibility. * Looks for element with id from `loading-el` attribute, or `#loading`. */ private _manageLoadingElement(status: string, error?: unknown): void { const loadingId = this.getAttribute('loading-el') || 'loading'; const loadingEl = document.getElementById(loadingId); if (status === 'initializing') { if (loadingEl) loadingEl.style.display = ''; if (this._splashShownAt === null) { this._splashShownAt = performance.now(); } this.style.display = 'none'; } else if (status === 'connected' || status === 'local' || status === 'login-required') { this._scheduleSplashHide(loadingEl); this.style.display = ''; } else if (status === 'disconnected') { // Show app in offline mode — UI loads, just no backbone this._scheduleSplashHide(loadingEl); this.style.display = ''; } else if (status === 'error') { this.style.display = 'none'; if (loadingEl) { const root = this.getAttribute('bus-root') ?? '(not set)'; const backbone = this.getAttribute('backbone') || '(none)'; const errMsg = error instanceof Error ? error.message : String(error); const esc = (s: string) => s.replace(//g, '>'); loadingEl.outerHTML = `
Bootstrap Failed
${esc(errMsg)}
Root${esc(root)}
Backbone${esc(backbone)}
Status${this._peer?.hasBackbone ? 'connected' : 'not connected'}
`; } } } /** * Hide the splash with a minimum-visible-time guard and a fade-out. * * Bootstrap can complete in <100ms on a warm cache, which makes the splash * appear and disappear in a single frame — users perceive a flash rather * than a brand frame. We enforce a 700ms minimum visible time and fade the * last 200ms via opacity so the dismissal reads as intentional. The * `splash-min-ms` attribute on `` overrides the default; * `splash-min-ms="0"` restores the original instant-hide behavior. */ private _scheduleSplashHide(loadingEl: HTMLElement | null): void { if (!loadingEl || this._splashHideScheduled) return; this._splashHideScheduled = true; const minMs = (() => { const raw = this.getAttribute('splash-min-ms'); if (raw === null) return 700; const n = Number.parseInt(raw, 10); return Number.isFinite(n) && n >= 0 ? n : 700; })(); const fadeMs = Math.min(200, Math.floor(minMs / 3)); const shownAt = this._splashShownAt ?? performance.now(); const elapsed = performance.now() - shownAt; const remaining = Math.max(0, minMs - elapsed - fadeMs); const startFade = () => { loadingEl.style.transition = `opacity ${fadeMs}ms ease-out`; loadingEl.style.opacity = '0'; window.setTimeout(() => { loadingEl.style.display = 'none'; loadingEl.style.opacity = ''; loadingEl.style.transition = ''; }, fadeMs); }; if (remaining > 0) { window.setTimeout(startFade, remaining); } else { startFade(); } } private _redirectToDeviceLogin(): void { if (this.dataset.loginRedirectStarted === 'true') { return; } this.dataset.loginRedirectStarted = 'true'; const startUrl = this.dataset.loginStartUrl || '/_auth/device-session/start'; const loginUrl = new URL(startUrl, window.location.origin); loginUrl.searchParams.set('returnTo', `${window.location.pathname}${window.location.search}${window.location.hash}`); window.location.assign(loginUrl.toString()); } private _shouldRedirectToDeviceLogin(hostedAppName: string | null): boolean { const normalizedAppName = hostedAppName?.trim().toLowerCase() ?? ''; return normalizedAppName === 'edge'; } private _redirectToPlatformLogin(): void { if (this.dataset.loginRedirectStarted === 'true') { return; } this.dataset.loginRedirectStarted = 'true'; const returnTo = `${window.location.pathname}${window.location.search}${window.location.hash}`; const loginUrl = new URL('/apps/web/', window.location.origin); loginUrl.hash = `login?redirect=${encodeURIComponent(returnTo)}`; window.location.assign(loginUrl.toString()); } // ═══════════════════════════════════════════════════════════════════════════ // Events // ═══════════════════════════════════════════════════════════════════════════ private _fireReadyEvent(): void { this.dispatchEvent(new CustomEvent('matrix-dsl-ready', { bubbles: true, detail: { peer: this._peer, runtime: this._runtime, context: this._context, root: this.getAttribute('bus-root'), realm: this.getAttribute('bus-root'), backbone: this.getAttribute('backbone'), hasBackbone: this._peer?.hasBackbone ?? false, status: this.dataset.status, }, })); } /** * Async runtime registry discovery — fires after full bootstrap (children already mounted). * * Confirms the runtime registry actor is reachable, then publishes readiness on the root * $events topic so child apps receive it through their transport subscriptions. * No DOM events — pure transport pub/sub so timing is deterministic. * * Publishes: * { op: 'daemonReady', payload: { root } } — on success * { op: 'daemonError', payload: { root, error } } — on failure * { op: 'daemonReady', payload: { root: '', skipped: true } } — no explicit runtime target set */ private async _discoverDaemon(): Promise { if (!this._context || !this._runtime) return; const rootCtx = this._runtime.getRootContext!(); const rootEventsTopic = TopicRouter.events(''); const daemonRoot = this.getAttribute('daemon-root') ?? this.getAttribute('daemon-realm'); if (this._isHostBootstrapMode() || this._isHostServiceBootstrap()) { const hostRoot = daemonRoot || this.getAttribute('bus-root') || ''; if (!hostRoot) { this.dataset.daemonStatus = 'skipped'; rootCtx.transport.publish(rootEventsTopic, { op: 'daemonReady', payload: { root: '', realm: '', skipped: true, hostMode: true }, }); return; } this.dataset.daemonStatus = 'connected'; rootCtx.transport.publish(rootEventsTopic, { op: 'daemonReady', payload: { root: hostRoot, realm: hostRoot, hostMode: true }, }); rootCtx.transport.publish(rootEventsTopic, { op: 'hostReady', payload: { root: hostRoot }, }); void this._registerWithRuntimeRegistry(hostRoot); return; } if (!daemonRoot) { // No explicit runtime target configured — signal immediately so children enable without waiting this.dataset.daemonStatus = 'skipped'; rootCtx.transport.publish(rootEventsTopic, { op: 'daemonReady', payload: { root: '', realm: '', skipped: true }, }); return; } if (this._daemonDiscoveryInFlight) { return; } const controlPlaneRoot = this._controlPlaneRootForSingletons(daemonRoot); const runtimeRoot = transportRootForContext(this._context) ?? '(unknown)'; const natsWsUrl = this.getAttribute('nats-ws-url') ?? this.getAttribute('backbone') ?? '(none)'; // First attempt uses a shorter timeout (500ms) — if the registry is ready, // it responds in <10ms. If it's still loading, we fail fast and retry // rather than wasting 2.5s on the first attempt. const firstAttemptTimeoutMs = 500; const attemptTimeoutMs = 2_500; const retryDelaysMs = [100, 250, 500, 1_000, 2_000] as const; const finishDiscoveryProfile = createBrowserProfileSpan(HOST_PROFILE_SCOPE, 'daemonDiscovery', { daemonRoot: daemonRoot ?? '', controlPlaneRoot, runtimeRoot, }); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info(`[MatrixDSLHost] runtime registry discovery: targetRoot="${controlPlaneRoot}", authorityRoot="${daemonRoot}", runtimeRoot="${runtimeRoot}", natsWsUrl="${natsWsUrl}"`); const runtimeRegistryTarget = this._runtimeRegistryTargetForRoot(controlPlaneRoot); const discoveryTargets: Array<{ readonly target: string; readonly kind: 'runtime-registry'; }> = [{ target: runtimeRegistryTarget, kind: 'runtime-registry' }]; const discoveryOp = '$ping'; let lastError = 'Unknown error'; this._daemonDiscoveryInFlight = true; this._stopDaemonDiscoveryRetry(); try { for (let attempt = 0; attempt <= retryDelaysMs.length; attempt += 1) { let attemptSucceeded = false; for (const discoveryTarget of discoveryTargets) { try { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info( `[MatrixDSLHost] Sending ${discoveryOp} to "${discoveryTarget.target}" ` + `(attempt ${attempt + 1}/${retryDelaysMs.length + 1}, timeout ${attemptTimeoutMs}ms)...`, ); await RequestReply.execute, unknown>( this._context, discoveryTarget.target, discoveryOp, {}, { timeoutMs: attempt === 0 ? firstAttemptTimeoutMs : attemptTimeoutMs }, ); this.dataset.daemonStatus = 'connected'; this._daemonDiscoveryRetryDelayMs = 1_000; // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.info( `[MatrixDSLHost] ${discoveryOp} success — system.runtimes at "${controlPlaneRoot}" is reachable`, ); rootCtx.transport.publish(rootEventsTopic, { op: 'daemonReady', payload: { root: daemonRoot, realm: daemonRoot, authorityRuntime: true, registryTarget: runtimeRegistryTarget, }, }); // Register this browser tab as a runtime in the namespace registry. void this._registerWithRuntimeRegistry(controlPlaneRoot); finishDiscoveryProfile({ result: 'connected', attempts: attempt + 1, targetKind: discoveryTarget.kind, }); attemptSucceeded = true; return; } catch (err) { lastError = err instanceof Error ? err.message : String(err); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn( `[MatrixDSLHost] Host Service runtime discovery failed at "${discoveryTarget.target}": ${lastError}.`, ); } } if (!attemptSucceeded) { const retryDelayMs = retryDelaysMs[attempt]; if (retryDelayMs === undefined) break; // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn( `[MatrixDSLHost] Runtime registry discovery attempt ${attempt + 1}/${retryDelaysMs.length + 1} failed: ` + `${lastError}. Retrying in ${retryDelayMs}ms...`, ); await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); } } const backgroundRetryDelayMs = this._daemonDiscoveryRetryDelayMs; this._daemonDiscoveryRetryDelayMs = Math.min(this._daemonDiscoveryRetryDelayMs * 2, 10_000); this.dataset.daemonStatus = 'retrying'; // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn( `[MatrixDSLHost] Runtime registry discovery failed: root="${controlPlaneRoot}", authorityRoot="${daemonRoot}", runtimeRoot="${runtimeRoot}", ` + `natsWsUrl="${natsWsUrl}", error="${lastError}". Background retry in ${backgroundRetryDelayMs}ms.`, ); rootCtx.transport.publish(rootEventsTopic, { op: 'daemonError', payload: { root: daemonRoot, realm: daemonRoot, error: lastError, retryInMs: backgroundRetryDelayMs, }, }); this._scheduleDaemonDiscoveryRetry(backgroundRetryDelayMs); finishDiscoveryProfile({ result: 'retrying', retryInMs: backgroundRetryDelayMs, }); } finally { this._daemonDiscoveryInFlight = false; } } /** * Claim this browser tab's runtime and mounted child actors in system.registry. */ private async _registerWithRuntimeRegistry(controlPlaneRoot: string): Promise { if (!this._context || !this._runtime) return; const registrationGeneration = this._lifecycleGeneration; const finishRegistrationProfile = createBrowserProfileSpan(HOST_PROFILE_SCOPE, 'runtimeRegistry.claim', { controlPlaneRoot, }); if (!this.hasBackbone) { finishRegistrationProfile({ result: 'skipped-no-backbone', controlPlaneRoot, }); return; } if (this._shouldSkipBrowserRuntimeRegistryClaim(controlPlaneRoot)) { finishRegistrationProfile({ result: 'skipped-no-registry-authority', controlPlaneRoot, }); return; } const rootCtx = this._runtime.getRootContext?.(); if (!rootCtx) { finishRegistrationProfile({ result: 'skipped-no-root-context' }); return; } const hostBootstrapMode = this._isHostBootstrapMode(); const contextMount = this._context.mount ?? ''; const runtimeRoot = transportRootForContext(rootCtx) ?? ''; const app = this.getAttribute('app-name') || document.title.split(/\s*[—–-]\s*/)[0]?.trim() || 'browser'; const registryTarget = this._serviceRegistryTargetForRoot(controlPlaneRoot); const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const sessionAuthorityRoot = (this.dataset.sessionAuthorityRoot ?? '').trim(); const authorityRoot = selectedAuthorityRoot || sessionAuthorityRoot; const runtimeInstanceRoot = (this.dataset.runtimeInstanceRoot ?? '').trim() || runtimeRoot; const runtimeId = this.dataset.hostRuntimeId || this._runtimeIdFromMount(contextMount) || this._getOrCreateBrowserRuntimeId(authorityRoot || controlPlaneRoot || runtimeRoot, app); this.dataset.hostRuntimeId = runtimeId; const runtimeWireRoot = runtimeRoot || controlPlaneRoot; const runtimeMount = `system.runtimes.${runtimeId}`; const controlMount = runtimeMount; const contextIsRuntimeMount = contextMount === runtimeMount || contextMount.startsWith(`${runtimeMount}.`); const sessionMount = hostBootstrapMode ? contextIsRuntimeMount ? runtimeWireRoot || rootCtx.mount || 'unknown' : [runtimeWireRoot || controlPlaneRoot, contextMount].filter(Boolean).join('.') : runtimeRoot || rootCtx.mount || 'unknown'; await this._ensureBrowserRuntimeControl(runtimeId, runtimeMount, controlMount); const inventory = this._browserRuntimeInventory(runtimeId, contextMount, hostBootstrapMode); // Strip namespace root from sessionMount to get relative prefix for claims const relativeMount = hostBootstrapMode && contextMount ? contextMount : sessionMount.startsWith(controlPlaneRoot + '.') ? sessionMount.slice(controlPlaneRoot.length + 1) : sessionMount; const runtimeHref = typeof globalThis.location?.href === 'string' ? globalThis.location.href : (typeof document !== 'undefined' && typeof document.baseURI === 'string' ? document.baseURI : ''); const state: IBrowserRuntimeRegistryState = { registryTarget, namespaceRoot: controlPlaneRoot, runtimeId, runtimeRoot: runtimeWireRoot, app, contextMount, hostBootstrapMode, sessionMount, relativeMount, runtimeMount, controlMount, authorityRoot, sessionAuthorityRoot, selectedAuthorityRoot, runtimeInstanceRoot, runtimeHref, registeredAt: Date.now(), }; try { this._runtimeInventoryVersion = this._runtimeInventoryVersionForHost(); await this._replaceBrowserRuntimeClaims(state, inventory, this._runtimeInventoryVersion); // eslint-disable-next-line no-console -- browser bootstrap console.info(`[MatrixDSLHost] Claimed browser runtime "${runtimeId}" in system.registry`); if (registrationGeneration !== this._lifecycleGeneration || !this._context || !this._runtime) { finishRegistrationProfile({ result: 'skipped-after-cleanup', runtimeId, }); return; } this._browserRuntimeRegistryState = state; this._runtimeRegistryId = runtimeId; this._publishBrowserRuntimePresence(state, inventory, 'announce'); finishRegistrationProfile({ result: 'ok', runtimeId, }); } catch (err) { // eslint-disable-next-line no-console -- browser bootstrap console.warn(`[MatrixDSLHost] Browser runtime registry claim failed (non-fatal):`, err); finishRegistrationProfile({ result: 'error', error: err instanceof Error ? err.message : String(err), }); return; } this._browserRuntimeRenewTimer = setInterval(() => { const currentState = this._browserRuntimeRegistryState; if (!currentState || !this._context) { return; } RequestReply.execute( this._context, currentState.registryTarget, 'registry.claim.renew', { providerRuntimeId: currentState.runtimeId, ttlMs: 45_000, health: { status: 'ok' }, }, { timeoutMs: 5000 }, ).catch(() => { /* lease renewal failure is non-fatal */ }); this._publishBrowserRuntimePresence( currentState, this._browserRuntimeInventory( currentState.runtimeId, currentState.contextMount, currentState.hostBootstrapMode, ), 'heartbeat', ); }, 15_000); this._browserRuntimeReleaseHandler = () => { const context = this._context; const currentState = this._browserRuntimeRegistryState; if (!context || !currentState) return; try { RequestReply.sendToInbox(context, currentState.registryTarget, 'registry.release', { providerRuntimeId: currentState.runtimeId, }); } catch { // Best-effort unload cleanup; the registry lease expires missed releases. } this._publishBrowserRuntimePresence( currentState, this._browserRuntimeInventory( currentState.runtimeId, currentState.contextMount, currentState.hostBootstrapMode, ), 'departed', ); }; window.addEventListener('beforeunload', this._browserRuntimeReleaseHandler); } /** * Publish browser runtime presence to the runtime-directory authority * (`system.runtimes`). Browser tabs claim child actors in `system.registry` * via `registry.claim`, but `system.runtimes.runtimes.list` is fed by * runtime presence — without this call, browser tabs are invisible to * Director Topology, Host Ops Workloads, and any other consumer of the * runtime directory. * * We route through the explicit `runtimes.presence.update` op via * `RequestReply.execute("${controlPlaneRoot}/system.runtimes", ...)`, NOT through * `publishRuntimePresence(ctx, record, event)`. The pub/sub path uses bare * `$runtime.*` topics on the local transport — in federated HiveCast mode * the tab realm is a synthetic per-tab root (`s-b-root-*`) so the publish * lands on a NATS subject prefix the `RuntimeManagerActor` at the * authority root never subscribes to. Routed RequestReply.execute uses * the same federation-peer cross-routing that `registry.claim` already * relies on. * * Best-effort: presence publication failures are non-fatal because the * registry claim already carries the authoritative liveness of the tab. */ private _publishBrowserRuntimePresence( state: IBrowserRuntimeRegistryState, inventory: readonly IBrowserRuntimeInventoryEntry[], event: RuntimePresenceEventV1, ): void { const context = this._context; if (!context) return; try { const presenceInventory = mapBrowserInventoryToPresence(inventory); const now = Date.now(); const record: RuntimePresenceRecordV1 = { version: '1', namespaceRoot: state.namespaceRoot, runtimeId: state.runtimeId, runtimeType: 'browser', runtimeRoot: state.runtimeRoot, ...(state.authorityRoot.length > 0 ? { authorityRoot: state.authorityRoot } : {}), app: state.app, claims: [{ prefix: state.runtimeMount, kind: 'local-only' }], inventoryMode: 'embedded', ...(presenceInventory.length > 0 ? { inventory: presenceInventory } : {}), controlMount: state.controlMount, heartbeatTtlMs: 45_000, registeredAt: state.registeredAt, lastHeartbeat: now, health: { status: 'ok', updatedAt: now }, metadata: { source: 'browser-runtime', surface: 'webapp', ownerKind: 'client-session', packageRef: state.app, runtimeInstanceRoot: state.runtimeInstanceRoot, // Render as " (browser)" so it's distinguishable from // the server-side runtime sharing the same packageRef. displayName: `${state.app} (browser)`, ...(state.sessionAuthorityRoot.length > 0 ? { sessionAuthorityRoot: state.sessionAuthorityRoot } : {}), ...(state.selectedAuthorityRoot.length > 0 ? { selectedAuthorityRoot: state.selectedAuthorityRoot } : {}), ...(state.runtimeHref.length > 0 ? { href: state.runtimeHref.slice(0, 200) } : {}), ...(typeof navigator !== 'undefined' ? { userAgent: navigator.userAgent.slice(0, 120) } : {}), }, }; const target = this._runtimeRegistryTargetForRoot(state.namespaceRoot); RequestReply.execute( context, target, 'runtimes.presence.update', { record, event }, { timeoutMs: 5000 }, ).catch((err) => { // eslint-disable-next-line no-console -- browser bootstrap console.warn(`[MatrixDSLHost] Browser runtime presence (${event}) failed (non-fatal):`, err); }); } catch (err) { // eslint-disable-next-line no-console -- browser bootstrap console.warn(`[MatrixDSLHost] Browser runtime presence publish (${event}) failed (non-fatal):`, err); } } private async _replaceBrowserRuntimeClaims( state: IBrowserRuntimeRegistryState, inventory: readonly IBrowserRuntimeInventoryEntry[], inventoryVersion: number, ): Promise { if (!this._context) { return; } const nextComponentIds = new Set(inventory.map((entry) => browserInventoryComponentId(state, entry))); for (const entry of inventory) { await this._claimBrowserRuntimeEntry(state, entry, inventory.length, inventoryVersion); } for (const componentId of this._browserRuntimeClaimedComponentIds) { if (nextComponentIds.has(componentId)) { continue; } await RequestReply.execute( this._context, state.registryTarget, 'registry.release', { componentId, providerRuntimeId: state.runtimeId, }, { timeoutMs: 5000 }, ); } this._browserRuntimeClaimedComponentIds = nextComponentIds; } private async _claimBrowserRuntimeEntry( state: IBrowserRuntimeRegistryState, entry: IBrowserRuntimeInventoryEntry, actorCount: number, inventoryVersion: number, ): Promise { if (!this._context) { return; } const kind = browserInventoryRegistryKind(entry); await RequestReply.execute( this._context, state.registryTarget, 'registry.claim', { namespaceRoot: state.namespaceRoot, mount: entry.mount, kind, componentId: browserInventoryComponentId(state, entry), componentType: entry.type, providerRuntimeId: state.runtimeId, runtimeWireRoot: state.runtimeRoot, localMount: entry.mount, packageName: state.app, runtimeId: state.runtimeId, packageId: state.app, mode: 'local-only', ttlMs: 45_000, health: { status: 'ok' }, metadata: { source: 'browser-runtime-registry-claim', runtimeType: 'browser', ownerKind: 'client-session', runtimeRoot: state.runtimeRoot, runtimeInstanceRoot: state.runtimeInstanceRoot, sessionMount: state.sessionMount, relativeMount: state.relativeMount, controlMount: state.controlMount, actorCount, inventoryVersion, surface: entry.surface, ...(entry.description ? { description: entry.description } : {}), ...(state.authorityRoot.length > 0 ? { addressRoot: state.authorityRoot, authorityRoot: state.authorityRoot, } : {}), ...(state.sessionAuthorityRoot.length > 0 ? { sessionAuthorityRoot: state.sessionAuthorityRoot } : {}), ...(state.selectedAuthorityRoot.length > 0 ? { selectedAuthorityRoot: state.selectedAuthorityRoot } : {}), ...(state.runtimeHref.length > 0 ? { href: state.runtimeHref.slice(0, 200) } : {}), ...(typeof navigator !== 'undefined' ? { userAgent: navigator.userAgent.slice(0, 120) } : {}), }, }, { timeoutMs: 5000 }, ); } private _runtimeRegistryTargetForRoot(root: string): string { const trimmed = root.trim(); return trimmed ? `${trimmed}/system.runtimes` : 'system.runtimes'; } private _serviceRegistryTargetForRoot(root: string): string { const trimmed = root.trim(); return trimmed ? `${trimmed}/system.registry` : 'system.registry'; } private _shouldSkipBrowserRuntimeRegistryClaim(controlPlaneRoot: string): boolean { if (this.dataset.authorityCoordinatorActive === 'false') { return true; } const targetRoot = controlPlaneRoot.trim(); const platformRoot = ( this.dataset.platformServiceRoot ?? this.dataset.platformRoot ?? '' ).trim(); if (!targetRoot || !platformRoot || targetRoot !== platformRoot) { return false; } if (this.dataset.isPlatformAdmin === 'true') { return false; } if (this.dataset.authenticated !== 'true' && this._isAuthenticated !== true) { return false; } const candidateAuthorityRoots = [ this.dataset.selectedAuthorityRoot, this.dataset.sessionAuthorityRoot, this.dataset.principalAuthorityRoot, ].map((root) => (root ?? '').trim()); return !candidateAuthorityRoots.some((root) => root.length > 0 && root !== platformRoot); } private _controlPlaneRootForSingletons(authorityRoot: string | null | undefined): string { const selectedRoot = typeof this.dataset.selectedAuthorityRoot === 'string' ? this.dataset.selectedAuthorityRoot.trim() : ''; if (selectedRoot) { return selectedRoot; } const principalRoot = typeof this.dataset.principalAuthorityRoot === 'string' ? this.dataset.principalAuthorityRoot.trim() : ''; if (principalRoot) { return principalRoot; } return typeof authorityRoot === 'string' ? authorityRoot.trim() : ''; } private _runtimeIdFromMount(mount: string): string { const parts = mount.split('.').filter(Boolean); return parts.find((part) => part.startsWith(BROWSER_RUNTIME_ID_PREFIX)) ?? ''; } /** * Phase B bus discovery resolves identity and service metadata after first render. * * This must not block child mounting. Apps already tolerate late identity * attributes on the host and should render while the bus discovery completes. */ private async _runPhaseBBusDiscovery(usedBootstrap: boolean): Promise { if (!usedBootstrap || !this._context || !this._transport || !this._runtime) return; const finishPhaseBProfile = createBrowserProfileSpan(HOST_PROFILE_SCOPE, 'phaseB.discovery', { usedBootstrap, }); const daemonRoot = this.getAttribute('daemon-root') ?? ''; const serviceRoot = this.dataset.platformServiceRoot ?? this.dataset.platformRoot ?? daemonRoot; const explicitAuthorityRoot = this.dataset.explicitAuthorityRoot; if (explicitAuthorityRoot) { // Explicit authority URLs already declare the target identity in the // path. Do not probe platform-local actors such as system.auth or // system.directory here; that turns a shareable user URL into a hidden // HiveCast-local lookup and can wait on unrelated platform timeouts. console.info('[MatrixDSLHost] Phase B platform probe skipped for explicit authority URL - retaining explicit authority root'); finishPhaseBProfile({ result: 'skipped-explicit-authority', daemonRoot, serviceRoot, authorityRoot: explicitAuthorityRoot, }); return; } const authTarget = serviceRoot ? `${serviceRoot}/system.auth` : 'system.auth'; const dirTarget = serviceRoot ? `${serviceRoot}/system.directory` : 'system.directory'; const busTokenPayload = this.dataset.busToken ? { busToken: this.dataset.busToken } : {}; const rootCtx = this._runtime.getRootContext!(); const bootstrapAuthenticated = (this.dataset.authenticated === 'true') || this._isAuthenticated === true; if (!bootstrapAuthenticated && !this.dataset.busToken) { console.info('[MatrixDSLHost] Phase B platform probe skipped for anonymous bootstrap'); finishPhaseBProfile({ result: 'skipped-anonymous', daemonRoot, serviceRoot, }); return; } const isHostedLeafTarget = this.dataset.runtimeTargetKind === 'leaf' && daemonRoot.length > 0 && serviceRoot.length > 0 && daemonRoot !== serviceRoot; const selectedAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim(); const userAuthorityRoot = selectedAuthorityRoot || (this.dataset.principalAuthorityRoot ?? '').trim(); const isPlatformAdmin = this.dataset.isPlatformAdmin === 'true'; const isNormalUserAuthoritySession = bootstrapAuthenticated && userAuthorityRoot.length > 0 && serviceRoot.length > 0 && userAuthorityRoot !== serviceRoot && !isPlatformAdmin; if (isNormalUserAuthoritySession) { console.info('[MatrixDSLHost] Phase B platform probe skipped for user authority session — retaining bootstrap identity'); finishPhaseBProfile({ result: 'skipped-user-authority', daemonRoot, serviceRoot, authorityRoot: userAuthorityRoot, }); return; } if ( bootstrapAuthenticated && ( this._isHostBootstrapMode() || this._isHostServiceBootstrap() || this._shouldRetainBootstrapIdentity(daemonRoot) || this._shouldUseRuntimeRegistryDiscovery(daemonRoot) ) ) { console.info('[MatrixDSLHost] Phase B service probe skipped for Host bootstrap — retaining bootstrap identity'); finishPhaseBProfile({ result: 'skipped-host-bootstrap', daemonRoot, serviceRoot, }); return; } if (bootstrapAuthenticated && isHostedLeafTarget) { // Leaf-targeted hosted apps already have HTTP-session identity from // bootstrap. The browser transport is scoped to the leaf account, so a // platform Phase B RR would require a cross-account reply path and only // produces a non-fatal timeout. console.info('[MatrixDSLHost] Phase B platform probe skipped for hosted leaf runtime — retaining bootstrap identity'); finishPhaseBProfile({ result: 'skipped-leaf-runtime', daemonRoot, serviceRoot, }); return; } // Phase B: auth + directory in PARALLEL (saves ~400ms vs sequential) const authPromise = this._executePhaseBRequest, { ok: boolean; principalId?: string; displayName?: string; root?: string; tier?: string; authenticated?: boolean; }>(authTarget, 'auth.whoami', busTokenPayload).catch((err) => { if (bootstrapAuthenticated) { console.info('[MatrixDSLHost] Phase B auth probe deferred — retaining bootstrap identity:', err); } else { console.warn('[MatrixDSLHost] Phase B auth probe failed:', err); } return null; }); const dirPromise = this._executePhaseBRequest, { ok: boolean; root?: string; services?: Array<{ name: string; mount: string }>; parentRoots?: unknown[]; }>(dirTarget, 'directory.whoami', busTokenPayload).catch((err) => { console.info('[MatrixDSLHost] Phase B directory probe deferred (non-fatal):', err); return null; }); const [authResult, dirResult] = await Promise.all([authPromise, dirPromise]); if (authResult?.ok) { console.info(`[MatrixDSLHost] Phase B: ${authTarget} → principalId="${authResult.principalId}", authenticated=${authResult.authenticated}, root="${authResult.root}"`); if (authResult.principalId) { this.dataset.principalId = authResult.principalId; rootCtx.setService('principal-id', authResult.principalId); } const orgRoot = this.dataset.platformRoot || serviceRoot || authResult.root || ''; if (orgRoot) { this.dataset.orgId = orgRoot; rootCtx.setService('org-id', orgRoot); } if (authResult.root) { this.dataset.workspaceId = authResult.root; rootCtx.setService('workspace-id', authResult.root); } if (authResult.displayName) this.dataset.displayName = authResult.displayName; this.dataset.authenticated = String(authResult.authenticated ?? false); } if (dirResult?.ok) { console.info(`[MatrixDSLHost] Phase B: ${dirTarget} → root="${dirResult.root}", services=${dirResult.services?.length ?? 0}`); if (dirResult.parentRoots) { this.setAttribute('parent-roots', JSON.stringify(dirResult.parentRoots)); this.setAttribute('parent-realms', JSON.stringify(dirResult.parentRoots)); } } if (this.dataset.busToken) { this._startBusTokenRefresh(authTarget); } finishPhaseBProfile({ authOk: authResult?.ok === true, dirOk: dirResult?.ok === true, daemonRoot, serviceRoot, }); } private async _executePhaseBRequest, TResult>( target: string, op: string, payload: TPayload, ): Promise { const retryDelaysMs = [0, 200, 500] as const; let lastError: unknown; for (const delayMs of retryDelaysMs) { if (delayMs > 0) { await new Promise((resolve) => setTimeout(resolve, delayMs)); } try { return await RequestReply.execute( this._context!, target, op, payload, { timeoutMs: 5000 }, ); } catch (error) { lastError = error; } } throw lastError instanceof Error ? lastError : new Error(`Phase B request failed: ${String(lastError)}`); } // ═══════════════════════════════════════════════════════════════════════════ // Cleanup // ═══════════════════════════════════════════════════════════════════════════ /** * Start a 4-minute interval to refresh the bus token before its 5-min expiry. * Calls system.auth auth.refresh on the bus with the current token. */ private _startBusTokenRefresh(authTarget: string): void { this._stopBusTokenRefresh(); if (this._shouldSkipBusTokenRefreshForTarget(authTarget)) { console.info('[MatrixDSLHost] Bus token refresh skipped for platform auth target in user authority session'); return; } const REFRESH_INTERVAL_MS = 4 * 60 * 1000; // 4 minutes this._busTokenRefreshTimer = setInterval(async () => { const currentToken = this.dataset.busToken; if (!currentToken || !this._context) { this._stopBusTokenRefresh(); return; } try { const result = await RequestReply.execute, { ok: boolean; busToken?: string; expiresAt?: number; principalId?: string; }>(this._context, authTarget, 'auth.refresh', { busToken: currentToken }, { timeoutMs: 5000 }); if (result?.ok && result.busToken) { this.dataset.busToken = result.busToken; this._context.setService('bus-token', result.busToken); this._installScopedActorServices(this._context, this.dataset.runtimeRoot ?? this.dataset.sessionRoot ?? null); // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.debug(`[MatrixDSLHost] Bus token refreshed for ${result.principalId}`); } else { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn('[MatrixDSLHost] Bus token refresh failed — clearing token'); delete this.dataset.busToken; this.dataset.authenticated = 'false'; this._stopBusTokenRefresh(); } } catch (err) { // eslint-disable-next-line no-console -- browser bootstrap, not an actor console.warn('[MatrixDSLHost] Bus token refresh error:', err); } }, REFRESH_INTERVAL_MS); } private _shouldSkipBusTokenRefreshForTarget(authTarget: string): boolean { const targetRoot = authTarget.includes('/') ? authTarget.slice(0, authTarget.indexOf('/')).trim() : ''; if (!targetRoot) { return false; } const userAuthorityRoot = (this.dataset.selectedAuthorityRoot ?? '').trim() || (this.dataset.principalAuthorityRoot ?? '').trim(); return (this.dataset.authenticated === 'true' || this._isAuthenticated === true) && this.dataset.isPlatformAdmin !== 'true' && userAuthorityRoot.length > 0 && targetRoot !== userAuthorityRoot; } private _installScopedActorServices(context: IMatrixContext, runtimeInstanceRoot: string | null): void { const platformRoot = ( this.dataset.platformServiceRoot ?? this.dataset.platformRoot ?? '' ).trim(); const userSpaceRoot = ( this.dataset.selectedAuthorityRoot ?? this.dataset.sessionAuthorityRoot ?? this.dataset.principalAuthorityRoot ?? '' ).trim(); const accountRoot = ( this.dataset.sessionAuthorityRoot ?? this.dataset.principalAuthorityRoot ?? this.dataset.selectedAuthorityRoot ?? '' ).trim(); const addressContext: RuntimeAddressContext = { runtimeInstanceRoot: runtimeInstanceRoot && runtimeInstanceRoot.trim().length > 0 ? runtimeInstanceRoot.trim() : null, accountRoot: accountRoot.length > 0 ? accountRoot : null, userSpaceRoot: userSpaceRoot.length > 0 ? userSpaceRoot : null, platformRoot, isPlatformAdmin: this.dataset.isPlatformAdmin === 'true', principalId: this.dataset.principalId ?? null, busToken: this.dataset.busToken ?? null, }; const callClient = new ActorCallClient(context, addressContext); const resolver = new ScopedIntentResolver(callClient, WELL_KNOWN_SERVICE_BINDINGS); const services = new ActorServices(resolver, addressContext); context.setService(RUNTIME_ADDRESS_CONTEXT, addressContext); context.setService(ACTOR_CALL_CLIENT, callClient); context.setService(SCOPED_INTENT_RESOLVER, resolver); context.setService(ACTOR_SERVICES, services); } private _stopBusTokenRefresh(): void { if (this._busTokenRefreshTimer) { clearInterval(this._busTokenRefreshTimer); this._busTokenRefreshTimer = null; } } private _startHostedRuntimeTargetRefresh(): void { this._stopHostedRuntimeTargetRefresh(); this._hostedRuntimeTargetRefreshTimer = setInterval(() => { void this._refreshHostedRuntimeTargetMetadata(); }, HOSTED_RUNTIME_TARGET_REFRESH_INTERVAL_MS); } private _stopHostedRuntimeTargetRefresh(): void { if (this._hostedRuntimeTargetRefreshTimer) { clearInterval(this._hostedRuntimeTargetRefreshTimer); this._hostedRuntimeTargetRefreshTimer = null; } this._hostedRuntimeTargetRefreshInFlight = false; } private _scheduleDaemonDiscoveryRetry(delayMs: number): void { if (this._daemonDiscoveryRetryTimer || !this._context || !this._runtime) { return; } const retryTimer = setTimeout(() => { this._daemonDiscoveryRetryTimer = null; void this._discoverDaemon(); }, delayMs); if (typeof retryTimer === 'object' && retryTimer !== null && 'unref' in retryTimer) { const unref = retryTimer.unref; if (typeof unref === 'function') { unref.call(retryTimer); } } this._daemonDiscoveryRetryTimer = retryTimer; } private _stopDaemonDiscoveryRetry(): void { if (this._daemonDiscoveryRetryTimer) { clearTimeout(this._daemonDiscoveryRetryTimer); this._daemonDiscoveryRetryTimer = null; } this._daemonDiscoveryRetryDelayMs = 1_000; } private _cleanup(): void { this._lifecycleGeneration += 1; const transportToDisconnect = this._transport; this._stopBusTokenRefresh(); this._stopHostedRuntimeTargetRefresh(); this._stopDaemonDiscoveryRetry(); delete this.dataset.sessionRoot; delete this.dataset.runtimeRoot; delete this.dataset.daemonStatus; if (this._observer) { this._observer.disconnect(); this._observer = null; } if (transportToDisconnect) { void transportToDisconnect.disconnect().catch((error) => { // eslint-disable-next-line no-console -- browser host teardown has no actor context console.warn('[MatrixDSLHost] Transport disconnect during cleanup failed:', error); }); } if (this._joinUnsub) { this._joinUnsub(); this._joinUnsub = null; } if (this._browserRuntimeRenewTimer) { clearInterval(this._browserRuntimeRenewTimer); this._browserRuntimeRenewTimer = null; } if (this._runtimeInventoryClaimRefreshTimer) { clearTimeout(this._runtimeInventoryClaimRefreshTimer); this._runtimeInventoryClaimRefreshTimer = null; } if (this._browserRuntimeRegistryState && this._runtime) { this._publishBrowserRuntimePresence( this._browserRuntimeRegistryState, this._browserRuntimeInventory( this._browserRuntimeRegistryState.runtimeId, this._browserRuntimeRegistryState.contextMount, this._browserRuntimeRegistryState.hostBootstrapMode, ), 'departed', ); } this._browserRuntimeRegistryState = null; this._browserRuntimeClaimedComponentIds.clear(); this._runtimeRegistryId = ''; this._runtimeControlStartedAt = ''; if (this._browserRuntimeReleaseHandler) { if (typeof window !== 'undefined') { window.removeEventListener('beforeunload', this._browserRuntimeReleaseHandler); } this._browserRuntimeReleaseHandler = null; } if (this._peer) { this._peer.stop(); this._peer = null; } this._context = null; this._runtime = null; this._transport = null; this._rawAdapter = null; this._routingPeer = null; this._wrapTransport = null; this._initialized = false; } } const ROUTE_KEY_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/; const RESERVED_ROUTE_KEYS = new Set([ 'api', 'auth', 'login', 'logout', 'signup', 'setup', 'settings', 'apps', 'app', 'assets', 'static', 'branding', 'nats-ws', 'matrix', 'system', 'admin', 'root', 'support', 'docs', 'help', 'billing', 'status', 'healthz', 'well-known', 'favicon.ico', 'manifest.json', 'hivecast', 'open-matrix', 'me', ]); function isValidRouteKey(routeKey: string): boolean { return ROUTE_KEY_PATTERN.test(routeKey) && !RESERVED_ROUTE_KEYS.has(routeKey); } function routeKeyFromPublicNamespace(publicNamespace: string): string | null { const normalized = publicNamespace.trim().toLowerCase(); if (!normalized.startsWith('space.')) { return null; } const routeKey = normalized.slice('space.'.length); return isValidRouteKey(routeKey) ? routeKey : null; } function matrixChildElementHooks(element: HTMLElement): IMatrixChildElementHooks { return element as IMatrixChildElementHooks; } function matrixActorElementVersionSource(element: HTMLElement): IMatrixActorElementVersionSource { return element as IMatrixActorElementVersionSource; } function matrixRootContextReceiver(element: Element): IMatrixRootContextReceiver { return element as IMatrixRootContextReceiver; } function transportRootForContext(context: IMatrixContext): string | undefined { return context.transport.root || context.transport.realm; } function browserInventoryRegistryKind(entry: IBrowserRuntimeInventoryEntry): ServiceRegistryEntryKindV1 { return entry.surface === 'runtime' ? 'runtime' : 'actor'; } function browserInventoryComponentId( state: IBrowserRuntimeRegistryState, entry: IBrowserRuntimeInventoryEntry, ): string { return browserInventoryRegistryKind(entry) === 'runtime' ? `runtime:${state.runtimeId}` : `runtime:${state.runtimeId}:actor:${entry.mount}`; } function mapBrowserInventoryToPresence( inventory: readonly IBrowserRuntimeInventoryEntry[], ): RuntimeInventoryEntryV1[] { return inventory.map((entry) => ({ mount: entry.mount, type: entry.type, surface: entry.surface === 'dom' ? 'dom' : 'webapp', runtimeId: entry.runtimeId, claimKind: 'local-only', ...(entry.description ? { description: entry.description } : {}), })); } function browserRouterTransportAdapter( adapter: BrowserRouterResult['adapter'], peer: FederationPeer, ): ITransportAdapter { return { get root() { return adapter.root ?? peer.localRoot; }, get realm() { return adapter.realm ?? peer.localRoot; }, publish: adapter.publish, subscribe: adapter.subscribe, getWireTopicInfo: adapter.getWireTopicInfo, getUnderlyingClient: () => adapter.getUnderlyingClient?.() ?? peer, disconnect: async () => { peer.stop(); }, }; } function serviceRegistryEntry(value: unknown): ServiceRegistryEntryV1 | null { const record = registryRecord(value); const kind = serviceRegistryEntryKind(record.kind); const placement = registryPlacement(record.placement); const claim = registryClaim(record.claim); const health = registryHealth(record.health); if ( record.version !== '1' || typeof record.namespaceRoot !== 'string' || typeof record.mount !== 'string' || kind === null || placement === null || claim === null || health === null || typeof record.ttlMs !== 'number' ) { return null; } return { version: '1', namespaceRoot: record.namespaceRoot, mount: record.mount, kind, ...(registryService(record.service) ? { service: registryService(record.service) } : {}), ...(registryApp(record.app) ? { app: registryApp(record.app) } : {}), placement, claim, health, ttlMs: record.ttlMs, ...(isRegistryRecord(record.metadata) ? { metadata: record.metadata } : {}), }; } function serviceRegistryEntryKind(value: unknown): ServiceRegistryEntryKindV1 | null { return value === 'actor' || value === 'service' || value === 'app' || value === 'feed' || value === 'runtime' || value === 'gateway' || value === 'connector' || value === 'resource' ? value : null; } function registryService(value: unknown): ServiceRegistryEntryV1['service'] | undefined { const record = registryRecord(value); if (Object.keys(record).length === 0) { return undefined; } return { accepts: registryStringArray(record.accepts), emits: registryStringArray(record.emits), streams: registryStringArray(record.streams), capabilities: [], intents: [], }; } function registryApp(value: unknown): ServiceRegistryEntryV1['app'] | undefined { const record = registryRecord(value); const route = registryString(record.route); const title = registryString(record.title); if (!route || !title) { return undefined; } return { route, title, ...(registryString(record.icon) ? { icon: registryString(record.icon) } : {}), surface: registryAppSurface(record.surface), openable: record.openable !== false, ...(registryString(record.appName) ? { appName: registryString(record.appName) } : {}), }; } function registryPlacement(value: unknown): ServiceRegistryEntryV1['placement'] | null { if (!isRegistryRecord(value)) { return null; } return { ...(registryString(value.deviceId) ? { deviceId: registryString(value.deviceId) } : {}), ...(registryString(value.hostId) ? { hostId: registryString(value.hostId) } : {}), ...(registryString(value.runtimeId) ? { runtimeId: registryString(value.runtimeId) } : {}), ...(registryString(value.packageId) ? { packageId: registryString(value.packageId) } : {}), ...(registryString(value.actorId) ? { actorId: registryString(value.actorId) } : {}), ...(registryString(value.deploymentInstanceId) ? { deploymentInstanceId: registryString(value.deploymentInstanceId) } : {}), ...(registryString(value.instanceName) ? { instanceName: registryString(value.instanceName) } : {}), ...(registryString(value.packageRef) ? { packageRef: registryString(value.packageRef) } : {}), }; } function registryClaim(value: unknown): ServiceRegistryEntryV1['claim'] | null { if (!isRegistryRecord(value)) { return null; } const mode = registryClaimMode(value.mode); const result = registryClaimResult(value.result); const ownerComponentId = registryString(value.ownerComponentId); const loadBalancing = registryLoadBalancing(value.loadBalancing); if ( mode === null || result === null || !ownerComponentId || loadBalancing === null || typeof value.claimedAt !== 'number' || typeof value.renewedAt !== 'number' || typeof value.expiresAt !== 'number' ) { return null; } return { mode, result, ownerComponentId, ...(registryString(value.electionGroup) ? { electionGroup: registryString(value.electionGroup) } : {}), ...(registryString(value.leaderId) ? { leaderId: registryString(value.leaderId) } : {}), loadBalancing, claimedAt: value.claimedAt, renewedAt: value.renewedAt, expiresAt: value.expiresAt, }; } function registryHealth(value: unknown): ServiceRegistryEntryV1['health'] | null { if (!isRegistryRecord(value)) { return null; } const status = registryHealthStatus(value.status); if (status === null || typeof value.updatedAt !== 'number') { return null; } return { status, ...(registryString(value.reason) ? { reason: registryString(value.reason) } : {}), updatedAt: value.updatedAt, }; } function registryRecord(value: unknown): Record { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; } function bootstrapNodeRoleActive(value: unknown, roleName: string): boolean | undefined { if (!value || typeof value !== 'object' || Array.isArray(value)) { return undefined; } const role = (value as Record)[roleName]; if (!role || typeof role !== 'object' || Array.isArray(role)) { return undefined; } const active = (role as Record).active; return typeof active === 'boolean' ? active : undefined; } function isRegistryRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function registryArtifact(value: unknown): Record { return registryRecord(value); } function registryString(value: unknown): string | undefined { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined; } function registryStringArray(value: unknown): string[] { return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') .map((item) => item.trim()) .filter(Boolean) : []; } function registryAppSurface(value: unknown): NonNullable['surface'] { return value === 'panel' || value === 'modal' || value === 'widget' ? value : 'page'; } function registryClaimMode(value: unknown): ServiceRegistryEntryV1['claim']['mode'] | null { return value === 'authoritative' || value === 'proxy' || value === 'mirror' || value === 'local-only' || value === 'pool-member' ? value : null; } function registryClaimResult(value: unknown): ServiceRegistryEntryV1['claim']['result'] | null { return value === 'accepted' || value === 'rejected' || value === 'collision' || value === 'joined-pool' || value === 'standby' || value === 'leader' || value === 'mirror' || value === 'local-only' ? value : null; } function registryLoadBalancing(value: unknown): ServiceRegistryEntryV1['claim']['loadBalancing'] | null { return value === 'none' || value === 'round-robin' || value === 'least-loaded' || value === 'broadcast' ? value : null; } function registryHealthStatus(value: unknown): ServiceRegistryEntryV1['health']['status'] | null { return value === 'ok' || value === 'degraded' || value === 'error' || value === 'unknown' || value === 'starting' || value === 'stopped' ? value : null; } function registrySourceKind(value: unknown): ComponentSourceKind | undefined { const sourceKind = registryString(value); return sourceKind === 'installed-webapp' || sourceKind === 'external-webapp' ? sourceKind : undefined; } function registryRegistrationSource(value: unknown): ComponentRegistrationSource | undefined { const registrationSource = registryString(value); return registrationSource === 'package-manifest' || registrationSource === 'daemon-config' || registrationSource === 'deployment-spec' ? registrationSource : undefined; } function registryArtifactFreshness(value: unknown): ComponentArtifactFreshness | undefined { const freshness = registryString(value); return freshness === 'current' || freshness === 'stale' || freshness === 'incomplete' || freshness === 'missing-manifest' || freshness === 'invalid-manifest' || freshness === 'unverifiable' || freshness === 'missing-output' ? freshness : undefined; } // ═══════════════════════════════════════════════════════════════════════════ // Custom Element Registration // ═══════════════════════════════════════════════════════════════════════════ if (typeof customElements !== 'undefined' && !customElements.get('matrix-dsl-host')) { customElements.define('matrix-dsl-host', MatrixDSLHost); }