(() => { "use strict"; const MESSAGE_TYPE = "CDN_SPA_PAGE_STATE"; const IFRAME_MARKER = "__cdn_spa_iframe"; const isInsideIframe = window.self !== window.top; if (isInsideIframe) { initializeIframe(); } else { initializeHost(); } function initializeIframe() { if (!document.body) { const bodyWaiter = new MutationObserver(() => { if (document.body) { bodyWaiter.disconnect(); initializeIframe(); } }); bodyWaiter.observe(document.documentElement, { childList: true }); return; } let lastTitle = document.title; let lastURL = window.location.href; let lastFavicon = getFavicon(); let lastBackground = getBackgroundColor(); function sendPageState(action = "replace") { const state = { type: MESSAGE_TYPE, action, title: document.title, url: window.location.href, favicon: getFavicon(), background: getBackgroundColor() }; try { window.parent.postMessage(state, "*"); } catch { } lastTitle = state.title; lastURL = state.url; lastFavicon = state.favicon; lastBackground = state.background; } function getFavicon() { const icon = document.querySelector('link[rel~="icon"]') || document.querySelector('link[rel="shortcut icon"]') || document.querySelector('link[rel="apple-touch-icon"]'); if (!icon) { return null; } try { return new URL(icon.href, window.location.href).href; } catch { return icon.href || null; } } function getBackgroundColor() { const bodyBackground = getComputedStyle(document.body).backgroundColor; const htmlBackground = getComputedStyle(document.documentElement).backgroundColor; if ( bodyBackground && bodyBackground !== "rgba(0, 0, 0, 0)" && bodyBackground !== "transparent" ) { return bodyBackground; } return htmlBackground || "transparent"; } function checkForChanges() { const currentTitle = document.title; const currentURL = window.location.href; const currentFavicon = getFavicon(); const currentBackground = getBackgroundColor(); if ( currentTitle !== lastTitle || currentURL !== lastURL || currentFavicon !== lastFavicon || currentBackground !== lastBackground ) { sendPageState(); } } function resolveCrossOrigin(rawURL) { try { const currentURL = new URL(window.location.href); const targetURL = new URL(rawURL, currentURL.href); if (targetURL.origin !== currentURL.origin) { return targetURL; } } catch { } return null; } function notifyParentOfExternalNavigation(targetURL) { try { window.parent.postMessage({ type: MESSAGE_TYPE, navigate: targetURL.href }, "*"); } catch { } } sendPageState(); const titleObserver = new MutationObserver(() => { checkForChanges(); }); const titleElement = document.querySelector("title"); if (titleElement) { titleObserver.observe(titleElement, { childList: true, characterData: true, subtree: true }); } const headObserver = new MutationObserver(() => { checkForChanges(); }); if (document.head) { headObserver.observe(document.head, { childList: true, subtree: true, attributes: true, attributeFilter: [ "href", "rel" ] }); } const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { const result = originalPushState.apply(this, args); queueMicrotask(() => { sendPageState("push"); }); return result; }; history.replaceState = function (...args) { const result = originalReplaceState.apply(this, args); queueMicrotask(() => { sendPageState("replace"); }); return result; }; window.addEventListener("popstate", sendPageState); window.addEventListener("hashchange", sendPageState); window.addEventListener("message", event => { if ( event.data?.type === MESSAGE_TYPE && event.data.navigate && event.data.navigate !== window.location.href ) { window.location.href = event.data.navigate; } }); document.addEventListener("click", event => { if (event.defaultPrevented || event.button !== 0) { return; } const anchor = event.target?.closest?.("a[href]"); if (!anchor) { return; } if ( anchor.target === "_blank" || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) { return; } const targetURL = resolveCrossOrigin(anchor.getAttribute("href")); if (targetURL) { event.preventDefault(); event.stopImmediatePropagation(); notifyParentOfExternalNavigation(targetURL); } }, true); document.addEventListener("submit", event => { if (event.defaultPrevented) { return; } const form = event.target; if (!(form instanceof HTMLFormElement)) { return; } const action = form.getAttribute("action") || window.location.href; const targetURL = resolveCrossOrigin(action); if (targetURL) { event.preventDefault(); event.stopImmediatePropagation(); notifyParentOfExternalNavigation(targetURL); } }, true); if ("navigation" in window) { navigation.addEventListener("navigate", event => { const destination = event.destination?.url; if (!destination) { return; } const targetURL = resolveCrossOrigin(destination); if (targetURL) { event.preventDefault(); notifyParentOfExternalNavigation(targetURL); } }); } setInterval(checkForChanges, 500); try { window.parent.postMessage({ type: MESSAGE_TYPE, ready: true, title: document.title, url: window.location.href, favicon: getFavicon(), background: getBackgroundColor() }, "*"); } catch { } } function initializeHost() { const originalURL = window.location.href; const iframe = document.createElement("iframe"); iframe.setAttribute(IFRAME_MARKER, "true"); iframe.src = originalURL; iframe.style.position = "fixed"; iframe.style.inset = "0"; iframe.style.width = "100vw"; iframe.style.height = "100vh"; iframe.style.border = "0"; iframe.style.margin = "0"; iframe.style.padding = "0"; iframe.style.display = "block"; iframe.style.background = "white"; iframe.style.zIndex = "2147483647"; let lastKnownGoodURL = originalURL; function mountIframe() { const html = document.documentElement; document.body.innerHTML = ""; html.style.margin = "0"; html.style.padding = "0"; html.style.width = "100%"; html.style.height = "100%"; html.style.overflow = "hidden"; document.body.style.margin = "0"; document.body.style.padding = "0"; document.body.style.width = "100%"; document.body.style.height = "100%"; document.body.style.overflow = "hidden"; document.body.appendChild(iframe); } function setBackground(color) { if (!color) { return; } document.documentElement.style.backgroundColor = color; document.body.style.backgroundColor = color; iframe.style.backgroundColor = color; } function setFavicon(url) { if (!url) { return; } let favicon = document.querySelector('link[data-cdn-spa-favicon="true"]'); if (!favicon) { favicon = document.createElement("link"); favicon.rel = "icon"; favicon.dataset.cdnSpaFavicon = "true"; document.head.appendChild(favicon); } if (favicon.href !== url) { favicon.href = url; } } function setTitle(title) { if (typeof title === "string") { document.title = title; } } function syncURL(url, action = "replace") { try { const newURL = new URL(url); const currentURL = new URL(window.location.href); if (newURL.origin !== currentURL.origin) { iframe.style.visibility = "hidden"; window.location.replace(newURL.href); return; } const newPath = newURL.pathname + newURL.search + newURL.hash; if (action === "push") { history.pushState( history.state, "", newPath ); } else { history.replaceState( history.state, "", newPath ); } lastKnownGoodURL = newURL.href; } catch { } } window.addEventListener("message", event => { const data = event.data; if (!data || data.type !== MESSAGE_TYPE) { return; } if (event.source !== iframe.contentWindow) { return; } if (data.navigate) { window.location.replace(data.navigate); return; } if (typeof data.title === "string") { setTitle(data.title); } if (data.favicon) { setFavicon(data.favicon); } if (data.background) { setBackground(data.background); } if (data.url) { syncURL(data.url, data.action); } }); iframe.addEventListener("load", () => { let sameOrigin = true; try { void iframe.contentWindow.location.href; } catch { sameOrigin = false; } if (!sameOrigin) { try { iframe.contentWindow.location.replace(lastKnownGoodURL); } catch { window.location.reload(); } } }); window.addEventListener("popstate", () => { iframe.contentWindow.postMessage({ type: MESSAGE_TYPE, navigate: window.location.href }, "*"); }); if (document.readyState === "loading") { document.addEventListener( "DOMContentLoaded", mountIframe, { once: true } ); } else { mountIframe(); } } })();