wu-framework 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +511 -977
- package/dist/wu-framework.cjs.js +3 -1
- package/dist/wu-framework.cjs.js.map +1 -0
- package/dist/wu-framework.dev.js +7533 -2761
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +3 -0
- package/dist/wu-framework.esm.js.map +1 -0
- package/dist/wu-framework.umd.js +3 -1
- package/dist/wu-framework.umd.js.map +1 -0
- package/integrations/astro/README.md +127 -0
- package/integrations/astro/WuApp.astro +63 -0
- package/integrations/astro/WuShell.astro +39 -0
- package/integrations/astro/index.js +68 -0
- package/integrations/astro/package.json +38 -0
- package/integrations/astro/types.d.ts +53 -0
- package/package.json +94 -74
- package/src/adapters/angular/ai.js +30 -0
- package/src/adapters/angular/index.d.ts +154 -0
- package/src/adapters/angular/index.js +932 -0
- package/src/adapters/angular.d.ts +3 -154
- package/src/adapters/angular.js +3 -813
- package/src/adapters/index.js +35 -24
- package/src/adapters/lit/ai.js +20 -0
- package/src/adapters/lit/index.d.ts +120 -0
- package/src/adapters/lit/index.js +721 -0
- package/src/adapters/lit.d.ts +3 -120
- package/src/adapters/lit.js +3 -726
- package/src/adapters/preact/ai.js +33 -0
- package/src/adapters/preact/index.d.ts +108 -0
- package/src/adapters/preact/index.js +661 -0
- package/src/adapters/preact.d.ts +3 -108
- package/src/adapters/preact.js +3 -665
- package/src/adapters/react/ai.js +135 -0
- package/src/adapters/react/index.d.ts +246 -0
- package/src/adapters/react/index.js +689 -0
- package/src/adapters/react.d.ts +3 -212
- package/src/adapters/react.js +3 -513
- package/src/adapters/shared.js +64 -0
- package/src/adapters/solid/ai.js +32 -0
- package/src/adapters/solid/index.d.ts +101 -0
- package/src/adapters/solid/index.js +586 -0
- package/src/adapters/solid.d.ts +3 -101
- package/src/adapters/solid.js +3 -591
- package/src/adapters/svelte/ai.js +31 -0
- package/src/adapters/svelte/index.d.ts +166 -0
- package/src/adapters/svelte/index.js +798 -0
- package/src/adapters/svelte.d.ts +3 -166
- package/src/adapters/svelte.js +3 -803
- package/src/adapters/vanilla/ai.js +30 -0
- package/src/adapters/vanilla/index.d.ts +179 -0
- package/src/adapters/vanilla/index.js +785 -0
- package/src/adapters/vanilla.d.ts +3 -179
- package/src/adapters/vanilla.js +3 -791
- package/src/adapters/vue/ai.js +52 -0
- package/src/adapters/vue/index.d.ts +299 -0
- package/src/adapters/vue/index.js +608 -0
- package/src/adapters/vue.d.ts +3 -299
- package/src/adapters/vue.js +3 -611
- package/src/ai/wu-ai-actions.js +261 -0
- package/src/ai/wu-ai-browser.js +663 -0
- package/src/ai/wu-ai-context.js +332 -0
- package/src/ai/wu-ai-conversation.js +554 -0
- package/src/ai/wu-ai-permissions.js +381 -0
- package/src/ai/wu-ai-provider.js +605 -0
- package/src/ai/wu-ai-schema.js +225 -0
- package/src/ai/wu-ai-triggers.js +396 -0
- package/src/ai/wu-ai.js +474 -0
- package/src/core/wu-app.js +50 -8
- package/src/core/wu-cache.js +1 -1
- package/src/core/wu-core.js +645 -677
- package/src/core/wu-html-parser.js +121 -211
- package/src/core/wu-iframe-sandbox.js +328 -0
- package/src/core/wu-mcp-bridge.js +647 -0
- package/src/core/wu-overrides.js +510 -0
- package/src/core/wu-prefetch.js +414 -0
- package/src/core/wu-proxy-sandbox.js +398 -75
- package/src/core/wu-sandbox.js +86 -268
- package/src/core/wu-script-executor.js +79 -182
- package/src/core/wu-snapshot-sandbox.js +149 -106
- package/src/core/wu-strategies.js +13 -0
- package/src/core/wu-style-bridge.js +0 -2
- package/src/index.js +139 -665
- package/dist/wu-framework.hex.js +0 -23
- package/dist/wu-framework.min.js +0 -1
- package/dist/wu-framework.obf.js +0 -1
- package/scripts/build-protected.js +0 -366
- package/scripts/build.js +0 -212
- package/scripts/rollup-plugin-hex.js +0 -143
- package/src/core/wu-registry.js +0 -60
- package/src/core/wu-sandbox-pool.js +0 -390
|
@@ -1,68 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Basado en video-code - Aislamiento real de variables globales
|
|
2
|
+
* WU-PROXY-SANDBOX: Hardened JavaScript Isolation
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* ES6 Proxy-based sandbox with side-effect tracking:
|
|
5
|
+
* - Timer hijacking (setTimeout, setInterval, requestAnimationFrame)
|
|
6
|
+
* - Event listener tracking (window + document addEventListener)
|
|
7
|
+
* - DOM scoping (querySelector/querySelectorAll → shadow root)
|
|
8
|
+
* - Storage scoping (localStorage/sessionStorage → prefixed keys)
|
|
9
|
+
*
|
|
10
|
+
* All tracked side effects are automatically cleaned up on deactivate().
|
|
7
11
|
*/
|
|
8
12
|
|
|
13
|
+
import { logger } from './wu-logger.js';
|
|
14
|
+
|
|
9
15
|
export class WuProxySandbox {
|
|
10
16
|
constructor(appName) {
|
|
11
17
|
this.appName = appName;
|
|
12
18
|
this.proxy = null;
|
|
13
|
-
this.fakeWindow = Object.create(null);
|
|
19
|
+
this.fakeWindow = Object.create(null);
|
|
14
20
|
this.active = false;
|
|
15
|
-
this.modifiedKeys = new Set();
|
|
21
|
+
this.modifiedKeys = new Set();
|
|
22
|
+
|
|
23
|
+
// --- Side-effect tracking ---
|
|
24
|
+
this._timers = new Set();
|
|
25
|
+
this._intervals = new Set();
|
|
26
|
+
this._rafs = new Set();
|
|
27
|
+
this._eventListeners = []; // [{target, event, handler, options}]
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
// --- DOM & Storage scoping ---
|
|
30
|
+
this._container = null;
|
|
31
|
+
this._shadowRoot = null;
|
|
32
|
+
this._scopedDocument = null;
|
|
33
|
+
this._scopedLocalStorage = null;
|
|
34
|
+
this._scopedSessionStorage = null;
|
|
35
|
+
|
|
36
|
+
// --- Window patching state ---
|
|
37
|
+
this._patched = false;
|
|
38
|
+
this._originals = null;
|
|
18
39
|
}
|
|
19
40
|
|
|
20
41
|
/**
|
|
21
|
-
*
|
|
42
|
+
* Set the DOM scope for this sandbox.
|
|
43
|
+
* Must be called before activate() for DOM scoping to work.
|
|
44
|
+
* @param {HTMLElement} container - App container element
|
|
45
|
+
* @param {ShadowRoot} shadowRoot - Shadow root containing the container
|
|
46
|
+
*/
|
|
47
|
+
setContainer(container, shadowRoot) {
|
|
48
|
+
this._container = container;
|
|
49
|
+
this._shadowRoot = shadowRoot;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Activate the sandbox. Creates the Proxy and starts tracking.
|
|
54
|
+
* @returns {Proxy} The sandboxed window proxy
|
|
22
55
|
*/
|
|
23
56
|
activate() {
|
|
24
|
-
if (this.active)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
57
|
+
if (this.active) return this.proxy;
|
|
58
|
+
|
|
59
|
+
const self = this;
|
|
28
60
|
|
|
29
61
|
this.proxy = new Proxy(window, {
|
|
30
|
-
get
|
|
31
|
-
//
|
|
32
|
-
if (prop in
|
|
33
|
-
return
|
|
62
|
+
get(target, prop) {
|
|
63
|
+
// 1. App's own isolated globals
|
|
64
|
+
if (prop in self.fakeWindow) {
|
|
65
|
+
return self.fakeWindow[prop];
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
68
|
+
// 2. Intercepted APIs
|
|
69
|
+
const intercepted = self._intercept(prop, target);
|
|
70
|
+
if (intercepted !== undefined) {
|
|
71
|
+
return intercepted;
|
|
72
|
+
}
|
|
38
73
|
|
|
39
|
-
//
|
|
40
|
-
|
|
74
|
+
// 3. Real window value with correct binding
|
|
75
|
+
const value = target[prop];
|
|
76
|
+
if (typeof value === 'function' && !self._isConstructor(value)) {
|
|
41
77
|
return value.bind(target);
|
|
42
78
|
}
|
|
43
|
-
|
|
44
79
|
return value;
|
|
45
80
|
},
|
|
46
81
|
|
|
47
|
-
set
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.modifiedKeys.add(prop);
|
|
51
|
-
|
|
52
|
-
console.log(`[WuProxySandbox] 📝 ${this.appName} set: ${String(prop)}`);
|
|
82
|
+
set(target, prop, value) {
|
|
83
|
+
self.fakeWindow[prop] = value;
|
|
84
|
+
self.modifiedKeys.add(prop);
|
|
53
85
|
return true;
|
|
54
86
|
},
|
|
55
87
|
|
|
56
|
-
has
|
|
57
|
-
|
|
58
|
-
return prop in this.fakeWindow || prop in target;
|
|
88
|
+
has(target, prop) {
|
|
89
|
+
return prop in self.fakeWindow || prop in target;
|
|
59
90
|
},
|
|
60
91
|
|
|
61
|
-
deleteProperty
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
delete
|
|
65
|
-
this.modifiedKeys.delete(prop);
|
|
92
|
+
deleteProperty(target, prop) {
|
|
93
|
+
if (prop in self.fakeWindow) {
|
|
94
|
+
delete self.fakeWindow[prop];
|
|
95
|
+
self.modifiedKeys.delete(prop);
|
|
66
96
|
return true;
|
|
67
97
|
}
|
|
68
98
|
return false;
|
|
@@ -70,84 +100,377 @@ export class WuProxySandbox {
|
|
|
70
100
|
});
|
|
71
101
|
|
|
72
102
|
this.active = true;
|
|
73
|
-
|
|
74
|
-
|
|
103
|
+
logger.wuDebug(`[ProxySandbox] Activated for ${this.appName}`);
|
|
75
104
|
return this.proxy;
|
|
76
105
|
}
|
|
77
106
|
|
|
78
107
|
/**
|
|
79
|
-
*
|
|
108
|
+
* Deactivate the sandbox. Cleans up ALL tracked side effects.
|
|
80
109
|
*/
|
|
81
110
|
deactivate() {
|
|
82
|
-
if (!this.active)
|
|
83
|
-
|
|
84
|
-
|
|
111
|
+
if (!this.active) return;
|
|
112
|
+
|
|
113
|
+
// Unpatch window if patched
|
|
114
|
+
this.unpatchWindow();
|
|
115
|
+
|
|
116
|
+
// --- Clean timers ---
|
|
117
|
+
for (const id of this._timers) {
|
|
118
|
+
try { clearTimeout(id); } catch {}
|
|
119
|
+
}
|
|
120
|
+
for (const id of this._intervals) {
|
|
121
|
+
try { clearInterval(id); } catch {}
|
|
85
122
|
}
|
|
123
|
+
for (const id of this._rafs) {
|
|
124
|
+
try { cancelAnimationFrame(id); } catch {}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const timerCount = this._timers.size + this._intervals.size + this._rafs.size;
|
|
128
|
+
this._timers.clear();
|
|
129
|
+
this._intervals.clear();
|
|
130
|
+
this._rafs.clear();
|
|
86
131
|
|
|
87
|
-
|
|
132
|
+
// --- Clean event listeners ---
|
|
133
|
+
const listenerCount = this._eventListeners.length;
|
|
134
|
+
for (const { target, event, handler, options } of this._eventListeners) {
|
|
135
|
+
try { target.removeEventListener(event, handler, options); } catch {}
|
|
136
|
+
}
|
|
137
|
+
this._eventListeners = [];
|
|
88
138
|
|
|
89
|
-
//
|
|
139
|
+
// --- Clean namespace ---
|
|
90
140
|
this.fakeWindow = Object.create(null);
|
|
91
141
|
this.modifiedKeys.clear();
|
|
142
|
+
this._scopedDocument = null;
|
|
143
|
+
this._scopedLocalStorage = null;
|
|
144
|
+
this._scopedSessionStorage = null;
|
|
92
145
|
this.proxy = null;
|
|
93
146
|
this.active = false;
|
|
94
147
|
|
|
95
|
-
|
|
148
|
+
if (timerCount > 0 || listenerCount > 0) {
|
|
149
|
+
logger.wuDebug(
|
|
150
|
+
`[ProxySandbox] ${this.appName} cleanup: ${timerCount} timers, ${listenerCount} listeners`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
logger.wuDebug(`[ProxySandbox] Deactivated for ${this.appName}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ================================================================
|
|
157
|
+
// WINDOW PATCHING - patches real window APIs during module loading
|
|
158
|
+
// ================================================================
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Patch real window APIs to track side effects from global code.
|
|
162
|
+
* Call before loading app module, unpatch after.
|
|
163
|
+
*
|
|
164
|
+
* IMPORTANT: Uses closure over originals so patched functions remain
|
|
165
|
+
* valid even after unpatchWindow() — prevents crashes when frameworks
|
|
166
|
+
* (React 19, etc.) cache references to patched setTimeout during import.
|
|
167
|
+
*/
|
|
168
|
+
patchWindow() {
|
|
169
|
+
if (this._patched) return;
|
|
170
|
+
|
|
171
|
+
const self = this;
|
|
172
|
+
|
|
173
|
+
// Capture originals in a local closure that survives unpatch
|
|
174
|
+
const originals = {
|
|
175
|
+
setTimeout: window.setTimeout,
|
|
176
|
+
clearTimeout: window.clearTimeout,
|
|
177
|
+
setInterval: window.setInterval,
|
|
178
|
+
clearInterval: window.clearInterval,
|
|
179
|
+
requestAnimationFrame: window.requestAnimationFrame,
|
|
180
|
+
cancelAnimationFrame: window.cancelAnimationFrame,
|
|
181
|
+
addEventListener: window.addEventListener,
|
|
182
|
+
removeEventListener: window.removeEventListener
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Store reference (used by unpatchWindow to restore)
|
|
186
|
+
this._originals = originals;
|
|
187
|
+
|
|
188
|
+
// Patch timers — closure captures `originals`, not `self._originals`
|
|
189
|
+
window.setTimeout = function(fn, delay, ...args) {
|
|
190
|
+
const id = originals.setTimeout.call(window, fn, delay, ...args);
|
|
191
|
+
if (self._patched) self._timers.add(id);
|
|
192
|
+
return id;
|
|
193
|
+
};
|
|
194
|
+
window.clearTimeout = function(id) {
|
|
195
|
+
self._timers.delete(id);
|
|
196
|
+
return originals.clearTimeout.call(window, id);
|
|
197
|
+
};
|
|
198
|
+
window.setInterval = function(fn, delay, ...args) {
|
|
199
|
+
const id = originals.setInterval.call(window, fn, delay, ...args);
|
|
200
|
+
if (self._patched) self._intervals.add(id);
|
|
201
|
+
return id;
|
|
202
|
+
};
|
|
203
|
+
window.clearInterval = function(id) {
|
|
204
|
+
self._intervals.delete(id);
|
|
205
|
+
return originals.clearInterval.call(window, id);
|
|
206
|
+
};
|
|
207
|
+
window.requestAnimationFrame = function(fn) {
|
|
208
|
+
const id = originals.requestAnimationFrame.call(window, fn);
|
|
209
|
+
if (self._patched) self._rafs.add(id);
|
|
210
|
+
return id;
|
|
211
|
+
};
|
|
212
|
+
window.cancelAnimationFrame = function(id) {
|
|
213
|
+
self._rafs.delete(id);
|
|
214
|
+
return originals.cancelAnimationFrame.call(window, id);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Patch event listeners
|
|
218
|
+
window.addEventListener = function(event, handler, options) {
|
|
219
|
+
if (self._patched) self._eventListeners.push({ target: window, event, handler, options });
|
|
220
|
+
return originals.addEventListener.call(window, event, handler, options);
|
|
221
|
+
};
|
|
222
|
+
window.removeEventListener = function(event, handler, options) {
|
|
223
|
+
self._eventListeners = self._eventListeners.filter(
|
|
224
|
+
l => !(l.target === window && l.event === event && l.handler === handler)
|
|
225
|
+
);
|
|
226
|
+
return originals.removeEventListener.call(window, event, handler, options);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
this._patched = true;
|
|
230
|
+
logger.wuDebug(`[ProxySandbox] Window patched for ${this.appName}`);
|
|
96
231
|
}
|
|
97
232
|
|
|
98
233
|
/**
|
|
99
|
-
*
|
|
234
|
+
* Restore original window APIs.
|
|
235
|
+
* Safe: patched functions still work via closure even after restore.
|
|
100
236
|
*/
|
|
101
|
-
|
|
237
|
+
unpatchWindow() {
|
|
238
|
+
if (!this._patched || !this._originals) return;
|
|
239
|
+
|
|
240
|
+
window.setTimeout = this._originals.setTimeout;
|
|
241
|
+
window.clearTimeout = this._originals.clearTimeout;
|
|
242
|
+
window.setInterval = this._originals.setInterval;
|
|
243
|
+
window.clearInterval = this._originals.clearInterval;
|
|
244
|
+
window.requestAnimationFrame = this._originals.requestAnimationFrame;
|
|
245
|
+
window.cancelAnimationFrame = this._originals.cancelAnimationFrame;
|
|
246
|
+
window.addEventListener = this._originals.addEventListener;
|
|
247
|
+
window.removeEventListener = this._originals.removeEventListener;
|
|
248
|
+
|
|
249
|
+
// NOTE: Do NOT null _originals — patched closures may still reference
|
|
250
|
+
// the sandbox instance (e.g. React scheduler caches setTimeout).
|
|
251
|
+
// The closure uses `originals` (local const), not `this._originals`.
|
|
252
|
+
this._patched = false;
|
|
253
|
+
logger.wuDebug(`[ProxySandbox] Window unpatched for ${this.appName}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ================================================================
|
|
257
|
+
// PROXY INTERCEPTS - for code running through the proxy
|
|
258
|
+
// ================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Intercept property access on the proxy.
|
|
262
|
+
* Returns wrapped API or undefined to fall through.
|
|
263
|
+
*/
|
|
264
|
+
_intercept(prop, target) {
|
|
265
|
+
const self = this;
|
|
266
|
+
|
|
267
|
+
switch (prop) {
|
|
268
|
+
// --- Timer hijacking ---
|
|
269
|
+
case 'setTimeout':
|
|
270
|
+
return function(fn, delay, ...args) {
|
|
271
|
+
const id = target.setTimeout(fn, delay, ...args);
|
|
272
|
+
self._timers.add(id);
|
|
273
|
+
return id;
|
|
274
|
+
};
|
|
275
|
+
case 'clearTimeout':
|
|
276
|
+
return function(id) {
|
|
277
|
+
self._timers.delete(id);
|
|
278
|
+
target.clearTimeout(id);
|
|
279
|
+
};
|
|
280
|
+
case 'setInterval':
|
|
281
|
+
return function(fn, delay, ...args) {
|
|
282
|
+
const id = target.setInterval(fn, delay, ...args);
|
|
283
|
+
self._intervals.add(id);
|
|
284
|
+
return id;
|
|
285
|
+
};
|
|
286
|
+
case 'clearInterval':
|
|
287
|
+
return function(id) {
|
|
288
|
+
self._intervals.delete(id);
|
|
289
|
+
target.clearInterval(id);
|
|
290
|
+
};
|
|
291
|
+
case 'requestAnimationFrame':
|
|
292
|
+
return function(fn) {
|
|
293
|
+
const id = target.requestAnimationFrame(fn);
|
|
294
|
+
self._rafs.add(id);
|
|
295
|
+
return id;
|
|
296
|
+
};
|
|
297
|
+
case 'cancelAnimationFrame':
|
|
298
|
+
return function(id) {
|
|
299
|
+
self._rafs.delete(id);
|
|
300
|
+
target.cancelAnimationFrame(id);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// --- Event listener tracking ---
|
|
304
|
+
case 'addEventListener':
|
|
305
|
+
return function(event, handler, options) {
|
|
306
|
+
self._eventListeners.push({ target, event, handler, options });
|
|
307
|
+
target.addEventListener(event, handler, options);
|
|
308
|
+
};
|
|
309
|
+
case 'removeEventListener':
|
|
310
|
+
return function(event, handler, options) {
|
|
311
|
+
self._eventListeners = self._eventListeners.filter(
|
|
312
|
+
l => !(l.target === target && l.event === event && l.handler === handler)
|
|
313
|
+
);
|
|
314
|
+
target.removeEventListener(event, handler, options);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// --- DOM scoping ---
|
|
318
|
+
case 'document':
|
|
319
|
+
return this._getScopedDocument();
|
|
320
|
+
|
|
321
|
+
// --- Storage scoping ---
|
|
322
|
+
case 'localStorage':
|
|
323
|
+
return this._getScopedStorage('local');
|
|
324
|
+
case 'sessionStorage':
|
|
325
|
+
return this._getScopedStorage('session');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ================================================================
|
|
332
|
+
// DOM SCOPING - querySelector searches inside shadow root
|
|
333
|
+
// ================================================================
|
|
334
|
+
|
|
335
|
+
_getScopedDocument() {
|
|
336
|
+
if (this._scopedDocument) return this._scopedDocument;
|
|
337
|
+
|
|
338
|
+
const root = this._shadowRoot || this._container;
|
|
339
|
+
if (!root) return document; // No container set, pass through
|
|
340
|
+
|
|
341
|
+
const self = this;
|
|
342
|
+
|
|
343
|
+
this._scopedDocument = new Proxy(document, {
|
|
344
|
+
get(target, prop) {
|
|
345
|
+
switch (prop) {
|
|
346
|
+
case 'querySelector':
|
|
347
|
+
return (selector) => root.querySelector(selector);
|
|
348
|
+
case 'querySelectorAll':
|
|
349
|
+
return (selector) => root.querySelectorAll(selector);
|
|
350
|
+
case 'getElementById':
|
|
351
|
+
return (id) => root.querySelector(`#${CSS.escape(id)}`);
|
|
352
|
+
case 'getElementsByClassName':
|
|
353
|
+
return (className) => root.querySelectorAll(`.${CSS.escape(className)}`);
|
|
354
|
+
case 'getElementsByTagName':
|
|
355
|
+
return (tag) => root.querySelectorAll(tag);
|
|
356
|
+
|
|
357
|
+
// Track document event listeners too
|
|
358
|
+
case 'addEventListener':
|
|
359
|
+
return function(event, handler, options) {
|
|
360
|
+
self._eventListeners.push({ target, event, handler, options });
|
|
361
|
+
target.addEventListener(event, handler, options);
|
|
362
|
+
};
|
|
363
|
+
case 'removeEventListener':
|
|
364
|
+
return function(event, handler, options) {
|
|
365
|
+
self._eventListeners = self._eventListeners.filter(
|
|
366
|
+
l => !(l.target === target && l.event === event && l.handler === handler)
|
|
367
|
+
);
|
|
368
|
+
target.removeEventListener(event, handler, options);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// createElement, createTextNode, etc. - pass through
|
|
372
|
+
default: {
|
|
373
|
+
const value = target[prop];
|
|
374
|
+
if (typeof value === 'function') {
|
|
375
|
+
return value.bind(target);
|
|
376
|
+
}
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return this._scopedDocument;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ================================================================
|
|
387
|
+
// STORAGE SCOPING - localStorage/sessionStorage with app prefix
|
|
388
|
+
// ================================================================
|
|
389
|
+
|
|
390
|
+
_getScopedStorage(type) {
|
|
391
|
+
const cacheKey = type === 'local' ? '_scopedLocalStorage' : '_scopedSessionStorage';
|
|
392
|
+
if (this[cacheKey]) return this[cacheKey];
|
|
393
|
+
|
|
394
|
+
const realStorage = type === 'local' ? window.localStorage : window.sessionStorage;
|
|
395
|
+
if (!realStorage) return realStorage;
|
|
396
|
+
|
|
397
|
+
const prefix = `wu_${this.appName}_`;
|
|
398
|
+
|
|
399
|
+
this[cacheKey] = {
|
|
400
|
+
getItem(key) {
|
|
401
|
+
return realStorage.getItem(prefix + key);
|
|
402
|
+
},
|
|
403
|
+
setItem(key, value) {
|
|
404
|
+
realStorage.setItem(prefix + key, String(value));
|
|
405
|
+
},
|
|
406
|
+
removeItem(key) {
|
|
407
|
+
realStorage.removeItem(prefix + key);
|
|
408
|
+
},
|
|
409
|
+
clear() {
|
|
410
|
+
// Only clear this app's keys
|
|
411
|
+
const toRemove = [];
|
|
412
|
+
for (let i = 0; i < realStorage.length; i++) {
|
|
413
|
+
const k = realStorage.key(i);
|
|
414
|
+
if (k && k.startsWith(prefix)) toRemove.push(k);
|
|
415
|
+
}
|
|
416
|
+
toRemove.forEach(k => realStorage.removeItem(k));
|
|
417
|
+
},
|
|
418
|
+
key(index) {
|
|
419
|
+
let count = 0;
|
|
420
|
+
for (let i = 0; i < realStorage.length; i++) {
|
|
421
|
+
const k = realStorage.key(i);
|
|
422
|
+
if (k && k.startsWith(prefix)) {
|
|
423
|
+
if (count === index) return k.slice(prefix.length);
|
|
424
|
+
count++;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
},
|
|
429
|
+
get length() {
|
|
430
|
+
let count = 0;
|
|
431
|
+
for (let i = 0; i < realStorage.length; i++) {
|
|
432
|
+
if (realStorage.key(i)?.startsWith(prefix)) count++;
|
|
433
|
+
}
|
|
434
|
+
return count;
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
return this[cacheKey];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ================================================================
|
|
442
|
+
// UTILITIES
|
|
443
|
+
// ================================================================
|
|
444
|
+
|
|
445
|
+
_isConstructor(fn) {
|
|
102
446
|
try {
|
|
103
|
-
// Detectar constructores/classes por su prototipo
|
|
104
447
|
return fn.prototype && fn.prototype.constructor === fn;
|
|
105
448
|
} catch {
|
|
106
449
|
return false;
|
|
107
450
|
}
|
|
108
451
|
}
|
|
109
452
|
|
|
110
|
-
/**
|
|
111
|
-
* Obtener el proxy activo (o null si no está activo)
|
|
112
|
-
*/
|
|
113
453
|
getProxy() {
|
|
114
454
|
return this.active ? this.proxy : null;
|
|
115
455
|
}
|
|
116
456
|
|
|
117
|
-
/**
|
|
118
|
-
* Verificar si el sandbox está activo
|
|
119
|
-
*/
|
|
120
457
|
isActive() {
|
|
121
458
|
return this.active;
|
|
122
459
|
}
|
|
123
460
|
|
|
124
|
-
/**
|
|
125
|
-
* Obtener estadísticas del sandbox
|
|
126
|
-
*/
|
|
127
461
|
getStats() {
|
|
128
462
|
return {
|
|
129
463
|
appName: this.appName,
|
|
130
464
|
active: this.active,
|
|
465
|
+
patched: this._patched,
|
|
131
466
|
modifiedKeys: Array.from(this.modifiedKeys),
|
|
132
|
-
isolatedPropsCount: Object.keys(this.fakeWindow).length
|
|
467
|
+
isolatedPropsCount: Object.keys(this.fakeWindow).length,
|
|
468
|
+
trackedTimers: this._timers.size,
|
|
469
|
+
trackedIntervals: this._intervals.size,
|
|
470
|
+
trackedRAFs: this._rafs.size,
|
|
471
|
+
trackedEventListeners: this._eventListeners.length,
|
|
472
|
+
hasContainer: !!this._container,
|
|
473
|
+
hasShadowRoot: !!this._shadowRoot
|
|
133
474
|
};
|
|
134
475
|
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Limpiar propiedades específicas del namespace
|
|
138
|
-
*/
|
|
139
|
-
clearProperty(prop) {
|
|
140
|
-
if (prop in this.fakeWindow) {
|
|
141
|
-
delete this.fakeWindow[prop];
|
|
142
|
-
this.modifiedKeys.delete(prop);
|
|
143
|
-
console.log(`[WuProxySandbox] 🗑️ Cleared property: ${String(prop)}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Obtener todas las propiedades aisladas
|
|
149
|
-
*/
|
|
150
|
-
getIsolatedProperties() {
|
|
151
|
-
return { ...this.fakeWindow };
|
|
152
|
-
}
|
|
153
476
|
}
|