import { createEveCommandIntent, emptyState, renderEveSurface, resolveRequiredPluginAdapters, } from "../packages/eve-browser-lowering/dist/index.js"; import { compileEveDsl } from "./eve-dsl.js"; import { mergeProviderAdvertisement } from "./provider-advertisements.mjs"; import { createHermodrProviderTargets, selectInitialProviderTarget, } from "./hermodr-provider-catalog.mjs"; const statusEl = document.querySelector("#status"); const app = document.querySelector("#app"); const surfaceId = document.querySelector("#surface-id"); const surfaceVersion = document.querySelector("#surface-version"); const providerSelect = document.querySelector("#provider-select"); const providerMeta = document.querySelector("#provider-meta"); let providers = []; let currentProvider; let liveHermodr = false; let openProviderGeneration = 0; let providerRetryTimer; providerSelect.addEventListener("change", () => { const provider = providers.find(candidate => (candidate.targetId || candidate.providerId) === providerSelect.value); if (provider) void openProvider(provider); }); void bootProviders(); async function bootProviders() { clearTimeout(providerRetryTimer); let liveProviders = []; try { liveProviders = await loadHermodrProviders(); } catch { // The fallback below is deliberately visible; a failed live catalog must not strand the renderer at boot. } liveHermodr = liveProviders.length > 0; try { providers = liveHermodr ? liveProviders : await Promise.all((await loadLocalProviderCatalog()).map(loadProviderAdvertisement)); } catch (error) { showProviderLoadFailure(error); providerRetryTimer = setTimeout(() => void bootProviders(), 3_000); return; } if (!providers.length) { showProviderLoadFailure(new Error("No provider targets are currently visible.")); providerRetryTimer = setTimeout(() => void bootProviders(), 3_000); return; } providerSelect.disabled = false; providerSelect.replaceChildren(...providers.map(provider => { const option = document.createElement("option"); option.value = provider.targetId || provider.providerId; option.textContent = provider.title || provider.providerId; return option; })); const params = new URLSearchParams(location.search); const requestedProviderId = params.get("surface") || params.get("provider") || ""; const firstProduct = liveHermodr ? selectInitialProviderTarget(providers, requestedProviderId) : providers.find(provider => provider.providerId === requestedProviderId || provider.aliases?.includes(requestedProviderId)) || providers[0]; providerSelect.value = firstProduct.targetId || firstProduct.providerId; await openProvider(firstProduct); if (!liveHermodr) { providerRetryTimer = setTimeout(() => void bootProviders(), 3_000); } } function showProviderLoadFailure(error) { const detail = error instanceof Error ? error.message : String(error); providerSelect.replaceChildren(); providerSelect.disabled = true; providerMeta.textContent = "provider catalog unavailable; retrying"; statusEl.textContent = "provider catalog unavailable"; surfaceId.textContent = "surface none"; surfaceVersion.textContent = "retrying"; app.replaceChildren(emptyState(detail)); } async function loadLocalProviderCatalog() { const response = await fetch("./local-provider-catalog.json", { cache: "no-store" }); const catalog = await response.json(); return catalog.providers || []; } async function loadHermodrProviders() { try { const response = await fetch("/hermodr/verse/catalog", { cache: "no-store" }); if (!response.ok) return []; const catalog = await response.json(); return createHermodrProviderTargets(catalog); } catch { return []; } } async function loadProviderAdvertisement(provider) { if (!provider.advertisement) return provider; const response = await fetch(provider.advertisement); const advertisement = await response.json(); return mergeProviderAdvertisement(provider, advertisement); } async function openProvider(provider) { const generation = ++openProviderGeneration; currentProvider = provider; providerMeta.textContent = `${provider.kind || "provider"} | ${provider.providerId} | ${provider.freshness?.state || "unknown"}`; surfaceId.textContent = provider.providerId; surfaceVersion.textContent = "opening"; const surface = provider.surfaces?.[0]; if (!surface) { statusEl.textContent = `${provider.title || provider.providerId} has no advertised surface`; app.replaceChildren(emptyState("No advertised surface")); return; } if (surface.transport === "hermodr-surface") { try { const state = await fetchHermodrSurface(surface.url); if (generation !== openProviderGeneration) return; renderSurface(state, "hermodr cultmesh"); } catch (error) { if (generation !== openProviderGeneration) return; statusEl.textContent = `${provider.title || provider.providerId} failed to load`; surfaceVersion.textContent = "error"; app.replaceChildren(emptyState(error instanceof Error ? error.message : String(error))); } return; } if (surface.transport === "local-eve-dsl") { const response = await fetch(surface.url); const state = compileEveDsl(await response.text()); if (generation !== openProviderGeneration) return; renderSurface(state, "local dsl"); return; } const response = await fetch(surface.url); const state = await response.json(); if (generation !== openProviderGeneration) return; renderSurface(state, "local fixture"); } async function fetchHermodrSurface(url) { let lastError; for (let attempt = 0; attempt < 4; attempt += 1) { try { const response = await fetch(url, { cache: "no-store" }); const state = await response.json().catch(() => ({})); if (!response.ok || state?.ok === false) { throw new Error(state?.error || `Hermodr surface request failed with HTTP ${response.status}`); } if (!state?.surface?.root) { throw new Error("Hermodr returned a surface document without a root."); } return state; } catch (error) { lastError = error; await delay(250 * (attempt + 1)); } } throw lastError instanceof Error ? lastError : new Error(String(lastError)); } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function renderSurface(state, source) { surfaceId.textContent = state.providerId || "surface unknown"; surfaceVersion.textContent = `v${state.version ?? "?"}`; const providerId = state.providerId || currentProvider?.providerId || ""; const advertisedSurface = currentProvider?.surfaces?.find(candidate => candidate.surfaceId === state.surface?.id) || currentProvider?.surfaces?.[0] || {}; const pluginAdapters = resolveRequiredPluginAdapters(advertisedSurface); renderEveSurface(state, app, { activeSurfaceId: state.surface?.id, body: document.body, clientId: liveHermodr ? "hermodr.browser" : "browser.reference", commandSink: publishCommandIntent, documentResolver: liveHermodr ? createHermodrDocumentResolver(providerId) : undefined, assetUrlResolver: liveHermodr ? resolveHermodrAssetUrl : undefined, provider: currentProvider, pluginAdapters, source, statusElement: statusEl, }); } function createHermodrDocumentResolver(providerId) { return async function resolveHermodrDocument(request) { if (!providerId) return undefined; const params = new URLSearchParams(); params.set("documentId", request.documentId); if (request.schemaId) params.set("schemaId", request.schemaId); if (request.slotId) params.set("slotId", request.slotId); const response = await fetch(`/hermodr/document/${encodeURIComponent(providerId)}?${params.toString()}`, { cache: "no-store" }); if (!response.ok) throw new Error(await response.text()); return response.json(); }; } function resolveHermodrAssetUrl(uri) { if (/^(cultmesh:\/\/|resources:\/\/)/i.test(uri)) { return `/cultmesh-cdn/${encodeURIComponent(uri)}`; } return uri; } async function publishCommandIntent(intent, component) { const props = component?.props || {}; const command = intent.command ? intent : createEveCommandIntent(props.command || props.commandId || "invoke", props, { clientId: "browser.reference", provider: currentProvider, }); statusEl.textContent = `command ${command.command}`; if (!liveHermodr) { console.info("Eve command intent", command); return; } const response = await fetch("/hermodr/commands/eve", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(command), }); const receipt = await response.json().catch(() => ({})); if (!response.ok || receipt.ok === false) { statusEl.textContent = `command failed ${command.command}`; console.error("Eve command failed", receipt); return; } if ( receipt.schema !== "gamecult.eve.command_receipt.v1" || !receipt.commandId || receipt.command !== command.command || receipt.state !== "reconciled" ) { statusEl.textContent = `command failed ${command.command}`; console.error("Eve command returned no correlated provider receipt", receipt); return; } applyCommandReceipt(receipt); statusEl.textContent = `command ${receipt.state} ${command.command}`; console.info("Eve command receipt", receipt); } function applyCommandReceipt(receipt) { for (const diagnostic of receipt.diagnostics || []) { if (!diagnostic?.binding || diagnostic.value === undefined) continue; for (const control of document.querySelectorAll(`[data-bind="${CSS.escape(diagnostic.binding)}"]`)) { const input = control.querySelector("input[type=range]"); if (input) { input.value = String(diagnostic.value); const min = Number(input.min || 0); const max = Number(input.max || 1); const value = Number(diagnostic.value); const percent = max === min ? 0 : ((value - min) / (max - min)) * 100; control.style.setProperty("--cultui-slider-value", `${Math.max(0, Math.min(100, percent))}%`); } control.dataset.value = String(diagnostic.value); } } }