document.addEventListener('DOMContentLoaded', async function() { const loading = document.getElementById('__bundler_loading'); function setStatus(msg) { if (loading) loading.textContent = msg; }
// Error sink persists across replaceWith since it's on window, not the DOM. window.addEventListener('error', function(e) { var p = document.body || document.documentElement; var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div')); d.id = '__bundler_err'; d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto'; d.textContent = (d.textContent ? d.textContent + String.fromCharCode(10) : '') + '[bundle] ' + (e.message || e.type) + (e.filename ? ' (' + e.filename.slice(0, 60) + ':' + e.lineno + ')' : ''); }, true);
try { const manifestEl = document.querySelector('script[type="__bundler/manifest"]'); const templateEl = document.querySelector('script[type="__bundler/template"]'); if (!manifestEl || !templateEl) { setStatus('Error: missing bundle data'); console.error('[bundler] Missing script tags — manifestEl:', !!manifestEl, 'templateEl:', !!templateEl); return; }
const manifest = JSON.parse(manifestEl.textContent); const templateData = JSON.parse(templateEl.textContent); const pages = templateData.pages; const entryId = templateData.entry; const pageIds = Object.keys(pages);
const extResEl = document.querySelector('script[type="__bundler/ext_resources"]'); const extResources = extResEl ? JSON.parse(extResEl.textContent) : {};
const uuids = Object.keys(manifest); setStatus('Unpacking ' + uuids.length + ' assets...');
const blobUrls = {}; await Promise.all(uuids.map(async (uuid) => { const entry = manifest[uuid]; try { const binaryStr = atob(entry.data); const bytes = new Uint8Array(binaryStr.length); for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i); let finalBytes = bytes; if (entry.compressed) { if (typeof DecompressionStream !== 'undefined') { const ds = new DecompressionStream('gzip'); const writer = ds.writable.getWriter(); const reader = ds.readable.getReader(); writer.write(bytes); writer.close(); const chunks = []; let totalLen = 0; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); totalLen += value.length; } finalBytes = new Uint8Array(totalLen); let offset = 0; for (const chunk of chunks) { finalBytes.set(chunk, offset); offset += chunk.length; } } else { console.warn('DecompressionStream not available, asset ' + uuid + ' may not render'); } } blobUrls[uuid] = URL.createObjectURL(new Blob([finalBytes], { type: entry.mime })); } catch (err) { console.error('Failed to decode asset ' + uuid + ':', err); blobUrls[uuid] = URL.createObjectURL(new Blob([], { type: entry.mime })); } })); // Per-page prep: substitute UUIDs, strip SRI/crossorigin (blob URLs from a // file:// document inherit a null origin, so crossorigin forces a CORS // fetch that SRI then rejects — the manifest bytes are ours; SRI protects // against CDN compromise, not this), and inject the page's own // window.__resources + the click interceptor into
. slice() rather // than replace() keeps us clear of $-pattern substitution. function resourcesFor(id) { var map = {}; var list = extResources[id] || []; for (var k = 0; k < list.length; k++) { if (blobUrls[list[k].uuid]) map[list[k].id] = blobUrls[list[k].uuid]; } return map; } for (const id of pageIds) { let t = pages[id]; for (const uuid of uuids) t = t.split(uuid).join(blobUrls[uuid]); t = t.replace(/\s+integrity="[^"]*"/gi, '').replace(/\s+crossorigin="[^"]*"/gi, ''); // The click interceptor owns every anchor click (not a