wu-framework 2.1.0 → 2.1.2

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.
Files changed (63) hide show
  1. package/LICENSE +39 -39
  2. package/README.md +570 -570
  3. package/dist/adapters/angular/index.d.ts +154 -154
  4. package/dist/adapters/angular/index.js.map +1 -1
  5. package/dist/adapters/angular.d.ts +3 -3
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/adapters/lit/index.d.ts +120 -120
  8. package/dist/adapters/lit/index.js.map +1 -1
  9. package/dist/adapters/lit.d.ts +3 -3
  10. package/dist/adapters/preact/index.d.ts +108 -108
  11. package/dist/adapters/preact/index.js.map +1 -1
  12. package/dist/adapters/preact.d.ts +3 -3
  13. package/dist/adapters/qwik/index.js +1 -1
  14. package/dist/adapters/qwik/index.js.map +1 -1
  15. package/dist/adapters/react/index.d.ts +246 -246
  16. package/dist/adapters/react/index.js.map +1 -1
  17. package/dist/adapters/react.d.ts +3 -3
  18. package/dist/adapters/shared.js.map +1 -1
  19. package/dist/adapters/solid/index.d.ts +101 -101
  20. package/dist/adapters/solid/index.js.map +1 -1
  21. package/dist/adapters/solid.d.ts +3 -3
  22. package/dist/adapters/svelte/index.d.ts +166 -166
  23. package/dist/adapters/svelte/index.js.map +1 -1
  24. package/dist/adapters/svelte.d.ts +3 -3
  25. package/dist/adapters/vanilla/index.d.ts +179 -179
  26. package/dist/adapters/vanilla/index.js.map +1 -1
  27. package/dist/adapters/vanilla.d.ts +3 -3
  28. package/dist/adapters/vue/index.d.ts +299 -299
  29. package/dist/adapters/vue/index.js.map +1 -1
  30. package/dist/adapters/vue.d.ts +3 -3
  31. package/dist/ai/wu-ai.js.map +1 -1
  32. package/dist/core/wu-html-parser.js +2 -0
  33. package/dist/core/wu-html-parser.js.map +1 -0
  34. package/dist/core/wu-iframe-sandbox.js +2 -0
  35. package/dist/core/wu-iframe-sandbox.js.map +1 -0
  36. package/dist/core/wu-loader.js +2 -0
  37. package/dist/core/wu-loader.js.map +1 -0
  38. package/dist/core/wu-mcp-bridge.js.map +1 -1
  39. package/dist/core/wu-script-executor.js +2 -0
  40. package/dist/core/wu-script-executor.js.map +1 -0
  41. package/dist/wu-ai-browser-primitives-BDKXJlwc.js.map +1 -1
  42. package/dist/wu-framework.cjs.js +2 -2
  43. package/dist/wu-framework.cjs.js.map +1 -1
  44. package/dist/wu-framework.dev.js +8697 -9142
  45. package/dist/wu-framework.dev.js.map +1 -1
  46. package/dist/wu-framework.esm.js +2 -2
  47. package/dist/wu-framework.esm.js.map +1 -1
  48. package/dist/wu-framework.umd.js +2 -2
  49. package/dist/wu-framework.umd.js.map +1 -1
  50. package/dist/wu-logger-fJfUHBGA.js.map +1 -1
  51. package/integrations/astro/README.md +127 -127
  52. package/integrations/astro/WuApp.astro +63 -63
  53. package/integrations/astro/WuShell.astro +39 -39
  54. package/integrations/astro/index.js +68 -68
  55. package/integrations/astro/package.json +38 -38
  56. package/integrations/astro/types.d.ts +53 -53
  57. package/package.json +218 -218
  58. package/dist/wu-html-parser.js +0 -2
  59. package/dist/wu-html-parser.js.map +0 -1
  60. package/dist/wu-iframe-sandbox.js +0 -2
  61. package/dist/wu-iframe-sandbox.js.map +0 -1
  62. package/dist/wu-script-executor.js +0 -2
  63. package/dist/wu-script-executor.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ import{l as t}from"../wu-logger-fJfUHBGA.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
