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.
Files changed (90) hide show
  1. package/README.md +511 -977
  2. package/dist/wu-framework.cjs.js +3 -1
  3. package/dist/wu-framework.cjs.js.map +1 -0
  4. package/dist/wu-framework.dev.js +7533 -2761
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +3 -0
  7. package/dist/wu-framework.esm.js.map +1 -0
  8. package/dist/wu-framework.umd.js +3 -1
  9. package/dist/wu-framework.umd.js.map +1 -0
  10. package/integrations/astro/README.md +127 -0
  11. package/integrations/astro/WuApp.astro +63 -0
  12. package/integrations/astro/WuShell.astro +39 -0
  13. package/integrations/astro/index.js +68 -0
  14. package/integrations/astro/package.json +38 -0
  15. package/integrations/astro/types.d.ts +53 -0
  16. package/package.json +94 -74
  17. package/src/adapters/angular/ai.js +30 -0
  18. package/src/adapters/angular/index.d.ts +154 -0
  19. package/src/adapters/angular/index.js +932 -0
  20. package/src/adapters/angular.d.ts +3 -154
  21. package/src/adapters/angular.js +3 -813
  22. package/src/adapters/index.js +35 -24
  23. package/src/adapters/lit/ai.js +20 -0
  24. package/src/adapters/lit/index.d.ts +120 -0
  25. package/src/adapters/lit/index.js +721 -0
  26. package/src/adapters/lit.d.ts +3 -120
  27. package/src/adapters/lit.js +3 -726
  28. package/src/adapters/preact/ai.js +33 -0
  29. package/src/adapters/preact/index.d.ts +108 -0
  30. package/src/adapters/preact/index.js +661 -0
  31. package/src/adapters/preact.d.ts +3 -108
  32. package/src/adapters/preact.js +3 -665
  33. package/src/adapters/react/ai.js +135 -0
  34. package/src/adapters/react/index.d.ts +246 -0
  35. package/src/adapters/react/index.js +689 -0
  36. package/src/adapters/react.d.ts +3 -212
  37. package/src/adapters/react.js +3 -513
  38. package/src/adapters/shared.js +64 -0
  39. package/src/adapters/solid/ai.js +32 -0
  40. package/src/adapters/solid/index.d.ts +101 -0
  41. package/src/adapters/solid/index.js +586 -0
  42. package/src/adapters/solid.d.ts +3 -101
  43. package/src/adapters/solid.js +3 -591
  44. package/src/adapters/svelte/ai.js +31 -0
  45. package/src/adapters/svelte/index.d.ts +166 -0
  46. package/src/adapters/svelte/index.js +798 -0
  47. package/src/adapters/svelte.d.ts +3 -166
  48. package/src/adapters/svelte.js +3 -803
  49. package/src/adapters/vanilla/ai.js +30 -0
  50. package/src/adapters/vanilla/index.d.ts +179 -0
  51. package/src/adapters/vanilla/index.js +785 -0
  52. package/src/adapters/vanilla.d.ts +3 -179
  53. package/src/adapters/vanilla.js +3 -791
  54. package/src/adapters/vue/ai.js +52 -0
  55. package/src/adapters/vue/index.d.ts +299 -0
  56. package/src/adapters/vue/index.js +608 -0
  57. package/src/adapters/vue.d.ts +3 -299
  58. package/src/adapters/vue.js +3 -611
  59. package/src/ai/wu-ai-actions.js +261 -0
  60. package/src/ai/wu-ai-browser.js +663 -0
  61. package/src/ai/wu-ai-context.js +332 -0
  62. package/src/ai/wu-ai-conversation.js +554 -0
  63. package/src/ai/wu-ai-permissions.js +381 -0
  64. package/src/ai/wu-ai-provider.js +605 -0
  65. package/src/ai/wu-ai-schema.js +225 -0
  66. package/src/ai/wu-ai-triggers.js +396 -0
  67. package/src/ai/wu-ai.js +474 -0
  68. package/src/core/wu-app.js +50 -8
  69. package/src/core/wu-cache.js +1 -1
  70. package/src/core/wu-core.js +645 -677
  71. package/src/core/wu-html-parser.js +121 -211
  72. package/src/core/wu-iframe-sandbox.js +328 -0
  73. package/src/core/wu-mcp-bridge.js +647 -0
  74. package/src/core/wu-overrides.js +510 -0
  75. package/src/core/wu-prefetch.js +414 -0
  76. package/src/core/wu-proxy-sandbox.js +398 -75
  77. package/src/core/wu-sandbox.js +86 -268
  78. package/src/core/wu-script-executor.js +79 -182
  79. package/src/core/wu-snapshot-sandbox.js +149 -106
  80. package/src/core/wu-strategies.js +13 -0
  81. package/src/core/wu-style-bridge.js +0 -2
  82. package/src/index.js +139 -665
  83. package/dist/wu-framework.hex.js +0 -23
  84. package/dist/wu-framework.min.js +0 -1
  85. package/dist/wu-framework.obf.js +0 -1
  86. package/scripts/build-protected.js +0 -366
  87. package/scripts/build.js +0 -212
  88. package/scripts/rollup-plugin-hex.js +0 -143
  89. package/src/core/wu-registry.js +0 -60
  90. package/src/core/wu-sandbox-pool.js +0 -390
