/* global fetch */ const $ = (id) => document.getElementById(id); const state = { eventMap: new Map(), }; function nowISO() { return new Date().toISOString(); } function baseURL() { return $("apiBase").value.trim().replace(/\/+$/, ""); } function wallet() { return $("walletAddress").value.trim(); } function chainID() { return Number($("chainId").value || 0); } function logLine(label, payload) { const log = $("log"); const line = `[${nowISO()}] ${label}\n${JSON.stringify(payload, null, 2)}\n\n`; log.textContent = line + log.textContent; } async function request(method, path, body) { const opts = { method, headers: { "Content-Type": "application/json" }, }; if (body !== undefined) { opts.body = JSON.stringify(body); } const res = await fetch(`${baseURL()}${path}`, opts); const text = await res.text(); let json = {}; if (text.trim() !== "") { try { json = JSON.parse(text); } catch { json = { raw: text }; } } if (!res.ok) { throw new Error(`${res.status} ${res.statusText}: ${JSON.stringify(json)}`); } return json; } function requireWallet() { const value = wallet(); if (!value) { throw new Error("wallet is required"); } return value; } function deviceID() { return $("deviceId").value.trim(); } function orgRootID() { return $("orgRootId").value.trim(); } function principalID() { return $("principalId").value.trim(); } function principalRole() { return $("principalRole").value.trim(); } function renderEvents(events) { const list = $("eventList"); list.innerHTML = ""; for (const evt of events) { state.eventMap.set(evt.event_id, evt); const card = document.createElement("article"); card.className = "event"; const title = document.createElement("h3"); title.textContent = `${evt.class} · ${evt.event_id}`; card.appendChild(title); const body = document.createElement("p"); body.textContent = evt.body || evt.title || ""; card.appendChild(body); const meta = document.createElement("div"); meta.className = "meta"; meta.textContent = `${evt.created_at} · scope=${evt.visibility_scope} · ack=${evt.requires_ack}`; card.appendChild(meta); if (evt.requires_ack) { const actions = document.createElement("div"); actions.className = "actions"; const button = document.createElement("button"); button.textContent = "Ack"; button.addEventListener("click", async () => { try { const out = await request("POST", `/member/channel/events/${evt.event_id}/ack`, { wallet: requireWallet(), device_id: deviceID(), acknowledged_at: nowISO(), }); logLine(`event ack ${evt.event_id}`, out); } catch (err) { logLine(`event ack ${evt.event_id} error`, { error: String(err) }); } }); actions.appendChild(button); card.appendChild(actions); } list.appendChild(card); } } async function onIntent() { const payload = { address: requireWallet(), origin: $("walletOrigin").value.trim() || "https://edut.ai", locale: $("walletLocale").value.trim() || "en", chain_id: chainID(), }; const out = await request("POST", "/secret/wallet/intent", payload); $("intentId").value = out.intent_id || ""; $("designationCode").value = out.designation_code || ""; $("displayToken").value = out.display_token || ""; logLine("wallet intent", out); } async function onVerify() { const out = await request("POST", "/secret/wallet/verify", { intent_id: $("intentId").value.trim(), address: requireWallet(), chain_id: chainID(), signature: $("walletSignature").value.trim(), }); if (out.designation_code) { $("designationCode").value = out.designation_code; } logLine("wallet verify", out); } async function onStatus() { const out = await request( "GET", `/secret/membership/status?wallet=${encodeURIComponent(requireWallet())}`, ); if (out.designation_code) { $("designationCode").value = out.designation_code; } logLine("membership status", out); } async function onQuote() { const payload = { designation_code: $("designationCode").value.trim(), address: requireWallet(), chain_id: chainID(), }; const payerWallet = $("payerWallet").value.trim(); const payerProof = $("payerProof").value.trim(); const sponsorOrgRoot = $("sponsorOrgRoot").value.trim(); if (payerWallet) { payload.payer_wallet = payerWallet; } if (payerProof) { payload.payer_proof = payerProof; } if (sponsorOrgRoot) { payload.sponsor_org_root_id = sponsorOrgRoot; } const out = await request("POST", "/secret/membership/quote", payload); $("quoteId").value = out.quote_id || ""; $("quoteValue").value = out.value || ""; $("quotePayer").value = out.payer_wallet || ""; logLine("membership quote", out); } async function onConfirmMembership() { const out = await request("POST", "/secret/membership/confirm", { designation_code: $("designationCode").value.trim(), quote_id: $("quoteId").value.trim(), tx_hash: $("confirmTxHash").value.trim(), address: requireWallet(), chain_id: chainID(), }); logLine("membership confirm", out); } async function onRegisterChannel() { const out = await request("POST", "/member/channel/device/register", { wallet: requireWallet(), chain_id: chainID(), device_id: deviceID(), platform: $("platform").value.trim(), org_root_id: orgRootID(), principal_id: principalID(), principal_role: principalRole(), app_version: $("appVersion").value.trim(), push_provider: "none", }); logLine("channel register", out); } async function onUnregisterChannel() { const out = await request("POST", "/member/channel/device/unregister", { wallet: requireWallet(), device_id: deviceID(), }); logLine("channel unregister", out); } async function onPollEvents() { const cursor = $("eventCursor").value.trim(); const limit = $("eventLimit").value.trim() || "25"; const query = new URLSearchParams({ wallet: requireWallet(), device_id: deviceID(), limit, }); if (cursor) { query.set("cursor", cursor); } const out = await request("GET", `/member/channel/events?${query.toString()}`); if (out.next_cursor) { $("eventCursor").value = out.next_cursor; } renderEvents(out.events || []); logLine("channel poll", out); } async function onSupportTicket() { const out = await request("POST", "/member/channel/support/ticket", { wallet: requireWallet(), org_root_id: orgRootID(), principal_id: principalID(), category: "admin_support", summary: $("supportSummary").value.trim(), context: { source: "launcher-shell", requested_at: nowISO(), }, }); logLine("support ticket", out); } async function onInstallToken() { const out = await request("POST", "/governance/install/token", { wallet: requireWallet(), org_root_id: orgRootID(), principal_id: principalID(), principal_role: principalRole(), device_id: deviceID(), launcher_version: $("appVersion").value.trim(), platform: $("platform").value.trim(), }); $("installToken").value = out.install_token || ""; $("entitlementId").value = out.entitlement_id || ""; $("runtimeVersion").value = out.package?.runtime_version || ""; $("packageHash").value = out.package?.package_hash || ""; logLine("install token", out); } async function onInstallConfirm() { const out = await request("POST", "/governance/install/confirm", { install_token: $("installToken").value.trim(), wallet: requireWallet(), device_id: deviceID(), entitlement_id: $("entitlementId").value.trim(), package_hash: $("packageHash").value.trim(), runtime_version: $("runtimeVersion").value.trim(), installed_at: nowISO(), launcher_receipt_hash: `receipt-${Date.now()}`, }); logLine("install confirm", out); } async function onInstallStatus() { const query = new URLSearchParams({ wallet: requireWallet(), device_id: deviceID(), }); const out = await request("GET", `/governance/install/status?${query.toString()}`); logLine("install status", out); } async function onLeaseHeartbeat() { const out = await request("POST", "/governance/lease/heartbeat", { wallet: requireWallet(), org_root_id: orgRootID(), principal_id: principalID(), device_id: deviceID(), }); logLine("lease heartbeat", out); } async function onOfflineRenew() { const out = await request("POST", "/governance/lease/offline-renew", { wallet: requireWallet(), org_root_id: orgRootID(), principal_id: principalID(), renewal_bundle: { bundle_id: `renew-${Date.now()}`, source: "launcher-shell", }, }); logLine("offline renew", out); } function bind(id, handler) { $(id).addEventListener("click", async () => { try { await handler(); } catch (err) { logLine(`${id} error`, { error: String(err) }); } }); } bind("btnIntent", onIntent); bind("btnVerify", onVerify); bind("btnStatus", onStatus); bind("btnQuote", onQuote); bind("btnConfirmMembership", onConfirmMembership); bind("btnRegisterChannel", onRegisterChannel); bind("btnUnregisterChannel", onUnregisterChannel); bind("btnPollEvents", onPollEvents); bind("btnSupportTicket", onSupportTicket); bind("btnInstallToken", onInstallToken); bind("btnInstallConfirm", onInstallConfirm); bind("btnInstallStatus", onInstallStatus); bind("btnLeaseHeartbeat", onLeaseHeartbeat); bind("btnOfflineRenew", onOfflineRenew); logLine("launcher shell ready", { api_base: baseURL(), chain_id: chainID(), });