/* global window, document, navigator, CustomEvent, HTMLElement, HTMLFormElement, MutationObserver, fetch, atob, btoa, Blob, URL, sessionStorage, localStorage, location, crypto */ (() => { if (window.WRNexusAuth) return; const mounted = new WeakSet(); const requestControllers = new WeakMap(); const cleanups = new WeakMap(); const recoveryMounted = new WeakSet(); const recoveryCleanups = new WeakMap(); const flowMounted = new WeakSet(); const flowCleanups = new WeakMap(); const MFA_STORAGE_KEY = "wrnexus.auth.mfa"; const DEVICE_STORAGE_KEY = "wrnexus.auth.device"; function fromBase64Url(value) { const input = String(value); if (!input || !/^[A-Za-z0-9_-]+$/.test(input) || input.length % 4 === 1) { throw new TypeError("Passkey data must be canonical Base64URL without padding"); } const normalized = input.replace(/-/g, "+").replace(/_/g, "/"); const binary = atob(normalized + "=".repeat((4 - (normalized.length % 4)) % 4)); const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0)); if (toBase64Url(bytes) !== input) { throw new TypeError("Passkey data must be canonical Base64URL without padding"); } return bytes; } function toBase64Url(value) { const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength); let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } function publicKeyCreationOptions(options) { return { ...options, challenge: fromBase64Url(options.challenge), user: { ...options.user, id: fromBase64Url(options.user.id) }, excludeCredentials: (options.excludeCredentials || []).map((item) => ({ ...item, id: fromBase64Url(item.id), })), }; } function publicKeyRequestOptions(options) { return { ...options, challenge: fromBase64Url(options.challenge), allowCredentials: (options.allowCredentials || []).map((item) => ({ ...item, id: fromBase64Url(item.id), })), }; } function credentialToJson(credential) { if (!credential) return null; const response = credential.response; const base = { id: credential.id, type: credential.type, rawId: toBase64Url(credential.rawId), authenticatorAttachment: credential.authenticatorAttachment, clientExtensionResults: credential.getClientExtensionResults ? credential.getClientExtensionResults() : {}, }; if (response && "attestationObject" in response) { return { ...base, response: { clientDataJSON: toBase64Url(response.clientDataJSON), attestationObject: toBase64Url(response.attestationObject), transports: response.getTransports ? response.getTransports() : [], }, }; } return { ...base, response: { clientDataJSON: toBase64Url(response.clientDataJSON), authenticatorData: toBase64Url(response.authenticatorData), signature: toBase64Url(response.signature), userHandle: response.userHandle ? toBase64Url(response.userHandle) : null, }, }; } function csrfHeaders() { const match = document.cookie.match(/(?:^|;\s*)wrn-csrf=([^;]+)/); return match ? { "x-csrf-token": decodeURIComponent(match[1]) } : {}; } async function json(url, body, signal) { const response = await fetch(url, { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json", accept: "application/json", ...csrfHeaders(), }, body: JSON.stringify(body), signal, }); const payload = await response.json().catch(() => ({})); if (!response.ok || payload.ok === false) { const error = new Error( payload.message || payload.error || `Request failed (${response.status})`, ); error.status = response.status; error.payload = payload; throw error; } return payload; } function setState(element, state, message) { element.dataset.authState = state; element.setAttribute("aria-busy", state === "loading" ? "true" : "false"); const status = element.querySelector("[data-auth-status]"); if (status) { status.textContent = message || ""; status.hidden = !message; } const button = element.matches("button") ? element : element.querySelector("button"); if (button) button.disabled = state === "loading"; } function dispatch(element, name, detail) { element.dispatchEvent(new CustomEvent(name, { bubbles: true, detail })); } function navigate(target, allowCrossOrigin = false) { if (!target) return false; const url = new URL(target, location.href); if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return false; if (url.origin !== location.origin) { if (!allowCrossOrigin) return false; location.assign(url.href); return true; } if (window.__wrnexusNavigate) { window.__wrnexusNavigate(url.pathname + url.search + url.hash); } else { location.assign(url.href); } return true; } function randomDeviceId() { if (crypto.randomUUID) return crypto.randomUUID(); const bytes = crypto.getRandomValues(new Uint8Array(24)); return toBase64Url(bytes); } function deviceFingerprint() { try { let value = localStorage.getItem(DEVICE_STORAGE_KEY); if (!value) { value = randomDeviceId(); localStorage.setItem(DEVICE_STORAGE_KEY, value); } return value; } catch { return randomDeviceId(); } } function toggle(element, visible) { if (!element) return; element.hidden = !visible; element.classList.toggle("hidden", !visible); } function setMessage(element, message, isError = false) { if (!element) return; element.textContent = message || ""; element.hidden = !message; element.classList.toggle("hidden", !message); element.dataset.status = isError ? "error" : "success"; } function setFormBusy(form, busy) { form?.querySelectorAll("[type=submit]").forEach((button) => { button.disabled = busy; }); } async function registerPasskey(element) { setState(element, "loading", element.dataset.loadingMessage || "Preparing passkey…"); try { if (!window.PublicKeyCredential || !navigator.credentials || !window.AbortController) { throw new Error("Passkeys are not supported in this browser"); } const controller = new window.AbortController(); requestControllers.get(element)?.abort(); requestControllers.set(element, controller); const optionsPayload = await json( element.dataset.optionsEndpoint || "/api/auth/passkeys/register/options", {}, controller.signal, ); const credential = await navigator.credentials.create({ publicKey: publicKeyCreationOptions(optionsPayload.options), signal: controller.signal, }); const result = await json( element.dataset.verifyEndpoint || "/api/auth/passkeys/register/verify", { key: optionsPayload.key, response: credentialToJson(credential), name: element.dataset.passkeyName || "Passkey", }, controller.signal, ); setState(element, "success", element.dataset.successMessage || "Passkey added."); dispatch(element, "auth:passkey-registered", result); } catch (error) { if (error && error.name === "AbortError") return; setState( element, "error", error instanceof Error ? error.message : "Passkey registration failed", ); dispatch(element, "auth:error", { error }); } } async function authenticatePasskey(element) { setState(element, "loading", element.dataset.loadingMessage || "Waiting for your passkey…"); try { if (!window.PublicKeyCredential || !navigator.credentials || !window.AbortController) { throw new Error("Passkeys are not supported in this browser"); } const controller = new window.AbortController(); requestControllers.get(element)?.abort(); requestControllers.set(element, controller); const identifier = element.dataset.identifier || element.closest("form")?.querySelector("[name=identifier]")?.value || element.closest("[data-auth-sign-in]")?.querySelector("form [name=identifier]")?.value || undefined; const optionsPayload = await json( element.dataset.optionsEndpoint || "/api/auth/passkeys/login/options", { identifier }, controller.signal, ); const credential = await navigator.credentials.get({ publicKey: publicKeyRequestOptions(optionsPayload.options), signal: controller.signal, mediation: element.dataset.conditional === "true" ? "conditional" : "optional", }); const result = await json( element.dataset.verifyEndpoint || "/api/auth/passkeys/login/verify", { key: optionsPayload.key, response: credentialToJson(credential), }, controller.signal, ); setState(element, "success", element.dataset.successMessage || "Signed in."); dispatch(element, "auth:passkey-authenticated", result); // The same-origin verification endpoint resolves and validates the final // destination, including cross-app SSO handoffs. Never SPA-route those. if (typeof result.redirect === "string" && navigate(result.redirect, true)) return; if (element.dataset.redirect) navigate(element.dataset.redirect); } catch (error) { if (error && error.name === "AbortError") return; const detail = error?.payload || {}; if (detail.code === "mfa-required" && detail.mfaToken) { sessionStorage.setItem(MFA_STORAGE_KEY, JSON.stringify(detail)); dispatch(element, "auth:mfa-required", detail); navigate(element.dataset.mfaHref || "/two-factor"); return; } setState(element, "error", error instanceof Error ? error.message : "Passkey sign-in failed"); dispatch(element, "auth:error", { error, ...detail }); } } function recoveryCodes(element) { return Array.from(element.querySelectorAll("[data-recovery-code]")) .map((item) => String(item.textContent || "").trim()) .filter(Boolean); } function renderRecoveryCodes(element, codes) { const list = element.querySelector("[data-recovery-code-list]"); if (!list || !Array.isArray(codes)) return; list.textContent = ""; for (const code of codes) { const item = document.createElement("code"); item.dataset.recoveryCode = ""; item.className = "rounded bg-[var(--wrn-color-surface)] px-2 py-1.5 text-center"; item.textContent = String(code); list.appendChild(item); } } function downloadRecoveryCodes(element) { const codes = recoveryCodes(element); if (!codes.length) return; const blob = new Blob([codes.join("\n") + "\n"], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = element.dataset.recoveryFilename || "wrnexus-recovery-codes.txt"; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); } function mountRecoveryCodes(element) { if (!(element instanceof HTMLElement) || recoveryMounted.has(element)) return; recoveryMounted.add(element); const download = element.querySelector("[data-recovery-download]"); const onDownload = () => downloadRecoveryCodes(element); const onSuccess = (event) => { if (event.target instanceof HTMLFormElement && event.detail?.codes) { renderRecoveryCodes(element, event.detail.codes); } }; download?.addEventListener("click", onDownload); element.addEventListener("wrn:success", onSuccess); recoveryCleanups.set(element, () => { download?.removeEventListener("click", onDownload); element.removeEventListener("wrn:success", onSuccess); }); } function mountOtpSignIn(element) { if (!(element instanceof HTMLElement) || flowMounted.has(element)) return; flowMounted.add(element); const requestForm = element.querySelector("[data-auth-otp-request]"); const completeForm = element.querySelector("[data-auth-otp-complete]"); const back = element.querySelector("[data-auth-otp-back]"); const onSuccess = (event) => { if (event.target !== requestForm || !event.detail?.id) return; const challenge = completeForm?.querySelector("[name=challengeId]"); if (challenge) challenge.value = String(event.detail.id); toggle(requestForm, false); toggle(completeForm, true); completeForm?.querySelector("[name=code]")?.focus(); dispatch(element, "auth:otp-requested", event.detail); }; const onError = (event) => { if (event.target !== completeForm) return; const detail = event.detail || {}; if (detail.code !== "mfa-required" || !detail.mfaToken) return; sessionStorage.setItem(MFA_STORAGE_KEY, JSON.stringify(detail)); dispatch(element, "auth:mfa-required", detail); navigate(element.dataset.mfaHref || "/two-factor"); }; const onBack = () => { toggle(completeForm, false); toggle(requestForm, true); requestForm?.querySelector("[name=identifier]")?.focus(); }; element.addEventListener("wrn:success", onSuccess); element.addEventListener("wrn:error", onError); back?.addEventListener("click", onBack); flowCleanups.set(element, () => { element.removeEventListener("wrn:success", onSuccess); element.removeEventListener("wrn:error", onError); back?.removeEventListener("click", onBack); }); } function mountSignIn(element) { if (!(element instanceof HTMLElement) || flowMounted.has(element)) return; flowMounted.add(element); const form = element.querySelector("form[data-schema]"); const fingerprintInput = form?.querySelector("[name=deviceFingerprint]"); const deviceNameInput = form?.querySelector("[name=deviceName]"); if (fingerprintInput && !fingerprintInput.value) fingerprintInput.value = deviceFingerprint(); if (deviceNameInput && !deviceNameInput.value) { deviceNameInput.value = String(navigator.userAgent || navigator.platform || "Browser").slice( 0, 120, ); } const onError = (event) => { if (!(event.target instanceof HTMLFormElement)) return; const detail = event.detail || {}; if (detail.code !== "mfa-required" || !detail.mfaToken) return; sessionStorage.setItem(MFA_STORAGE_KEY, JSON.stringify(detail)); dispatch(element, "auth:mfa-required", detail); navigate(element.dataset.mfaHref || "/two-factor"); }; element.addEventListener("wrn:error", onError); flowCleanups.set(element, () => element.removeEventListener("wrn:error", onError)); } function readMfaState() { try { return JSON.parse(sessionStorage.getItem(MFA_STORAGE_KEY) || "null"); } catch { return null; } } function mountMfaChallenge(element) { if (!(element instanceof HTMLElement) || flowMounted.has(element)) return; flowMounted.add(element); const form = element.querySelector("[data-auth-mfa-form]"); const tokenInput = form?.querySelector("[name=mfaToken]"); const challengeInput = form?.querySelector("[name=challengeId]"); const radios = Array.from(form?.querySelectorAll("input[type=radio][name=method]") || []); const status = form?.querySelector("[data-auth-mfa-status]"); const resendGroup = form?.querySelector("[data-auth-mfa-resend-group]"); const resendButton = form?.querySelector("button[data-auth-mfa-resend]"); const resendDeadlines = new Map(); let resendTimer; const saved = readMfaState(); const allowed = Array.isArray(saved?.requires?.mfa) ? saved.requires.mfa : []; const challenges = new Map(); const pending = new Map(); let expired = false; let disposed = false; let expiryTimer; const expiresAt = Number(saved?.mfaExpiresAt); const expire = () => { if (disposed) return; expired = true; window.clearTimeout(expiryTimer); sessionStorage.removeItem(MFA_STORAGE_KEY); if (tokenInput) tokenInput.value = ""; form?.querySelectorAll("input, button").forEach((control) => { control.disabled = true; }); // A form response's finalizer or hydration may re-enable buttons. Hide the // entire stale form instead of leaving a second apparently usable action. element.dataset.mfaExpired = "true"; if (form) form.hidden = true; setMessage(status, ""); form?.querySelectorAll("[data-error]").forEach((error) => { error.textContent = ""; }); const expiredMessage = element.querySelector("[data-auth-mfa-expired]"); if (expiredMessage) expiredMessage.hidden = false; const restart = element.querySelector("[data-auth-mfa-restart]"); if (restart) restart.hidden = false; }; const checkExpiry = () => { // Old stored challenges have no verifiable deadline: require fresh sign-in. if (saved?.mfaToken && (!Number.isFinite(expiresAt) || expiresAt <= Date.now())) expire(); return expired; }; const restoreToken = () => { checkExpiry(); if (!expired && tokenInput && !tokenInput.value && saved?.mfaToken) tokenInput.value = String(saved.mfaToken); }; const method = () => radios.find((radio) => radio.checked && !radio.disabled)?.value; const isOtp = (value) => ["email-otp", "sms-otp"].includes(value); const updateResend = () => { window.clearTimeout(resendTimer); const selected = method(); if (resendGroup) resendGroup.hidden = !isOtp(selected) || expired; if (!resendButton || !isOtp(selected) || expired || disposed) return; const seconds = Math.max( 0, Math.ceil(((resendDeadlines.get(selected) || 0) - Date.now()) / 1000), ); resendButton.disabled = pending.has(selected) || seconds > 0; resendButton.textContent = seconds > 0 ? "Resend code in " + seconds + "s" : "Resend code"; if (seconds > 0) resendTimer = window.setTimeout(updateResend, 1000); }; for (const radio of radios) { radio.disabled = allowed.length > 0 && !allowed.includes(radio.value); const label = radio.closest("label"); if (label) label.hidden = radio.disabled; } const initial = radios.find((radio) => !radio.disabled && radio.value === element.dataset.mfaInitialMethod) || radios.find((radio) => !radio.disabled); for (const radio of radios) radio.checked = radio === initial; restoreToken(); const sendCode = async (selected, resend = false) => { if (disposed || expired || !isOtp(selected)) return; if (resend && (pending.has(selected) || (resendDeadlines.get(selected) || 0) > Date.now())) return; restoreToken(); if (expired) return; if (!tokenInput?.value) { setMessage(status, "Sign-in verification has expired. Start again.", true); const restart = element.querySelector("[data-auth-mfa-restart]"); if (restart) restart.hidden = false; return; } const previous = challenges.get(selected); if (previous?.expiresAt && Number(previous.expiresAt) <= Date.now()) challenges.delete(selected); if (!pending.has(selected) && (resend || !challenges.has(selected))) { pending.set( selected, json(element.dataset.mfaOtpAction || "/api/auth/mfa/otp", { mfaToken: tokenInput.value, method: selected, resend, }), ); } if (method() === selected) { setFormBusy(form, true); setMessage(status, "Sending a one-time code…"); } try { const result = (!resend && challenges.get(selected)) || (await pending.get(selected)); if (disposed || expired) return; challenges.set(selected, result); if (!resendDeadlines.has(selected) || resend) resendDeadlines.set(selected, Date.now() + (Number(result.retryAfter) || 60) * 1000); if (method() !== selected) return; if (challengeInput) challengeInput.value = String(result.id || ""); if (resend) { form?.querySelectorAll("[data-auth-mfa-pin] input, [name=code]").forEach((input) => { input.value = ""; input.dispatchEvent(new window.Event("input", { bubbles: true })); }); form?.querySelectorAll("[data-error]").forEach((error) => { error.textContent = ""; }); } setMessage( status, selected === "email-otp" ? "An email code has been requested. Check your Inbox and Spam folder." : "An SMS code has been requested. It may take a moment to arrive.", ); } catch (error) { if (!disposed && !expired && method() === selected) setMessage( status, error instanceof Error ? error.message : "Unable to send a one-time code", true, ); } finally { pending.delete(selected); if (!disposed && !expired && method() === selected) setFormBusy(form, false); updateResend(); } }; const updateMethod = () => { if (checkExpiry()) return; const selected = method(); setFormBusy(form, false); if (challengeInput) challengeInput.value = ""; const code = form?.querySelector("[name=code]"); if (code) code.value = ""; const error = form?.querySelector("[data-error=code]"); if (error) error.textContent = ""; setMessage(status, ""); updateResend(); for (const [selector, active] of [ ["[data-auth-mfa-pin]", selected !== "recovery-code"], ["[data-auth-mfa-recovery]", selected === "recovery-code"], ]) { const group = form?.querySelector(selector); if (!group) continue; group.hidden = !active; for (const input of group.querySelectorAll("input")) { input.disabled = !active; input.value = ""; input.dispatchEvent(new window.Event("input", { bubbles: true })); } } if (isOtp(selected)) void sendCode(selected); }; const onMethodChange = (event) => { if (radios.includes(event.target)) updateMethod(); }; const onResend = (event) => { event.preventDefault(); void sendCode(method(), true); }; const onSubmit = (event) => { restoreToken(); const code = form?.querySelector("[name=code]"); const active = form?.querySelector( method() === "recovery-code" ? "[name=recoveryCode]" : "[name=otpCode]", ); if (code && active) code.value = active.value; if (expired || !method() || (isOtp(method()) && !challengeInput?.value)) { event.preventDefault(); event.stopImmediatePropagation(); if (!expired && isOtp(method())) void sendCode(method()); } }; const onSuccess = (event) => { if (event.target === form) sessionStorage.removeItem(MFA_STORAGE_KEY); }; const onError = (event) => { if (event.target !== form || event.detail?.code !== "mfa-transaction-expired") return; expire(); }; updateMethod(); if (!expired && Number.isFinite(expiresAt)) { expiryTimer = window.setTimeout(expire, Math.max(0, expiresAt - Date.now())); } // Capture native change before the shared Radio emits its component output. form?.addEventListener("change", onMethodChange, true); form?.addEventListener("submit", onSubmit, true); resendButton?.addEventListener("click", onResend); element.addEventListener("wrn:success", onSuccess); element.addEventListener("wrn:error", onError); flowCleanups.set(element, () => { disposed = true; window.clearTimeout(expiryTimer); window.clearTimeout(resendTimer); resendButton?.removeEventListener("click", onResend); form?.removeEventListener("change", onMethodChange, true); form?.removeEventListener("submit", onSubmit, true); element.removeEventListener("wrn:success", onSuccess); element.removeEventListener("wrn:error", onError); }); } function mountAuthenticatorSetup(element) { if (!(element instanceof HTMLElement) || flowMounted.has(element)) return; flowMounted.add(element); const startForm = element.querySelector("[data-auth-authenticator-start]"); const details = element.querySelector("[data-auth-authenticator-details]"); const confirmForm = element.querySelector("[data-auth-authenticator-confirm]"); const secret = element.querySelector("[data-auth-authenticator-secret]"); const uri = element.querySelector("[data-auth-authenticator-uri]"); const onSuccess = (event) => { if (event.target === startForm && event.detail?.credentialId) { const credential = confirmForm?.querySelector("[name=credentialId]"); if (credential) credential.value = String(event.detail.credentialId); if (secret) secret.textContent = String(event.detail.secret || ""); if (uri) uri.textContent = String(event.detail.uri || ""); toggle(startForm, false); toggle(details, true); confirmForm?.querySelector("[name=code]")?.focus(); dispatch(element, "auth:authenticator-created", event.detail); } if (event.target === confirmForm) { dispatch(element, "auth:authenticator-enabled", event.detail); } }; element.addEventListener("wrn:success", onSuccess); flowCleanups.set(element, () => element.removeEventListener("wrn:success", onSuccess)); } function mountElement(element) { if (!(element instanceof HTMLElement) || mounted.has(element)) return; mounted.add(element); const action = element.dataset.authPasskey; const button = element.matches("button") ? element : element.querySelector("button"); if (!button || !action) return; const handler = () => action === "register" ? registerPasskey(element) : authenticatePasskey(element); button.addEventListener("click", handler); cleanups.set(element, () => button.removeEventListener("click", handler)); } function mount(root = document) { if (root instanceof HTMLElement && root.matches("[data-auth-passkey]")) mountElement(root); root.querySelectorAll?.("[data-auth-passkey]").forEach(mountElement); if (root instanceof HTMLElement && root.matches("[data-auth-recovery-codes]")) mountRecoveryCodes(root); root.querySelectorAll?.("[data-auth-recovery-codes]").forEach(mountRecoveryCodes); if (root instanceof HTMLElement && root.matches("[data-auth-otp-sign-in]")) mountOtpSignIn(root); root.querySelectorAll?.("[data-auth-otp-sign-in]").forEach(mountOtpSignIn); if (root instanceof HTMLElement && root.matches("[data-auth-sign-in]")) mountSignIn(root); root.querySelectorAll?.("[data-auth-sign-in]").forEach(mountSignIn); if (root instanceof HTMLElement && root.matches("[data-auth-mfa-challenge]")) mountMfaChallenge(root); root.querySelectorAll?.("[data-auth-mfa-challenge]").forEach(mountMfaChallenge); if (root instanceof HTMLElement && root.matches("[data-auth-authenticator-setup]")) mountAuthenticatorSetup(root); root.querySelectorAll?.("[data-auth-authenticator-setup]").forEach(mountAuthenticatorSetup); } function unmount(root = document) { const elements = []; if (root instanceof HTMLElement && root.matches("[data-auth-passkey]")) elements.push(root); root.querySelectorAll?.("[data-auth-passkey]").forEach((element) => elements.push(element)); for (const element of elements) { requestControllers.get(element)?.abort(); requestControllers.delete(element); cleanups.get(element)?.(); cleanups.delete(element); mounted.delete(element); } const recoveryElements = []; if (root instanceof HTMLElement && root.matches("[data-auth-recovery-codes]")) recoveryElements.push(root); root .querySelectorAll?.("[data-auth-recovery-codes]") .forEach((element) => recoveryElements.push(element)); for (const element of recoveryElements) { recoveryCleanups.get(element)?.(); recoveryCleanups.delete(element); recoveryMounted.delete(element); } const flowElements = []; const selector = "[data-auth-otp-sign-in],[data-auth-sign-in],[data-auth-mfa-challenge],[data-auth-authenticator-setup]"; if (root instanceof HTMLElement && root.matches(selector)) flowElements.push(root); root.querySelectorAll?.(selector).forEach((element) => flowElements.push(element)); for (const element of flowElements) { flowCleanups.get(element)?.(); flowCleanups.delete(element); flowMounted.delete(element); } } window.WRNexusAuth = { mount, unmount, registerPasskey, authenticatePasskey }; window.__wrnexusRuntimes = window.__wrnexusRuntimes || {}; window.__wrnexusRuntimes.auth = { mount, unmount }; if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => mount(document), { once: true }); else mount(document); new MutationObserver((records) => { for (const record of records) for (const node of record.addedNodes) if (node instanceof HTMLElement) mount(node); }).observe(document.documentElement, { childList: true, subtree: true }); })();