@@ -1,68 +1,98 @@
1
1
  /**
2
- * 🛡️ WU-PROXY-SANDBOX: JavaScript Isolation con Proxy
3
- * Basado en video-code - Aislamiento real de variables globales
2
+ * WU-PROXY-SANDBOX: Hardened JavaScript Isolation
4
3
  *
5
- * Este sandbox usa ES6 Proxy para interceptar accesos a window
6
- * y aislar completamente el JavaScript entre micro-apps
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); // Namespace aislado
19
+ this.fakeWindow = Object.create(null);
14
20
  this.active = false;
15
- this.modifiedKeys = new Set(); // Track de propiedades modificadas
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
- console.log(`[WuProxySandbox] 🛡️ Creating proxy sandbox for: ${appName}`);
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
- * Activar sandbox - Crea el Proxy que aísla window
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
- console.warn(`[WuProxySandbox] Sandbox already active for ${this.appName}`);
26
- return this.proxy;
27
- }
57
+ if (this.active) return this.proxy;
58
+
59
+ const self = this;
28
60
 
29
61
  this.proxy = new Proxy(window, {
30
- get: (target, prop) => {
31
- // 🔍 Primero buscar en el namespace aislado
32
- if (prop in this.fakeWindow) {
33
- return this.fakeWindow[prop];
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
- // 🎯 Propiedades del window real
37
- const value = target[prop];
68
+ // 2. Intercepted APIs
69
+ const intercepted = self._intercept(prop, target);
70
+ if (intercepted !== undefined) {
71
+ return intercepted;
72
+ }
38
73
 
39
- // ⚠️ CRÍTICO: Bind de funciones para mantener contexto
40
- if (typeof value === 'function' && !this.isConstructor(value)) {
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: (target, prop, value) => {
48
- // 🛡️ AISLAMIENTO: Todo set va al namespace aislado
49
- this.fakeWindow[prop] = value;
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: (target, prop) => {
57
- // Verificar tanto en fakeWindow como en window real
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: (target, prop) => {
62
- // Solo permitir delete en el namespace aislado
63
- if (prop in this.fakeWindow) {
64
- delete this.fakeWindow[prop];
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
- console.log(`[WuProxySandbox] Proxy sandbox activated for ${this.appName}`);
74
-
103
+ logger.wuDebug(`[ProxySandbox] Activated for ${this.appName}`);
75
104
  return this.proxy;
76
105
  }
77
106
 
78
107
  /**
79
- * Desactivar sandbox - Limpia el namespace aislado
108
+ * Deactivate the sandbox. Cleans up ALL tracked side effects.
80
109
  */
81
110
  deactivate() {
82
- if (!this.active) {
83
- console.warn(`[WuProxySandbox] Sandbox not active for ${this.appName}`);
84
- return;
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
- console.log(`[WuProxySandbox] 🧹 Deactivating sandbox for ${this.appName}`);
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
- // 🧹 Limpiar todas las propiedades modificadas
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
- console.log(`[WuProxySandbox] Sandbox deactivated for ${this.appName}`);
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
- * Verificar si un valor es un constructor (class)
234
+ * Restore original window APIs.
235
+ * Safe: patched functions still work via closure even after restore.
100
236
  */
101
- isConstructor(fn) {
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
  }