wu-framework 2.1.2 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/adapters/alpine/index.d.ts +1 -1
- package/dist/adapters/angular/index.d.ts +1 -1
- package/dist/adapters/htmx/index.d.ts +1 -1
- package/dist/adapters/lit/index.d.ts +1 -1
- package/dist/adapters/lit/index.js +2 -2
- package/dist/adapters/lit/index.js.map +1 -1
- package/dist/adapters/preact/index.d.ts +1 -1
- package/dist/adapters/preact/index.js +1 -1
- package/dist/adapters/preact/index.js.map +1 -1
- package/dist/adapters/qwik/index.d.ts +3 -10
- package/dist/adapters/qwik/index.js +1 -1
- package/dist/adapters/qwik/index.js.map +1 -1
- package/dist/adapters/react/index.js +1 -1
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/adapters/shared.d.ts +44 -0
- package/dist/adapters/shared.js +1 -1
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/solid/index.d.ts +1 -1
- package/dist/adapters/solid/index.js +1 -1
- package/dist/adapters/solid/index.js.map +1 -1
- package/dist/adapters/stencil/index.d.ts +1 -1
- package/dist/adapters/stimulus/index.d.ts +1 -1
- package/dist/adapters/svelte/index.d.ts +1 -1
- package/dist/adapters/svelte/index.js +1 -1
- package/dist/adapters/svelte/index.js.map +1 -1
- package/dist/adapters/vanilla/index.d.ts +1 -1
- package/dist/adapters/vanilla/index.js +1 -1
- package/dist/adapters/vanilla/index.js.map +1 -1
- package/dist/adapters/vue/index.js +1 -1
- package/dist/adapters/vue/index.js.map +1 -1
- package/dist/ai/wu-ai.js +1 -1
- package/dist/ai/wu-ai.js.map +1 -1
- package/dist/core/wu-devtools.js +2 -0
- package/dist/core/wu-devtools.js.map +1 -0
- package/dist/core/wu-html-parser.js +1 -1
- package/dist/core/wu-html-parser.js.map +1 -1
- package/dist/core/wu-iframe-sandbox.js +1 -1
- package/dist/core/wu-iframe-sandbox.js.map +1 -1
- package/dist/core/wu-loader.js +1 -1
- package/dist/core/wu-loader.js.map +1 -1
- package/dist/core/wu-logger.js +2 -0
- package/dist/core/wu-logger.js.map +1 -0
- package/dist/core/wu-mcp-bridge.js +1 -1
- package/dist/core/wu-mcp-bridge.js.map +1 -1
- package/dist/core/wu-script-executor.js +1 -1
- package/dist/core/wu-script-executor.js.map +1 -1
- package/dist/core/wu-store-sync.js +2 -0
- package/dist/core/wu-store-sync.js.map +1 -0
- package/dist/core/wu-timeline.js +2 -0
- package/dist/core/wu-timeline.js.map +1 -0
- package/dist/index.d.cts +759 -0
- package/dist/index.d.ts +315 -1
- package/dist/wu-ai-browser-primitives-CaUCk1Xl.js +2 -0
- package/dist/wu-ai-browser-primitives-CaUCk1Xl.js.map +1 -0
- package/dist/wu-framework.cjs +3 -0
- package/dist/wu-framework.cjs.map +1 -0
- package/dist/wu-framework.dev.js +1296 -275
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +2 -2
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +2 -2
- package/dist/wu-framework.umd.js.map +1 -1
- package/integrations/astro/WuApp.astro +16 -11
- package/integrations/astro/WuShell.astro +11 -3
- package/package.json +14 -6
- package/dist/wu-ai-browser-primitives-BDKXJlwc.js +0 -2
- package/dist/wu-ai-browser-primitives-BDKXJlwc.js.map +0 -1
- package/dist/wu-framework.cjs.js +0 -3
- package/dist/wu-framework.cjs.js.map +0 -1
- package/dist/wu-logger-fJfUHBGA.js +0 -2
- package/dist/wu-logger-fJfUHBGA.js.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=null,n=null,t=null,o=!1;function a(e){return String(e).replace(/[&<>]/g,e=>({"&":"&","<":"<",">":">"}[e]))}function i(e,n){const t=n.inspect(),o=t.apps.length?t.apps.map(e=>`\n <div class="app">\n <div><span class="name">${a(e.name)}</span>\n <span class="badge ${"mounted"===e.status?"b-mounted":"b-hidden"}">${e.status}</span>\n <span class="badge ${e.liveProps?"b-live":"b-static"}">${e.liveProps?"live props":"static props"}</span>\n </div>\n <div class="row"><span class="k">container</span> ${a(e.containerSelector||"—")}</div>\n <div class="row"><span class="k">sandbox</span> ${a(e.sandbox?.isolationLevel||e.sandbox?.styleMode||"—")}</div>\n </div>`).join(""):'<div class="empty">No apps mounted</div>',i=t.events.recent.length?t.events.recent.slice().reverse().map(e=>`<div class="ev"><span class="t">${a(e.type||"?")}</span> ${a(e.appName||"")}</div>`).join(""):'<div class="empty">No events yet</div>',s=t.capabilities&&t.capabilities.length?t.capabilities.map(e=>`\n <div class="app">\n <div><span class="name">${a(e.name)}</span>\n <span class="badge b-live">v${a(e.version)}</span>\n ${e.app?`<span class="badge b-static">${a(e.app)}</span>`:""}\n </div>\n </div>`).join(""):'<div class="empty">No capabilities provided</div>';e.querySelector(".body").innerHTML=`\n <div class="row"><span class="k">apps</span>\n ${t.summary.mounted} mounted · ${t.summary.hidden} hidden · ${t.summary.registered} registered</div>\n <h4>Apps</h4>${o}\n <h4>Capabilities</h4>${s}\n <h4>Recent events</h4>${i}\n <h4>Store</h4>\n <pre>${a(JSON.stringify(t.store.snapshot??{},null,2)).slice(0,2e3)}</pre>\n `}function s(e){const n=e.querySelector(".tl-range"),a=e.querySelector(".tl-rec"),i=e.querySelector(".tl-pos"),s=e.querySelector(".tl-state"),r=e.querySelector(".tl-live");if(!n)return;const l=t?t.status():{recording:!1,live:!0,position:0,length:0};n.max=String(l.length),o||(n.value=String(Math.min(l.position,l.length))),n.disabled=!t||0===l.length&&!l.recording,a.textContent=l.recording?"■ Stop":"● Rec",a.classList.toggle("on",!!l.recording),a.disabled=!t,i.textContent=l.length?`${l.position}/${l.length}`:"—",s.textContent=t?l.recording&&l.live?"rec ● live":l.recording?"rec ⏸ past":l.live?"stopped":"⏪ past":"idle",r.style.display=t&&!l.live?"":"none"}function r(r){if(e){if(e.isConnected)return{close:l};l()}o=!1,e=document.createElement("div"),e.id="wu-devtools-overlay";const d=e.attachShadow({mode:"open"});d.innerHTML=`\n <style>\n :host { all: initial; }\n .panel {\n position: fixed; bottom: 16px; right: 16px; z-index: 2147483647;\n width: 360px; max-height: 70vh; display: flex; flex-direction: column;\n font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #e5e7eb; background: #0b1020; border: 1px solid #2a3350;\n border-radius: 10px; box-shadow: 0 12px 40px rgba(0,0,0,.5); overflow: hidden;\n }\n .head {\n display: flex; align-items: center; gap: 8px; padding: 8px 12px;\n background: linear-gradient(90deg,#6366f1,#14b8a6); color: #fff; font-weight: 600;\n }\n .head .ver { opacity: .85; font-weight: 400; }\n .head .x { margin-left: auto; cursor: pointer; background: rgba(255,255,255,.15);\n border: none; color: #fff; border-radius: 6px; width: 22px; height: 22px; }\n .tl { display: flex; align-items: center; gap: 8px; padding: 6px 12px;\n background: #0e1428; border-bottom: 1px solid #1c2540; }\n .tl button { cursor: pointer; border: 1px solid #2a3350; background: #141b33;\n color: #e5e7eb; border-radius: 6px; padding: 2px 8px; font: inherit; white-space: nowrap; }\n .tl button.on { background: #7f1d1d; border-color: #b91c1c; color: #fecaca; }\n .tl .tl-range { flex: 1; min-width: 40px; accent-color: #6366f1; }\n .tl .tl-pos { color: #93c5fd; min-width: 44px; text-align: right; }\n .tl .tl-state { color: #fbbf24; min-width: 56px; font-size: 10px; }\n .body { overflow: auto; padding: 8px 12px; }\n .row { display: flex; gap: 6px; padding: 2px 0; }\n .k { color: #93c5fd; }\n h4 { margin: 12px 0 4px; color: #a5b4fc; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }\n .app { border: 1px solid #232c47; border-radius: 7px; padding: 6px 8px; margin: 5px 0; background: #0e1428; }\n .app .name { font-weight: 700; color: #fff; }\n .badge { display: inline-block; padding: 0 6px; border-radius: 999px; font-size: 10px; margin-left: 4px; }\n .b-mounted { background: #064e3b; color: #6ee7b7; }\n .b-hidden { background: #4c1d95; color: #ddd6fe; }\n .b-live { background: #1e3a8a; color: #bfdbfe; }\n .b-static { background: #3f3f46; color: #d4d4d8; }\n .ev { color: #cbd5e1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .ev .t { color: #fbbf24; }\n pre { margin: 4px 0 0; padding: 6px; background: #060a16; border-radius: 6px; overflow: auto;\n max-height: 120px; color: #a7f3d0; }\n .empty { opacity: .5; }\n</style>\n <div class="panel">\n <div class="head">Wu Inspector <span class="ver">v${a(r.version||"")}</span>\n <button class="x" title="Close">×</button>\n </div>\n <div class="tl">\n <button class="tl-rec" title="Record / stop time travel">● Rec</button>\n <input class="tl-range" type="range" min="0" max="0" value="0" step="1" title="Scrub the page through time" />\n <span class="tl-pos">—</span>\n <span class="tl-state">idle</span>\n <button class="tl-live" title="Return to the present" style="display:none">⏭ Live</button>\n </div>\n <div class="body"></div>\n </div>\n `,document.body.appendChild(e),d.querySelector(".x").addEventListener("click",()=>l());const p=d.querySelector(".tl-range");return d.querySelector(".tl-rec").addEventListener("click",()=>{t&&(t.status().recording?t.stop():t.record(),setTimeout(()=>e&&s(d),30))}),p.addEventListener("input",()=>{o=!0,t&&t.seek(Number(p.value))}),p.addEventListener("change",()=>{o=!1}),d.querySelector(".tl-live").addEventListener("click",()=>{o=!1,t&&t.live(),setTimeout(()=>e&&s(d),30)}),"function"==typeof r._ensureTimeline&&r._ensureTimeline().then(n=>{t=n,e&&s(d)}).catch(()=>{}),i(d,r),s(d),n=setInterval(()=>{e&&e.isConnected?(i(d,r),s(d)):l()},1e3),{close:l}}function l(){n&&(clearInterval(n),n=null),e&&(e.remove(),e=null),t=null,o=!1}export{r as mountInspector,l as unmountInspector};
|
|
2
|
+
//# sourceMappingURL=wu-devtools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wu-devtools.js","sources":["../../src/core/wu-devtools.js"],"sourcesContent":["/**\n * WU DEVTOOLS — visual inspector overlay\n *\n * Lazy-loaded chunk (never in the main bundle). Renders a Shadow-DOM-isolated\n * panel that shows the whole multi-framework board at once: mounted apps, their\n * sandbox mode and live-props capability, recent events, and a store snapshot —\n * the one view per-framework devtools structurally cannot give, because each of\n * them only sees its own island.\n *\n * Board data comes from `wu.inspect()` (pure presentation). The timeline bar\n * drives `wu.timeline` (cross-framework time travel): record, scrub the\n * journal to rewind the whole page, and jump back to live.\n */\n\nconst HOST_ID = 'wu-devtools-overlay';\nlet host = null;\nlet timer = null;\nlet tl = null; // resolved WuTimeline instance (lazy)\nlet holding = false; // user is dragging the scrubber — don't move it under them\n\nconst css = `\n :host { all: initial; }\n .panel {\n position: fixed; bottom: 16px; right: 16px; z-index: 2147483647;\n width: 360px; max-height: 70vh; display: flex; flex-direction: column;\n font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #e5e7eb; background: #0b1020; border: 1px solid #2a3350;\n border-radius: 10px; box-shadow: 0 12px 40px rgba(0,0,0,.5); overflow: hidden;\n }\n .head {\n display: flex; align-items: center; gap: 8px; padding: 8px 12px;\n background: linear-gradient(90deg,#6366f1,#14b8a6); color: #fff; font-weight: 600;\n }\n .head .ver { opacity: .85; font-weight: 400; }\n .head .x { margin-left: auto; cursor: pointer; background: rgba(255,255,255,.15);\n border: none; color: #fff; border-radius: 6px; width: 22px; height: 22px; }\n .tl { display: flex; align-items: center; gap: 8px; padding: 6px 12px;\n background: #0e1428; border-bottom: 1px solid #1c2540; }\n .tl button { cursor: pointer; border: 1px solid #2a3350; background: #141b33;\n color: #e5e7eb; border-radius: 6px; padding: 2px 8px; font: inherit; white-space: nowrap; }\n .tl button.on { background: #7f1d1d; border-color: #b91c1c; color: #fecaca; }\n .tl .tl-range { flex: 1; min-width: 40px; accent-color: #6366f1; }\n .tl .tl-pos { color: #93c5fd; min-width: 44px; text-align: right; }\n .tl .tl-state { color: #fbbf24; min-width: 56px; font-size: 10px; }\n .body { overflow: auto; padding: 8px 12px; }\n .row { display: flex; gap: 6px; padding: 2px 0; }\n .k { color: #93c5fd; }\n h4 { margin: 12px 0 4px; color: #a5b4fc; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }\n .app { border: 1px solid #232c47; border-radius: 7px; padding: 6px 8px; margin: 5px 0; background: #0e1428; }\n .app .name { font-weight: 700; color: #fff; }\n .badge { display: inline-block; padding: 0 6px; border-radius: 999px; font-size: 10px; margin-left: 4px; }\n .b-mounted { background: #064e3b; color: #6ee7b7; }\n .b-hidden { background: #4c1d95; color: #ddd6fe; }\n .b-live { background: #1e3a8a; color: #bfdbfe; }\n .b-static { background: #3f3f46; color: #d4d4d8; }\n .ev { color: #cbd5e1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .ev .t { color: #fbbf24; }\n pre { margin: 4px 0 0; padding: 6px; background: #060a16; border-radius: 6px; overflow: auto;\n max-height: 120px; color: #a7f3d0; }\n .empty { opacity: .5; }\n`;\n\nfunction esc(s) {\n return String(s).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]));\n}\n\nfunction render(shadow, wu) {\n const snap = wu.inspect();\n const apps = snap.apps.length\n ? snap.apps.map((a) => `\n <div class=\"app\">\n <div><span class=\"name\">${esc(a.name)}</span>\n <span class=\"badge ${a.status === 'mounted' ? 'b-mounted' : 'b-hidden'}\">${a.status}</span>\n <span class=\"badge ${a.liveProps ? 'b-live' : 'b-static'}\">${a.liveProps ? 'live props' : 'static props'}</span>\n </div>\n <div class=\"row\"><span class=\"k\">container</span> ${esc(a.containerSelector || '—')}</div>\n <div class=\"row\"><span class=\"k\">sandbox</span> ${esc(a.sandbox?.isolationLevel || a.sandbox?.styleMode || '—')}</div>\n </div>`).join('')\n : '<div class=\"empty\">No apps mounted</div>';\n\n const events = snap.events.recent.length\n ? snap.events.recent.slice().reverse().map((e) =>\n `<div class=\"ev\"><span class=\"t\">${esc(e.type || '?')}</span> ${esc(e.appName || '')}</div>`).join('')\n : '<div class=\"empty\">No events yet</div>';\n\n const caps = (snap.capabilities && snap.capabilities.length)\n ? snap.capabilities.map((c) => `\n <div class=\"app\">\n <div><span class=\"name\">${esc(c.name)}</span>\n <span class=\"badge b-live\">v${esc(c.version)}</span>\n ${c.app ? `<span class=\"badge b-static\">${esc(c.app)}</span>` : ''}\n </div>\n </div>`).join('')\n : '<div class=\"empty\">No capabilities provided</div>';\n\n shadow.querySelector('.body').innerHTML = `\n <div class=\"row\"><span class=\"k\">apps</span>\n ${snap.summary.mounted} mounted · ${snap.summary.hidden} hidden · ${snap.summary.registered} registered</div>\n <h4>Apps</h4>${apps}\n <h4>Capabilities</h4>${caps}\n <h4>Recent events</h4>${events}\n <h4>Store</h4>\n <pre>${esc(JSON.stringify(snap.store.snapshot ?? {}, null, 2)).slice(0, 2000)}</pre>\n `;\n}\n\n/** Update the timeline bar in place (never recreates the slider mid-drag). */\nfunction syncTimeline(shadow) {\n const range = shadow.querySelector('.tl-range');\n const rec = shadow.querySelector('.tl-rec');\n const pos = shadow.querySelector('.tl-pos');\n const state = shadow.querySelector('.tl-state');\n const liveBtn = shadow.querySelector('.tl-live');\n if (!range) return;\n\n const st = tl ? tl.status() : { recording: false, live: true, position: 0, length: 0, loaded: false };\n\n range.max = String(st.length);\n if (!holding) range.value = String(Math.min(st.position, st.length));\n range.disabled = !tl || (st.length === 0 && !st.recording);\n\n rec.textContent = st.recording ? '■ Stop' : '● Rec';\n rec.classList.toggle('on', !!st.recording);\n rec.disabled = !tl;\n\n pos.textContent = st.length ? `${st.position}/${st.length}` : '—';\n state.textContent = !tl ? 'idle'\n : st.recording && st.live ? 'rec ● live'\n : st.recording ? 'rec ⏸ past'\n : st.live ? 'stopped' : '⏪ past';\n liveBtn.style.display = (tl && !st.live) ? '' : 'none';\n}\n\n/**\n * Mount the inspector overlay. Idempotent — returns the existing handle if\n * already open.\n * @param {object} wu - The WuCore instance\n * @returns {{close: Function}}\n */\nexport function mountInspector(wu) {\n // If a previous overlay node was removed without going through\n // unmountInspector (node.remove(), body.innerHTML='', SPA route change),\n // the module `host` is stale — reset it so the inspector can reopen and the\n // orphaned refresh timer is cleared.\n if (host) {\n if (host.isConnected) return { close: unmountInspector };\n unmountInspector();\n }\n\n holding = false;\n host = document.createElement('div');\n host.id = HOST_ID;\n const shadow = host.attachShadow({ mode: 'open' });\n shadow.innerHTML = `\n <style>${css}</style>\n <div class=\"panel\">\n <div class=\"head\">Wu Inspector <span class=\"ver\">v${esc(wu.version || '')}</span>\n <button class=\"x\" title=\"Close\">×</button>\n </div>\n <div class=\"tl\">\n <button class=\"tl-rec\" title=\"Record / stop time travel\">● Rec</button>\n <input class=\"tl-range\" type=\"range\" min=\"0\" max=\"0\" value=\"0\" step=\"1\" title=\"Scrub the page through time\" />\n <span class=\"tl-pos\">—</span>\n <span class=\"tl-state\">idle</span>\n <button class=\"tl-live\" title=\"Return to the present\" style=\"display:none\">⏭ Live</button>\n </div>\n <div class=\"body\"></div>\n </div>\n `;\n document.body.appendChild(host);\n shadow.querySelector('.x').addEventListener('click', () => unmountInspector());\n\n // Wire timeline controls once. They operate on the real WuTimeline instance,\n // lazy-loaded here (opening the inspector means you're debugging anyway).\n const range = shadow.querySelector('.tl-range');\n shadow.querySelector('.tl-rec').addEventListener('click', () => {\n if (!tl) return;\n if (tl.status().recording) tl.stop(); else tl.record();\n setTimeout(() => host && syncTimeline(shadow), 30);\n });\n range.addEventListener('input', () => {\n holding = true;\n if (tl) tl.seek(Number(range.value));\n });\n range.addEventListener('change', () => { holding = false; });\n shadow.querySelector('.tl-live').addEventListener('click', () => {\n holding = false;\n if (tl) tl.live();\n setTimeout(() => host && syncTimeline(shadow), 30);\n });\n\n // Resolve the timeline instance (does not start recording — zero overhead\n // until the user hits Rec). Closing the inspector never stops recording.\n if (typeof wu._ensureTimeline === 'function') {\n wu._ensureTimeline().then((t) => { tl = t; if (host) syncTimeline(shadow); }).catch(() => {});\n }\n\n render(shadow, wu);\n syncTimeline(shadow);\n // Live refresh while open — self-clears if the node was removed externally.\n timer = setInterval(() => {\n if (host && host.isConnected) { render(shadow, wu); syncTimeline(shadow); }\n else unmountInspector();\n }, 1000);\n\n return { close: unmountInspector };\n}\n\n/** Remove the inspector overlay. Does NOT stop an active recording. */\nexport function unmountInspector() {\n if (timer) { clearInterval(timer); timer = null; }\n if (host) { host.remove(); host = null; }\n tl = null;\n holding = false;\n}\n"],"names":["host","timer","tl","holding","esc","s","String","replace","c","render","shadow","wu","snap","inspect","apps","length","map","a","name","status","liveProps","containerSelector","sandbox","isolationLevel","styleMode","join","events","recent","slice","reverse","e","type","appName","caps","capabilities","version","app","querySelector","innerHTML","summary","mounted","hidden","registered","JSON","stringify","store","snapshot","syncTimeline","range","rec","pos","state","liveBtn","st","recording","live","position","max","value","Math","min","disabled","textContent","classList","toggle","style","display","mountInspector","isConnected","close","unmountInspector","document","createElement","id","attachShadow","mode","body","appendChild","addEventListener","stop","record","setTimeout","seek","Number","_ensureTimeline","then","t","catch","setInterval","clearInterval","remove"],"mappings":"AAeA,IAAIA,EAAO,KACPC,EAAQ,KACRC,EAAK,KACLC,GAAU,EA4Cd,SAASC,EAAIC,GACX,OAAOC,OAAOD,GAAGE,QAAQ,SAAWC,IAAO,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,QAASA,IACxF,CAEA,SAASC,EAAOC,EAAQC,GACtB,MAAMC,EAAOD,EAAGE,UACVC,EAAOF,EAAKE,KAAKC,OACnBH,EAAKE,KAAKE,IAAKC,GAAM,8DAEOb,EAAIa,EAAEC,8CACI,YAAbD,EAAEE,OAAuB,YAAc,eAAeF,EAAEE,+CACxDF,EAAEG,UAAY,SAAW,eAAeH,EAAEG,UAAY,aAAe,oGAExChB,EAAIa,EAAEI,mBAAqB,uEAC7BjB,EAAIa,EAAEK,SAASC,gBAAkBN,EAAEK,SAASE,WAAa,4BACpGC,KAAK,IACd,2CAEEC,EAASd,EAAKc,OAAOC,OAAOZ,OAC9BH,EAAKc,OAAOC,OAAOC,QAAQC,UAAUb,IAAKc,GACxC,mCAAmC1B,EAAI0B,EAAEC,MAAQ,eAAe3B,EAAI0B,EAAEE,SAAW,aAAaP,KAAK,IACrG,yCAEEQ,EAAQrB,EAAKsB,cAAgBtB,EAAKsB,aAAanB,OACjDH,EAAKsB,aAAalB,IAAKR,GAAM,8DAEDJ,EAAII,EAAEU,uDACAd,EAAII,EAAE2B,8BAClC3B,EAAE4B,IAAM,gCAAgChC,EAAII,EAAE4B,cAAgB,oCAE3DX,KAAK,IACd,oDAEJf,EAAO2B,cAAc,SAASC,UAAY,6DAEpC1B,EAAK2B,QAAQC,qBAAqB5B,EAAK2B,QAAQE,mBAAmB7B,EAAK2B,QAAQG,iDACpE5B,+BACQmB,gCACCP,mCAEjBtB,EAAIuC,KAAKC,UAAUhC,EAAKiC,MAAMC,UAAY,CAAA,EAAI,KAAM,IAAIlB,MAAM,EAAG,gBAE5E,CAGA,SAASmB,EAAarC,GACpB,MAAMsC,EAAQtC,EAAO2B,cAAc,aAC7BY,EAAMvC,EAAO2B,cAAc,WAC3Ba,EAAMxC,EAAO2B,cAAc,WAC3Bc,EAAQzC,EAAO2B,cAAc,aAC7Be,EAAU1C,EAAO2B,cAAc,YACrC,IAAKW,EAAO,OAEZ,MAAMK,EAAKnD,EAAKA,EAAGiB,SAAW,CAAEmC,WAAW,EAAOC,MAAM,EAAMC,SAAU,EAAGzC,OAAQ,GAEnFiC,EAAMS,IAAMnD,OAAO+C,EAAGtC,QACjBZ,IAAS6C,EAAMU,MAAQpD,OAAOqD,KAAKC,IAAIP,EAAGG,SAAUH,EAAGtC,UAC5DiC,EAAMa,UAAY3D,GAAqB,IAAdmD,EAAGtC,SAAiBsC,EAAGC,UAEhDL,EAAIa,YAAcT,EAAGC,UAAY,SAAW,QAC5CL,EAAIc,UAAUC,OAAO,OAAQX,EAAGC,WAChCL,EAAIY,UAAY3D,EAEhBgD,EAAIY,YAAcT,EAAGtC,OAAS,GAAGsC,EAAGG,YAAYH,EAAGtC,SAAW,IAC9DoC,EAAMW,YAAe5D,EACjBmD,EAAGC,WAAaD,EAAGE,KAAO,aAC1BF,EAAGC,UAAY,aACfD,EAAGE,KAAO,UAAY,SAHA,OAI1BH,EAAQa,MAAMC,QAAWhE,IAAOmD,EAAGE,KAAQ,GAAK,MAClD,CAQO,SAASY,EAAexD,GAK7B,GAAIX,EAAM,CACR,GAAIA,EAAKoE,YAAa,MAAO,CAAEC,MAAOC,GACtCA,GACF,CAEAnE,GAAU,EACVH,EAAOuE,SAASC,cAAc,OAC9BxE,EAAKyE,GAzIS,sBA0Id,MAAM/D,EAASV,EAAK0E,aAAa,CAAEC,KAAM,SACzCjE,EAAO4B,UAAY,m+EAGqClC,EAAIO,EAAGwB,SAAW,qiBAa1EoC,SAASK,KAAKC,YAAY7E,GAC1BU,EAAO2B,cAAc,MAAMyC,iBAAiB,QAAS,IAAMR,KAI3D,MAAMtB,EAAQtC,EAAO2B,cAAc,aA+BnC,OA9BA3B,EAAO2B,cAAc,WAAWyC,iBAAiB,QAAS,KACnD5E,IACDA,EAAGiB,SAASmC,UAAWpD,EAAG6E,OAAa7E,EAAG8E,SAC9CC,WAAW,IAAMjF,GAAQ+C,EAAarC,GAAS,OAEjDsC,EAAM8B,iBAAiB,QAAS,KAC9B3E,GAAU,EACND,GAAIA,EAAGgF,KAAKC,OAAOnC,EAAMU,UAE/BV,EAAM8B,iBAAiB,SAAU,KAAQ3E,GAAU,IACnDO,EAAO2B,cAAc,YAAYyC,iBAAiB,QAAS,KACzD3E,GAAU,EACND,GAAIA,EAAGqD,OACX0B,WAAW,IAAMjF,GAAQ+C,EAAarC,GAAS,MAKf,mBAAvBC,EAAGyE,iBACZzE,EAAGyE,kBAAkBC,KAAMC,IAAQpF,EAAKoF,EAAOtF,GAAM+C,EAAarC,KAAY6E,MAAM,QAGtF9E,EAAOC,EAAQC,GACfoC,EAAarC,GAEbT,EAAQuF,YAAY,KACdxF,GAAQA,EAAKoE,aAAe3D,EAAOC,EAAQC,GAAKoC,EAAarC,IAC5D4D,KACJ,KAEI,CAAED,MAAOC,EAClB,CAGO,SAASA,IACVrE,IAASwF,cAAcxF,GAAQA,EAAQ,MACvCD,IAAQA,EAAK0F,SAAU1F,EAAO,MAClCE,EAAK,KACLC,GAAU,CACZ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{logger as t}from"./wu-logger.js";class e{constructor(){this._cache=new Map}async fetchHtml(e,r){t.wuDebug(`[HtmlParser] Fetching HTML for ${r} from ${e}`);const s=await fetch(e,{method:"GET",headers:{Accept:"text/html,application/xhtml+xml,*/*"}});if(!s.ok)throw new Error(`[HtmlParser] Failed to fetch ${e}: HTTP ${s.status}`);const n=await s.text();if(!n||!n.trim())throw new Error(`[HtmlParser] Empty HTML response from ${e}`);return t.wuDebug(`[HtmlParser] Fetched ${n.length} chars for ${r}`),n}parse(e,r,s){const n=`${r}:${e.length}`;if(this._cache.has(n))return this._cache.get(n);const c=(new DOMParser).parseFromString(e,"text/html"),i=[],l=[],a=[],o=[],h={inlineScripts:i,externalScripts:l,inlineStyles:a,externalStyles:o,baseUrl:s};c.head&&this._extractResources(c.head,h);const u=c.body||c.documentElement;this._extractResources(u,h);const m={dom:u.innerHTML,scripts:{inline:i,external:l},styles:{inline:a,external:o}};return this._cache.set(n,m),t.wuDebug(`[HtmlParser] ${r}: ${i.length} inline scripts, ${l.length} external scripts, ${a.length+o.length} styles`),m}async fetchAndParse(t,e){const r=await this.fetchHtml(t,e);return this.parse(r,e,t)}_extractResources(t,e){const r=Array.from(t.children);for(const t of r){const r=t.nodeName.toLowerCase();if("script"!==r){if("style"===r){const r=t.textContent?.trim();r&&e.inlineStyles.push(r),t.replaceWith(document.createComment("wu:style"));continue}if("link"===r){const r=t.getAttribute("rel"),s=t.getAttribute("href");if("stylesheet"===r&&s){e.externalStyles.push(this._resolveUrl(s,e.baseUrl)),t.replaceWith(document.createComment("wu:link"));continue}}t.children.length>0&&this._extractResources(t,e)}else this._extractScript(t,e),t.replaceWith(document.createComment("wu:script"))}}_extractScript(e,r){const s=e.getAttribute("type")||"",n=e.getAttribute("src");if("module"!==s)if(n)r.externalScripts.push(this._resolveUrl(n,r.baseUrl));else{const t=e.textContent?.trim();t&&r.inlineScripts.push(t)}else t.wuDebug('[HtmlParser] Skipping type="module" script (use sandbox: "module" for ES modules)')}_resolveUrl(t,e){if(t.startsWith("http://")||t.startsWith("https://"))return t;if(t.startsWith("//"))return`https:${t}`;try{return new URL(t,e).href}catch{const r=e.replace(/\/$/,"");return t.startsWith("/")?r+t:`${r}/${t}`}}clearCache(){this._cache.clear()}}export{e as WuHtmlParser};
|
|
2
2
|
//# sourceMappingURL=wu-html-parser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wu-html-parser.js","sources":["../../src/core/wu-html-parser.js"],"sourcesContent":["/**\n * WU-HTML-PARSER: Fetch and parse HTML entries from micro-apps.\n *\n * Used in \"strict\" sandbox mode. The flow:\n * 1. Fetch the HTML page at the app's URL\n * 2. Parse it: extract inline/external scripts, inline/external styles, and clean DOM\n * 3. Return structured result so wu-core can inject DOM + styles into Shadow DOM\n * and execute scripts inside the proxy sandbox via WuScriptExecutor.\n *\n * This is the qiankun-style \"HTML entry\" approach that enables real JS isolation.\n */\n\nimport { logger } from './wu-logger.js';\n\nexport class WuHtmlParser {\n constructor() {\n this._cache = new Map();\n }\n\n /**\n * Fetch HTML content from a URL.\n * @param {string} url - App URL (e.g. http://localhost:3001)\n * @param {string} appName - For logging\n * @returns {Promise<string>} Raw HTML string\n */\n async fetchHtml(url, appName) {\n logger.wuDebug(`[HtmlParser] Fetching HTML for ${appName} from ${url}`);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: { 'Accept': 'text/html,application/xhtml+xml,*/*' }\n });\n\n if (!response.ok) {\n throw new Error(`[HtmlParser] Failed to fetch ${url}: HTTP ${response.status}`);\n }\n\n const html = await response.text();\n if (!html || !html.trim()) {\n throw new Error(`[HtmlParser] Empty HTML response from ${url}`);\n }\n\n logger.wuDebug(`[HtmlParser] Fetched ${html.length} chars for ${appName}`);\n return html;\n }\n\n /**\n * Parse HTML string into structured parts.\n *\n * @param {string} html - Raw HTML\n * @param {string} appName - App identifier\n * @param {string} baseUrl - Base URL for resolving relative paths\n * @returns {{\n * dom: string,\n * scripts: { inline: string[], external: string[] },\n * styles: { inline: string[], external: string[] }\n * }}\n */\n parse(html, appName, baseUrl) {\n const cacheKey = `${appName}:${html.length}`;\n if (this._cache.has(cacheKey)) {\n return this._cache.get(cacheKey);\n }\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n\n const inlineScripts = [];\n const externalScripts = [];\n const inlineStyles = [];\n const externalStyles = [];\n\n const ctx = {\n inlineScripts, externalScripts,\n inlineStyles, externalStyles,\n baseUrl\n };\n\n // DOMParser moves <style>, <link>, and some <script> tags to <head>.\n // Extract resources from both head and body to capture everything.\n if (doc.head) {\n this._extractResources(doc.head, ctx);\n }\n\n const temp = doc.body || doc.documentElement;\n this._extractResources(temp, ctx);\n\n const result = {\n dom: temp.innerHTML,\n scripts: { inline: inlineScripts, external: externalScripts },\n styles: { inline: inlineStyles, external: externalStyles }\n };\n\n this._cache.set(cacheKey, result);\n\n logger.wuDebug(\n `[HtmlParser] ${appName}: ${inlineScripts.length} inline scripts, ` +\n `${externalScripts.length} external scripts, ` +\n `${inlineStyles.length + externalStyles.length} styles`\n );\n\n return result;\n }\n\n /**\n * Convenience: fetch + parse in one call.\n */\n async fetchAndParse(url, appName) {\n const html = await this.fetchHtml(url, appName);\n return this.parse(html, appName, url);\n }\n\n /**\n * Recursively walk the DOM, extracting scripts and styles,\n * replacing them with comments to keep the DOM clean.\n */\n _extractResources(element, ctx) {\n // Iterate over a static copy since we mutate the DOM\n const children = Array.from(element.children);\n\n for (const child of children) {\n const tag = child.nodeName.toLowerCase();\n\n if (tag === 'script') {\n this._extractScript(child, ctx);\n child.replaceWith(document.createComment('wu:script'));\n continue;\n }\n\n if (tag === 'style') {\n const text = child.textContent?.trim();\n if (text) ctx.inlineStyles.push(text);\n child.replaceWith(document.createComment('wu:style'));\n continue;\n }\n\n if (tag === 'link') {\n const rel = child.getAttribute('rel');\n const href = child.getAttribute('href');\n if (rel === 'stylesheet' && href) {\n ctx.externalStyles.push(this._resolveUrl(href, ctx.baseUrl));\n child.replaceWith(document.createComment('wu:link'));\n continue;\n }\n }\n\n // Recurse into children\n if (child.children.length > 0) {\n this._extractResources(child, ctx);\n }\n }\n }\n\n /**\n * Extract a <script> tag into inline or external list.\n * Skips type=\"module\" scripts (they can't be eval'd — use module mode for those).\n */\n _extractScript(el, ctx) {\n const type = el.getAttribute('type') || '';\n const src = el.getAttribute('src');\n\n // Module scripts can't be executed via new Function / eval.\n // If the app uses ES modules, it should use sandbox: 'module' mode.\n if (type === 'module') {\n logger.wuDebug('[HtmlParser] Skipping type=\"module\" script (use sandbox: \"module\" for ES modules)');\n return;\n }\n\n if (src) {\n ctx.externalScripts.push(this._resolveUrl(src, ctx.baseUrl));\n } else {\n const text = el.textContent?.trim();\n if (text) ctx.inlineScripts.push(text);\n }\n }\n\n /**\n * Resolve a relative URL against a base URL.\n */\n _resolveUrl(url, baseUrl) {\n if (url.startsWith('http://') || url.startsWith('https://')) return url;\n if (url.startsWith('//')) return `https:${url}`;\n\n try {\n return new URL(url, baseUrl).href;\n } catch {\n // Fallback for environments without URL constructor\n const base = baseUrl.replace(/\\/$/, '');\n return url.startsWith('/') ? base + url : `${base}/${url}`;\n }\n }\n\n /**\n * Clear the parse cache.\n */\n clearCache() {\n this._cache.clear();\n }\n}\n"],"names":["WuHtmlParser","constructor","this","_cache","Map","fetchHtml","url","appName","logger","wuDebug","response","fetch","method","headers","Accept","ok","Error","status","html","text","trim","length","parse","baseUrl","cacheKey","has","get","doc","DOMParser","parseFromString","inlineScripts","externalScripts","inlineStyles","externalStyles","ctx","head","_extractResources","temp","body","documentElement","result","dom","innerHTML","scripts","inline","external","styles","set","fetchAndParse","element","children","Array","from","child","tag","nodeName","toLowerCase","textContent","push","replaceWith","document","createComment","rel","getAttribute","href","_resolveUrl","_extractScript","el","type","src","startsWith","URL","base","replace","clearCache","clear"],"mappings":"
|
|
1
|
+
{"version":3,"file":"wu-html-parser.js","sources":["../../src/core/wu-html-parser.js"],"sourcesContent":["/**\n * WU-HTML-PARSER: Fetch and parse HTML entries from micro-apps.\n *\n * Used in \"strict\" sandbox mode. The flow:\n * 1. Fetch the HTML page at the app's URL\n * 2. Parse it: extract inline/external scripts, inline/external styles, and clean DOM\n * 3. Return structured result so wu-core can inject DOM + styles into Shadow DOM\n * and execute scripts inside the proxy sandbox via WuScriptExecutor.\n *\n * This is the qiankun-style \"HTML entry\" approach that enables real JS isolation.\n */\n\nimport { logger } from './wu-logger.js';\n\nexport class WuHtmlParser {\n constructor() {\n this._cache = new Map();\n }\n\n /**\n * Fetch HTML content from a URL.\n * @param {string} url - App URL (e.g. http://localhost:3001)\n * @param {string} appName - For logging\n * @returns {Promise<string>} Raw HTML string\n */\n async fetchHtml(url, appName) {\n logger.wuDebug(`[HtmlParser] Fetching HTML for ${appName} from ${url}`);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: { 'Accept': 'text/html,application/xhtml+xml,*/*' }\n });\n\n if (!response.ok) {\n throw new Error(`[HtmlParser] Failed to fetch ${url}: HTTP ${response.status}`);\n }\n\n const html = await response.text();\n if (!html || !html.trim()) {\n throw new Error(`[HtmlParser] Empty HTML response from ${url}`);\n }\n\n logger.wuDebug(`[HtmlParser] Fetched ${html.length} chars for ${appName}`);\n return html;\n }\n\n /**\n * Parse HTML string into structured parts.\n *\n * @param {string} html - Raw HTML\n * @param {string} appName - App identifier\n * @param {string} baseUrl - Base URL for resolving relative paths\n * @returns {{\n * dom: string,\n * scripts: { inline: string[], external: string[] },\n * styles: { inline: string[], external: string[] }\n * }}\n */\n parse(html, appName, baseUrl) {\n const cacheKey = `${appName}:${html.length}`;\n if (this._cache.has(cacheKey)) {\n return this._cache.get(cacheKey);\n }\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n\n const inlineScripts = [];\n const externalScripts = [];\n const inlineStyles = [];\n const externalStyles = [];\n\n const ctx = {\n inlineScripts, externalScripts,\n inlineStyles, externalStyles,\n baseUrl\n };\n\n // DOMParser moves <style>, <link>, and some <script> tags to <head>.\n // Extract resources from both head and body to capture everything.\n if (doc.head) {\n this._extractResources(doc.head, ctx);\n }\n\n const temp = doc.body || doc.documentElement;\n this._extractResources(temp, ctx);\n\n const result = {\n dom: temp.innerHTML,\n scripts: { inline: inlineScripts, external: externalScripts },\n styles: { inline: inlineStyles, external: externalStyles }\n };\n\n this._cache.set(cacheKey, result);\n\n logger.wuDebug(\n `[HtmlParser] ${appName}: ${inlineScripts.length} inline scripts, ` +\n `${externalScripts.length} external scripts, ` +\n `${inlineStyles.length + externalStyles.length} styles`\n );\n\n return result;\n }\n\n /**\n * Convenience: fetch + parse in one call.\n */\n async fetchAndParse(url, appName) {\n const html = await this.fetchHtml(url, appName);\n return this.parse(html, appName, url);\n }\n\n /**\n * Recursively walk the DOM, extracting scripts and styles,\n * replacing them with comments to keep the DOM clean.\n */\n _extractResources(element, ctx) {\n // Iterate over a static copy since we mutate the DOM\n const children = Array.from(element.children);\n\n for (const child of children) {\n const tag = child.nodeName.toLowerCase();\n\n if (tag === 'script') {\n this._extractScript(child, ctx);\n child.replaceWith(document.createComment('wu:script'));\n continue;\n }\n\n if (tag === 'style') {\n const text = child.textContent?.trim();\n if (text) ctx.inlineStyles.push(text);\n child.replaceWith(document.createComment('wu:style'));\n continue;\n }\n\n if (tag === 'link') {\n const rel = child.getAttribute('rel');\n const href = child.getAttribute('href');\n if (rel === 'stylesheet' && href) {\n ctx.externalStyles.push(this._resolveUrl(href, ctx.baseUrl));\n child.replaceWith(document.createComment('wu:link'));\n continue;\n }\n }\n\n // Recurse into children\n if (child.children.length > 0) {\n this._extractResources(child, ctx);\n }\n }\n }\n\n /**\n * Extract a <script> tag into inline or external list.\n * Skips type=\"module\" scripts (they can't be eval'd — use module mode for those).\n */\n _extractScript(el, ctx) {\n const type = el.getAttribute('type') || '';\n const src = el.getAttribute('src');\n\n // Module scripts can't be executed via new Function / eval.\n // If the app uses ES modules, it should use sandbox: 'module' mode.\n if (type === 'module') {\n logger.wuDebug('[HtmlParser] Skipping type=\"module\" script (use sandbox: \"module\" for ES modules)');\n return;\n }\n\n if (src) {\n ctx.externalScripts.push(this._resolveUrl(src, ctx.baseUrl));\n } else {\n const text = el.textContent?.trim();\n if (text) ctx.inlineScripts.push(text);\n }\n }\n\n /**\n * Resolve a relative URL against a base URL.\n */\n _resolveUrl(url, baseUrl) {\n if (url.startsWith('http://') || url.startsWith('https://')) return url;\n if (url.startsWith('//')) return `https:${url}`;\n\n try {\n return new URL(url, baseUrl).href;\n } catch {\n // Fallback for environments without URL constructor\n const base = baseUrl.replace(/\\/$/, '');\n return url.startsWith('/') ? base + url : `${base}/${url}`;\n }\n }\n\n /**\n * Clear the parse cache.\n */\n clearCache() {\n this._cache.clear();\n }\n}\n"],"names":["WuHtmlParser","constructor","this","_cache","Map","fetchHtml","url","appName","logger","wuDebug","response","fetch","method","headers","Accept","ok","Error","status","html","text","trim","length","parse","baseUrl","cacheKey","has","get","doc","DOMParser","parseFromString","inlineScripts","externalScripts","inlineStyles","externalStyles","ctx","head","_extractResources","temp","body","documentElement","result","dom","innerHTML","scripts","inline","external","styles","set","fetchAndParse","element","children","Array","from","child","tag","nodeName","toLowerCase","textContent","push","replaceWith","document","createComment","rel","getAttribute","href","_resolveUrl","_extractScript","el","type","src","startsWith","URL","base","replace","clearCache","clear"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{logger as e}from"./wu-logger.js";const t=e=>{const t=String(e);return"undefined"!=typeof CSS&&"function"==typeof CSS.escape?CSS.escape(t):t.replace(/[^a-zA-Z0-9_\u00A0-\uFFFF-]/g,"\\$&").replace(/^(\d)/,(e,t)=>`\\3${t} `)};class n{constructor(e){this.appName=e,this.iframe=null,this._active=!1,this._timers=new Set,this._intervals=new Set,this._rafs=new Set,this._listeners=[]}activate(t,n,r){if(this._active)return this.iframe.contentWindow;const o=document.createElement("iframe");o.setAttribute("data-wu-sandbox",this.appName),o.style.cssText="display:none !important;position:absolute;width:0;height:0;border:0;",document.body.appendChild(o),this.iframe=o;const i=t.replace(/\/$/,""),s=o.contentWindow,a=s.document;return a.open(),a.write(`<!DOCTYPE html><html><head><base href="${i}/"></head><body></body></html>`),a.close(),s.wu=this._buildRestrictedWu(window.wu),s.__wuHostPostMessage=e=>window.postMessage(e,"*"),this._confineParentAccess(s),this._patchDocument(s,n,r),this._patchTimers(s),this._active=!0,e.wuDebug(`[IframeSandbox] Activated for ${this.appName} (base: ${i})`),s}_buildRestrictedWu(e){if(!e)return;const t=t=>"function"==typeof t?t.bind(e):t,n=e.store?Object.freeze({get:t(e.store.get),set:t(e.store.set),on:t(e.store.on),batch:t(e.store.batch)}):null,r=e.eventBus?Object.freeze({emit:t(e.eventBus.emit),on:t(e.eventBus.on),off:t(e.eventBus.off),once:t(e.eventBus.once),registerApp:t(e.eventBus.registerApp),unregisterApp:t(e.eventBus.unregisterApp)}):null,o={version:e.version,info:e.info,_isWuFramework:!0,define:t(e.define),mount:t(e.mount),unmount:t(e.unmount),app:t(e.app),hide:t(e.hide),show:t(e.show),isHidden:t(e.isHidden),store:n,eventBus:r,emit:t(e.emit),on:t(e.on),off:t(e.off),once:t(e.once),getState:t(e.getState),setState:t(e.setState),onStateChange:t(e.onStateChange),ai:e.ai,aiReady:t(e.aiReady),getStats:t(e.getStats),getSandboxInfo:t(e.getSandboxInfo),silence:t(e.silence),verbose:t(e.verbose)};return Object.freeze(o)}_confineParentAccess(t){const n=window,r=new Proxy(t,{get(e,t){if("postMessage"===t)return(...e)=>n.postMessage(...e);const r=Reflect.get(e,t);return"function"==typeof r?r.bind(e):r}});for(const n of["parent","top"])try{Object.defineProperty(t,n,{get:()=>r,configurable:!0})}catch{e.wuDebug(`[IframeSandbox] Could not confine window.${n}`)}try{Object.defineProperty(t,"frameElement",{get:()=>null,configurable:!0})}catch{e.wuDebug("[IframeSandbox] Could not confine window.frameElement")}}importModule(t,n=3e4){if(!this._active)throw new Error(`[IframeSandbox] Not active for ${this.appName}`);return new Promise((r,o)=>{const i=`wu_${this.appName}_${Date.now()}`,s=e=>{e.data?.channelId===i&&(c(),e.data.error?o(new Error(e.data.error)):r())},a=setTimeout(()=>{c(),o(new Error(`[IframeSandbox] import() timed out for ${this.appName}: ${t}`))},n),c=()=>{window.removeEventListener("message",s),clearTimeout(a)};window.addEventListener("message",s);const m=this.iframe.contentWindow.document,d=m.createElement("script");d.type="module",d.textContent=`import("${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}").then(() => __wuHostPostMessage({ channelId: "${i}", success: true })).catch(e => __wuHostPostMessage({ channelId: "${i}", error: e.message || String(e) }));`,m.head.appendChild(d),e.wuDebug(`[IframeSandbox] Importing module: ${t}`)})}_patchDocument(n,r,o){const i=n.document,s=o||r,a=document;i.createElement=(e,t)=>a.createElement(e,t),i.createElementNS=(e,t,n)=>a.createElementNS(e,t,n),i.createTextNode=e=>a.createTextNode(e),i.createComment=e=>a.createComment(e),i.createDocumentFragment=()=>a.createDocumentFragment(),i.querySelector=e=>s.querySelector(e),i.querySelectorAll=e=>s.querySelectorAll(e),i.getElementById=e=>s.querySelector(`#${t(e)}`),i.getElementsByClassName=e=>s.querySelectorAll(`.${t(e)}`),i.getElementsByTagName=e=>s.querySelectorAll(e);try{Object.defineProperty(i,"body",{get:()=>r,configurable:!0})}catch{e.wuDebug("[IframeSandbox] Could not redefine document.body")}const c=i.addEventListener.bind(i),m=i.removeEventListener.bind(i);i.addEventListener=(e,t,n)=>{this._listeners.push({target:i,event:e,handler:t,options:n}),c(e,t,n)},i.removeEventListener=(e,t,n)=>{this._listeners=this._listeners.filter(n=>!(n.target===i&&n.event===e&&n.handler===t)),m(e,t,n)},e.wuDebug(`[IframeSandbox] Document patched for ${this.appName}`)}_patchTimers(t){const n=t.setTimeout.bind(t),r=t.clearTimeout.bind(t),o=t.setInterval.bind(t),i=t.clearInterval.bind(t);if(t.setTimeout=(e,t,...r)=>{const o=n((...t)=>{this._timers.delete(o),"function"==typeof e&&e(...t)},t,...r);return this._timers.add(o),o},t.clearTimeout=e=>{this._timers.delete(e),r(e)},t.setInterval=(e,t,...n)=>{const r=o(e,t,...n);return this._intervals.add(r),r},t.clearInterval=e=>{this._intervals.delete(e),i(e)},t.requestAnimationFrame){const e=t.requestAnimationFrame.bind(t),n=t.cancelAnimationFrame.bind(t);t.requestAnimationFrame=t=>{const n=e((...e)=>{this._rafs.delete(n),t(...e)});return this._rafs.add(n),n},t.cancelAnimationFrame=e=>{this._rafs.delete(e),n(e)}}e.wuDebug(`[IframeSandbox] Timer tracking active for ${this.appName}`)}destroy(){if(this._active){this._active=!1;for(const e of this._timers)try{clearTimeout(e)}catch{}for(const e of this._intervals)try{clearInterval(e)}catch{}for(const e of this._rafs)try{cancelAnimationFrame(e)}catch{}this._timers.clear(),this._intervals.clear(),this._rafs.clear();for(const{target:e,event:t,handler:n,options:r}of this._listeners)try{e.removeEventListener(t,n,r)}catch{}if(this._listeners=[],this.iframe){try{const e=this.iframe.contentDocument;e&&(e.open(),e.write(""),e.close())}catch{}this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null}e.wuDebug(`[IframeSandbox] Destroyed for ${this.appName}`)}}isActive(){return this._active}}export{n as WuIframeSandbox};
|
|
2
2
|
//# sourceMappingURL=wu-iframe-sandbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wu-iframe-sandbox.js","sources":["../../src/core/wu-iframe-sandbox.js"],"sourcesContent":["/**\n * WU-IFRAME-SANDBOX: Real JS isolation using hidden iframes.\n *\n * Architecture:\n * ┌── Main Window ────────────────────────────────┐\n * │ ┌── Shadow DOM Container ──────────────────┐ │\n * │ │ App renders here (CSS isolated) │ │\n * │ └──────────────────────────────────────────┘ │\n * │ ┌── Hidden iframe ────────────────────────┐ │\n * │ │ import() runs here (REAL modules) │ │\n * │ │ window = iframe.contentWindow (ISOLATED)│ │\n * │ │ document patched → Shadow DOM │ │\n * │ └────────────────────────────────────────-─┘ │\n * └───────────────────────────────────────────────┘\n *\n * Why iframe?\n * - import() is REAL → tree shaking, source maps, HMR all work\n * - iframe has its own window → globals are isolated\n * - Destroying iframe kills all timers/listeners at once\n *\n * How it works:\n * 1. Create hidden iframe with <base href=\"appUrl\"> for URL resolution\n * 2. Patch iframe's document: createElement → main document (no ownerDocument issues),\n * querySelector/body → Shadow DOM container\n * 3. Track timers for guaranteed cleanup (some browsers don't kill iframe timers)\n * 4. import() the app module inside iframe → runs in isolated context\n * 5. App calls wu.define() → lifecycle registered on parent's WuCore\n * 6. On unmount: destroy iframe = nuclear cleanup\n *\n * Fallback:\n * If import() fails (CORS, module errors), wu-core falls back to eval mode\n * (fetch HTML + parse + execute with(proxy)).\n */\n\nimport { logger } from './wu-logger.js';\n\nexport class WuIframeSandbox {\n constructor(appName) {\n this.appName = appName;\n this.iframe = null;\n this._active = false;\n\n // Side-effect tracking for guaranteed cleanup\n this._timers = new Set();\n this._intervals = new Set();\n this._rafs = new Set();\n this._listeners = [];\n }\n\n /**\n * Create and activate the iframe sandbox.\n *\n * @param {string} appUrl - App's base URL (for <base href> and relative imports)\n * @param {HTMLElement} shadowContainer - Shadow DOM container for DOM redirection\n * @param {ShadowRoot|null} shadowRoot - Shadow root for query scoping\n * @returns {Window} The iframe's contentWindow (isolated execution context)\n */\n activate(appUrl, shadowContainer, shadowRoot) {\n if (this._active) return this.iframe.contentWindow;\n\n // 1. Create hidden iframe\n const iframe = document.createElement('iframe');\n iframe.setAttribute('data-wu-sandbox', this.appName);\n iframe.style.cssText = 'display:none !important;position:absolute;width:0;height:0;border:0;';\n\n // Must be in DOM before accessing contentWindow\n document.body.appendChild(iframe);\n this.iframe = iframe;\n\n // 2. Write base HTML with <base href> pointing to app URL.\n // This makes relative URL resolution work for fetch(), CSS url(), etc.\n // import() of full URLs works regardless of base.\n const baseUrl = appUrl.replace(/\\/$/, '');\n const iframeWin = iframe.contentWindow;\n const iframeDoc = iframeWin.document;\n\n iframeDoc.open();\n iframeDoc.write(\n `<!DOCTYPE html><html><head><base href=\"${baseUrl}/\"></head><body></body></html>`\n );\n iframeDoc.close();\n\n // 3. Expose a RESTRICTED wu inside the iframe.\n //\n // Pre-v2.1 we leaked the full `window.wu` — meaning sandboxed app code\n // could touch internals like wu.core, wu.cache, wu.errorBoundary,\n // wu.pluginSystem, etc. That defeats much of the iframe isolation.\n //\n // Now we expose only the public surface an app legitimately needs:\n // - lifecycle: define, mount, unmount, app, hide, show, isHidden\n // - communication: emit, on, off, once\n // - state: store (get/set/on), getState/setState/onStateChange\n // - AI: ai (lazy proxy, already locked down)\n // - meta: version, info, getStats, getSandboxInfo\n // - logger: silence, verbose\n iframeWin.wu = this._buildRestrictedWu(window.wu);\n\n // 4. Patch document: redirect DOM operations to Shadow DOM\n this._patchDocument(iframeWin, shadowContainer, shadowRoot);\n\n // 5. Track timers for guaranteed cleanup\n this._patchTimers(iframeWin);\n\n this._active = true;\n logger.wuDebug(`[IframeSandbox] Activated for ${this.appName} (base: ${baseUrl})`);\n return iframeWin;\n }\n\n /**\n * Build a Object.freeze'd, restricted view of `window.wu` for exposing\n * inside the iframe. Methods are bound so the app can call them naturally\n * (no need to `wu.emit.bind(wu)`); internals are hidden.\n *\n * @param {object} fullWu - The full framework instance (window.wu)\n * @returns {Readonly<object>} Frozen restricted facade\n * @private\n */\n _buildRestrictedWu(fullWu) {\n // Tests and SSR-like setups may have no global wu — keep the surface\n // optional rather than crashing during activate().\n if (!fullWu) return undefined;\n\n const bind = (fn) => (typeof fn === 'function' ? fn.bind(fullWu) : fn);\n\n // Wrap nested store so we don't expose its internals (ringbuffer, listeners Map)\n const storeFacade = fullWu.store ? Object.freeze({\n get: bind(fullWu.store.get),\n set: bind(fullWu.store.set),\n on: bind(fullWu.store.on),\n batch: bind(fullWu.store.batch),\n }) : null;\n\n // EventBus facade: expose pub/sub but not internals (authorizedApps, history, _internalTokens)\n const eventBusFacade = fullWu.eventBus ? Object.freeze({\n emit: bind(fullWu.eventBus.emit),\n on: bind(fullWu.eventBus.on),\n off: bind(fullWu.eventBus.off),\n once: bind(fullWu.eventBus.once),\n registerApp: bind(fullWu.eventBus.registerApp),\n unregisterApp: bind(fullWu.eventBus.unregisterApp),\n }) : null;\n\n const facade = {\n // Identity\n version: fullWu.version,\n info: fullWu.info,\n _isWuFramework: true,\n // Lifecycle the app needs to register itself\n define: bind(fullWu.define),\n mount: bind(fullWu.mount),\n unmount: bind(fullWu.unmount),\n app: bind(fullWu.app),\n hide: bind(fullWu.hide),\n show: bind(fullWu.show),\n isHidden: bind(fullWu.isHidden),\n // Sub-objects (restricted facades, not raw refs)\n store: storeFacade,\n eventBus: eventBusFacade,\n // Top-level pub/sub shortcuts\n emit: bind(fullWu.emit),\n on: bind(fullWu.on),\n off: bind(fullWu.off),\n once: bind(fullWu.once),\n // Store shortcuts (read/write only)\n getState: bind(fullWu.getState),\n setState: bind(fullWu.setState),\n onStateChange: bind(fullWu.onStateChange),\n // AI proxy is already lazy-loaded + restricted by setupLazyAi\n ai: fullWu.ai,\n aiReady: bind(fullWu.aiReady),\n // Meta\n getStats: bind(fullWu.getStats),\n getSandboxInfo: bind(fullWu.getSandboxInfo),\n // Logging control\n silence: bind(fullWu.silence),\n verbose: bind(fullWu.verbose),\n };\n\n return Object.freeze(facade);\n }\n\n /**\n * Import an ES module inside the iframe via real import().\n * Preserves tree shaking, source maps, and Vite HMR.\n *\n * @param {string} url - Full module URL to import\n * @param {number} [timeout=30000] - Max wait time in ms\n * @returns {Promise<void>}\n */\n importModule(url, timeout = 30000) {\n if (!this._active) {\n throw new Error(`[IframeSandbox] Not active for ${this.appName}`);\n }\n\n return new Promise((resolve, reject) => {\n const channelId = `wu_${this.appName}_${Date.now()}`;\n\n // Listen for import completion via postMessage\n const onMessage = (event) => {\n if (event.data?.channelId !== channelId) return;\n cleanup();\n if (event.data.error) {\n reject(new Error(event.data.error));\n } else {\n resolve();\n }\n };\n\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(\n `[IframeSandbox] import() timed out for ${this.appName}: ${url}`\n ));\n }, timeout);\n\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearTimeout(timer);\n };\n\n window.addEventListener('message', onMessage);\n\n // Inject module script into iframe\n const iframeDoc = this.iframe.contentWindow.document;\n const script = iframeDoc.createElement('script');\n script.type = 'module';\n script.textContent =\n `import(\"${url.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\")` +\n `.then(() => parent.postMessage({ channelId: \"${channelId}\", success: true }, '*'))` +\n `.catch(e => parent.postMessage({ channelId: \"${channelId}\", error: e.message || String(e) }, '*'));`;\n\n iframeDoc.head.appendChild(script);\n logger.wuDebug(`[IframeSandbox] Importing module: ${url}`);\n });\n }\n\n /**\n * Patch the iframe's document to redirect DOM operations.\n *\n * Critical patches:\n * - createElement/createTextNode → main document (avoids ownerDocument mismatch)\n * React/Vue create nodes and append to Shadow DOM container.\n * Nodes must belong to the main document to avoid cross-document adoption issues.\n *\n * - querySelector/body → Shadow DOM container\n * Libraries that query the document will find app elements in the Shadow DOM.\n *\n * - addEventListener → tracked for cleanup\n */\n _patchDocument(iframeWin, shadowContainer, shadowRoot) {\n const iframeDoc = iframeWin.document;\n const queryTarget = shadowRoot || shadowContainer;\n const mainDoc = document; // parent document\n\n // --- Node creation: use main document to avoid ownerDocument mismatch ---\n // React uses container.ownerDocument.createElement() internally,\n // but other code might use document.createElement() directly.\n // By redirecting to main document, all nodes belong to the same document tree.\n iframeDoc.createElement = (tag, options) => mainDoc.createElement(tag, options);\n iframeDoc.createElementNS = (ns, tag, options) => mainDoc.createElementNS(ns, tag, options);\n iframeDoc.createTextNode = (text) => mainDoc.createTextNode(text);\n iframeDoc.createComment = (text) => mainDoc.createComment(text);\n iframeDoc.createDocumentFragment = () => mainDoc.createDocumentFragment();\n\n // --- DOM queries: redirect to Shadow DOM ---\n iframeDoc.querySelector = (sel) => queryTarget.querySelector(sel);\n iframeDoc.querySelectorAll = (sel) => queryTarget.querySelectorAll(sel);\n iframeDoc.getElementById = (id) => queryTarget.querySelector(`#${id}`);\n iframeDoc.getElementsByClassName = (cls) => queryTarget.querySelectorAll(`.${cls}`);\n iframeDoc.getElementsByTagName = (tag) => queryTarget.querySelectorAll(tag);\n\n // --- document.body → shadow container ---\n // Frameworks that append to document.body (portals, modals) will target the Shadow DOM.\n try {\n Object.defineProperty(iframeDoc, 'body', {\n get: () => shadowContainer,\n configurable: true\n });\n } catch {\n // Some environments don't allow redefining body — not critical\n logger.wuDebug('[IframeSandbox] Could not redefine document.body');\n }\n\n // --- document.addEventListener: track for cleanup ---\n const origDocAdd = iframeDoc.addEventListener.bind(iframeDoc);\n const origDocRemove = iframeDoc.removeEventListener.bind(iframeDoc);\n\n iframeDoc.addEventListener = (event, handler, options) => {\n this._listeners.push({ target: iframeDoc, event, handler, options });\n origDocAdd(event, handler, options);\n };\n\n iframeDoc.removeEventListener = (event, handler, options) => {\n this._listeners = this._listeners.filter(\n l => !(l.target === iframeDoc && l.event === event && l.handler === handler)\n );\n origDocRemove(event, handler, options);\n };\n\n logger.wuDebug(`[IframeSandbox] Document patched for ${this.appName}`);\n }\n\n /**\n * Patch timers in the iframe for guaranteed cleanup.\n * Some browsers don't fully kill timers when an iframe is removed.\n * We track all IDs and clear them explicitly on destroy.\n */\n _patchTimers(iframeWin) {\n const origSetTimeout = iframeWin.setTimeout.bind(iframeWin);\n const origClearTimeout = iframeWin.clearTimeout.bind(iframeWin);\n const origSetInterval = iframeWin.setInterval.bind(iframeWin);\n const origClearInterval = iframeWin.clearInterval.bind(iframeWin);\n\n iframeWin.setTimeout = (fn, ms, ...args) => {\n const id = origSetTimeout((...a) => {\n this._timers.delete(id);\n if (typeof fn === 'function') fn(...a);\n }, ms, ...args);\n this._timers.add(id);\n return id;\n };\n\n iframeWin.clearTimeout = (id) => {\n this._timers.delete(id);\n origClearTimeout(id);\n };\n\n iframeWin.setInterval = (fn, ms, ...args) => {\n const id = origSetInterval(fn, ms, ...args);\n this._intervals.add(id);\n return id;\n };\n\n iframeWin.clearInterval = (id) => {\n this._intervals.delete(id);\n origClearInterval(id);\n };\n\n // requestAnimationFrame may not exist in all iframe contexts\n if (iframeWin.requestAnimationFrame) {\n const origRAF = iframeWin.requestAnimationFrame.bind(iframeWin);\n const origCancelRAF = iframeWin.cancelAnimationFrame.bind(iframeWin);\n\n iframeWin.requestAnimationFrame = (fn) => {\n const id = origRAF((...a) => {\n this._rafs.delete(id);\n fn(...a);\n });\n this._rafs.add(id);\n return id;\n };\n\n iframeWin.cancelAnimationFrame = (id) => {\n this._rafs.delete(id);\n origCancelRAF(id);\n };\n }\n\n logger.wuDebug(`[IframeSandbox] Timer tracking active for ${this.appName}`);\n }\n\n /**\n * Destroy the iframe and all side effects.\n * Nuclear cleanup: kills everything at once.\n */\n destroy() {\n if (!this._active) return;\n this._active = false;\n\n // 1. Clear all tracked timers\n for (const id of this._timers) { try { clearTimeout(id); } catch {} }\n for (const id of this._intervals) { try { clearInterval(id); } catch {} }\n for (const id of this._rafs) { try { cancelAnimationFrame(id); } catch {} }\n this._timers.clear();\n this._intervals.clear();\n this._rafs.clear();\n\n // 2. Remove all tracked event listeners\n for (const { target, event, handler, options } of this._listeners) {\n try { target.removeEventListener(event, handler, options); } catch {}\n }\n this._listeners = [];\n\n // 3. Wipe and remove iframe\n if (this.iframe) {\n try {\n const doc = this.iframe.contentDocument;\n if (doc) {\n doc.open();\n doc.write('');\n doc.close();\n }\n } catch {\n // Cross-origin or already detached — ignore\n }\n\n if (this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n }\n this.iframe = null;\n }\n\n logger.wuDebug(`[IframeSandbox] Destroyed for ${this.appName}`);\n }\n\n /**\n * Check if this sandbox is active.\n * @returns {boolean}\n */\n isActive() {\n return this._active;\n }\n}\n"],"names":["WuIframeSandbox","constructor","appName","this","iframe","_active","_timers","Set","_intervals","_rafs","_listeners","activate","appUrl","shadowContainer","shadowRoot","contentWindow","document","createElement","setAttribute","style","cssText","body","appendChild","baseUrl","replace","iframeWin","iframeDoc","open","write","close","wu","_buildRestrictedWu","window","_patchDocument","_patchTimers","logger","wuDebug","fullWu","bind","fn","storeFacade","store","Object","freeze","get","set","on","batch","eventBusFacade","eventBus","emit","off","once","registerApp","unregisterApp","facade","version","info","_isWuFramework","define","mount","unmount","app","hide","show","isHidden","getState","setState","onStateChange","ai","aiReady","getStats","getSandboxInfo","silence","verbose","importModule","url","timeout","Error","Promise","resolve","reject","channelId","Date","now","onMessage","event","data","cleanup","error","timer","setTimeout","removeEventListener","clearTimeout","addEventListener","script","type","textContent","head","queryTarget","mainDoc","tag","options","createElementNS","ns","createTextNode","text","createComment","createDocumentFragment","querySelector","sel","querySelectorAll","getElementById","id","getElementsByClassName","cls","getElementsByTagName","defineProperty","configurable","origDocAdd","origDocRemove","handler","push","target","filter","l","origSetTimeout","origClearTimeout","origSetInterval","setInterval","origClearInterval","clearInterval","ms","args","a","delete","add","requestAnimationFrame","origRAF","origCancelRAF","cancelAnimationFrame","destroy","clear","doc","contentDocument","parentNode","removeChild","isActive"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wu-iframe-sandbox.js","sources":["../../src/core/wu-iframe-sandbox.js"],"sourcesContent":["/**\n * WU-IFRAME-SANDBOX: Real JS isolation using hidden iframes.\n *\n * Architecture:\n * ┌── Main Window ────────────────────────────────┐\n * │ ┌── Shadow DOM Container ──────────────────┐ │\n * │ │ App renders here (CSS isolated) │ │\n * │ └──────────────────────────────────────────┘ │\n * │ ┌── Hidden iframe ────────────────────────┐ │\n * │ │ import() runs here (REAL modules) │ │\n * │ │ window = iframe.contentWindow (ISOLATED)│ │\n * │ │ document patched → Shadow DOM │ │\n * │ └────────────────────────────────────────-─┘ │\n * └───────────────────────────────────────────────┘\n *\n * Why iframe?\n * - import() is REAL → tree shaking, source maps, HMR all work\n * - iframe has its own window → globals are isolated\n * - Destroying iframe kills all timers/listeners at once\n *\n * How it works:\n * 1. Create hidden iframe with <base href=\"appUrl\"> for URL resolution\n * 2. Patch iframe's document: createElement → main document (no ownerDocument issues),\n * querySelector/body → Shadow DOM container\n * 3. Track timers for guaranteed cleanup (some browsers don't kill iframe timers)\n * 4. import() the app module inside iframe → runs in isolated context\n * 5. App calls wu.define() → lifecycle registered on parent's WuCore\n * 6. On unmount: destroy iframe = nuclear cleanup\n *\n * Fallback:\n * If import() fails (CORS, module errors), wu-core falls back to eval mode\n * (fetch HTML + parse + execute with(proxy)).\n */\n\nimport { logger } from './wu-logger.js';\n\n// CSS.escape with a fallback for environments that lack it (e.g. jsdom).\n// getElementById/getElementsByClassName accept ANY string, but interpolating\n// raw values into querySelector throws on non-CSS-identifiers (\"2col\", \"a.b\").\nconst cssEscape = (value) => {\n const str = String(value);\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(str);\n }\n return str\n .replace(/[^a-zA-Z0-9_\\u00A0-\\uFFFF-]/g, '\\\\$&')\n .replace(/^(\\d)/, (_, digit) => `\\\\3${digit} `);\n};\n\nexport class WuIframeSandbox {\n constructor(appName) {\n this.appName = appName;\n this.iframe = null;\n this._active = false;\n\n // Side-effect tracking for guaranteed cleanup\n this._timers = new Set();\n this._intervals = new Set();\n this._rafs = new Set();\n this._listeners = [];\n }\n\n /**\n * Create and activate the iframe sandbox.\n *\n * @param {string} appUrl - App's base URL (for <base href> and relative imports)\n * @param {HTMLElement} shadowContainer - Shadow DOM container for DOM redirection\n * @param {ShadowRoot|null} shadowRoot - Shadow root for query scoping\n * @returns {Window} The iframe's contentWindow (isolated execution context)\n */\n activate(appUrl, shadowContainer, shadowRoot) {\n if (this._active) return this.iframe.contentWindow;\n\n // 1. Create hidden iframe\n const iframe = document.createElement('iframe');\n iframe.setAttribute('data-wu-sandbox', this.appName);\n iframe.style.cssText = 'display:none !important;position:absolute;width:0;height:0;border:0;';\n\n // Must be in DOM before accessing contentWindow\n document.body.appendChild(iframe);\n this.iframe = iframe;\n\n // 2. Write base HTML with <base href> pointing to app URL.\n // This makes relative URL resolution work for fetch(), CSS url(), etc.\n // import() of full URLs works regardless of base.\n const baseUrl = appUrl.replace(/\\/$/, '');\n const iframeWin = iframe.contentWindow;\n const iframeDoc = iframeWin.document;\n\n iframeDoc.open();\n iframeDoc.write(\n `<!DOCTYPE html><html><head><base href=\"${baseUrl}/\"></head><body></body></html>`\n );\n iframeDoc.close();\n\n // 3. Expose a RESTRICTED wu inside the iframe.\n //\n // Pre-v2.1 we leaked the full `window.wu` — meaning sandboxed app code\n // could touch internals like wu.core, wu.cache, wu.errorBoundary,\n // wu.pluginSystem, etc. That defeats much of the iframe isolation.\n //\n // Now we expose only the public surface an app legitimately needs:\n // - lifecycle: define, mount, unmount, app, hide, show, isHidden\n // - communication: emit, on, off, once\n // - state: store (get/set/on), getState/setState/onStateChange\n // - AI: ai (lazy proxy, already locked down)\n // - meta: version, info, getStats, getSandboxInfo\n // - logger: silence, verbose\n iframeWin.wu = this._buildRestrictedWu(window.wu);\n\n // postMessage bridge to the host window, captured before parent/top\n // are confined so importModule() completion events can still reach us.\n iframeWin.__wuHostPostMessage = (data) => window.postMessage(data, '*');\n\n // Confine parent access: the iframe is same-origin, so window.parent /\n // window.frameElement would hand app code the host window (and the full\n // window.wu), bypassing the restricted facade. Point them back at the\n // sandbox so communication stays postMessage-only.\n this._confineParentAccess(iframeWin);\n\n // 4. Patch document: redirect DOM operations to Shadow DOM\n this._patchDocument(iframeWin, shadowContainer, shadowRoot);\n\n // 5. Track timers for guaranteed cleanup\n this._patchTimers(iframeWin);\n\n this._active = true;\n logger.wuDebug(`[IframeSandbox] Activated for ${this.appName} (base: ${baseUrl})`);\n return iframeWin;\n }\n\n /**\n * Build a Object.freeze'd, restricted view of `window.wu` for exposing\n * inside the iframe. Methods are bound so the app can call them naturally\n * (no need to `wu.emit.bind(wu)`); internals are hidden.\n *\n * @param {object} fullWu - The full framework instance (window.wu)\n * @returns {Readonly<object>} Frozen restricted facade\n * @private\n */\n _buildRestrictedWu(fullWu) {\n // Tests and SSR-like setups may have no global wu — keep the surface\n // optional rather than crashing during activate().\n if (!fullWu) return undefined;\n\n const bind = (fn) => (typeof fn === 'function' ? fn.bind(fullWu) : fn);\n\n // Wrap nested store so we don't expose its internals (ringbuffer, listeners Map)\n const storeFacade = fullWu.store ? Object.freeze({\n get: bind(fullWu.store.get),\n set: bind(fullWu.store.set),\n on: bind(fullWu.store.on),\n batch: bind(fullWu.store.batch),\n }) : null;\n\n // EventBus facade: expose pub/sub but not internals (authorizedApps, history, _internalTokens)\n const eventBusFacade = fullWu.eventBus ? Object.freeze({\n emit: bind(fullWu.eventBus.emit),\n on: bind(fullWu.eventBus.on),\n off: bind(fullWu.eventBus.off),\n once: bind(fullWu.eventBus.once),\n registerApp: bind(fullWu.eventBus.registerApp),\n unregisterApp: bind(fullWu.eventBus.unregisterApp),\n }) : null;\n\n const facade = {\n // Identity\n version: fullWu.version,\n info: fullWu.info,\n _isWuFramework: true,\n // Lifecycle the app needs to register itself\n define: bind(fullWu.define),\n mount: bind(fullWu.mount),\n unmount: bind(fullWu.unmount),\n app: bind(fullWu.app),\n hide: bind(fullWu.hide),\n show: bind(fullWu.show),\n isHidden: bind(fullWu.isHidden),\n // Sub-objects (restricted facades, not raw refs)\n store: storeFacade,\n eventBus: eventBusFacade,\n // Top-level pub/sub shortcuts\n emit: bind(fullWu.emit),\n on: bind(fullWu.on),\n off: bind(fullWu.off),\n once: bind(fullWu.once),\n // Store shortcuts (read/write only)\n getState: bind(fullWu.getState),\n setState: bind(fullWu.setState),\n onStateChange: bind(fullWu.onStateChange),\n // AI proxy is already lazy-loaded + restricted by setupLazyAi\n ai: fullWu.ai,\n aiReady: bind(fullWu.aiReady),\n // Meta\n getStats: bind(fullWu.getStats),\n getSandboxInfo: bind(fullWu.getSandboxInfo),\n // Logging control\n silence: bind(fullWu.silence),\n verbose: bind(fullWu.verbose),\n };\n\n return Object.freeze(facade);\n }\n\n /**\n * Mask the iframe's references back to the host window so sandboxed app\n * code cannot reach the full framework via window.parent.wu or\n * window.frameElement.ownerDocument. Best-effort: window.top is\n * [LegacyUnforgeable] in spec-compliant browsers and cannot be redefined.\n *\n * @param {Window} iframeWin - The iframe's contentWindow\n * @private\n */\n _confineParentAccess(iframeWin) {\n // parent/top resolve to a proxy of the iframe's own window — except\n // postMessage, which keeps reaching the host: parent.postMessage is the\n // canonical app→host channel and must survive the confinement. Every\n // other read (wu, document, ...) stays inside the iframe realm.\n const hostWindow = window;\n const confined = new Proxy(iframeWin, {\n get(target, prop) {\n if (prop === 'postMessage') {\n return (...args) => hostWindow.postMessage(...args);\n }\n const value = Reflect.get(target, prop);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n for (const prop of ['parent', 'top']) {\n try {\n Object.defineProperty(iframeWin, prop, {\n get: () => confined,\n configurable: true\n });\n } catch {\n logger.wuDebug(`[IframeSandbox] Could not confine window.${prop}`);\n }\n }\n try {\n Object.defineProperty(iframeWin, 'frameElement', {\n get: () => null,\n configurable: true\n });\n } catch {\n logger.wuDebug('[IframeSandbox] Could not confine window.frameElement');\n }\n }\n\n /**\n * Import an ES module inside the iframe via real import().\n * Preserves tree shaking, source maps, and Vite HMR.\n *\n * @param {string} url - Full module URL to import\n * @param {number} [timeout=30000] - Max wait time in ms\n * @returns {Promise<void>}\n */\n importModule(url, timeout = 30000) {\n if (!this._active) {\n throw new Error(`[IframeSandbox] Not active for ${this.appName}`);\n }\n\n return new Promise((resolve, reject) => {\n const channelId = `wu_${this.appName}_${Date.now()}`;\n\n // Listen for import completion via postMessage\n const onMessage = (event) => {\n if (event.data?.channelId !== channelId) return;\n cleanup();\n if (event.data.error) {\n reject(new Error(event.data.error));\n } else {\n resolve();\n }\n };\n\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(\n `[IframeSandbox] import() timed out for ${this.appName}: ${url}`\n ));\n }, timeout);\n\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearTimeout(timer);\n };\n\n window.addEventListener('message', onMessage);\n\n // Inject module script into iframe\n const iframeDoc = this.iframe.contentWindow.document;\n const script = iframeDoc.createElement('script');\n script.type = 'module';\n script.textContent =\n `import(\"${url.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\")` +\n `.then(() => __wuHostPostMessage({ channelId: \"${channelId}\", success: true }))` +\n `.catch(e => __wuHostPostMessage({ channelId: \"${channelId}\", error: e.message || String(e) }));`;\n\n iframeDoc.head.appendChild(script);\n logger.wuDebug(`[IframeSandbox] Importing module: ${url}`);\n });\n }\n\n /**\n * Patch the iframe's document to redirect DOM operations.\n *\n * Critical patches:\n * - createElement/createTextNode → main document (avoids ownerDocument mismatch)\n * React/Vue create nodes and append to Shadow DOM container.\n * Nodes must belong to the main document to avoid cross-document adoption issues.\n *\n * - querySelector/body → Shadow DOM container\n * Libraries that query the document will find app elements in the Shadow DOM.\n *\n * - addEventListener → tracked for cleanup\n */\n _patchDocument(iframeWin, shadowContainer, shadowRoot) {\n const iframeDoc = iframeWin.document;\n const queryTarget = shadowRoot || shadowContainer;\n const mainDoc = document; // parent document\n\n // --- Node creation: use main document to avoid ownerDocument mismatch ---\n // React uses container.ownerDocument.createElement() internally,\n // but other code might use document.createElement() directly.\n // By redirecting to main document, all nodes belong to the same document tree.\n iframeDoc.createElement = (tag, options) => mainDoc.createElement(tag, options);\n iframeDoc.createElementNS = (ns, tag, options) => mainDoc.createElementNS(ns, tag, options);\n iframeDoc.createTextNode = (text) => mainDoc.createTextNode(text);\n iframeDoc.createComment = (text) => mainDoc.createComment(text);\n iframeDoc.createDocumentFragment = () => mainDoc.createDocumentFragment();\n\n // --- DOM queries: redirect to Shadow DOM ---\n iframeDoc.querySelector = (sel) => queryTarget.querySelector(sel);\n iframeDoc.querySelectorAll = (sel) => queryTarget.querySelectorAll(sel);\n iframeDoc.getElementById = (id) => queryTarget.querySelector(`#${cssEscape(id)}`);\n iframeDoc.getElementsByClassName = (cls) => queryTarget.querySelectorAll(`.${cssEscape(cls)}`);\n iframeDoc.getElementsByTagName = (tag) => queryTarget.querySelectorAll(tag);\n\n // --- document.body → shadow container ---\n // Frameworks that append to document.body (portals, modals) will target the Shadow DOM.\n try {\n Object.defineProperty(iframeDoc, 'body', {\n get: () => shadowContainer,\n configurable: true\n });\n } catch {\n // Some environments don't allow redefining body — not critical\n logger.wuDebug('[IframeSandbox] Could not redefine document.body');\n }\n\n // --- document.addEventListener: track for cleanup ---\n const origDocAdd = iframeDoc.addEventListener.bind(iframeDoc);\n const origDocRemove = iframeDoc.removeEventListener.bind(iframeDoc);\n\n iframeDoc.addEventListener = (event, handler, options) => {\n this._listeners.push({ target: iframeDoc, event, handler, options });\n origDocAdd(event, handler, options);\n };\n\n iframeDoc.removeEventListener = (event, handler, options) => {\n this._listeners = this._listeners.filter(\n l => !(l.target === iframeDoc && l.event === event && l.handler === handler)\n );\n origDocRemove(event, handler, options);\n };\n\n logger.wuDebug(`[IframeSandbox] Document patched for ${this.appName}`);\n }\n\n /**\n * Patch timers in the iframe for guaranteed cleanup.\n * Some browsers don't fully kill timers when an iframe is removed.\n * We track all IDs and clear them explicitly on destroy.\n */\n _patchTimers(iframeWin) {\n const origSetTimeout = iframeWin.setTimeout.bind(iframeWin);\n const origClearTimeout = iframeWin.clearTimeout.bind(iframeWin);\n const origSetInterval = iframeWin.setInterval.bind(iframeWin);\n const origClearInterval = iframeWin.clearInterval.bind(iframeWin);\n\n iframeWin.setTimeout = (fn, ms, ...args) => {\n const id = origSetTimeout((...a) => {\n this._timers.delete(id);\n if (typeof fn === 'function') fn(...a);\n }, ms, ...args);\n this._timers.add(id);\n return id;\n };\n\n iframeWin.clearTimeout = (id) => {\n this._timers.delete(id);\n origClearTimeout(id);\n };\n\n iframeWin.setInterval = (fn, ms, ...args) => {\n const id = origSetInterval(fn, ms, ...args);\n this._intervals.add(id);\n return id;\n };\n\n iframeWin.clearInterval = (id) => {\n this._intervals.delete(id);\n origClearInterval(id);\n };\n\n // requestAnimationFrame may not exist in all iframe contexts\n if (iframeWin.requestAnimationFrame) {\n const origRAF = iframeWin.requestAnimationFrame.bind(iframeWin);\n const origCancelRAF = iframeWin.cancelAnimationFrame.bind(iframeWin);\n\n iframeWin.requestAnimationFrame = (fn) => {\n const id = origRAF((...a) => {\n this._rafs.delete(id);\n fn(...a);\n });\n this._rafs.add(id);\n return id;\n };\n\n iframeWin.cancelAnimationFrame = (id) => {\n this._rafs.delete(id);\n origCancelRAF(id);\n };\n }\n\n logger.wuDebug(`[IframeSandbox] Timer tracking active for ${this.appName}`);\n }\n\n /**\n * Destroy the iframe and all side effects.\n * Nuclear cleanup: kills everything at once.\n */\n destroy() {\n if (!this._active) return;\n this._active = false;\n\n // 1. Clear all tracked timers\n for (const id of this._timers) { try { clearTimeout(id); } catch {} }\n for (const id of this._intervals) { try { clearInterval(id); } catch {} }\n for (const id of this._rafs) { try { cancelAnimationFrame(id); } catch {} }\n this._timers.clear();\n this._intervals.clear();\n this._rafs.clear();\n\n // 2. Remove all tracked event listeners\n for (const { target, event, handler, options } of this._listeners) {\n try { target.removeEventListener(event, handler, options); } catch {}\n }\n this._listeners = [];\n\n // 3. Wipe and remove iframe\n if (this.iframe) {\n try {\n const doc = this.iframe.contentDocument;\n if (doc) {\n doc.open();\n doc.write('');\n doc.close();\n }\n } catch {\n // Cross-origin or already detached — ignore\n }\n\n if (this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n }\n this.iframe = null;\n }\n\n logger.wuDebug(`[IframeSandbox] Destroyed for ${this.appName}`);\n }\n\n /**\n * Check if this sandbox is active.\n * @returns {boolean}\n */\n isActive() {\n return this._active;\n }\n}\n"],"names":["cssEscape","value","str","String","CSS","escape","replace","_","digit","WuIframeSandbox","constructor","appName","this","iframe","_active","_timers","Set","_intervals","_rafs","_listeners","activate","appUrl","shadowContainer","shadowRoot","contentWindow","document","createElement","setAttribute","style","cssText","body","appendChild","baseUrl","iframeWin","iframeDoc","open","write","close","wu","_buildRestrictedWu","window","__wuHostPostMessage","data","postMessage","_confineParentAccess","_patchDocument","_patchTimers","logger","wuDebug","fullWu","bind","fn","storeFacade","store","Object","freeze","get","set","on","batch","eventBusFacade","eventBus","emit","off","once","registerApp","unregisterApp","facade","version","info","_isWuFramework","define","mount","unmount","app","hide","show","isHidden","getState","setState","onStateChange","ai","aiReady","getStats","getSandboxInfo","silence","verbose","hostWindow","confined","Proxy","target","prop","args","Reflect","defineProperty","configurable","importModule","url","timeout","Error","Promise","resolve","reject","channelId","Date","now","onMessage","event","cleanup","error","timer","setTimeout","removeEventListener","clearTimeout","addEventListener","script","type","textContent","head","queryTarget","mainDoc","tag","options","createElementNS","ns","createTextNode","text","createComment","createDocumentFragment","querySelector","sel","querySelectorAll","getElementById","id","getElementsByClassName","cls","getElementsByTagName","origDocAdd","origDocRemove","handler","push","filter","l","origSetTimeout","origClearTimeout","origSetInterval","setInterval","origClearInterval","clearInterval","ms","a","delete","add","requestAnimationFrame","origRAF","origCancelRAF","cancelAnimationFrame","destroy","clear","doc","contentDocument","parentNode","removeChild","isActive"],"mappings":"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"}
|
package/dist/core/wu-loader.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{logger as e}from"./wu-logger.js";class t{constructor(t={}){this.maxCacheSize=t.maxCacheSize??50,this.cacheTTL=t.cacheTTL??18e5,this.cache=new Map,this.loadingPromises=new Map,e.debug("[WuLoader] Dynamic loader initialized")}_cacheGet(t){if(!this.cache.has(t))return;const o=this.cache.get(t),a=Date.now();return a-o.timestamp>this.cacheTTL?(this.cache.delete(t),void e.debug(`[WuLoader] Cache expired for: ${t}`)):(this.cache.delete(t),o.lastAccess=a,this.cache.set(t,o),o.code)}_cacheSet(e,t){this.cache.has(e)&&this.cache.delete(e),this._evictIfNeeded();const o=Date.now();this.cache.set(e,{code:t,timestamp:o,lastAccess:o})}_evictIfNeeded(){const t=Date.now();for(const[o,a]of this.cache)t-a.timestamp>this.cacheTTL&&(this.cache.delete(o),e.debug(`[WuLoader] Evicted expired entry: ${o}`));for(;this.cache.size>=this.maxCacheSize;){const t=this.cache.keys().next().value;this.cache.delete(t),e.debug(`[WuLoader] Evicted LRU entry: ${t}`)}}async loadApp(t,o){await this._ensureSentinelVerified();const a=`${t}/${o?.entry||"index.js"}`;e.debug(`[WuLoader] Loading app from: ${a}`);try{const t=this._cacheGet(a);if(void 0!==t)return e.debug(`[WuLoader] Cache hit for: ${a}`),t;if(this.loadingPromises.has(a))return e.debug(`[WuLoader] Loading in progress for: ${a}`),await this.loadingPromises.get(a);const o=this.fetchCode(a);this.loadingPromises.set(a,o);const i=await o;return this.loadingPromises.delete(a),this._cacheSet(a,i),e.debug(`[WuLoader] App loaded successfully: ${a}`),i}catch(e){throw this.loadingPromises.delete(a),console.error(`[WuLoader] Failed to load app: ${a}`,e),new Error(`Failed to load app from ${a}: ${e.message}`)}}async loadComponent(t,o){await this._ensureSentinelVerified();let a=o;a.startsWith("./")&&(a=a.substring(2)),a.endsWith(".js")||a.endsWith(".jsx")||(a+=".js");const i=`${t}/${a}`;e.debug(`[WuLoader] Loading component from: ${i}`);try{const t=await this.loadCode(i),a=new Function("require","module","exports",`\n ${t}\n return typeof module.exports === 'function' ? module.exports :\n typeof module.exports === 'object' && module.exports.default ? module.exports.default :\n exports.default || exports;\n `),s={exports:{}},r=a(t=>(e.warn(`[WuLoader] Component ${o} requires ${t} - not supported yet`),{}),s,s.exports);return e.debug(`[WuLoader] Component loaded: ${o}`),r}catch(e){throw console.error(`[WuLoader] Failed to load component: ${o}`,e),new Error(`Failed to load component ${o}: ${e.message}`)}}async loadCode(e){const t=this._cacheGet(e);if(void 0!==t)return t;if(this.loadingPromises.has(e))return await this.loadingPromises.get(e);const o=this.fetchCode(e);this.loadingPromises.set(e,o);try{const t=await o;return this.loadingPromises.delete(e),this._cacheSet(e,t),t}catch(t){throw this.loadingPromises.delete(e),t}}async fetchCode(e,t=3e4){const o=await fetch(e,{cache:"no-cache",headers:{Accept:"application/javascript, text/javascript, */*"},signal:AbortSignal.timeout(t)});if(!o.ok)throw new Error(`HTTP ${o.status}: ${o.statusText}`);const a=await o.text();if(!a.trim())throw new Error("Empty response");return a}async preload(t){e.debug(`[WuLoader] Preloading ${t.length} apps...`);const o=t.map(async t=>{try{await this.loadApp(t.url,t.manifest),e.debug(`[WuLoader] Preloaded: ${t.name}`)}catch(o){e.warn(`[WuLoader] Failed to preload ${t.name}:`,o.message)}});await Promise.allSettled(o),e.debug("[WuLoader] Preload completed")}async isAvailable(e){try{return(await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(5e3)})).ok}catch{return!1}}async resolveDependencies(t,o){const a=new Map;for(const i of t||[]){const[t,s]=i.split(".");if(!t||!s){e.warn(`[WuLoader] Invalid import format: ${i}`);continue}const r=o.get(t);if(!r){e.warn(`[WuLoader] Import app not found: ${t}`);continue}const n=r.manifest,c=n?.wu?.exports?.[s];if(c)try{const t=await this.loadComponent(r.url,c);a.set(i,t),e.debug(`[WuLoader] Resolved dependency: ${i}`)}catch(e){console.error(`[WuLoader] Failed to resolve: ${i}`,e)}else e.warn(`[WuLoader] Export not found: ${i}`)}return a}clearCache(t){if(t){let o;try{o=new RegExp(t)}catch(o){return void e.warn(`[WuLoader] Invalid clearCache pattern: ${t} (${o.message})`)}for(const[t]of this.cache)o.test(t)&&(this.cache.delete(t),e.debug(`[WuLoader] Cleared cache for: ${t}`))}else this.cache.clear(),e.debug("[WuLoader] Cache cleared completely")}getStats(){return{cached:this.cache.size,maxCacheSize:this.maxCacheSize,cacheTTL:this.cacheTTL,loading:this.loadingPromises.size,cacheKeys:Array.from(this.cache.keys())}}async _ensureSentinelVerified(){if("undefined"==typeof window)return;const e=window.location?.hostname||"";"localhost"!==e&&"127.0.0.1"!==e&&"0.0.0.0"!==e&&window.__wu_sentinel&&(window.__wu_sentinel.isVerified()||await new Promise((e,t)=>{if(window.__wu_sentinel.isVerified())return void e();const o=setTimeout(()=>{i(),t(new Error("[WuLoader] Sentinel verification timeout — content blocked"))},1e4),a=()=>{i(),e()};function i(){clearTimeout(o),window.removeEventListener("wu:sentinel:verified",a)}window.addEventListener("wu:sentinel:verified",a);const s=setInterval(()=>{window.__wu_sentinel.isVerified()&&(clearInterval(s),i(),e())},100),r=i;i=function(){clearInterval(s),r()}}))}}export{t as WuLoader};
|
|
2
2
|
//# sourceMappingURL=wu-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wu-loader.js","sources":["../../src/core/wu-loader.js"],"sourcesContent":["/**\n * WU-LOADER: SISTEMA DE CARGA DINAMICA UNIVERSAL\n * Carga aplicaciones y componentes sin depender del framework\n *\n * Cache strategy: LRU with TTL eviction.\n * Entries track lastAccess time. When the cache reaches maxCacheSize,\n * the least recently accessed entry is evicted. Entries older than\n * cacheTTL are treated as stale and removed on access or eviction.\n */\n\nimport { logger } from './wu-logger.js';\n\n/**\n * @typedef {Object} WuLoaderOptions\n * @property {number} [maxCacheSize=50] - Maximum cache entries\n * @property {number} [cacheTTL=1800000] - Cache TTL in ms (default 30min)\n */\n\n/**\n * @typedef {Object} WuLoaderStats\n * @property {number} cached - Number of cached entries\n * @property {number} loading - Number of in-flight loads\n * @property {number} maxCacheSize - Max cache size setting\n * @property {number} cacheTTL - Cache TTL setting\n * @property {string[]} cacheKeys - Cached URL keys\n */\n\nexport class WuLoader {\n /**\n * @param {Object} options\n * @param {number} [options.maxCacheSize=50] - Maximum number of entries in the cache\n * @param {number} [options.cacheTTL=1800000] - Time-to-live for cache entries in ms (default 30 minutes)\n */\n constructor(options = {}) {\n this.maxCacheSize = options.maxCacheSize ?? 50;\n this.cacheTTL = options.cacheTTL ?? 1800000;\n this.cache = new Map();\n this.loadingPromises = new Map();\n\n logger.debug('[WuLoader] Dynamic loader initialized');\n }\n\n /**\n * Read from cache with TTL validation and LRU access tracking.\n * Returns undefined if the entry does not exist or has expired.\n * @param {string} key\n * @returns {string|undefined}\n */\n _cacheGet(key) {\n if (!this.cache.has(key)) {\n return undefined;\n }\n\n const entry = this.cache.get(key);\n const now = Date.now();\n\n if (now - entry.timestamp > this.cacheTTL) {\n this.cache.delete(key);\n logger.debug(`[WuLoader] Cache expired for: ${key}`);\n return undefined;\n }\n\n // Promote: delete and re-insert so iteration order reflects recency.\n // Map iteration order in JS follows insertion order, so the oldest\n // inserted entry is always first -- exactly what we need for LRU eviction.\n this.cache.delete(key);\n entry.lastAccess = now;\n this.cache.set(key, entry);\n\n return entry.code;\n }\n\n /**\n * Write to cache. Evicts stale and LRU entries before inserting.\n * @param {string} key\n * @param {string} code\n */\n _cacheSet(key, code) {\n // If the key already exists, remove it first so re-insertion\n // moves it to the end (most-recently-used position).\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n this._evictIfNeeded();\n\n const now = Date.now();\n this.cache.set(key, {\n code,\n timestamp: now,\n lastAccess: now\n });\n }\n\n /**\n * Evict entries until cache is below maxCacheSize.\n *\n * Two-pass strategy:\n * 1. Remove all expired entries (TTL exceeded).\n * 2. If still at capacity, remove the least recently accessed entry.\n * Because Map preserves insertion order and _cacheGet promotes on\n * access, the first key from the iterator is always the LRU entry.\n */\n _evictIfNeeded() {\n const now = Date.now();\n\n // Pass 1: purge expired entries\n for (const [key, entry] of this.cache) {\n if (now - entry.timestamp > this.cacheTTL) {\n this.cache.delete(key);\n logger.debug(`[WuLoader] Evicted expired entry: ${key}`);\n }\n }\n\n // Pass 2: evict LRU entries until we are under the limit\n while (this.cache.size >= this.maxCacheSize) {\n // Map.keys().next() gives us the oldest-inserted key (LRU)\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey);\n logger.debug(`[WuLoader] Evicted LRU entry: ${oldestKey}`);\n }\n }\n\n /**\n * Cargar aplicacion completa\n * @param {string} appUrl - URL base de la aplicacion\n * @param {Object} manifest - Manifest de la aplicacion\n * @returns {string} Codigo JavaScript de la aplicacion\n */\n async loadApp(appUrl, manifest) {\n // Sentinel gate: block code loading for unverified clients\n await this._ensureSentinelVerified();\n\n const entryFile = manifest?.entry || 'index.js';\n const fullUrl = `${appUrl}/${entryFile}`;\n\n logger.debug(`[WuLoader] Loading app from: ${fullUrl}`);\n\n try {\n // Check cache with TTL and LRU tracking\n const cached = this._cacheGet(fullUrl);\n if (cached !== undefined) {\n logger.debug(`[WuLoader] Cache hit for: ${fullUrl}`);\n return cached;\n }\n\n // Check if already loading\n if (this.loadingPromises.has(fullUrl)) {\n logger.debug(`[WuLoader] Loading in progress for: ${fullUrl}`);\n return await this.loadingPromises.get(fullUrl);\n }\n\n // Create loading promise\n const loadingPromise = this.fetchCode(fullUrl);\n this.loadingPromises.set(fullUrl, loadingPromise);\n\n const code = await loadingPromise;\n\n // Clean up loading promise and cache result\n this.loadingPromises.delete(fullUrl);\n this._cacheSet(fullUrl, code);\n\n logger.debug(`[WuLoader] App loaded successfully: ${fullUrl}`);\n return code;\n\n } catch (error) {\n this.loadingPromises.delete(fullUrl);\n console.error(`[WuLoader] Failed to load app: ${fullUrl}`, error);\n throw new Error(`Failed to load app from ${fullUrl}: ${error.message}`);\n }\n }\n\n /**\n * Cargar componente especifico\n * @param {string} appUrl - URL base de la aplicacion\n * @param {string} componentPath - Ruta del componente\n * @returns {Function} Funcion del componente\n */\n async loadComponent(appUrl, componentPath) {\n // Sentinel gate\n await this._ensureSentinelVerified();\n\n // Normalizar ruta del componente\n let normalizedPath = componentPath;\n if (normalizedPath.startsWith('./')) {\n normalizedPath = normalizedPath.substring(2);\n }\n if (!normalizedPath.endsWith('.js') && !normalizedPath.endsWith('.jsx')) {\n normalizedPath += '.js';\n }\n\n const fullUrl = `${appUrl}/${normalizedPath}`;\n\n logger.debug(`[WuLoader] Loading component from: ${fullUrl}`);\n\n try {\n // Cargar codigo del componente\n const code = await this.loadCode(fullUrl);\n\n // Crear funcion que retorna el componente\n const componentFunction = new Function('require', 'module', 'exports', `\n ${code}\n return typeof module.exports === 'function' ? module.exports :\n typeof module.exports === 'object' && module.exports.default ? module.exports.default :\n exports.default || exports;\n `);\n\n // Ejecutar y obtener el componente\n const fakeModule = { exports: {} };\n const fakeRequire = (name) => {\n logger.warn(`[WuLoader] Component ${componentPath} requires ${name} - not supported yet`);\n return {};\n };\n\n const component = componentFunction(fakeRequire, fakeModule, fakeModule.exports);\n\n logger.debug(`[WuLoader] Component loaded: ${componentPath}`);\n return component;\n\n } catch (error) {\n console.error(`[WuLoader] Failed to load component: ${componentPath}`, error);\n throw new Error(`Failed to load component ${componentPath}: ${error.message}`);\n }\n }\n\n /**\n * Cargar codigo con cache\n * @param {string} url - URL del archivo\n * @returns {string} Codigo JavaScript\n */\n async loadCode(url) {\n // Check cache with TTL and LRU tracking\n const cached = this._cacheGet(url);\n if (cached !== undefined) {\n return cached;\n }\n\n // Check if already loading\n if (this.loadingPromises.has(url)) {\n return await this.loadingPromises.get(url);\n }\n\n // Create loading promise\n const loadingPromise = this.fetchCode(url);\n this.loadingPromises.set(url, loadingPromise);\n\n try {\n const code = await loadingPromise;\n this.loadingPromises.delete(url);\n this._cacheSet(url, code);\n return code;\n } catch (error) {\n this.loadingPromises.delete(url);\n throw error;\n }\n }\n\n /**\n * Realizar fetch del codigo\n * @param {string} url - URL del archivo\n * @returns {string} Codigo JavaScript\n */\n async fetchCode(url, timeoutMs = 30000) {\n // 30s default: long enough for slow dev servers + large bundles, short\n // enough that a hung app does not block mount forever. Pre-v2.0 there was\n // no timeout — a non-responsive remote could stall the framework.\n const response = await fetch(url, {\n cache: 'no-cache',\n headers: {\n 'Accept': 'application/javascript, text/javascript, */*'\n },\n signal: AbortSignal.timeout(timeoutMs)\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const code = await response.text();\n\n if (!code.trim()) {\n throw new Error('Empty response');\n }\n\n return code;\n }\n\n /**\n * Precargar aplicaciones\n * @param {Array} appConfigs - Configuraciones de aplicaciones\n */\n async preload(appConfigs) {\n logger.debug(`[WuLoader] Preloading ${appConfigs.length} apps...`);\n\n const preloadPromises = appConfigs.map(async (config) => {\n try {\n await this.loadApp(config.url, config.manifest);\n logger.debug(`[WuLoader] Preloaded: ${config.name}`);\n } catch (error) {\n logger.warn(`[WuLoader] Failed to preload ${config.name}:`, error.message);\n }\n });\n\n await Promise.allSettled(preloadPromises);\n logger.debug(`[WuLoader] Preload completed`);\n }\n\n /**\n * Verificar si una URL esta disponible\n * @param {string} url - URL a verificar\n * @returns {boolean} True si esta disponible\n */\n async isAvailable(url) {\n try {\n const response = await fetch(url, {\n method: 'HEAD',\n signal: AbortSignal.timeout(5000) // Don't hang on dead hosts\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Resolver dependencias de imports\n * @param {Array} imports - Lista de imports del manifest\n * @param {Map} availableApps - Apps disponibles\n */\n async resolveDependencies(imports, availableApps) {\n const resolved = new Map();\n\n for (const importPath of imports || []) {\n const [appName, componentName] = importPath.split('.');\n\n if (!appName || !componentName) {\n logger.warn(`[WuLoader] Invalid import format: ${importPath}`);\n continue;\n }\n\n const app = availableApps.get(appName);\n if (!app) {\n logger.warn(`[WuLoader] Import app not found: ${appName}`);\n continue;\n }\n\n const manifest = app.manifest;\n const exportPath = manifest?.wu?.exports?.[componentName];\n\n if (!exportPath) {\n logger.warn(`[WuLoader] Export not found: ${importPath}`);\n continue;\n }\n\n try {\n const component = await this.loadComponent(app.url, exportPath);\n resolved.set(importPath, component);\n logger.debug(`[WuLoader] Resolved dependency: ${importPath}`);\n } catch (error) {\n console.error(`[WuLoader] Failed to resolve: ${importPath}`, error);\n }\n }\n\n return resolved;\n }\n\n /**\n * Limpiar cache\n * @param {string} pattern - Patron opcional para limpiar URLs especificas\n */\n clearCache(pattern) {\n if (pattern) {\n let regex;\n try {\n regex = new RegExp(pattern);\n } catch (err) {\n logger.warn(`[WuLoader] Invalid clearCache pattern: ${pattern} (${err.message})`);\n return;\n }\n for (const [url] of this.cache) {\n if (regex.test(url)) {\n this.cache.delete(url);\n logger.debug(`[WuLoader] Cleared cache for: ${url}`);\n }\n }\n } else {\n this.cache.clear();\n logger.debug(`[WuLoader] Cache cleared completely`);\n }\n }\n\n /**\n * Obtener estadisticas del loader\n */\n getStats() {\n return {\n cached: this.cache.size,\n maxCacheSize: this.maxCacheSize,\n cacheTTL: this.cacheTTL,\n loading: this.loadingPromises.size,\n cacheKeys: Array.from(this.cache.keys())\n };\n }\n\n // ── Sentinel Gate ─────────────────────────────────────────────────\n // Blocks code loading until the client is verified as human.\n // Scrapers that execute JS but don't pass proof-of-work get nothing.\n\n async _ensureSentinelVerified() {\n // Skip in development / localhost (sentinel is for production)\n if (typeof window === 'undefined') return;\n const host = window.location?.hostname || '';\n if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') return;\n\n // If sentinel is not loaded, allow (backwards compat — site doesn't use sentinel)\n if (!window.__wu_sentinel) return;\n\n // Already verified? Proceed.\n if (window.__wu_sentinel.isVerified()) return;\n\n // Wait for sentinel verification (max 10 seconds)\n await new Promise((resolve, reject) => {\n // Check if verified during wait\n if (window.__wu_sentinel.isVerified()) {\n resolve();\n return;\n }\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('[WuLoader] Sentinel verification timeout — content blocked'));\n }, 10000);\n\n const onVerified = () => {\n cleanup();\n resolve();\n };\n\n function cleanup() {\n clearTimeout(timeout);\n window.removeEventListener('wu:sentinel:verified', onVerified);\n }\n\n window.addEventListener('wu:sentinel:verified', onVerified);\n\n // Check periodically in case event was missed\n const check = setInterval(() => {\n if (window.__wu_sentinel.isVerified()) {\n clearInterval(check);\n cleanup();\n resolve();\n }\n }, 100);\n\n // Clean up interval on timeout too\n const origCleanup = cleanup;\n cleanup = function() {\n clearInterval(check);\n origCleanup();\n };\n });\n }\n}\n"],"names":["WuLoader","constructor","options","this","maxCacheSize","cacheTTL","cache","Map","loadingPromises","logger","debug","_cacheGet","key","has","entry","get","now","Date","timestamp","delete","lastAccess","set","code","_cacheSet","_evictIfNeeded","size","oldestKey","keys","next","value","loadApp","appUrl","manifest","_ensureSentinelVerified","fullUrl","cached","undefined","loadingPromise","fetchCode","error","console","Error","message","loadComponent","componentPath","normalizedPath","startsWith","substring","endsWith","loadCode","componentFunction","Function","fakeModule","exports","component","name","warn","url","timeoutMs","response","fetch","headers","Accept","signal","AbortSignal","timeout","ok","status","statusText","text","trim","preload","appConfigs","length","preloadPromises","map","async","config","Promise","allSettled","isAvailable","method","resolveDependencies","imports","availableApps","resolved","importPath","appName","componentName","split","app","exportPath","wu","clearCache","pattern","regex","RegExp","err","test","clear","getStats","loading","cacheKeys","Array","from","window","host","location","hostname","__wu_sentinel","isVerified","resolve","reject","setTimeout","cleanup","onVerified","clearTimeout","removeEventListener","addEventListener","check","setInterval","clearInterval","origCleanup"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wu-loader.js","sources":["../../src/core/wu-loader.js"],"sourcesContent":["/**\n * WU-LOADER: SISTEMA DE CARGA DINAMICA UNIVERSAL\n * Carga aplicaciones y componentes sin depender del framework\n *\n * Cache strategy: LRU with TTL eviction.\n * Entries track lastAccess time. When the cache reaches maxCacheSize,\n * the least recently accessed entry is evicted. Entries older than\n * cacheTTL are treated as stale and removed on access or eviction.\n */\n\nimport { logger } from './wu-logger.js';\n\n/**\n * @typedef {Object} WuLoaderOptions\n * @property {number} [maxCacheSize=50] - Maximum cache entries\n * @property {number} [cacheTTL=1800000] - Cache TTL in ms (default 30min)\n */\n\n/**\n * @typedef {Object} WuLoaderStats\n * @property {number} cached - Number of cached entries\n * @property {number} loading - Number of in-flight loads\n * @property {number} maxCacheSize - Max cache size setting\n * @property {number} cacheTTL - Cache TTL setting\n * @property {string[]} cacheKeys - Cached URL keys\n */\n\nexport class WuLoader {\n /**\n * @param {Object} options\n * @param {number} [options.maxCacheSize=50] - Maximum number of entries in the cache\n * @param {number} [options.cacheTTL=1800000] - Time-to-live for cache entries in ms (default 30 minutes)\n */\n constructor(options = {}) {\n this.maxCacheSize = options.maxCacheSize ?? 50;\n this.cacheTTL = options.cacheTTL ?? 1800000;\n this.cache = new Map();\n this.loadingPromises = new Map();\n\n logger.debug('[WuLoader] Dynamic loader initialized');\n }\n\n /**\n * Read from cache with TTL validation and LRU access tracking.\n * Returns undefined if the entry does not exist or has expired.\n * @param {string} key\n * @returns {string|undefined}\n */\n _cacheGet(key) {\n if (!this.cache.has(key)) {\n return undefined;\n }\n\n const entry = this.cache.get(key);\n const now = Date.now();\n\n if (now - entry.timestamp > this.cacheTTL) {\n this.cache.delete(key);\n logger.debug(`[WuLoader] Cache expired for: ${key}`);\n return undefined;\n }\n\n // Promote: delete and re-insert so iteration order reflects recency.\n // Map iteration order in JS follows insertion order, so the oldest\n // inserted entry is always first -- exactly what we need for LRU eviction.\n this.cache.delete(key);\n entry.lastAccess = now;\n this.cache.set(key, entry);\n\n return entry.code;\n }\n\n /**\n * Write to cache. Evicts stale and LRU entries before inserting.\n * @param {string} key\n * @param {string} code\n */\n _cacheSet(key, code) {\n // If the key already exists, remove it first so re-insertion\n // moves it to the end (most-recently-used position).\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n this._evictIfNeeded();\n\n const now = Date.now();\n this.cache.set(key, {\n code,\n timestamp: now,\n lastAccess: now\n });\n }\n\n /**\n * Evict entries until cache is below maxCacheSize.\n *\n * Two-pass strategy:\n * 1. Remove all expired entries (TTL exceeded).\n * 2. If still at capacity, remove the least recently accessed entry.\n * Because Map preserves insertion order and _cacheGet promotes on\n * access, the first key from the iterator is always the LRU entry.\n */\n _evictIfNeeded() {\n const now = Date.now();\n\n // Pass 1: purge expired entries\n for (const [key, entry] of this.cache) {\n if (now - entry.timestamp > this.cacheTTL) {\n this.cache.delete(key);\n logger.debug(`[WuLoader] Evicted expired entry: ${key}`);\n }\n }\n\n // Pass 2: evict LRU entries until we are under the limit\n while (this.cache.size >= this.maxCacheSize) {\n // Map.keys().next() gives us the oldest-inserted key (LRU)\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey);\n logger.debug(`[WuLoader] Evicted LRU entry: ${oldestKey}`);\n }\n }\n\n /**\n * Cargar aplicacion completa\n * @param {string} appUrl - URL base de la aplicacion\n * @param {Object} manifest - Manifest de la aplicacion\n * @returns {string} Codigo JavaScript de la aplicacion\n */\n async loadApp(appUrl, manifest) {\n // Sentinel gate: block code loading for unverified clients\n await this._ensureSentinelVerified();\n\n const entryFile = manifest?.entry || 'index.js';\n const fullUrl = `${appUrl}/${entryFile}`;\n\n logger.debug(`[WuLoader] Loading app from: ${fullUrl}`);\n\n try {\n // Check cache with TTL and LRU tracking\n const cached = this._cacheGet(fullUrl);\n if (cached !== undefined) {\n logger.debug(`[WuLoader] Cache hit for: ${fullUrl}`);\n return cached;\n }\n\n // Check if already loading\n if (this.loadingPromises.has(fullUrl)) {\n logger.debug(`[WuLoader] Loading in progress for: ${fullUrl}`);\n return await this.loadingPromises.get(fullUrl);\n }\n\n // Create loading promise\n const loadingPromise = this.fetchCode(fullUrl);\n this.loadingPromises.set(fullUrl, loadingPromise);\n\n const code = await loadingPromise;\n\n // Clean up loading promise and cache result\n this.loadingPromises.delete(fullUrl);\n this._cacheSet(fullUrl, code);\n\n logger.debug(`[WuLoader] App loaded successfully: ${fullUrl}`);\n return code;\n\n } catch (error) {\n this.loadingPromises.delete(fullUrl);\n console.error(`[WuLoader] Failed to load app: ${fullUrl}`, error);\n throw new Error(`Failed to load app from ${fullUrl}: ${error.message}`);\n }\n }\n\n /**\n * Cargar componente especifico\n * @param {string} appUrl - URL base de la aplicacion\n * @param {string} componentPath - Ruta del componente\n * @returns {Function} Funcion del componente\n */\n async loadComponent(appUrl, componentPath) {\n // Sentinel gate\n await this._ensureSentinelVerified();\n\n // Normalizar ruta del componente\n let normalizedPath = componentPath;\n if (normalizedPath.startsWith('./')) {\n normalizedPath = normalizedPath.substring(2);\n }\n if (!normalizedPath.endsWith('.js') && !normalizedPath.endsWith('.jsx')) {\n normalizedPath += '.js';\n }\n\n const fullUrl = `${appUrl}/${normalizedPath}`;\n\n logger.debug(`[WuLoader] Loading component from: ${fullUrl}`);\n\n try {\n // Cargar codigo del componente\n const code = await this.loadCode(fullUrl);\n\n // Crear funcion que retorna el componente\n const componentFunction = new Function('require', 'module', 'exports', `\n ${code}\n return typeof module.exports === 'function' ? module.exports :\n typeof module.exports === 'object' && module.exports.default ? module.exports.default :\n exports.default || exports;\n `);\n\n // Ejecutar y obtener el componente\n const fakeModule = { exports: {} };\n const fakeRequire = (name) => {\n logger.warn(`[WuLoader] Component ${componentPath} requires ${name} - not supported yet`);\n return {};\n };\n\n const component = componentFunction(fakeRequire, fakeModule, fakeModule.exports);\n\n logger.debug(`[WuLoader] Component loaded: ${componentPath}`);\n return component;\n\n } catch (error) {\n console.error(`[WuLoader] Failed to load component: ${componentPath}`, error);\n throw new Error(`Failed to load component ${componentPath}: ${error.message}`);\n }\n }\n\n /**\n * Cargar codigo con cache\n * @param {string} url - URL del archivo\n * @returns {string} Codigo JavaScript\n */\n async loadCode(url) {\n // Check cache with TTL and LRU tracking\n const cached = this._cacheGet(url);\n if (cached !== undefined) {\n return cached;\n }\n\n // Check if already loading\n if (this.loadingPromises.has(url)) {\n return await this.loadingPromises.get(url);\n }\n\n // Create loading promise\n const loadingPromise = this.fetchCode(url);\n this.loadingPromises.set(url, loadingPromise);\n\n try {\n const code = await loadingPromise;\n this.loadingPromises.delete(url);\n this._cacheSet(url, code);\n return code;\n } catch (error) {\n this.loadingPromises.delete(url);\n throw error;\n }\n }\n\n /**\n * Realizar fetch del codigo\n * @param {string} url - URL del archivo\n * @returns {string} Codigo JavaScript\n */\n async fetchCode(url, timeoutMs = 30000) {\n // 30s default: long enough for slow dev servers + large bundles, short\n // enough that a hung app does not block mount forever. Pre-v2.0 there was\n // no timeout — a non-responsive remote could stall the framework.\n const response = await fetch(url, {\n cache: 'no-cache',\n headers: {\n 'Accept': 'application/javascript, text/javascript, */*'\n },\n signal: AbortSignal.timeout(timeoutMs)\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const code = await response.text();\n\n if (!code.trim()) {\n throw new Error('Empty response');\n }\n\n return code;\n }\n\n /**\n * Precargar aplicaciones\n * @param {Array} appConfigs - Configuraciones de aplicaciones\n */\n async preload(appConfigs) {\n logger.debug(`[WuLoader] Preloading ${appConfigs.length} apps...`);\n\n const preloadPromises = appConfigs.map(async (config) => {\n try {\n await this.loadApp(config.url, config.manifest);\n logger.debug(`[WuLoader] Preloaded: ${config.name}`);\n } catch (error) {\n logger.warn(`[WuLoader] Failed to preload ${config.name}:`, error.message);\n }\n });\n\n await Promise.allSettled(preloadPromises);\n logger.debug(`[WuLoader] Preload completed`);\n }\n\n /**\n * Verificar si una URL esta disponible\n * @param {string} url - URL a verificar\n * @returns {boolean} True si esta disponible\n */\n async isAvailable(url) {\n try {\n const response = await fetch(url, {\n method: 'HEAD',\n signal: AbortSignal.timeout(5000) // Don't hang on dead hosts\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Resolver dependencias de imports\n * @param {Array} imports - Lista de imports del manifest\n * @param {Map} availableApps - Apps disponibles\n */\n async resolveDependencies(imports, availableApps) {\n const resolved = new Map();\n\n for (const importPath of imports || []) {\n const [appName, componentName] = importPath.split('.');\n\n if (!appName || !componentName) {\n logger.warn(`[WuLoader] Invalid import format: ${importPath}`);\n continue;\n }\n\n const app = availableApps.get(appName);\n if (!app) {\n logger.warn(`[WuLoader] Import app not found: ${appName}`);\n continue;\n }\n\n const manifest = app.manifest;\n const exportPath = manifest?.wu?.exports?.[componentName];\n\n if (!exportPath) {\n logger.warn(`[WuLoader] Export not found: ${importPath}`);\n continue;\n }\n\n try {\n const component = await this.loadComponent(app.url, exportPath);\n resolved.set(importPath, component);\n logger.debug(`[WuLoader] Resolved dependency: ${importPath}`);\n } catch (error) {\n console.error(`[WuLoader] Failed to resolve: ${importPath}`, error);\n }\n }\n\n return resolved;\n }\n\n /**\n * Limpiar cache\n * @param {string} pattern - Patron opcional para limpiar URLs especificas\n */\n clearCache(pattern) {\n if (pattern) {\n let regex;\n try {\n regex = new RegExp(pattern);\n } catch (err) {\n logger.warn(`[WuLoader] Invalid clearCache pattern: ${pattern} (${err.message})`);\n return;\n }\n for (const [url] of this.cache) {\n if (regex.test(url)) {\n this.cache.delete(url);\n logger.debug(`[WuLoader] Cleared cache for: ${url}`);\n }\n }\n } else {\n this.cache.clear();\n logger.debug(`[WuLoader] Cache cleared completely`);\n }\n }\n\n /**\n * Obtener estadisticas del loader\n */\n getStats() {\n return {\n cached: this.cache.size,\n maxCacheSize: this.maxCacheSize,\n cacheTTL: this.cacheTTL,\n loading: this.loadingPromises.size,\n cacheKeys: Array.from(this.cache.keys())\n };\n }\n\n // ── Sentinel Gate ─────────────────────────────────────────────────\n // Blocks code loading until the client is verified as human.\n // Scrapers that execute JS but don't pass proof-of-work get nothing.\n\n async _ensureSentinelVerified() {\n // Skip in development / localhost (sentinel is for production)\n if (typeof window === 'undefined') return;\n const host = window.location?.hostname || '';\n if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') return;\n\n // If sentinel is not loaded, allow (backwards compat — site doesn't use sentinel)\n if (!window.__wu_sentinel) return;\n\n // Already verified? Proceed.\n if (window.__wu_sentinel.isVerified()) return;\n\n // Wait for sentinel verification (max 10 seconds)\n await new Promise((resolve, reject) => {\n // Check if verified during wait\n if (window.__wu_sentinel.isVerified()) {\n resolve();\n return;\n }\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('[WuLoader] Sentinel verification timeout — content blocked'));\n }, 10000);\n\n const onVerified = () => {\n cleanup();\n resolve();\n };\n\n function cleanup() {\n clearTimeout(timeout);\n window.removeEventListener('wu:sentinel:verified', onVerified);\n }\n\n window.addEventListener('wu:sentinel:verified', onVerified);\n\n // Check periodically in case event was missed\n const check = setInterval(() => {\n if (window.__wu_sentinel.isVerified()) {\n clearInterval(check);\n cleanup();\n resolve();\n }\n }, 100);\n\n // Clean up interval on timeout too\n const origCleanup = cleanup;\n cleanup = function() {\n clearInterval(check);\n origCleanup();\n };\n });\n }\n}\n"],"names":["WuLoader","constructor","options","this","maxCacheSize","cacheTTL","cache","Map","loadingPromises","logger","debug","_cacheGet","key","has","entry","get","now","Date","timestamp","delete","lastAccess","set","code","_cacheSet","_evictIfNeeded","size","oldestKey","keys","next","value","loadApp","appUrl","manifest","_ensureSentinelVerified","fullUrl","cached","undefined","loadingPromise","fetchCode","error","console","Error","message","loadComponent","componentPath","normalizedPath","startsWith","substring","endsWith","loadCode","componentFunction","Function","fakeModule","exports","component","name","warn","url","timeoutMs","response","fetch","headers","Accept","signal","AbortSignal","timeout","ok","status","statusText","text","trim","preload","appConfigs","length","preloadPromises","map","async","config","Promise","allSettled","isAvailable","method","resolveDependencies","imports","availableApps","resolved","importPath","appName","componentName","split","app","exportPath","wu","clearCache","pattern","regex","RegExp","err","test","clear","getStats","loading","cacheKeys","Array","from","window","host","location","hostname","__wu_sentinel","isVerified","resolve","reject","setTimeout","cleanup","onVerified","clearTimeout","removeEventListener","addEventListener","check","setInterval","clearInterval","origCleanup"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
class e{constructor(){this.isDevelopment=this.detectEnvironment(),this.logLevel=this.isDevelopment?"warn":"error",this.levels={debug:0,info:1,warn:2,error:3,silent:4}}detectEnvironment(){if("undefined"!=typeof window&&!0===window.WU_DEBUG)return!0;if("undefined"!=typeof window&&!1===window.WU_DEBUG)return!1;if("undefined"!=typeof process&&"production"===process.env?.NODE_ENV)return!1;if("undefined"!=typeof process&&"development"===process.env?.NODE_ENV)return!0;if("undefined"!=typeof window&&window.location){const e=window.location.hostname;if("localhost"===e||"127.0.0.1"===e||"[::1]"===e)return!0;try{if(new URLSearchParams(window.location.search).has("wu-debug"))return!0}catch{}}return!1}setLevel(e){return this.logLevel=e,this}setDevelopment(e){return this.isDevelopment=e,this.logLevel=e?"debug":"error",this}shouldLog(e){return this.levels[e]>=this.levels[this.logLevel]}debug(...e){this.shouldLog("debug")&&console.log(...e)}info(...e){this.shouldLog("info")&&console.info(...e)}warn(...e){this.shouldLog("warn")&&console.warn(...e)}error(...e){this.shouldLog("error")&&console.error(...e)}wu(e,...o){if(this.shouldLog(e)){console["debug"===e?"log":e]("[Wu]",...o)}}wuDebug(...e){this.wu("debug",...e)}wuInfo(...e){this.wu("info",...e)}wuWarn(...e){this.wu("warn",...e)}wuError(...e){this.wu("error",...e)}}const o=new e,n={debug:(...e)=>o.wuDebug(...e),info:(...e)=>o.wuInfo(...e),warn:(...e)=>o.wuWarn(...e),error:(...e)=>o.wuError(...e)};function r(){o.setLevel("silent")}function t(){o.setLevel("debug")}export{e as WuLogger,t as enableAllLogs,o as logger,r as silenceAllLogs,n as wuLog};
|
|
2
|
+
//# sourceMappingURL=wu-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wu-logger.js","sources":["../../src/core/wu-logger.js"],"sourcesContent":["/**\n * 📝 WU-LOGGER: Sistema de logging inteligente para entornos\n * Controla los logs automáticamente según el entorno\n */\n\nexport class WuLogger {\n constructor() {\n // Detectar entorno automáticamente\n this.isDevelopment = this.detectEnvironment();\n // En desarrollo: warn (menos ruido), en producción: error\n this.logLevel = this.isDevelopment ? 'warn' : 'error';\n\n this.levels = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4\n };\n }\n\n /**\n * Detectar si estamos en desarrollo\n */\n detectEnvironment() {\n // 1. Explicit flag takes priority\n if (typeof window !== 'undefined' && window.WU_DEBUG === true) return true;\n if (typeof window !== 'undefined' && window.WU_DEBUG === false) return false;\n\n // 2. NODE_ENV check (works in bundlers and Node)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return false;\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') return true;\n\n // 3. Browser heuristics (only if window exists)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') return true;\n\n // URL param override\n try {\n if (new URLSearchParams(window.location.search).has('wu-debug')) return true;\n } catch {}\n }\n\n // 4. Default: assume production\n return false;\n }\n\n /**\n * Configurar nivel de logging\n */\n setLevel(level) {\n this.logLevel = level;\n return this;\n }\n\n /**\n * Habilitar/deshabilitar development mode\n */\n setDevelopment(isDev) {\n this.isDevelopment = isDev;\n this.logLevel = isDev ? 'debug' : 'error';\n return this;\n }\n\n /**\n * Verificar si debemos mostrar el log\n */\n shouldLog(level) {\n return this.levels[level] >= this.levels[this.logLevel];\n }\n\n /**\n * Logging methods\n */\n debug(...args) {\n if (this.shouldLog('debug')) {\n console.log(...args);\n }\n }\n\n info(...args) {\n if (this.shouldLog('info')) {\n console.info(...args);\n }\n }\n\n warn(...args) {\n if (this.shouldLog('warn')) {\n console.warn(...args);\n }\n }\n\n error(...args) {\n if (this.shouldLog('error')) {\n console.error(...args);\n }\n }\n\n /**\n * Logging con contexto Wu\n */\n wu(level, ...args) {\n if (this.shouldLog(level)) {\n const method = level === 'debug' ? 'log' : level;\n console[method]('[Wu]', ...args);\n }\n }\n\n /**\n * Helper methods específicos para Wu\n */\n wuDebug(...args) { this.wu('debug', ...args); }\n wuInfo(...args) { this.wu('info', ...args); }\n wuWarn(...args) { this.wu('warn', ...args); }\n wuError(...args) { this.wu('error', ...args); }\n}\n\n// Singleton instance\nexport const logger = new WuLogger();\n\n// Helper para compatibilidad con logs existentes\nexport const wuLog = {\n debug: (...args) => logger.wuDebug(...args),\n info: (...args) => logger.wuInfo(...args),\n warn: (...args) => logger.wuWarn(...args),\n error: (...args) => logger.wuError(...args)\n};\n\n/**\n * 🔇 Silenciar todos los logs de Wu Framework\n * Útil en producción para eliminar todo el ruido\n */\nexport function silenceAllLogs() {\n logger.setLevel('silent');\n}\n\n/**\n * 🔊 Restaurar logs (nivel debug)\n */\nexport function enableAllLogs() {\n logger.setLevel('debug');\n}"],"names":["WuLogger","constructor","this","isDevelopment","detectEnvironment","logLevel","levels","debug","info","warn","error","silent","window","WU_DEBUG","process","env","NODE_ENV","location","hostname","URLSearchParams","search","has","setLevel","level","setDevelopment","isDev","shouldLog","args","console","log","wu","wuDebug","wuInfo","wuWarn","wuError","logger","wuLog","silenceAllLogs","enableAllLogs"],"mappings":"AAKO,MAAMA,EACX,WAAAC,GAEEC,KAAKC,cAAgBD,KAAKE,oBAE1BF,KAAKG,SAAWH,KAAKC,cAAgB,OAAS,QAE9CD,KAAKI,OAAS,CACZC,MAAO,EACPC,KAAM,EACNC,KAAM,EACNC,MAAO,EACPC,OAAQ,EAEZ,CAKA,iBAAAP,GAEE,GAAsB,oBAAXQ,SAA8C,IAApBA,OAAOC,SAAmB,OAAO,EACtE,GAAsB,oBAAXD,SAA8C,IAApBA,OAAOC,SAAoB,OAAO,EAGvE,GAAuB,oBAAZC,SAAqD,eAA1BA,QAAQC,KAAKC,SAA2B,OAAO,EACrF,GAAuB,oBAAZF,SAAqD,gBAA1BA,QAAQC,KAAKC,SAA4B,OAAO,EAGtF,GAAsB,oBAAXJ,QAA0BA,OAAOK,SAAU,CACpD,MAAMC,EAAWN,OAAOK,SAASC,SACjC,GAAiB,cAAbA,GAAyC,cAAbA,GAAyC,UAAbA,EAAsB,OAAO,EAGzF,IACE,GAAI,IAAIC,gBAAgBP,OAAOK,SAASG,QAAQC,IAAI,YAAa,OAAO,CAC1E,CAAE,MAAO,CACX,CAGA,OAAO,CACT,CAKA,QAAAC,CAASC,GAEP,OADArB,KAAKG,SAAWkB,EACTrB,IACT,CAKA,cAAAsB,CAAeC,GAGb,OAFAvB,KAAKC,cAAgBsB,EACrBvB,KAAKG,SAAWoB,EAAQ,QAAU,QAC3BvB,IACT,CAKA,SAAAwB,CAAUH,GACR,OAAOrB,KAAKI,OAAOiB,IAAUrB,KAAKI,OAAOJ,KAAKG,SAChD,CAKA,KAAAE,IAASoB,GACHzB,KAAKwB,UAAU,UACjBE,QAAQC,OAAOF,EAEnB,CAEA,IAAAnB,IAAQmB,GACFzB,KAAKwB,UAAU,SACjBE,QAAQpB,QAAQmB,EAEpB,CAEA,IAAAlB,IAAQkB,GACFzB,KAAKwB,UAAU,SACjBE,QAAQnB,QAAQkB,EAEpB,CAEA,KAAAjB,IAASiB,GACHzB,KAAKwB,UAAU,UACjBE,QAAQlB,SAASiB,EAErB,CAKA,EAAAG,CAAGP,KAAUI,GACX,GAAIzB,KAAKwB,UAAUH,GAAQ,CAEzBK,QADyB,UAAVL,EAAoB,MAAQA,GAC3B,UAAWI,EAC7B,CACF,CAKA,OAAAI,IAAWJ,GAAQzB,KAAK4B,GAAG,WAAYH,EAAO,CAC9C,MAAAK,IAAUL,GAAQzB,KAAK4B,GAAG,UAAWH,EAAO,CAC5C,MAAAM,IAAUN,GAAQzB,KAAK4B,GAAG,UAAWH,EAAO,CAC5C,OAAAO,IAAWP,GAAQzB,KAAK4B,GAAG,WAAYH,EAAO,EAIpC,MAACQ,EAAS,IAAInC,EAGboC,EAAQ,CACnB7B,MAAO,IAAIoB,IAASQ,EAAOJ,WAAWJ,GACtCnB,KAAM,IAAImB,IAASQ,EAAOH,UAAUL,GACpClB,KAAM,IAAIkB,IAASQ,EAAOF,UAAUN,GACpCjB,MAAO,IAAIiB,IAASQ,EAAOD,WAAWP,IAOjC,SAASU,IACdF,EAAOb,SAAS,SAClB,CAKO,SAASgB,IACdH,EAAOb,SAAS,QAClB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{e,t,c as r,g as n,a as o,d as i,b as a}from"../wu-ai-browser-primitives-
|
|
1
|
+
import{e,t,c as r,g as n,a as o,d as i,b as a}from"../wu-ai-browser-primitives-CaUCk1Xl.js";import{logger as s}from"./wu-logger.js";function u(u){let c=null,d=null,m=0,l=!1,p=null;const f=u.eventBus?.getInternalToken?.("wu-mcp-bridge")||null,g=(e,t)=>u.eventBus?.emit(e,t,{appName:"wu-mcp-bridge",token:f}),w=[];function v(e){if(u.ai&&u.ai.permissions)return u.ai.permissions.check(e);return["readStore","executeActions"].includes(e)}function h(e,t,r){u.eventBus&&g("mcp:bridge:operation",{operation:e,params:t,result:r?.error?{error:r.error}:{success:!0},timestamp:Date.now()})}u.eventBus&&u.eventBus.on("*",e=>{w.push({name:e.name,data:e.data,timestamp:e.timestamp||Date.now(),source:e.source||"unknown"}),w.length>200&&w.shift()}),e();const b={status:()=>({connected:!0,framework:"wu-framework",apps:D(),storeKeys:u.store?Object.keys(u.store.get("")||{}):[],actionsCount:u.ai?u.ai.tools().length:0,eventLogSize:w.length}),list_apps:()=>D(),list_events:({limit:e=20})=>w.slice(-e),list_actions(){if(!u.ai)return{actions:[],note:"wu.ai not initialized"};const e=u.ai.tools();return{actions:e,count:e.length}},snapshot({appName:e}){try{const t=e?document.querySelector(`[data-wu-app="${e}"]`)||document.querySelector(`#wu-app-${e}`):document.body;return t?{app:e||"(page)",snapshot:a(t,0,5),timestamp:Date.now()}:{error:`App "${e}" not found in DOM`}}catch(e){return{error:e.message}}},console:({level:e="all",limit:t=50})=>i(e,t),async screenshot({selector:e,quality:t=.8}){const r=await o(e,t);return r.error||(r.timestamp=Date.now()),r},network:({method:e,status:t,limit:r=50})=>n(e,t,r),get_state({path:e}){if(!u.store)return{error:"wu.store not available"};if(!v("readStore"))return{error:"Permission denied: readStore is disabled"};return{path:e||"(root)",value:u.store.get(e||"")}},set_state:({path:e,value:t})=>u.store?e?v("writeStore")?(u.store.set(e,t),h("set_state",{path:e,value:t},{}),{path:e,value:t,updated:!0}):(h("set_state",{path:e},{error:"Permission denied"}),{error:"Permission denied: writeStore is disabled"}):{error:"path is required"}:{error:"wu.store not available"},emit_event:({event:e,data:t})=>u.eventBus?e?v("emitEvents")?(g(e,t),h("emit_event",{event:e,data:t},{}),{emitted:e,data:t}):(h("emit_event",{event:e},{error:"Permission denied"}),{error:"Permission denied: emitEvents is disabled"}):{error:"event name is required"}:{error:"wu.eventBus not available"},navigate:({route:e})=>e?v("emitEvents")?(g("shell:navigate",{route:e}),u.store&&v("writeStore")&&u.store.set("currentPath",e),h("navigate",{route:e},{}),{navigated:e}):(h("navigate",{route:e},{error:"Permission denied: emitEvents"}),{error:"Permission denied: emitEvents is disabled"}):{error:"Route is required"},mount_app({appName:e,container:t}){if(!e)return{error:"appName is required"};if(!v("modifyDOM"))return h("mount_app",{appName:e},{error:"Permission denied"}),{error:"Permission denied: modifyDOM is disabled"};try{return u.mount?(u.mount(e,t),h("mount_app",{appName:e,container:t},{success:!0}),{mounted:e,container:t}):{error:"wu.mount not available"}}catch(e){return{error:e.message}}},unmount_app({appName:e}){if(!e)return{error:"appName is required"};if(!v("modifyDOM"))return h("unmount_app",{appName:e},{error:"Permission denied"}),{error:"Permission denied: modifyDOM is disabled"};try{return u.unmount?(u.unmount(e),h("unmount_app",{appName:e},{success:!0}),{unmounted:e}):{error:"wu.unmount not available"}}catch(e){return{error:e.message}}},click({selector:e,text:t}){if(!v("modifyDOM"))return{error:"Permission denied: modifyDOM is disabled"};const n=r(e,t);return h("click",{selector:e,text:t},n),n},type({selector:e,text:r,clear:n=!1,submit:o=!1}){if(!v("modifyDOM"))return{error:"Permission denied: modifyDOM is disabled"};const i=t(e,r,{clear:n,submit:o});return h("type",{selector:e,textLength:r?.length},i),i},async execute_action({action:e,params:t}){if(!u.ai)return{error:"wu.ai not available"};if(!e)return{error:"action name is required"};try{return{action:e,...await u.ai.execute(e,t||{})}}catch(e){return{error:e.message}}}};function y(e="ws://localhost:19100",t={}){if(c&&c.readyState<=1)s.warn("[wu-mcp-bridge] Already connected or connecting");else{p=t.token||null,l=!p;try{c=new WebSocket(e),c.onopen=()=>{s.debug("[wu-mcp-bridge] Connected to wu-mcp-server"),m=0,p&&c.send(JSON.stringify({type:"auth",token:p}))},c.onmessage=async e=>{try{const t=JSON.parse(e.data);if("auth_result"===t.type)return l=!0===t.success,void(l?s.debug("[wu-mcp-bridge] Authenticated successfully"):(console.error("[wu-mcp-bridge] Authentication failed:",t.reason||"Invalid token"),_()));if(!l)return void(t.id&&S(t.id,null,"Not authenticated. Send auth token first."));const{id:r,command:n,params:o}=t;if(!r||!n)return void s.warn("[wu-mcp-bridge] Invalid message:",t);const i=b[n];if(!i)return void S(r,null,`Unknown command: ${n}`);try{S(r,await i(o||{}))}catch(e){S(r,null,e.message)}}catch(e){console.error("[wu-mcp-bridge] Failed to handle message:",e)}},c.onclose=()=>{s.debug("[wu-mcp-bridge] Disconnected"),c=null,l=!1,k(e,t)},c.onerror=()=>{}}catch(r){console.error("[wu-mcp-bridge] Connection failed:",r.message),k(e,t)}}}function _(){d&&(clearTimeout(d),d=null),m=10,c&&(c.close(),c=null),l=!1}function S(e,t,r){if(!c||1!==c.readyState)return;const n=r?{id:e,error:r}:{id:e,result:t};c.send(JSON.stringify(n))}function k(e,t){if(m>=10)return;m++;const r=2e3*Math.min(m,5);d=setTimeout(()=>y(e,t),r)}function D(){const e=[];if(u._apps)for(const[t,r]of Object.entries(u._apps))e.push({name:t,mounted:r.mounted||r.isMounted||!1,url:r.url||r.info?.url||"",status:r.status||r.info?.status||"unknown"});return 0===e.length&&document.querySelectorAll("[data-wu-app]").forEach(t=>{e.push({name:t.getAttribute("data-wu-app"),mounted:!0,container:`#${t.id||"(no-id)"}`})}),e}return{connect:y,disconnect:_,isConnected:function(){return null!==c&&1===c.readyState&&l}}}export{u as createMcpBridge};
|
|
2
2
|
//# sourceMappingURL=wu-mcp-bridge.js.map
|