+ //# sourceMappingURL=wu-html-parser.js.map
@@ -0,0 +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":"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"}
@@ -0,0 +1,2 @@
1
+ import{l as e}from"../wu-logger-fJfUHBGA.js";class t{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,r,n){if(this._active)return this.iframe.contentWindow;const i=document.createElement("iframe");i.setAttribute("data-wu-sandbox",this.appName),i.style.cssText="display:none !important;position:absolute;width:0;height:0;border:0;",document.body.appendChild(i),this.iframe=i;const a=t.replace(/\/$/,""),s=i.contentWindow,o=s.document;return o.open(),o.write(`<!DOCTYPE html><html><head><base href="${a}/"></head><body></body></html>`),o.close(),s.wu=this._buildRestrictedWu(window.wu),this._patchDocument(s,r,n),this._patchTimers(s),this._active=!0,e.wuDebug(`[IframeSandbox] Activated for ${this.appName} (base: ${a})`),s}_buildRestrictedWu(e){if(!e)return;const t=t=>"function"==typeof t?t.bind(e):t,r=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,n=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,i={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:r,eventBus:n,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(i)}importModule(t,r=3e4){if(!this._active)throw new Error(`[IframeSandbox] Not active for ${this.appName}`);return new Promise((n,i)=>{const a=`wu_${this.appName}_${Date.now()}`,s=e=>{e.data?.channelId===a&&(c(),e.data.error?i(new Error(e.data.error)):n())},o=setTimeout(()=>{c(),i(new Error(`[IframeSandbox] import() timed out for ${this.appName}: ${t}`))},r),c=()=>{window.removeEventListener("message",s),clearTimeout(o)};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(() => parent.postMessage({ channelId: "${a}", success: true }, '*')).catch(e => parent.postMessage({ channelId: "${a}", error: e.message || String(e) }, '*'));`,m.head.appendChild(d),e.wuDebug(`[IframeSandbox] Importing module: ${t}`)})}_patchDocument(t,r,n){const i=t.document,a=n||r,s=document;i.createElement=(e,t)=>s.createElement(e,t),i.createElementNS=(e,t,r)=>s.createElementNS(e,t,r),i.createTextNode=e=>s.createTextNode(e),i.createComment=e=>s.createComment(e),i.createDocumentFragment=()=>s.createDocumentFragment(),i.querySelector=e=>a.querySelector(e),i.querySelectorAll=e=>a.querySelectorAll(e),i.getElementById=e=>a.querySelector(`#${e}`),i.getElementsByClassName=e=>a.querySelectorAll(`.${e}`),i.getElementsByTagName=e=>a.querySelectorAll(e);try{Object.defineProperty(i,"body",{get:()=>r,configurable:!0})}catch{e.wuDebug("[IframeSandbox] Could not redefine document.body")}const o=i.addEventListener.bind(i),c=i.removeEventListener.bind(i);i.addEventListener=(e,t,r)=>{this._listeners.push({target:i,event:e,handler:t,options:r}),o(e,t,r)},i.removeEventListener=(e,t,r)=>{this._listeners=this._listeners.filter(r=>!(r.target===i&&r.event===e&&r.handler===t)),c(e,t,r)},e.wuDebug(`[IframeSandbox] Document patched for ${this.appName}`)}_patchTimers(t){const r=t.setTimeout.bind(t),n=t.clearTimeout.bind(t),i=t.setInterval.bind(t),a=t.clearInterval.bind(t);if(t.setTimeout=(e,t,...n)=>{const i=r((...t)=>{this._timers.delete(i),"function"==typeof e&&e(...t)},t,...n);return this._timers.add(i),i},t.clearTimeout=e=>{this._timers.delete(e),n(e)},t.setInterval=(e,t,...r)=>{const n=i(e,t,...r);return this._intervals.add(n),n},t.clearInterval=e=>{this._intervals.delete(e),a(e)},t.requestAnimationFrame){const e=t.requestAnimationFrame.bind(t),r=t.cancelAnimationFrame.bind(t);t.requestAnimationFrame=t=>{const r=e((...e)=>{this._rafs.delete(r),t(...e)});return this._rafs.add(r),r},t.cancelAnimationFrame=e=>{this._rafs.delete(e),r(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:r,options:n}of this._listeners)try{e.removeEventListener(t,r,n)}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{t as WuIframeSandbox};
2
+ //# sourceMappingURL=wu-iframe-sandbox.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,2 @@
1
+ import{l as e}from"../wu-logger-fJfUHBGA.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
+ //# sourceMappingURL=wu-loader.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"wu-mcp-bridge.js","sources":["../../src/core/wu-mcp-bridge.js"],"sourcesContent":["/**\r\n * WU-MCP Bridge (Browser Side)\r\n *\r\n * Connects to the wu-mcp-server via WebSocket and executes\r\n * commands using wu.* APIs. This is the \"eyes and hands\" of\r\n * the MCP server inside the browser.\r\n *\r\n * Security:\r\n * - Optional auth token sent on first message (handshake)\r\n * - All state/event/mount operations check wu.ai permissions\r\n * - Mutating operations emit audit events\r\n * - Read-only operations (status, list_apps, snapshot, console, network) are unrestricted\r\n *\r\n * @example\r\n * // Connect with auth token\r\n * wu.mcp.connect('ws://localhost:19100', { token: 'my-secret' });\r\n *\r\n * // Connect without auth (development only)\r\n * wu.mcp.connect();\r\n */\r\n\r\nimport {\r\n ensureInterceptors,\r\n networkLog,\r\n consoleLog,\r\n captureScreenshot,\r\n buildA11yTree,\r\n clickElement,\r\n typeIntoElement,\r\n getFilteredNetwork,\r\n getFilteredConsole,\r\n} from '../ai/wu-ai-browser-primitives.js';\r\nimport { logger } from './wu-logger.js';\r\n\r\n/**\r\n * Create the MCP bridge for a Wu instance.\r\n *\r\n * @param {object} wu - The Wu Framework instance (window.wu)\r\n * @returns {object} Bridge API: { connect, disconnect, isConnected }\r\n */\r\nexport function createMcpBridge(wu) {\r\n let ws = null;\r\n let reconnectTimer = null;\r\n let reconnectAttempts = 0;\r\n let authenticated = false;\r\n let authToken = null;\r\n const MAX_RECONNECT_ATTEMPTS = 10;\r\n const RECONNECT_DELAY = 2000;\r\n\r\n // Closure-capture our internal token. Required so strictMode accepts\r\n // emits as appName='wu-mcp-bridge' (and rejects spoofers under the same name).\r\n const _bridgeToken = wu.eventBus?.getInternalToken?.('wu-mcp-bridge') || null;\r\n const _emitInternal = (event, data) =>\r\n wu.eventBus?.emit(event, data, { appName: 'wu-mcp-bridge', token: _bridgeToken });\r\n\r\n // Event log for wu_list_events\r\n const eventLog = [];\r\n const MAX_EVENT_LOG = 200;\r\n\r\n // Capture events for history\r\n if (wu.eventBus) {\r\n wu.eventBus.on('*', (event) => {\r\n eventLog.push({\r\n name: event.name,\r\n data: event.data,\r\n timestamp: event.timestamp || Date.now(),\r\n source: event.source || 'unknown',\r\n });\r\n if (eventLog.length > MAX_EVENT_LOG) eventLog.shift();\r\n });\r\n }\r\n\r\n // Install shared interceptors (idempotent — safe if wu-ai-browser already did it)\r\n ensureInterceptors();\r\n\r\n // ── Permission helpers ──\r\n\r\n /**\r\n * Check a permission flag via wu.ai.permissions if available.\r\n * Falls back to deny if wu.ai is not initialized.\r\n */\r\n function _checkPermission(perm) {\r\n if (wu.ai && wu.ai.permissions) {\r\n return wu.ai.permissions.check(perm);\r\n }\r\n // If AI module not initialized, deny write operations, allow reads\r\n const readPerms = ['readStore', 'executeActions'];\r\n return readPerms.includes(perm);\r\n }\r\n\r\n /**\r\n * Emit an audit event for bridge operations.\r\n */\r\n function _audit(operation, params, result) {\r\n if (wu.eventBus) {\r\n wu.eventBus.emit('mcp:bridge:operation', {\r\n operation,\r\n params,\r\n result: result?.error ? { error: result.error } : { success: true },\r\n timestamp: Date.now(),\r\n }, { appName: 'wu-mcp-bridge' });\r\n }\r\n }\r\n\r\n // ── Command handlers ──\r\n\r\n const handlers = {\r\n // ── Read-only operations (no permission gates) ──\r\n\r\n status() {\r\n return {\r\n connected: true,\r\n framework: 'wu-framework',\r\n apps: _getAppList(),\r\n storeKeys: wu.store ? Object.keys(wu.store.get('') || {}) : [],\r\n actionsCount: wu.ai ? wu.ai.tools().length : 0,\r\n eventLogSize: eventLog.length,\r\n };\r\n },\r\n\r\n list_apps() {\r\n return _getAppList();\r\n },\r\n\r\n list_events({ limit = 20 }) {\r\n return eventLog.slice(-limit);\r\n },\r\n\r\n list_actions() {\r\n if (!wu.ai) return { actions: [], note: 'wu.ai not initialized' };\r\n const tools = wu.ai.tools();\r\n return { actions: tools, count: tools.length };\r\n },\r\n\r\n snapshot({ appName }) {\r\n try {\r\n const target = appName\r\n ? document.querySelector(`[data-wu-app=\"${appName}\"]`) || document.querySelector(`#wu-app-${appName}`)\r\n : document.body;\r\n\r\n if (!target) return { error: `App \"${appName}\" not found in DOM` };\r\n\r\n return {\r\n app: appName || '(page)',\r\n snapshot: buildA11yTree(target, 0, 5),\r\n timestamp: Date.now(),\r\n };\r\n } catch (err) {\r\n return { error: err.message };\r\n }\r\n },\r\n\r\n console({ level = 'all', limit = 50 }) {\r\n return getFilteredConsole(level, limit);\r\n },\r\n\r\n async screenshot({ selector, quality = 0.8 }) {\r\n const result = await captureScreenshot(selector, quality);\r\n if (!result.error) result.timestamp = Date.now();\r\n return result;\r\n },\r\n\r\n network({ method, status, limit = 50 }) {\r\n return getFilteredNetwork(method, status, limit);\r\n },\r\n\r\n // ── Permission-gated operations ──\r\n\r\n get_state({ path }) {\r\n if (!wu.store) return { error: 'wu.store not available' };\r\n if (!_checkPermission('readStore')) {\r\n return { error: 'Permission denied: readStore is disabled' };\r\n }\r\n const value = wu.store.get(path || '');\r\n return { path: path || '(root)', value };\r\n },\r\n\r\n set_state({ path, value }) {\r\n if (!wu.store) return { error: 'wu.store not available' };\r\n if (!path) return { error: 'path is required' };\r\n if (!_checkPermission('writeStore')) {\r\n _audit('set_state', { path }, { error: 'Permission denied' });\r\n return { error: 'Permission denied: writeStore is disabled' };\r\n }\r\n wu.store.set(path, value);\r\n _audit('set_state', { path, value }, { success: true });\r\n return { path, value, updated: true };\r\n },\r\n\r\n emit_event({ event, data }) {\r\n if (!wu.eventBus) return { error: 'wu.eventBus not available' };\r\n if (!event) return { error: 'event name is required' };\r\n if (!_checkPermission('emitEvents')) {\r\n _audit('emit_event', { event }, { error: 'Permission denied' });\r\n return { error: 'Permission denied: emitEvents is disabled' };\r\n }\r\n _emitInternal(event, data);\r\n _audit('emit_event', { event, data }, { success: true });\r\n return { emitted: event, data };\r\n },\r\n\r\n navigate({ route }) {\r\n if (!route) return { error: 'Route is required' };\r\n if (!_checkPermission('emitEvents')) {\r\n _audit('navigate', { route }, { error: 'Permission denied: emitEvents' });\r\n return { error: 'Permission denied: emitEvents is disabled' };\r\n }\r\n _emitInternal('shell:navigate', { route });\r\n if (wu.store && _checkPermission('writeStore')) {\r\n wu.store.set('currentPath', route);\r\n }\r\n _audit('navigate', { route }, { success: true });\r\n return { navigated: route };\r\n },\r\n\r\n mount_app({ appName, container }) {\r\n if (!appName) return { error: 'appName is required' };\r\n if (!_checkPermission('modifyDOM')) {\r\n _audit('mount_app', { appName }, { error: 'Permission denied' });\r\n return { error: 'Permission denied: modifyDOM is disabled' };\r\n }\r\n try {\r\n if (wu.mount) {\r\n wu.mount(appName, container);\r\n _audit('mount_app', { appName, container }, { success: true });\r\n return { mounted: appName, container };\r\n }\r\n return { error: 'wu.mount not available' };\r\n } catch (err) {\r\n return { error: err.message };\r\n }\r\n },\r\n\r\n unmount_app({ appName }) {\r\n if (!appName) return { error: 'appName is required' };\r\n if (!_checkPermission('modifyDOM')) {\r\n _audit('unmount_app', { appName }, { error: 'Permission denied' });\r\n return { error: 'Permission denied: modifyDOM is disabled' };\r\n }\r\n try {\r\n if (wu.unmount) {\r\n wu.unmount(appName);\r\n _audit('unmount_app', { appName }, { success: true });\r\n return { unmounted: appName };\r\n }\r\n return { error: 'wu.unmount not available' };\r\n } catch (err) {\r\n return { error: err.message };\r\n }\r\n },\r\n\r\n click({ selector, text }) {\r\n if (!_checkPermission('modifyDOM')) {\r\n return { error: 'Permission denied: modifyDOM is disabled' };\r\n }\r\n const result = clickElement(selector, text);\r\n _audit('click', { selector, text }, result);\r\n return result;\r\n },\r\n\r\n type({ selector, text, clear = false, submit = false }) {\r\n if (!_checkPermission('modifyDOM')) {\r\n return { error: 'Permission denied: modifyDOM is disabled' };\r\n }\r\n const result = typeIntoElement(selector, text, { clear, submit });\r\n _audit('type', { selector, textLength: text?.length }, result);\r\n return result;\r\n },\r\n\r\n async execute_action({ action, params }) {\r\n if (!wu.ai) return { error: 'wu.ai not available' };\r\n if (!action) return { error: 'action name is required' };\r\n\r\n try {\r\n // Execute through public API (respects permissions, validation, audit)\r\n const result = await wu.ai.execute(action, params || {});\r\n return { action, ...result };\r\n } catch (err) {\r\n return { error: err.message };\r\n }\r\n },\r\n };\r\n\r\n // ── WebSocket connection ──\r\n\r\n function connect(url = 'ws://localhost:19100', options = {}) {\r\n if (ws && ws.readyState <= 1) {\r\n logger.warn('[wu-mcp-bridge] Already connected or connecting');\r\n return;\r\n }\r\n\r\n authToken = options.token || null;\r\n authenticated = !authToken; // No token = auto-authenticated (dev mode)\r\n\r\n try {\r\n ws = new WebSocket(url);\r\n\r\n ws.onopen = () => {\r\n logger.debug('[wu-mcp-bridge] Connected to wu-mcp-server');\r\n reconnectAttempts = 0;\r\n\r\n // Send auth handshake if token provided\r\n if (authToken) {\r\n ws.send(JSON.stringify({\r\n type: 'auth',\r\n token: authToken,\r\n }));\r\n }\r\n };\r\n\r\n ws.onmessage = async (event) => {\r\n try {\r\n const msg = JSON.parse(event.data);\r\n\r\n // Handle auth response\r\n if (msg.type === 'auth_result') {\r\n authenticated = msg.success === true;\r\n if (!authenticated) {\r\n console.error('[wu-mcp-bridge] Authentication failed:', msg.reason || 'Invalid token');\r\n disconnect();\r\n } else {\r\n logger.debug('[wu-mcp-bridge] Authenticated successfully');\r\n }\r\n return;\r\n }\r\n\r\n // Reject commands if not authenticated\r\n if (!authenticated) {\r\n if (msg.id) {\r\n _respond(msg.id, null, 'Not authenticated. Send auth token first.');\r\n }\r\n return;\r\n }\r\n\r\n const { id, command, params } = msg;\r\n\r\n if (!id || !command) {\r\n logger.warn('[wu-mcp-bridge] Invalid message:', msg);\r\n return;\r\n }\r\n\r\n const handler = handlers[command];\r\n if (!handler) {\r\n _respond(id, null, `Unknown command: ${command}`);\r\n return;\r\n }\r\n\r\n try {\r\n const result = await handler(params || {});\r\n _respond(id, result);\r\n } catch (err) {\r\n _respond(id, null, err.message);\r\n }\r\n } catch (err) {\r\n console.error('[wu-mcp-bridge] Failed to handle message:', err);\r\n }\r\n };\r\n\r\n ws.onclose = () => {\r\n logger.debug('[wu-mcp-bridge] Disconnected');\r\n ws = null;\r\n authenticated = false;\r\n _scheduleReconnect(url, options);\r\n };\r\n\r\n ws.onerror = () => {\r\n // onclose will fire after this\r\n };\r\n } catch (err) {\r\n console.error('[wu-mcp-bridge] Connection failed:', err.message);\r\n _scheduleReconnect(url, options);\r\n }\r\n }\r\n\r\n function disconnect() {\r\n if (reconnectTimer) {\r\n clearTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n reconnectAttempts = MAX_RECONNECT_ATTEMPTS; // prevent reconnect\r\n if (ws) {\r\n ws.close();\r\n ws = null;\r\n }\r\n authenticated = false;\r\n }\r\n\r\n function isConnected() {\r\n return ws !== null && ws.readyState === 1 && authenticated;\r\n }\r\n\r\n // ── Private helpers ──\r\n\r\n function _respond(id, result, error) {\r\n if (!ws || ws.readyState !== 1) return;\r\n const msg = error ? { id, error } : { id, result };\r\n ws.send(JSON.stringify(msg));\r\n }\r\n\r\n function _scheduleReconnect(url, options) {\r\n if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;\r\n reconnectAttempts++;\r\n const delay = RECONNECT_DELAY * Math.min(reconnectAttempts, 5);\r\n reconnectTimer = setTimeout(() => connect(url, options), delay);\r\n }\r\n\r\n function _getAppList() {\r\n const apps = [];\r\n\r\n if (wu._apps) {\r\n for (const [name, app] of Object.entries(wu._apps)) {\r\n apps.push({\r\n name,\r\n mounted: app.mounted || app.isMounted || false,\r\n url: app.url || app.info?.url || '',\r\n status: app.status || app.info?.status || 'unknown',\r\n });\r\n }\r\n }\r\n\r\n // Fallback: scan DOM for wu-app elements\r\n if (apps.length === 0) {\r\n document.querySelectorAll('[data-wu-app]').forEach((el) => {\r\n apps.push({\r\n name: el.getAttribute('data-wu-app'),\r\n mounted: true,\r\n container: `#${el.id || '(no-id)'}`,\r\n });\r\n });\r\n }\r\n\r\n return apps;\r\n }\r\n\r\n return { connect, disconnect, isConnected };\r\n}\r\n"],"names":["createMcpBridge","wu","ws","reconnectTimer","reconnectAttempts","authenticated","authToken","_bridgeToken","eventBus","getInternalToken","_emitInternal","event","data","emit","appName","token","eventLog","_checkPermission","perm","ai","permissions","check","includes","_audit","operation","params","result","error","success","timestamp","Date","now","on","push","name","source","length","shift","ensureInterceptors","handlers","status","connected","framework","apps","_getAppList","storeKeys","store","Object","keys","get","actionsCount","tools","eventLogSize","list_apps","list_events","limit","slice","list_actions","actions","note","count","snapshot","target","document","querySelector","body","app","buildA11yTree","err","message","console","level","getFilteredConsole","screenshot","selector","quality","captureScreenshot","network","method","getFilteredNetwork","get_state","path","value","set_state","set","updated","emit_event","emitted","navigate","route","navigated","mount_app","container","mount","mounted","unmount_app","unmount","unmounted","click","text","clickElement","type","clear","submit","typeIntoElement","textLength","execute_action","action","execute","connect","url","options","readyState","logger","warn","WebSocket","onopen","debug","send","JSON","stringify","onmessage","async","msg","parse","reason","disconnect","id","_respond","command","handler","onclose","_scheduleReconnect","onerror","clearTimeout","close","delay","Math","min","setTimeout","_apps","entries","isMounted","info","querySelectorAll","forEach","el","getAttribute","isConnected"],"mappings":"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"}
1
+ {"version":3,"file":"wu-mcp-bridge.js","sources":["../../src/core/wu-mcp-bridge.js"],"sourcesContent":["/**\n * WU-MCP Bridge (Browser Side)\n *\n * Connects to the wu-mcp-server via WebSocket and executes\n * commands using wu.* APIs. This is the \"eyes and hands\" of\n * the MCP server inside the browser.\n *\n * Security:\n * - Optional auth token sent on first message (handshake)\n * - All state/event/mount operations check wu.ai permissions\n * - Mutating operations emit audit events\n * - Read-only operations (status, list_apps, snapshot, console, network) are unrestricted\n *\n * @example\n * // Connect with auth token\n * wu.mcp.connect('ws://localhost:19100', { token: 'my-secret' });\n *\n * // Connect without auth (development only)\n * wu.mcp.connect();\n */\n\nimport {\n ensureInterceptors,\n networkLog,\n consoleLog,\n captureScreenshot,\n buildA11yTree,\n clickElement,\n typeIntoElement,\n getFilteredNetwork,\n getFilteredConsole,\n} from '../ai/wu-ai-browser-primitives.js';\nimport { logger } from './wu-logger.js';\n\n/**\n * Create the MCP bridge for a Wu instance.\n *\n * @param {object} wu - The Wu Framework instance (window.wu)\n * @returns {object} Bridge API: { connect, disconnect, isConnected }\n */\nexport function createMcpBridge(wu) {\n let ws = null;\n let reconnectTimer = null;\n let reconnectAttempts = 0;\n let authenticated = false;\n let authToken = null;\n const MAX_RECONNECT_ATTEMPTS = 10;\n const RECONNECT_DELAY = 2000;\n\n // Closure-capture our internal token. Required so strictMode accepts\n // emits as appName='wu-mcp-bridge' (and rejects spoofers under the same name).\n const _bridgeToken = wu.eventBus?.getInternalToken?.('wu-mcp-bridge') || null;\n const _emitInternal = (event, data) =>\n wu.eventBus?.emit(event, data, { appName: 'wu-mcp-bridge', token: _bridgeToken });\n\n // Event log for wu_list_events\n const eventLog = [];\n const MAX_EVENT_LOG = 200;\n\n // Capture events for history\n if (wu.eventBus) {\n wu.eventBus.on('*', (event) => {\n eventLog.push({\n name: event.name,\n data: event.data,\n timestamp: event.timestamp || Date.now(),\n source: event.source || 'unknown',\n });\n if (eventLog.length > MAX_EVENT_LOG) eventLog.shift();\n });\n }\n\n // Install shared interceptors (idempotent — safe if wu-ai-browser already did it)\n ensureInterceptors();\n\n // ── Permission helpers ──\n\n /**\n * Check a permission flag via wu.ai.permissions if available.\n * Falls back to deny if wu.ai is not initialized.\n */\n function _checkPermission(perm) {\n if (wu.ai && wu.ai.permissions) {\n return wu.ai.permissions.check(perm);\n }\n // If AI module not initialized, deny write operations, allow reads\n const readPerms = ['readStore', 'executeActions'];\n return readPerms.includes(perm);\n }\n\n /**\n * Emit an audit event for bridge operations.\n */\n function _audit(operation, params, result) {\n if (wu.eventBus) {\n wu.eventBus.emit('mcp:bridge:operation', {\n operation,\n params,\n result: result?.error ? { error: result.error } : { success: true },\n timestamp: Date.now(),\n }, { appName: 'wu-mcp-bridge' });\n }\n }\n\n // ── Command handlers ──\n\n const handlers = {\n // ── Read-only operations (no permission gates) ──\n\n status() {\n return {\n connected: true,\n framework: 'wu-framework',\n apps: _getAppList(),\n storeKeys: wu.store ? Object.keys(wu.store.get('') || {}) : [],\n actionsCount: wu.ai ? wu.ai.tools().length : 0,\n eventLogSize: eventLog.length,\n };\n },\n\n list_apps() {\n return _getAppList();\n },\n\n list_events({ limit = 20 }) {\n return eventLog.slice(-limit);\n },\n\n list_actions() {\n if (!wu.ai) return { actions: [], note: 'wu.ai not initialized' };\n const tools = wu.ai.tools();\n return { actions: tools, count: tools.length };\n },\n\n snapshot({ appName }) {\n try {\n const target = appName\n ? document.querySelector(`[data-wu-app=\"${appName}\"]`) || document.querySelector(`#wu-app-${appName}`)\n : document.body;\n\n if (!target) return { error: `App \"${appName}\" not found in DOM` };\n\n return {\n app: appName || '(page)',\n snapshot: buildA11yTree(target, 0, 5),\n timestamp: Date.now(),\n };\n } catch (err) {\n return { error: err.message };\n }\n },\n\n console({ level = 'all', limit = 50 }) {\n return getFilteredConsole(level, limit);\n },\n\n async screenshot({ selector, quality = 0.8 }) {\n const result = await captureScreenshot(selector, quality);\n if (!result.error) result.timestamp = Date.now();\n return result;\n },\n\n network({ method, status, limit = 50 }) {\n return getFilteredNetwork(method, status, limit);\n },\n\n // ── Permission-gated operations ──\n\n get_state({ path }) {\n if (!wu.store) return { error: 'wu.store not available' };\n if (!_checkPermission('readStore')) {\n return { error: 'Permission denied: readStore is disabled' };\n }\n const value = wu.store.get(path || '');\n return { path: path || '(root)', value };\n },\n\n set_state({ path, value }) {\n if (!wu.store) return { error: 'wu.store not available' };\n if (!path) return { error: 'path is required' };\n if (!_checkPermission('writeStore')) {\n _audit('set_state', { path }, { error: 'Permission denied' });\n return { error: 'Permission denied: writeStore is disabled' };\n }\n wu.store.set(path, value);\n _audit('set_state', { path, value }, { success: true });\n return { path, value, updated: true };\n },\n\n emit_event({ event, data }) {\n if (!wu.eventBus) return { error: 'wu.eventBus not available' };\n if (!event) return { error: 'event name is required' };\n if (!_checkPermission('emitEvents')) {\n _audit('emit_event', { event }, { error: 'Permission denied' });\n return { error: 'Permission denied: emitEvents is disabled' };\n }\n _emitInternal(event, data);\n _audit('emit_event', { event, data }, { success: true });\n return { emitted: event, data };\n },\n\n navigate({ route }) {\n if (!route) return { error: 'Route is required' };\n if (!_checkPermission('emitEvents')) {\n _audit('navigate', { route }, { error: 'Permission denied: emitEvents' });\n return { error: 'Permission denied: emitEvents is disabled' };\n }\n _emitInternal('shell:navigate', { route });\n if (wu.store && _checkPermission('writeStore')) {\n wu.store.set('currentPath', route);\n }\n _audit('navigate', { route }, { success: true });\n return { navigated: route };\n },\n\n mount_app({ appName, container }) {\n if (!appName) return { error: 'appName is required' };\n if (!_checkPermission('modifyDOM')) {\n _audit('mount_app', { appName }, { error: 'Permission denied' });\n return { error: 'Permission denied: modifyDOM is disabled' };\n }\n try {\n if (wu.mount) {\n wu.mount(appName, container);\n _audit('mount_app', { appName, container }, { success: true });\n return { mounted: appName, container };\n }\n return { error: 'wu.mount not available' };\n } catch (err) {\n return { error: err.message };\n }\n },\n\n unmount_app({ appName }) {\n if (!appName) return { error: 'appName is required' };\n if (!_checkPermission('modifyDOM')) {\n _audit('unmount_app', { appName }, { error: 'Permission denied' });\n return { error: 'Permission denied: modifyDOM is disabled' };\n }\n try {\n if (wu.unmount) {\n wu.unmount(appName);\n _audit('unmount_app', { appName }, { success: true });\n return { unmounted: appName };\n }\n return { error: 'wu.unmount not available' };\n } catch (err) {\n return { error: err.message };\n }\n },\n\n click({ selector, text }) {\n if (!_checkPermission('modifyDOM')) {\n return { error: 'Permission denied: modifyDOM is disabled' };\n }\n const result = clickElement(selector, text);\n _audit('click', { selector, text }, result);\n return result;\n },\n\n type({ selector, text, clear = false, submit = false }) {\n if (!_checkPermission('modifyDOM')) {\n return { error: 'Permission denied: modifyDOM is disabled' };\n }\n const result = typeIntoElement(selector, text, { clear, submit });\n _audit('type', { selector, textLength: text?.length }, result);\n return result;\n },\n\n async execute_action({ action, params }) {\n if (!wu.ai) return { error: 'wu.ai not available' };\n if (!action) return { error: 'action name is required' };\n\n try {\n // Execute through public API (respects permissions, validation, audit)\n const result = await wu.ai.execute(action, params || {});\n return { action, ...result };\n } catch (err) {\n return { error: err.message };\n }\n },\n };\n\n // ── WebSocket connection ──\n\n function connect(url = 'ws://localhost:19100', options = {}) {\n if (ws && ws.readyState <= 1) {\n logger.warn('[wu-mcp-bridge] Already connected or connecting');\n return;\n }\n\n authToken = options.token || null;\n authenticated = !authToken; // No token = auto-authenticated (dev mode)\n\n try {\n ws = new WebSocket(url);\n\n ws.onopen = () => {\n logger.debug('[wu-mcp-bridge] Connected to wu-mcp-server');\n reconnectAttempts = 0;\n\n // Send auth handshake if token provided\n if (authToken) {\n ws.send(JSON.stringify({\n type: 'auth',\n token: authToken,\n }));\n }\n };\n\n ws.onmessage = async (event) => {\n try {\n const msg = JSON.parse(event.data);\n\n // Handle auth response\n if (msg.type === 'auth_result') {\n authenticated = msg.success === true;\n if (!authenticated) {\n console.error('[wu-mcp-bridge] Authentication failed:', msg.reason || 'Invalid token');\n disconnect();\n } else {\n logger.debug('[wu-mcp-bridge] Authenticated successfully');\n }\n return;\n }\n\n // Reject commands if not authenticated\n if (!authenticated) {\n if (msg.id) {\n _respond(msg.id, null, 'Not authenticated. Send auth token first.');\n }\n return;\n }\n\n const { id, command, params } = msg;\n\n if (!id || !command) {\n logger.warn('[wu-mcp-bridge] Invalid message:', msg);\n return;\n }\n\n const handler = handlers[command];\n if (!handler) {\n _respond(id, null, `Unknown command: ${command}`);\n return;\n }\n\n try {\n const result = await handler(params || {});\n _respond(id, result);\n } catch (err) {\n _respond(id, null, err.message);\n }\n } catch (err) {\n console.error('[wu-mcp-bridge] Failed to handle message:', err);\n }\n };\n\n ws.onclose = () => {\n logger.debug('[wu-mcp-bridge] Disconnected');\n ws = null;\n authenticated = false;\n _scheduleReconnect(url, options);\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n } catch (err) {\n console.error('[wu-mcp-bridge] Connection failed:', err.message);\n _scheduleReconnect(url, options);\n }\n }\n\n function disconnect() {\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n reconnectAttempts = MAX_RECONNECT_ATTEMPTS; // prevent reconnect\n if (ws) {\n ws.close();\n ws = null;\n }\n authenticated = false;\n }\n\n function isConnected() {\n return ws !== null && ws.readyState === 1 && authenticated;\n }\n\n // ── Private helpers ──\n\n function _respond(id, result, error) {\n if (!ws || ws.readyState !== 1) return;\n const msg = error ? { id, error } : { id, result };\n ws.send(JSON.stringify(msg));\n }\n\n function _scheduleReconnect(url, options) {\n if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;\n reconnectAttempts++;\n const delay = RECONNECT_DELAY * Math.min(reconnectAttempts, 5);\n reconnectTimer = setTimeout(() => connect(url, options), delay);\n }\n\n function _getAppList() {\n const apps = [];\n\n if (wu._apps) {\n for (const [name, app] of Object.entries(wu._apps)) {\n apps.push({\n name,\n mounted: app.mounted || app.isMounted || false,\n url: app.url || app.info?.url || '',\n status: app.status || app.info?.status || 'unknown',\n });\n }\n }\n\n // Fallback: scan DOM for wu-app elements\n if (apps.length === 0) {\n document.querySelectorAll('[data-wu-app]').forEach((el) => {\n apps.push({\n name: el.getAttribute('data-wu-app'),\n mounted: true,\n container: `#${el.id || '(no-id)'}`,\n });\n });\n }\n\n return apps;\n }\n\n return { connect, disconnect, isConnected };\n}\n"],"names":["createMcpBridge","wu","ws","reconnectTimer","reconnectAttempts","authenticated","authToken","_bridgeToken","eventBus","getInternalToken","_emitInternal","event","data","emit","appName","token","eventLog","_checkPermission","perm","ai","permissions","check","includes","_audit","operation","params","result","error","success","timestamp","Date","now","on","push","name","source","length","shift","ensureInterceptors","handlers","status","connected","framework","apps","_getAppList","storeKeys","store","Object","keys","get","actionsCount","tools","eventLogSize","list_apps","list_events","limit","slice","list_actions","actions","note","count","snapshot","target","document","querySelector","body","app","buildA11yTree","err","message","console","level","getFilteredConsole","screenshot","selector","quality","captureScreenshot","network","method","getFilteredNetwork","get_state","path","value","set_state","set","updated","emit_event","emitted","navigate","route","navigated","mount_app","container","mount","mounted","unmount_app","unmount","unmounted","click","text","clickElement","type","clear","submit","typeIntoElement","textLength","execute_action","action","execute","connect","url","options","readyState","logger","warn","WebSocket","onopen","debug","send","JSON","stringify","onmessage","async","msg","parse","reason","disconnect","id","_respond","command","handler","onclose","_scheduleReconnect","onerror","clearTimeout","close","delay","Math","min","setTimeout","_apps","entries","isMounted","info","querySelectorAll","forEach","el","getAttribute","isConnected"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{l as t}from"../wu-logger-fJfUHBGA.js";class r{static DANGEROUS_PATTERNS=[{pattern:/constructor\s*\[\s*['"`]constructor['"`]\s*\]/,label:"constructor chain access (sandbox escape)"},{pattern:/__proto__/,label:"__proto__ access (prototype pollution)"},{pattern:/Object\s*\.\s*getPrototypeOf\s*\(\s*proxy\s*\)/,label:"Object.getPrototypeOf(proxy) (sandbox escape)"},{pattern:/Function\s*\(\s*['"`]/,label:"Function() constructor (dynamic code generation)"},{pattern:/\beval\s*\(/,label:"eval() (dynamic code execution)"},{pattern:/\bimport\s*\(/,label:"import() (dynamic import escapes sandbox)"},{pattern:/document\s*\.\s*cookie/,label:"document.cookie (direct cookie access)"}];_validateScript(e,o){for(const{pattern:c,label:n}of r.DANGEROUS_PATTERNS)if(c.test(e)){const r=`[ScriptExecutor] Blocked dangerous pattern in "${o}": ${n}`;throw t.wuError(r),new Error(r)}}execute(r,e,o,c={}){const{strictGlobal:n=!0,sourceUrl:s=""}=c;if(!r||!r.trim())return;this._validateScript(r,e);const a=s?`\n//# sourceURL=wu-sandbox:///${e}/${s}\n`:"";let i;i=n?`;(function(window, self, globalThis, top, parent) {\n with(window) {\n ;${r}${a}\n }\n}).call(proxy, proxy, proxy, proxy, proxy, proxy);`:`;(function(window, self, globalThis, top, parent) {\n ;${r}${a}\n}).call(proxy, proxy, proxy, proxy, proxy, proxy);`;try{return new Function("proxy",i)(o)}catch(s){if(n)return t.wuWarn(`[ScriptExecutor] strictGlobal failed for ${e}, retrying without with(): ${s.message}`),this.execute(r,e,o,{...c,strictGlobal:!1});throw t.wuError(`[ScriptExecutor] Execution failed for ${e}:`,s),s}}async fetchScript(t){const r=await fetch(t);if(!r.ok)throw new Error(`Failed to fetch script ${t}: HTTP ${r.status}`);return r.text()}async executeAll(r,e,o,c={}){for(const n of r){let r=n.content;!r&&n.src&&(t.wuDebug(`[ScriptExecutor] Fetching external script: ${n.src}`),r=await this.fetchScript(n.src)),r&&r.trim()&&this.execute(r,e,o,{...c,sourceUrl:n.src||c.sourceUrl||""})}t.wuDebug(`[ScriptExecutor] Executed ${r.length} scripts for ${e}`)}}export{r as WuScriptExecutor};
2
+ //# sourceMappingURL=wu-script-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wu-script-executor.js","sources":["../../src/core/wu-script-executor.js"],"sourcesContent":["/**\n * WU-SCRIPT-EXECUTOR: Execute scripts inside a Proxy sandbox.\n *\n * Two isolation levels:\n * - strictGlobal: true → with(proxy) { code } — all global access goes through proxy\n * - strictGlobal: false → (function(window){ code })(proxy) — only explicit window.xxx\n *\n * This is what makes the sandbox REAL instead of decorative.\n * Without this, import() runs code in global scope and the proxy is just a cleanup tracker.\n * With this, code receives the proxy as \"window\" and every setTimeout, addEventListener,\n * document.querySelector, localStorage access goes through the proxy's traps.\n */\n\nimport { logger } from './wu-logger.js';\n\nexport class WuScriptExecutor {\n\n /**\n * Dangerous patterns that indicate prototype pollution, sandbox escape,\n * or direct access to sensitive APIs. Each entry is a regex paired with\n * a human-readable label used in error messages.\n *\n * This is a tripwire, not a full parser. It catches the most common\n * attack vectors without the overhead of AST analysis.\n */\n static DANGEROUS_PATTERNS = [\n // Prototype pollution vectors\n { pattern: /constructor\\s*\\[\\s*['\"`]constructor['\"`]\\s*\\]/, label: 'constructor chain access (sandbox escape)' },\n { pattern: /__proto__/, label: '__proto__ access (prototype pollution)' },\n\n // Sandbox escape via proxy introspection\n { pattern: /Object\\s*\\.\\s*getPrototypeOf\\s*\\(\\s*proxy\\s*\\)/, label: 'Object.getPrototypeOf(proxy) (sandbox escape)' },\n\n // Dynamic code generation that bypasses the sandbox\n { pattern: /Function\\s*\\(\\s*['\"`]/, label: 'Function() constructor (dynamic code generation)' },\n { pattern: /\\beval\\s*\\(/, label: 'eval() (dynamic code execution)' },\n\n // Dynamic import escapes the sandbox entirely (runs in global scope)\n { pattern: /\\bimport\\s*\\(/, label: 'import() (dynamic import escapes sandbox)' },\n\n // Direct cookie access (should go through proxy traps, not raw document)\n { pattern: /document\\s*\\.\\s*cookie/, label: 'document.cookie (direct cookie access)' },\n ];\n\n /**\n * Validate script text against known dangerous patterns before execution.\n * Throws if any pattern matches. This is intentionally lightweight --\n * pattern detection only, not a full parse.\n *\n * @param {string} scriptText - The raw script to validate\n * @param {string} appName - App identifier (for error context)\n * @throws {Error} If a dangerous pattern is detected\n */\n _validateScript(scriptText, appName) {\n for (const { pattern, label } of WuScriptExecutor.DANGEROUS_PATTERNS) {\n if (pattern.test(scriptText)) {\n const msg = `[ScriptExecutor] Blocked dangerous pattern in \"${appName}\": ${label}`;\n logger.wuError(msg);\n throw new Error(msg);\n }\n }\n }\n\n /**\n * Execute a script string inside the proxy sandbox.\n *\n * @param {string} scriptText - JavaScript code to execute\n * @param {string} appName - App identifier (for logging)\n * @param {Proxy} proxy - The activated proxy sandbox\n * @param {Object} [options]\n * @param {boolean} [options.strictGlobal=true] - Use with(proxy) for maximum isolation\n * @param {string} [options.sourceUrl=''] - Source URL for devtools (//# sourceURL)\n * @returns {*} Return value of the executed code\n */\n execute(scriptText, appName, proxy, options = {}) {\n const { strictGlobal = true, sourceUrl = '' } = options;\n\n if (!scriptText || !scriptText.trim()) return;\n\n this._validateScript(scriptText, appName);\n\n const sourceComment = sourceUrl ? `\\n//# sourceURL=wu-sandbox:///${appName}/${sourceUrl}\\n` : '';\n\n let wrappedCode;\n\n if (strictGlobal) {\n // MAXIMUM ISOLATION\n // with(window) makes ALL unqualified identifiers (setTimeout, fetch, document, etc.)\n // resolve through the proxy's has/get traps, not the real window.\n // Note: 'use strict' inside the with block becomes a no-op string expression,\n // so bundled code with strict mode still works.\n wrappedCode = `;(function(window, self, globalThis, top, parent) {\n with(window) {\n ;${scriptText}${sourceComment}\n }\n}).call(proxy, proxy, proxy, proxy, proxy, proxy);`;\n } else {\n // IIFE ONLY — only explicit window.xxx goes through proxy\n wrappedCode = `;(function(window, self, globalThis, top, parent) {\n ;${scriptText}${sourceComment}\n}).call(proxy, proxy, proxy, proxy, proxy, proxy);`;\n }\n\n try {\n // new Function('proxy', code) creates a function with 'proxy' as the single param.\n // This avoids polluting scope — the only bridge to the sandbox is the proxy argument.\n const fn = new Function('proxy', wrappedCode);\n return fn(proxy);\n } catch (error) {\n // If strictGlobal failed (rare edge case with with-statement), retry without it\n if (strictGlobal) {\n logger.wuWarn(`[ScriptExecutor] strictGlobal failed for ${appName}, retrying without with(): ${error.message}`);\n return this.execute(scriptText, appName, proxy, { ...options, strictGlobal: false });\n }\n logger.wuError(`[ScriptExecutor] Execution failed for ${appName}:`, error);\n throw error;\n }\n }\n\n /**\n * Fetch script content from a URL.\n * @param {string} url - Script URL\n * @returns {Promise<string>} Script text\n */\n async fetchScript(url) {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch script ${url}: HTTP ${response.status}`);\n }\n return response.text();\n }\n\n /**\n * Execute an array of scripts in sequence inside the proxy.\n * External scripts (with src) are fetched first.\n *\n * @param {Array<{content?: string, src?: string}>} scripts\n * @param {string} appName\n * @param {Proxy} proxy\n * @param {Object} [options]\n */\n async executeAll(scripts, appName, proxy, options = {}) {\n for (const script of scripts) {\n let text = script.content;\n\n if (!text && script.src) {\n logger.wuDebug(`[ScriptExecutor] Fetching external script: ${script.src}`);\n text = await this.fetchScript(script.src);\n }\n\n if (text && text.trim()) {\n this.execute(text, appName, proxy, {\n ...options,\n sourceUrl: script.src || options.sourceUrl || ''\n });\n }\n }\n\n logger.wuDebug(`[ScriptExecutor] Executed ${scripts.length} scripts for ${appName}`);\n }\n}\n"],"names":["WuScriptExecutor","static","pattern","label","_validateScript","scriptText","appName","DANGEROUS_PATTERNS","test","msg","logger","wuError","Error","execute","proxy","options","strictGlobal","sourceUrl","trim","this","sourceComment","wrappedCode","Function","fn","error","wuWarn","message","fetchScript","url","response","fetch","ok","status","text","executeAll","scripts","script","content","src","wuDebug","length"],"mappings":"6CAeO,MAAMA,EAUXC,0BAA4B,CAE1B,CAAEC,QAAS,gDAAiDC,MAAO,6CACnE,CAAED,QAAS,YAAaC,MAAO,0CAG/B,CAAED,QAAS,iDAAkDC,MAAO,iDAGpE,CAAED,QAAS,wBAAyBC,MAAO,oDAC3C,CAAED,QAAS,cAAeC,MAAO,mCAGjC,CAAED,QAAS,gBAAiBC,MAAO,6CAGnC,CAAED,QAAS,yBAA0BC,MAAO,2CAY9C,eAAAC,CAAgBC,EAAYC,GAC1B,IAAK,MAAMJ,QAAEA,EAAOC,MAAEA,KAAWH,EAAiBO,mBAChD,GAAIL,EAAQM,KAAKH,GAAa,CAC5B,MAAMI,EAAM,kDAAkDH,OAAaH,IAE3E,MADAO,EAAOC,QAAQF,GACT,IAAIG,MAAMH,EAClB,CAEJ,CAaA,OAAAI,CAAQR,EAAYC,EAASQ,EAAOC,EAAU,CAAA,GAC5C,MAAMC,aAAEA,GAAe,EAAIC,UAAEA,EAAY,IAAOF,EAEhD,IAAKV,IAAeA,EAAWa,OAAQ,OAEvCC,KAAKf,gBAAgBC,EAAYC,GAEjC,MAAMc,EAAgBH,EAAY,iCAAiCX,KAAWW,MAAgB,GAE9F,IAAII,EAQFA,EANEL,EAMY,+EAEbX,IAAae,6DAKA,2DACff,IAAae,wDAId,IAIE,OADW,IAAIE,SAAS,QAASD,EAC1BE,CAAGT,EACZ,CAAE,MAAOU,GAEP,GAAIR,EAEF,OADAN,EAAOe,OAAO,4CAA4CnB,+BAAqCkB,EAAME,WAC9FP,KAAKN,QAAQR,EAAYC,EAASQ,EAAO,IAAKC,EAASC,cAAc,IAG9E,MADAN,EAAOC,QAAQ,yCAAyCL,KAAYkB,GAC9DA,CACR,CACF,CAOA,iBAAMG,CAAYC,GAChB,MAAMC,QAAiBC,MAAMF,GAC7B,IAAKC,EAASE,GACZ,MAAM,IAAInB,MAAM,0BAA0BgB,WAAaC,EAASG,UAElE,OAAOH,EAASI,MAClB,CAWA,gBAAMC,CAAWC,EAAS7B,EAASQ,EAAOC,EAAU,CAAA,GAClD,IAAK,MAAMqB,KAAUD,EAAS,CAC5B,IAAIF,EAAOG,EAAOC,SAEbJ,GAAQG,EAAOE,MAClB5B,EAAO6B,QAAQ,8CAA8CH,EAAOE,OACpEL,QAAad,KAAKQ,YAAYS,EAAOE,MAGnCL,GAAQA,EAAKf,QACfC,KAAKN,QAAQoB,EAAM3B,EAASQ,EAAO,IAC9BC,EACHE,UAAWmB,EAAOE,KAAOvB,EAAQE,WAAa,IAGpD,CAEAP,EAAO6B,QAAQ,6BAA6BJ,EAAQK,sBAAsBlC,IAC5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"wu-ai-browser-primitives-BDKXJlwc.js","sources":["../src/ai/wu-ai-browser-primitives.js"],"sourcesContent":["/**\r\n * WU-AI Browser Primitives\r\n *\r\n * Shared browser capabilities used by both wu-ai-browser.js (Paradigm 1/2)\r\n * and wu-mcp-bridge.js (Paradigm 3). Single source of truth to avoid\r\n * duplicating interceptors, DOM traversal, and Canvas rendering.\r\n */\r\n\r\n// ── Shared capture buffers (singleton) ──\r\n\r\nexport const networkLog = [];\r\nexport const MAX_NETWORK_LOG = 300;\r\nexport const consoleLog = [];\r\nexport const MAX_CONSOLE_LOG = 500;\r\n\r\nlet _interceptorsInstalled = false;\r\n\r\n/**\r\n * Install network + console interceptors (idempotent — only runs once).\r\n */\r\nexport function ensureInterceptors() {\r\n if (_interceptorsInstalled) return;\r\n _installNetworkInterceptor();\r\n _installConsoleInterceptor();\r\n _interceptorsInstalled = true;\r\n}\r\n\r\n/**\r\n * Build an accessibility tree representation of a DOM element.\r\n * Traverses into Shadow DOM if present.\r\n */\r\nexport function buildA11yTree(el, depth = 0, maxDepth = 5) {\r\n if (depth > maxDepth || !el) return '';\r\n\r\n const indent = ' '.repeat(depth);\r\n const tag = el.tagName?.toLowerCase() || '';\r\n const role = el.getAttribute?.('role') || '';\r\n const ariaLabel = el.getAttribute?.('aria-label') || '';\r\n const text = el.childNodes?.length === 1 && el.childNodes[0].nodeType === 3\r\n ? el.textContent?.trim().slice(0, 80) : '';\r\n\r\n let line = `${indent}<${tag}`;\r\n if (el.id) line += ` id=\"${el.id}\"`;\r\n if (role) line += ` role=\"${role}\"`;\r\n if (ariaLabel) line += ` aria-label=\"${ariaLabel}\"`;\r\n if (el.className && typeof el.className === 'string') {\r\n const cls = el.className.trim().slice(0, 60);\r\n if (cls) line += ` class=\"${cls}\"`;\r\n }\r\n line += '>';\r\n if (text) line += ` \"${text}\"`;\r\n\r\n let result = line + '\\n';\r\n const root = el.shadowRoot || el;\r\n const children = root.children || [];\r\n\r\n for (let i = 0; i < children.length && i < 50; i++) {\r\n result += buildA11yTree(children[i], depth + 1, maxDepth);\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Recursively inline computed styles from source to clone for Canvas rendering.\r\n */\r\nexport function inlineComputedStyles(source, clone) {\r\n const props = ['color', 'background', 'background-color', 'font-family',\r\n 'font-size', 'font-weight', 'border', 'border-radius', 'padding', 'margin',\r\n 'display', 'flex-direction', 'align-items', 'justify-content', 'gap',\r\n 'width', 'height', 'max-width', 'max-height', 'overflow', 'opacity',\r\n 'box-shadow', 'text-align', 'line-height', 'position', 'top', 'left',\r\n 'right', 'bottom', 'z-index', 'transform', 'visibility'];\r\n\r\n try {\r\n const style = window.getComputedStyle(source);\r\n for (const prop of props) {\r\n const val = style.getPropertyValue(prop);\r\n if (val) clone.style?.setProperty(prop, val);\r\n }\r\n } catch (_) { /* skip */ }\r\n\r\n const srcKids = source.children || [];\r\n const cloneKids = clone.children || [];\r\n const max = Math.min(srcKids.length, cloneKids.length, 200);\r\n for (let i = 0; i < max; i++) {\r\n inlineComputedStyles(srcKids[i], cloneKids[i]);\r\n }\r\n}\r\n\r\n/**\r\n * Capture a screenshot of a DOM element via Canvas API (SVG foreignObject).\r\n * @returns {Promise<{ width, height, format, base64, sizeKB } | { error: string }>}\r\n */\r\nexport async function captureScreenshot(selector, quality = 0.8) {\r\n const target = selector\r\n ? document.querySelector(selector)\r\n : document.documentElement;\r\n\r\n if (!target) return { error: `Element not found: ${selector}` };\r\n\r\n const rect = target.getBoundingClientRect();\r\n const w = Math.ceil(Math.min(rect.width || window.innerWidth, 1920));\r\n const h = Math.ceil(Math.min(rect.height || window.innerHeight, 1080));\r\n\r\n const clone = target.cloneNode(true);\r\n inlineComputedStyles(target, clone);\r\n\r\n const serializer = new XMLSerializer();\r\n const xhtml = serializer.serializeToString(clone);\r\n\r\n const svgStr = [\r\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">`,\r\n '<foreignObject width=\"100%\" height=\"100%\">',\r\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${w}px;height:${h}px;overflow:hidden;\">`,\r\n xhtml,\r\n '</div>',\r\n '</foreignObject>',\r\n '</svg>',\r\n ].join('');\r\n\r\n const svgBlob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });\r\n const url = URL.createObjectURL(svgBlob);\r\n\r\n const dataUrl = await new Promise((resolve) => {\r\n const img = new Image();\r\n img.onload = () => {\r\n const canvas = document.createElement('canvas');\r\n canvas.width = w;\r\n canvas.height = h;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n URL.revokeObjectURL(url);\r\n resolve(canvas.toDataURL('image/png', quality));\r\n };\r\n img.onerror = () => {\r\n URL.revokeObjectURL(url);\r\n resolve(null);\r\n };\r\n img.src = url;\r\n });\r\n\r\n if (!dataUrl) return { error: 'Canvas rendering failed' };\r\n\r\n const base64 = dataUrl.split(',')[1];\r\n return {\r\n width: w,\r\n height: h,\r\n format: 'png',\r\n base64,\r\n sizeKB: Math.round((base64.length * 3) / 4 / 1024),\r\n };\r\n}\r\n\r\n/**\r\n * Click an element by CSS selector or visible text content.\r\n * @returns {{ clicked, text } | { error }}\r\n */\r\nexport function clickElement(selector, text) {\r\n let el = null;\r\n\r\n if (selector) {\r\n el = document.querySelector(selector);\r\n }\r\n\r\n if (!el && text) {\r\n const candidates = document.querySelectorAll(\r\n 'button, a, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"], [data-click], label, [onclick]'\r\n );\r\n const searchText = text.toLowerCase();\r\n for (const candidate of candidates) {\r\n if (candidate.textContent?.trim().toLowerCase().includes(searchText)) {\r\n el = candidate;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!el) return { error: `Element not found: ${selector || `text=\"${text}\"`}` };\r\n\r\n el.scrollIntoView({ behavior: 'instant', block: 'center' });\r\n el.click();\r\n\r\n const tag = el.tagName?.toLowerCase();\r\n const id = el.id ? `#${el.id}` : '';\r\n return {\r\n clicked: `${tag}${id}`,\r\n text: el.textContent?.trim().slice(0, 100) || '',\r\n rect: el.getBoundingClientRect().toJSON?.() || null,\r\n };\r\n}\r\n\r\n/**\r\n * Type into an input, textarea, or contenteditable element.\r\n * Works with React, Vue, Angular, and other frameworks.\r\n * @returns {{ selector, typed, currentValue, submitted } | { error }}\r\n */\r\nexport function typeIntoElement(selector, text, { clear = false, submit = false } = {}) {\r\n if (!selector) return { error: 'selector is required' };\r\n if (text === undefined) return { error: 'text is required' };\r\n\r\n const el = document.querySelector(selector);\r\n if (!el) return { error: `Element not found: ${selector}` };\r\n\r\n el.focus();\r\n\r\n if (clear) {\r\n el.value = '';\r\n el.dispatchEvent(new Event('input', { bubbles: true }));\r\n }\r\n\r\n // Use native setter to trigger framework reactivity (React, Vue, etc.)\r\n const nativeSetter = Object.getOwnPropertyDescriptor(\r\n window.HTMLInputElement.prototype, 'value'\r\n )?.set || Object.getOwnPropertyDescriptor(\r\n window.HTMLTextAreaElement.prototype, 'value'\r\n )?.set;\r\n\r\n const newValue = clear ? text : (el.value || '') + text;\r\n if (nativeSetter) {\r\n nativeSetter.call(el, newValue);\r\n } else {\r\n el.value = newValue;\r\n }\r\n\r\n el.dispatchEvent(new Event('input', { bubbles: true }));\r\n el.dispatchEvent(new Event('change', { bubbles: true }));\r\n\r\n if (submit) {\r\n const form = el.closest('form');\r\n if (form) {\r\n form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\r\n } else {\r\n el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true }));\r\n }\r\n }\r\n\r\n return {\r\n selector,\r\n typed: text,\r\n currentValue: el.value?.slice(0, 200),\r\n submitted: !!submit,\r\n };\r\n}\r\n\r\n/**\r\n * Filter and return network log entries.\r\n */\r\nexport function getFilteredNetwork(method, status, limit = 30) {\r\n let filtered = networkLog;\r\n if (method) {\r\n filtered = filtered.filter((r) => r.method === method.toUpperCase());\r\n }\r\n if (status) {\r\n if (status === 'error') {\r\n filtered = filtered.filter((r) => r.status === 0 || r.status >= 400);\r\n } else {\r\n filtered = filtered.filter((r) => String(r.status).startsWith(String(status)));\r\n }\r\n }\r\n return {\r\n requests: filtered.slice(-limit),\r\n total: networkLog.length,\r\n showing: Math.min(filtered.length, limit),\r\n };\r\n}\r\n\r\n/**\r\n * Filter and return console log entries.\r\n */\r\nexport function getFilteredConsole(level, limit = 30) {\r\n const filtered = level && level !== 'all'\r\n ? consoleLog.filter((m) => m.level === level)\r\n : consoleLog;\r\n return {\r\n messages: filtered.slice(-limit),\r\n total: consoleLog.length,\r\n showing: Math.min(filtered.length, limit),\r\n };\r\n}\r\n\r\n// ── Private: Interceptors ──\r\n\r\nfunction _installNetworkInterceptor() {\r\n // Intercept fetch\r\n const originalFetch = window.fetch;\r\n window.fetch = async function (...args) {\r\n const start = Date.now();\r\n const req = args[0];\r\n const url = typeof req === 'string' ? req : req?.url || '';\r\n const method = (args[1]?.method || req?.method || 'GET').toUpperCase();\r\n\r\n try {\r\n const response = await originalFetch.apply(window, args);\r\n const size = parseInt(response.headers?.get('content-length') || '0', 10);\r\n networkLog.push({\r\n type: 'fetch', method, url,\r\n status: response.status, statusText: response.statusText,\r\n duration: Date.now() - start, size, timestamp: start,\r\n });\r\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\r\n return response;\r\n } catch (err) {\r\n networkLog.push({\r\n type: 'fetch', method, url,\r\n status: 0, error: err.message,\r\n duration: Date.now() - start, timestamp: start,\r\n });\r\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\r\n throw err;\r\n }\r\n };\r\n\r\n // Intercept XMLHttpRequest\r\n const origOpen = XMLHttpRequest.prototype.open;\r\n const origSend = XMLHttpRequest.prototype.send;\r\n\r\n XMLHttpRequest.prototype.open = function (method, url, ...rest) {\r\n this._wuAi = { method: (method || 'GET').toUpperCase(), url: String(url) };\r\n return origOpen.call(this, method, url, ...rest);\r\n };\r\n\r\n XMLHttpRequest.prototype.send = function (...args) {\r\n if (this._wuAi) {\r\n this._wuAi.start = Date.now();\r\n this.addEventListener('loadend', () => {\r\n networkLog.push({\r\n type: 'xhr', method: this._wuAi.method, url: this._wuAi.url,\r\n status: this.status, statusText: this.statusText,\r\n duration: Date.now() - this._wuAi.start,\r\n size: parseInt(this.getResponseHeader('content-length') || '0', 10),\r\n timestamp: this._wuAi.start,\r\n });\r\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\r\n });\r\n }\r\n return origSend.apply(this, args);\r\n };\r\n}\r\n\r\nfunction _installConsoleInterceptor() {\r\n const levels = ['log', 'warn', 'error'];\r\n for (const level of levels) {\r\n const original = console[level];\r\n console[level] = (...args) => {\r\n consoleLog.push({\r\n level,\r\n message: args.map((a) => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' '),\r\n timestamp: Date.now(),\r\n });\r\n if (consoleLog.length > MAX_CONSOLE_LOG) consoleLog.shift();\r\n original.apply(console, args);\r\n };\r\n }\r\n}\r\n"],"names":["networkLog","MAX_NETWORK_LOG","consoleLog","_interceptorsInstalled","ensureInterceptors","originalFetch","window","fetch","async","args","start","Date","now","req","url","method","toUpperCase","response","apply","size","parseInt","headers","get","push","type","status","statusText","duration","timestamp","length","shift","err","error","message","origOpen","XMLHttpRequest","prototype","open","origSend","send","rest","this","_wuAi","String","call","addEventListener","getResponseHeader","_installNetworkInterceptor","levels","level","original","console","map","a","JSON","stringify","join","_installConsoleInterceptor","buildA11yTree","el","depth","maxDepth","indent","repeat","tag","tagName","toLowerCase","role","getAttribute","ariaLabel","text","childNodes","nodeType","textContent","trim","slice","line","id","className","cls","result","children","shadowRoot","i","inlineComputedStyles","source","clone","props","style","getComputedStyle","prop","val","getPropertyValue","setProperty","_","srcKids","cloneKids","max","Math","min","captureScreenshot","selector","quality","target","document","querySelector","documentElement","rect","getBoundingClientRect","w","ceil","width","innerWidth","h","height","innerHeight","cloneNode","xhtml","XMLSerializer","serializeToString","svgStr","svgBlob","Blob","URL","createObjectURL","dataUrl","Promise","resolve","img","Image","onload","canvas","createElement","getContext","drawImage","revokeObjectURL","toDataURL","onerror","src","base64","split","format","sizeKB","round","clickElement","candidates","querySelectorAll","searchText","candidate","includes","scrollIntoView","behavior","block","click","clicked","toJSON","typeIntoElement","clear","submit","undefined","focus","value","dispatchEvent","Event","bubbles","nativeSetter","Object","getOwnPropertyDescriptor","HTMLInputElement","set","HTMLTextAreaElement","newValue","form","closest","cancelable","KeyboardEvent","key","code","typed","currentValue","submitted","getFilteredNetwork","limit","filtered","filter","r","startsWith","requests","total","showing","getFilteredConsole","m","messages"],"mappings":"AAUY,MAACA,EAAa,GACbC,EAAkB,IAClBC,EAAa,GAG1B,IAAIC,GAAyB,EAKtB,SAASC,IACVD,KAqQN,WAEE,MAAME,EAAgBC,OAAOC,MAC7BD,OAAOC,MAAQC,kBAAmBC,GAChC,MAAMC,EAAQC,KAAKC,MACbC,EAAMJ,EAAK,GACXK,EAAqB,iBAARD,EAAmBA,EAAMA,GAAKC,KAAO,GAClDC,GAAUN,EAAK,IAAIM,QAAUF,GAAKE,QAAU,OAAOC,cAEzD,IACE,MAAMC,QAAiBZ,EAAca,MAAMZ,OAAQG,GAC7CU,EAAOC,SAASH,EAASI,SAASC,IAAI,mBAAqB,IAAK,IAOtE,OANAtB,EAAWuB,KAAK,CACdC,KAAM,QAAST,SAAQD,MACvBW,OAAQR,EAASQ,OAAQC,WAAYT,EAASS,WAC9CC,SAAUhB,KAAKC,MAAQF,EAAOS,OAAMS,UAAWlB,IAE7CV,EAAW6B,OAAS5B,GAAiBD,EAAW8B,QAC7Cb,CACT,CAAE,MAAOc,GAOP,MANA/B,EAAWuB,KAAK,CACdC,KAAM,QAAST,SAAQD,MACvBW,OAAQ,EAAGO,MAAOD,EAAIE,QACtBN,SAAUhB,KAAKC,MAAQF,EAAOkB,UAAWlB,IAEvCV,EAAW6B,OAAS5B,GAAiBD,EAAW8B,QAC9CC,CACR,CACF,EAGA,MAAMG,EAAWC,eAAeC,UAAUC,KACpCC,EAAWH,eAAeC,UAAUG,KAE1CJ,eAAeC,UAAUC,KAAO,SAAUtB,EAAQD,KAAQ0B,GAExD,OADAC,KAAKC,MAAQ,CAAE3B,QAASA,GAAU,OAAOC,cAAeF,IAAK6B,OAAO7B,IAC7DoB,EAASU,KAAKH,KAAM1B,EAAQD,KAAQ0B,EAC7C,EAEAL,eAAeC,UAAUG,KAAO,YAAa9B,GAc3C,OAbIgC,KAAKC,QACPD,KAAKC,MAAMhC,MAAQC,KAAKC,MACxB6B,KAAKI,iBAAiB,UAAW,KAC/B7C,EAAWuB,KAAK,CACdC,KAAM,MAAOT,OAAQ0B,KAAKC,MAAM3B,OAAQD,IAAK2B,KAAKC,MAAM5B,IACxDW,OAAQgB,KAAKhB,OAAQC,WAAYe,KAAKf,WACtCC,SAAUhB,KAAKC,MAAQ6B,KAAKC,MAAMhC,MAClCS,KAAMC,SAASqB,KAAKK,kBAAkB,mBAAqB,IAAK,IAChElB,UAAWa,KAAKC,MAAMhC,QAEpBV,EAAW6B,OAAS5B,GAAiBD,EAAW8B,WAGjDQ,EAASpB,MAAMuB,KAAMhC,EAC9B,CACF,CA3TEsC,GA6TF,WACE,MAAMC,EAAS,CAAC,MAAO,OAAQ,SAC/B,IAAK,MAAMC,KAASD,EAAQ,CAC1B,MAAME,EAAWC,QAAQF,GACzBE,QAAQF,GAAS,IAAIxC,KACnBP,EAAWqB,KAAK,CACd0B,QACAhB,QAASxB,EAAK2C,IAAKC,GAAoB,iBAANA,EAAiBC,KAAKC,UAAUF,GAAKV,OAAOU,IAAKG,KAAK,KACvF5B,UAAWjB,KAAKC,QAEdV,EAAW2B,OAhVU,KAgVgB3B,EAAW4B,QACpDoB,EAAShC,MAAMiC,QAAS1C,GAE5B,CACF,CA1UEgD,GACAtD,GAAyB,EAC3B,CAMO,SAASuD,EAAcC,EAAIC,EAAQ,EAAGC,EAAW,GACtD,GAAID,EAAQC,IAAaF,EAAI,MAAO,GAEpC,MAAMG,EAAS,KAAKC,OAAOH,GACrBI,EAAML,EAAGM,SAASC,eAAiB,GACnCC,EAAOR,EAAGS,eAAe,SAAW,GACpCC,EAAYV,EAAGS,eAAe,eAAiB,GAC/CE,EAAiC,IAA1BX,EAAGY,YAAY1C,QAA8C,IAA9B8B,EAAGY,WAAW,GAAGC,SACzDb,EAAGc,aAAaC,OAAOC,MAAM,EAAG,IAAM,GAE1C,IAAIC,EAAO,GAAGd,KAAUE,IAIxB,GAHIL,EAAGkB,KAAID,GAAQ,QAAQjB,EAAGkB,OAC1BV,IAAMS,GAAQ,UAAUT,MACxBE,IAAWO,GAAQ,gBAAgBP,MACnCV,EAAGmB,WAAqC,iBAAjBnB,EAAGmB,UAAwB,CACpD,MAAMC,EAAMpB,EAAGmB,UAAUJ,OAAOC,MAAM,EAAG,IACrCI,IAAKH,GAAQ,WAAWG,KAC9B,CACAH,GAAQ,IACJN,IAAMM,GAAQ,KAAKN,MAEvB,IAAIU,EAASJ,EAAO,KACpB,MACMK,GADOtB,EAAGuB,YAAcvB,GACRsB,UAAY,GAElC,IAAK,IAAIE,EAAI,EAAGA,EAAIF,EAASpD,QAAUsD,EAAI,GAAIA,IAC7CH,GAAUtB,EAAcuB,EAASE,GAAIvB,EAAQ,EAAGC,GAElD,OAAOmB,CACT,CAKO,SAASI,EAAqBC,EAAQC,GAC3C,MAAMC,EAAQ,CAAC,QAAS,aAAc,mBAAoB,cACxD,YAAa,cAAe,SAAU,gBAAiB,UAAW,SAClE,UAAW,iBAAkB,cAAe,kBAAmB,MAC/D,QAAS,SAAU,YAAa,aAAc,WAAY,UAC1D,aAAc,aAAc,cAAe,WAAY,MAAO,OAC9D,QAAS,SAAU,UAAW,YAAa,cAE7C,IACE,MAAMC,EAAQlF,OAAOmF,iBAAiBJ,GACtC,IAAK,MAAMK,KAAQH,EAAO,CACxB,MAAMI,EAAMH,EAAMI,iBAAiBF,GAC/BC,GAAKL,EAAME,OAAOK,YAAYH,EAAMC,EAC1C,CACF,CAAE,MAAOG,GAAgB,CAEzB,MAAMC,EAAUV,EAAOJ,UAAY,GAC7Be,EAAYV,EAAML,UAAY,GAC9BgB,EAAMC,KAAKC,IAAIJ,EAAQlE,OAAQmE,EAAUnE,OAAQ,KACvD,IAAK,IAAIsD,EAAI,EAAGA,EAAIc,EAAKd,IACvBC,EAAqBW,EAAQZ,GAAIa,EAAUb,GAE/C,CAMO3E,eAAe4F,EAAkBC,EAAUC,EAAU,IAC1D,MAAMC,EAASF,EACXG,SAASC,cAAcJ,GACvBG,SAASE,gBAEb,IAAKH,EAAQ,MAAO,CAAEvE,MAAO,sBAAsBqE,KAEnD,MAAMM,EAAOJ,EAAOK,wBACdC,EAAIX,KAAKY,KAAKZ,KAAKC,IAAIQ,EAAKI,OAASzG,OAAO0G,WAAY,OACxDC,EAAIf,KAAKY,KAAKZ,KAAKC,IAAIQ,EAAKO,QAAU5G,OAAO6G,YAAa,OAE1D7B,EAAQiB,EAAOa,WAAU,GAC/BhC,EAAqBmB,EAAQjB,GAE7B,MACM+B,GADa,IAAIC,eACEC,kBAAkBjC,GAErCkC,EAAS,CACb,kDAAkDX,cAAcI,MAChE,6CACA,0DAA0DJ,cAAcI,yBACxEI,EACA,SACA,mBACA,UACA7D,KAAK,IAEDiE,EAAU,IAAIC,KAAK,CAACF,GAAS,CAAEhG,KAAM,gCACrCV,EAAM6G,IAAIC,gBAAgBH,GAE1BI,QAAgB,IAAIC,QAASC,IACjC,MAAMC,EAAM,IAAIC,MAChBD,EAAIE,OAAS,KACX,MAAMC,EAAS3B,SAAS4B,cAAc,UACtCD,EAAOpB,MAAQF,EACfsB,EAAOjB,OAASD,EACJkB,EAAOE,WAAW,MAC1BC,UAAUN,EAAK,EAAG,GACtBL,IAAIY,gBAAgBzH,GACpBiH,EAAQI,EAAOK,UAAU,YAAalC,KAExC0B,EAAIS,QAAU,KACZd,IAAIY,gBAAgBzH,GACpBiH,EAAQ,OAEVC,EAAIU,IAAM5H,IAGZ,IAAK+G,EAAS,MAAO,CAAE7F,MAAO,2BAE9B,MAAM2G,EAASd,EAAQe,MAAM,KAAK,GAClC,MAAO,CACL7B,MAAOF,EACPK,OAAQD,EACR4B,OAAQ,MACRF,SACAG,OAAQ5C,KAAK6C,MAAuB,EAAhBJ,EAAO9G,OAAc,EAAI,MAEjD,CAMO,SAASmH,EAAa3C,EAAU/B,GACrC,IAAIX,EAAK,KAMT,GAJI0C,IACF1C,EAAK6C,SAASC,cAAcJ,KAGzB1C,GAAMW,EAAM,CACf,MAAM2E,EAAazC,SAAS0C,iBAC1B,0GAEIC,EAAa7E,EAAKJ,cACxB,IAAK,MAAMkF,KAAaH,EACtB,GAAIG,EAAU3E,aAAaC,OAAOR,cAAcmF,SAASF,GAAa,CACpExF,EAAKyF,EACL,KACF,CAEJ,CAEA,IAAKzF,EAAI,MAAO,CAAE3B,MAAO,sBAAsBqE,GAAY,SAAS/B,QAEpEX,EAAG2F,eAAe,CAAEC,SAAU,UAAWC,MAAO,WAChD7F,EAAG8F,QAEH,MAAMzF,EAAML,EAAGM,SAASC,cAExB,MAAO,CACLwF,QAAS,GAAG1F,IAFHL,EAAGkB,GAAK,IAAIlB,EAAGkB,KAAO,KAG/BP,KAAMX,EAAGc,aAAaC,OAAOC,MAAM,EAAG,MAAQ,GAC9CgC,KAAMhD,EAAGiD,wBAAwB+C,YAAc,KAEnD,CAOO,SAASC,EAAgBvD,EAAU/B,GAAMuF,MAAEA,GAAQ,EAAKC,OAAEA,GAAS,GAAU,IAClF,IAAKzD,EAAU,MAAO,CAAErE,MAAO,wBAC/B,QAAa+H,IAATzF,EAAoB,MAAO,CAAEtC,MAAO,oBAExC,MAAM2B,EAAK6C,SAASC,cAAcJ,GAClC,IAAK1C,EAAI,MAAO,CAAE3B,MAAO,sBAAsBqE,KAE/C1C,EAAGqG,QAECH,IACFlG,EAAGsG,MAAQ,GACXtG,EAAGuG,cAAc,IAAIC,MAAM,QAAS,CAAEC,SAAS,MAIjD,MAAMC,EAAeC,OAAOC,yBAC1BjK,OAAOkK,iBAAiBpI,UAAW,UAClCqI,KAAOH,OAAOC,yBACfjK,OAAOoK,oBAAoBtI,UAAW,UACrCqI,IAEGE,EAAWd,EAAQvF,GAAQX,EAAGsG,OAAS,IAAM3F,EAUnD,GATI+F,EACFA,EAAazH,KAAKe,EAAIgH,GAEtBhH,EAAGsG,MAAQU,EAGbhH,EAAGuG,cAAc,IAAIC,MAAM,QAAS,CAAEC,SAAS,KAC/CzG,EAAGuG,cAAc,IAAIC,MAAM,SAAU,CAAEC,SAAS,KAE5CN,EAAQ,CACV,MAAMc,EAAOjH,EAAGkH,QAAQ,QACpBD,EACFA,EAAKV,cAAc,IAAIC,MAAM,SAAU,CAAEC,SAAS,EAAMU,YAAY,KAEpEnH,EAAGuG,cAAc,IAAIa,cAAc,UAAW,CAAEC,IAAK,QAASC,KAAM,QAASb,SAAS,IAE1F,CAEA,MAAO,CACL/D,WACA6E,MAAO5G,EACP6G,aAAcxH,EAAGsG,OAAOtF,MAAM,EAAG,KACjCyG,YAAatB,EAEjB,CAKO,SAASuB,EAAmBtK,EAAQU,EAAQ6J,EAAQ,IACzD,IAAIC,EAAWvL,EAWf,OAVIe,IACFwK,EAAWA,EAASC,OAAQC,GAAMA,EAAE1K,SAAWA,EAAOC,gBAEpDS,IAEA8J,EADa,UAAX9J,EACS8J,EAASC,OAAQC,GAAmB,IAAbA,EAAEhK,QAAgBgK,EAAEhK,QAAU,KAErD8J,EAASC,OAAQC,GAAM9I,OAAO8I,EAAEhK,QAAQiK,WAAW/I,OAAOlB,MAGlE,CACLkK,SAAUJ,EAAS5G,OAAO2G,GAC1BM,MAAO5L,EAAW6B,OAClBgK,QAAS3F,KAAKC,IAAIoF,EAAS1J,OAAQyJ,GAEvC,CAKO,SAASQ,EAAmB7I,EAAOqI,EAAQ,IAChD,MAAMC,EAAWtI,GAAmB,QAAVA,EACtB/C,EAAWsL,OAAQO,GAAMA,EAAE9I,QAAUA,GACrC/C,EACJ,MAAO,CACL8L,SAAUT,EAAS5G,OAAO2G,GAC1BM,MAAO1L,EAAW2B,OAClBgK,QAAS3F,KAAKC,IAAIoF,EAAS1J,OAAQyJ,GAEvC"}
1
+ {"version":3,"file":"wu-ai-browser-primitives-BDKXJlwc.js","sources":["../src/ai/wu-ai-browser-primitives.js"],"sourcesContent":["/**\n * WU-AI Browser Primitives\n *\n * Shared browser capabilities used by both wu-ai-browser.js (Paradigm 1/2)\n * and wu-mcp-bridge.js (Paradigm 3). Single source of truth to avoid\n * duplicating interceptors, DOM traversal, and Canvas rendering.\n */\n\n// ── Shared capture buffers (singleton) ──\n\nexport const networkLog = [];\nexport const MAX_NETWORK_LOG = 300;\nexport const consoleLog = [];\nexport const MAX_CONSOLE_LOG = 500;\n\nlet _interceptorsInstalled = false;\n\n/**\n * Install network + console interceptors (idempotent — only runs once).\n */\nexport function ensureInterceptors() {\n if (_interceptorsInstalled) return;\n _installNetworkInterceptor();\n _installConsoleInterceptor();\n _interceptorsInstalled = true;\n}\n\n/**\n * Build an accessibility tree representation of a DOM element.\n * Traverses into Shadow DOM if present.\n */\nexport function buildA11yTree(el, depth = 0, maxDepth = 5) {\n if (depth > maxDepth || !el) return '';\n\n const indent = ' '.repeat(depth);\n const tag = el.tagName?.toLowerCase() || '';\n const role = el.getAttribute?.('role') || '';\n const ariaLabel = el.getAttribute?.('aria-label') || '';\n const text = el.childNodes?.length === 1 && el.childNodes[0].nodeType === 3\n ? el.textContent?.trim().slice(0, 80) : '';\n\n let line = `${indent}<${tag}`;\n if (el.id) line += ` id=\"${el.id}\"`;\n if (role) line += ` role=\"${role}\"`;\n if (ariaLabel) line += ` aria-label=\"${ariaLabel}\"`;\n if (el.className && typeof el.className === 'string') {\n const cls = el.className.trim().slice(0, 60);\n if (cls) line += ` class=\"${cls}\"`;\n }\n line += '>';\n if (text) line += ` \"${text}\"`;\n\n let result = line + '\\n';\n const root = el.shadowRoot || el;\n const children = root.children || [];\n\n for (let i = 0; i < children.length && i < 50; i++) {\n result += buildA11yTree(children[i], depth + 1, maxDepth);\n }\n return result;\n}\n\n/**\n * Recursively inline computed styles from source to clone for Canvas rendering.\n */\nexport function inlineComputedStyles(source, clone) {\n const props = ['color', 'background', 'background-color', 'font-family',\n 'font-size', 'font-weight', 'border', 'border-radius', 'padding', 'margin',\n 'display', 'flex-direction', 'align-items', 'justify-content', 'gap',\n 'width', 'height', 'max-width', 'max-height', 'overflow', 'opacity',\n 'box-shadow', 'text-align', 'line-height', 'position', 'top', 'left',\n 'right', 'bottom', 'z-index', 'transform', 'visibility'];\n\n try {\n const style = window.getComputedStyle(source);\n for (const prop of props) {\n const val = style.getPropertyValue(prop);\n if (val) clone.style?.setProperty(prop, val);\n }\n } catch (_) { /* skip */ }\n\n const srcKids = source.children || [];\n const cloneKids = clone.children || [];\n const max = Math.min(srcKids.length, cloneKids.length, 200);\n for (let i = 0; i < max; i++) {\n inlineComputedStyles(srcKids[i], cloneKids[i]);\n }\n}\n\n/**\n * Capture a screenshot of a DOM element via Canvas API (SVG foreignObject).\n * @returns {Promise<{ width, height, format, base64, sizeKB } | { error: string }>}\n */\nexport async function captureScreenshot(selector, quality = 0.8) {\n const target = selector\n ? document.querySelector(selector)\n : document.documentElement;\n\n if (!target) return { error: `Element not found: ${selector}` };\n\n const rect = target.getBoundingClientRect();\n const w = Math.ceil(Math.min(rect.width || window.innerWidth, 1920));\n const h = Math.ceil(Math.min(rect.height || window.innerHeight, 1080));\n\n const clone = target.cloneNode(true);\n inlineComputedStyles(target, clone);\n\n const serializer = new XMLSerializer();\n const xhtml = serializer.serializeToString(clone);\n\n const svgStr = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">`,\n '<foreignObject width=\"100%\" height=\"100%\">',\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${w}px;height:${h}px;overflow:hidden;\">`,\n xhtml,\n '</div>',\n '</foreignObject>',\n '</svg>',\n ].join('');\n\n const svgBlob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(svgBlob);\n\n const dataUrl = await new Promise((resolve) => {\n const img = new Image();\n img.onload = () => {\n const canvas = document.createElement('canvas');\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext('2d');\n ctx.drawImage(img, 0, 0);\n URL.revokeObjectURL(url);\n resolve(canvas.toDataURL('image/png', quality));\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n resolve(null);\n };\n img.src = url;\n });\n\n if (!dataUrl) return { error: 'Canvas rendering failed' };\n\n const base64 = dataUrl.split(',')[1];\n return {\n width: w,\n height: h,\n format: 'png',\n base64,\n sizeKB: Math.round((base64.length * 3) / 4 / 1024),\n };\n}\n\n/**\n * Click an element by CSS selector or visible text content.\n * @returns {{ clicked, text } | { error }}\n */\nexport function clickElement(selector, text) {\n let el = null;\n\n if (selector) {\n el = document.querySelector(selector);\n }\n\n if (!el && text) {\n const candidates = document.querySelectorAll(\n 'button, a, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"], [data-click], label, [onclick]'\n );\n const searchText = text.toLowerCase();\n for (const candidate of candidates) {\n if (candidate.textContent?.trim().toLowerCase().includes(searchText)) {\n el = candidate;\n break;\n }\n }\n }\n\n if (!el) return { error: `Element not found: ${selector || `text=\"${text}\"`}` };\n\n el.scrollIntoView({ behavior: 'instant', block: 'center' });\n el.click();\n\n const tag = el.tagName?.toLowerCase();\n const id = el.id ? `#${el.id}` : '';\n return {\n clicked: `${tag}${id}`,\n text: el.textContent?.trim().slice(0, 100) || '',\n rect: el.getBoundingClientRect().toJSON?.() || null,\n };\n}\n\n/**\n * Type into an input, textarea, or contenteditable element.\n * Works with React, Vue, Angular, and other frameworks.\n * @returns {{ selector, typed, currentValue, submitted } | { error }}\n */\nexport function typeIntoElement(selector, text, { clear = false, submit = false } = {}) {\n if (!selector) return { error: 'selector is required' };\n if (text === undefined) return { error: 'text is required' };\n\n const el = document.querySelector(selector);\n if (!el) return { error: `Element not found: ${selector}` };\n\n el.focus();\n\n if (clear) {\n el.value = '';\n el.dispatchEvent(new Event('input', { bubbles: true }));\n }\n\n // Use native setter to trigger framework reactivity (React, Vue, etc.)\n const nativeSetter = Object.getOwnPropertyDescriptor(\n window.HTMLInputElement.prototype, 'value'\n )?.set || Object.getOwnPropertyDescriptor(\n window.HTMLTextAreaElement.prototype, 'value'\n )?.set;\n\n const newValue = clear ? text : (el.value || '') + text;\n if (nativeSetter) {\n nativeSetter.call(el, newValue);\n } else {\n el.value = newValue;\n }\n\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n\n if (submit) {\n const form = el.closest('form');\n if (form) {\n form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));\n } else {\n el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true }));\n }\n }\n\n return {\n selector,\n typed: text,\n currentValue: el.value?.slice(0, 200),\n submitted: !!submit,\n };\n}\n\n/**\n * Filter and return network log entries.\n */\nexport function getFilteredNetwork(method, status, limit = 30) {\n let filtered = networkLog;\n if (method) {\n filtered = filtered.filter((r) => r.method === method.toUpperCase());\n }\n if (status) {\n if (status === 'error') {\n filtered = filtered.filter((r) => r.status === 0 || r.status >= 400);\n } else {\n filtered = filtered.filter((r) => String(r.status).startsWith(String(status)));\n }\n }\n return {\n requests: filtered.slice(-limit),\n total: networkLog.length,\n showing: Math.min(filtered.length, limit),\n };\n}\n\n/**\n * Filter and return console log entries.\n */\nexport function getFilteredConsole(level, limit = 30) {\n const filtered = level && level !== 'all'\n ? consoleLog.filter((m) => m.level === level)\n : consoleLog;\n return {\n messages: filtered.slice(-limit),\n total: consoleLog.length,\n showing: Math.min(filtered.length, limit),\n };\n}\n\n// ── Private: Interceptors ──\n\nfunction _installNetworkInterceptor() {\n // Intercept fetch\n const originalFetch = window.fetch;\n window.fetch = async function (...args) {\n const start = Date.now();\n const req = args[0];\n const url = typeof req === 'string' ? req : req?.url || '';\n const method = (args[1]?.method || req?.method || 'GET').toUpperCase();\n\n try {\n const response = await originalFetch.apply(window, args);\n const size = parseInt(response.headers?.get('content-length') || '0', 10);\n networkLog.push({\n type: 'fetch', method, url,\n status: response.status, statusText: response.statusText,\n duration: Date.now() - start, size, timestamp: start,\n });\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\n return response;\n } catch (err) {\n networkLog.push({\n type: 'fetch', method, url,\n status: 0, error: err.message,\n duration: Date.now() - start, timestamp: start,\n });\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\n throw err;\n }\n };\n\n // Intercept XMLHttpRequest\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method, url, ...rest) {\n this._wuAi = { method: (method || 'GET').toUpperCase(), url: String(url) };\n return origOpen.call(this, method, url, ...rest);\n };\n\n XMLHttpRequest.prototype.send = function (...args) {\n if (this._wuAi) {\n this._wuAi.start = Date.now();\n this.addEventListener('loadend', () => {\n networkLog.push({\n type: 'xhr', method: this._wuAi.method, url: this._wuAi.url,\n status: this.status, statusText: this.statusText,\n duration: Date.now() - this._wuAi.start,\n size: parseInt(this.getResponseHeader('content-length') || '0', 10),\n timestamp: this._wuAi.start,\n });\n if (networkLog.length > MAX_NETWORK_LOG) networkLog.shift();\n });\n }\n return origSend.apply(this, args);\n };\n}\n\nfunction _installConsoleInterceptor() {\n const levels = ['log', 'warn', 'error'];\n for (const level of levels) {\n const original = console[level];\n console[level] = (...args) => {\n consoleLog.push({\n level,\n message: args.map((a) => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' '),\n timestamp: Date.now(),\n });\n if (consoleLog.length > MAX_CONSOLE_LOG) consoleLog.shift();\n original.apply(console, args);\n };\n }\n}\n"],"names":["networkLog","MAX_NETWORK_LOG","consoleLog","_interceptorsInstalled","ensureInterceptors","originalFetch","window","fetch","async","args","start","Date","now","req","url","method","toUpperCase","response","apply","size","parseInt","headers","get","push","type","status","statusText","duration","timestamp","length","shift","err","error","message","origOpen","XMLHttpRequest","prototype","open","origSend","send","rest","this","_wuAi","String","call","addEventListener","getResponseHeader","_installNetworkInterceptor","levels","level","original","console","map","a","JSON","stringify","join","_installConsoleInterceptor","buildA11yTree","el","depth","maxDepth","indent","repeat","tag","tagName","toLowerCase","role","getAttribute","ariaLabel","text","childNodes","nodeType","textContent","trim","slice","line","id","className","cls","result","children","shadowRoot","i","inlineComputedStyles","source","clone","props","style","getComputedStyle","prop","val","getPropertyValue","setProperty","_","srcKids","cloneKids","max","Math","min","captureScreenshot","selector","quality","target","document","querySelector","documentElement","rect","getBoundingClientRect","w","ceil","width","innerWidth","h","height","innerHeight","cloneNode","xhtml","XMLSerializer","serializeToString","svgStr","svgBlob","Blob","URL","createObjectURL","dataUrl","Promise","resolve","img","Image","onload","canvas","createElement","getContext","drawImage","revokeObjectURL","toDataURL","onerror","src","base64","split","format","sizeKB","round","clickElement","candidates","querySelectorAll","searchText","candidate","includes","scrollIntoView","behavior","block","click","clicked","toJSON","typeIntoElement","clear","submit","undefined","focus","value","dispatchEvent","Event","bubbles","nativeSetter","Object","getOwnPropertyDescriptor","HTMLInputElement","set","HTMLTextAreaElement","newValue","form","closest","cancelable","KeyboardEvent","key","code","typed","currentValue","submitted","getFilteredNetwork","limit","filtered","filter","r","startsWith","requests","total","showing","getFilteredConsole","m","messages"],"mappings":"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"}