wu-framework 1.1.15 → 1.1.17

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 (88) hide show
  1. package/README.md +52 -20
  2. package/dist/wu-framework.cjs.js +1 -1
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15511 -15146
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +1 -1
  7. package/dist/wu-framework.esm.js.map +1 -1
  8. package/dist/wu-framework.umd.js +1 -1
  9. package/dist/wu-framework.umd.js.map +1 -1
  10. package/package.json +166 -161
  11. package/src/adapters/angular/ai.js +30 -30
  12. package/src/adapters/angular/index.d.ts +154 -154
  13. package/src/adapters/angular/index.js +932 -932
  14. package/src/adapters/angular.d.ts +3 -3
  15. package/src/adapters/angular.js +3 -3
  16. package/src/adapters/index.js +168 -168
  17. package/src/adapters/lit/ai.js +20 -20
  18. package/src/adapters/lit/index.d.ts +120 -120
  19. package/src/adapters/lit/index.js +721 -721
  20. package/src/adapters/lit.d.ts +3 -3
  21. package/src/adapters/lit.js +3 -3
  22. package/src/adapters/preact/ai.js +33 -33
  23. package/src/adapters/preact/index.d.ts +108 -108
  24. package/src/adapters/preact/index.js +661 -661
  25. package/src/adapters/preact.d.ts +3 -3
  26. package/src/adapters/preact.js +3 -3
  27. package/src/adapters/react/index.js +48 -54
  28. package/src/adapters/react.d.ts +3 -3
  29. package/src/adapters/react.js +3 -3
  30. package/src/adapters/shared.js +64 -64
  31. package/src/adapters/solid/ai.js +32 -32
  32. package/src/adapters/solid/index.d.ts +101 -101
  33. package/src/adapters/solid/index.js +586 -586
  34. package/src/adapters/solid.d.ts +3 -3
  35. package/src/adapters/solid.js +3 -3
  36. package/src/adapters/svelte/ai.js +31 -31
  37. package/src/adapters/svelte/index.d.ts +166 -166
  38. package/src/adapters/svelte/index.js +798 -798
  39. package/src/adapters/svelte.d.ts +3 -3
  40. package/src/adapters/svelte.js +3 -3
  41. package/src/adapters/vanilla/ai.js +30 -30
  42. package/src/adapters/vanilla/index.d.ts +179 -179
  43. package/src/adapters/vanilla/index.js +785 -785
  44. package/src/adapters/vanilla.d.ts +3 -3
  45. package/src/adapters/vanilla.js +3 -3
  46. package/src/adapters/vue/ai.js +52 -52
  47. package/src/adapters/vue/index.d.ts +299 -299
  48. package/src/adapters/vue/index.js +610 -610
  49. package/src/adapters/vue.d.ts +3 -3
  50. package/src/adapters/vue.js +3 -3
  51. package/src/ai/wu-ai-actions.js +261 -261
  52. package/src/ai/wu-ai-agent.js +546 -546
  53. package/src/ai/wu-ai-browser-primitives.js +354 -354
  54. package/src/ai/wu-ai-browser.js +380 -380
  55. package/src/ai/wu-ai-context.js +332 -332
  56. package/src/ai/wu-ai-conversation.js +613 -613
  57. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  58. package/src/ai/wu-ai-permissions.js +381 -381
  59. package/src/ai/wu-ai-provider.js +700 -700
  60. package/src/ai/wu-ai-schema.js +225 -225
  61. package/src/ai/wu-ai-triggers.js +396 -396
  62. package/src/ai/wu-ai.js +804 -804
  63. package/src/core/wu-app.js +236 -236
  64. package/src/core/wu-cache.js +498 -477
  65. package/src/core/wu-core.js +1412 -1398
  66. package/src/core/wu-error-boundary.js +396 -382
  67. package/src/core/wu-event-bus.js +390 -348
  68. package/src/core/wu-hooks.js +350 -350
  69. package/src/core/wu-html-parser.js +199 -190
  70. package/src/core/wu-iframe-sandbox.js +328 -328
  71. package/src/core/wu-loader.js +385 -273
  72. package/src/core/wu-logger.js +142 -134
  73. package/src/core/wu-manifest.js +532 -509
  74. package/src/core/wu-mcp-bridge.js +432 -432
  75. package/src/core/wu-overrides.js +510 -510
  76. package/src/core/wu-performance.js +228 -228
  77. package/src/core/wu-plugin.js +401 -348
  78. package/src/core/wu-prefetch.js +414 -414
  79. package/src/core/wu-proxy-sandbox.js +477 -476
  80. package/src/core/wu-sandbox.js +779 -779
  81. package/src/core/wu-script-executor.js +161 -113
  82. package/src/core/wu-snapshot-sandbox.js +227 -227
  83. package/src/core/wu-store.js +13 -3
  84. package/src/core/wu-strategies.js +256 -256
  85. package/src/core/wu-style-bridge.js +477 -477
  86. package/src/index.d.ts +317 -0
  87. package/src/index.js +234 -224
  88. package/src/utils/dependency-resolver.js +327 -327
@@ -1,779 +1,779 @@
1
- /**
2
- * 🛡️ WU-SANDBOX: ADVANCED ISOLATION SYSTEM
3
- * Shadow DOM + Proxy Sandbox + Script Execution + HTML Parsing
4
- * Combina lo mejor de video-code con Shadow DOM nativo
5
- */
6
-
7
- import { WuStyleBridge } from './wu-style-bridge.js';
8
- import { WuProxySandbox } from './wu-proxy-sandbox.js';
9
- import { WuSnapshotSandbox } from './wu-snapshot-sandbox.js';
10
- import { logger } from './wu-logger.js';
11
-
12
- export class WuSandbox {
13
- constructor() {
14
- // Registros existentes
15
- this.sandboxes = new Map();
16
- this.styleBridge = new WuStyleBridge();
17
-
18
- // 🚀 NUEVOS SISTEMAS INTEGRADOS
19
- this.jsSandboxes = new Map(); // ProxySandbox o SnapshotSandbox por app
20
- this.sandboxStrategy = this.detectSandboxStrategy();
21
-
22
- logger.wuDebug(`Advanced isolation system initialized (strategy: ${this.sandboxStrategy})`);
23
- }
24
-
25
- /**
26
- * Detectar estrategia de sandbox óptima
27
- * @returns {'proxy' | 'snapshot'} Estrategia a usar
28
- */
29
- detectSandboxStrategy() {
30
- // Verificar si Proxy está disponible
31
- if (typeof Proxy !== 'undefined') {
32
- try {
33
- // Test básico de Proxy
34
- const testProxy = new Proxy({}, {
35
- get(target, prop) { return target[prop]; }
36
- });
37
- testProxy.test = 'value';
38
-
39
- logger.wuDebug('Proxy available - using ProxySandbox strategy');
40
- return 'proxy';
41
- } catch (error) {
42
- logger.wuWarn('Proxy not working - falling back to SnapshotSandbox');
43
- return 'snapshot';
44
- }
45
- }
46
-
47
- logger.wuWarn('Proxy not available - using SnapshotSandbox strategy');
48
- return 'snapshot';
49
- }
50
-
51
- /**
52
- * 🔧 SMART SANDBOX: Advanced Shadow DOM creation with error recovery
53
- * @param {string} appName - Nombre de la aplicación
54
- * @param {HTMLElement} hostContainer - Contenedor host
55
- * @param {Object} options - Opciones adicionales (styleMode, manifest, etc.)
56
- * @returns {Object} Sandbox con shadow root y container
57
- */
58
- create(appName, hostContainer, options = {}) {
59
- logger.wuDebug(`Creating sandbox for: ${appName}`);
60
-
61
- try {
62
- // 🔧 SHADOW DOM VERIFICATION
63
- if (!hostContainer.attachShadow) {
64
- throw new Error('Shadow DOM not supported in this browser');
65
- }
66
-
67
- // 🛠️ SMART CLEANUP: Handle existing shadow roots
68
- let shadowRoot;
69
- if (hostContainer.shadowRoot) {
70
- logger.wuDebug(`Existing shadow root detected for ${appName}, performing cleanup...`);
71
-
72
- // Clear existing shadow root content
73
- hostContainer.shadowRoot.innerHTML = '';
74
- shadowRoot = hostContainer.shadowRoot;
75
-
76
- logger.wuDebug(`Existing shadow root cleaned and reused for ${appName}`);
77
- } else {
78
- // Create new Shadow DOM
79
- shadowRoot = hostContainer.attachShadow({
80
- mode: 'open',
81
- delegatesFocus: true
82
- });
83
-
84
- logger.wuDebug(`New shadow root created for ${appName}`);
85
- }
86
-
87
- // 🎯 Create app container with advanced features
88
- const appContainer = document.createElement('div');
89
- appContainer.id = `wu-app-${appName}`;
90
- appContainer.className = 'wu-app-root';
91
- appContainer.setAttribute('data-wu-enhanced', 'true');
92
- appContainer.setAttribute('data-wu-timestamp', Date.now().toString());
93
-
94
- // 🎨 Enhanced base styles with advanced properties
95
- const baseStyles = document.createElement('style');
96
- baseStyles.textContent = this.generateSandboxStyles(appName);
97
-
98
- // 🌟 Assemble enhanced Shadow DOM
99
- shadowRoot.appendChild(baseStyles);
100
- shadowRoot.appendChild(appContainer);
101
-
102
- // Create JS Sandbox with container reference for DOM/storage scoping
103
- const jsSandbox = this.createAdvancedJSSandbox(appName);
104
- if (jsSandbox.setContainer) {
105
- jsSandbox.setContainer(appContainer, shadowRoot);
106
- }
107
- const jsProxy = jsSandbox.activate();
108
-
109
- // Verificar styleMode del manifest antes de inyectar estilos
110
- const styleMode = options.styleMode || options.manifest?.styleMode;
111
-
112
- const sandbox = {
113
- appName,
114
- shadowRoot,
115
- container: appContainer,
116
- hostContainer,
117
- jsSandbox, // NUEVO: ProxySandbox o SnapshotSandbox
118
- jsProxy, // NUEVO: Proxy para ejecutar scripts
119
- styles: baseStyles,
120
- styleMode, // Guardar styleMode para uso futuro
121
- manifest: options.manifest, // Guardar manifest completo
122
- created: Date.now(),
123
- sandbox_state: 'stable',
124
- recovery_count: 0
125
- };
126
-
127
- // 🎨 INJECT STYLES: Comportamiento según styleMode
128
- // - "shared": Inyecta todos los estilos del documento padre
129
- // - "isolated": NO inyecta estilos externos (encapsulamiento nativo Shadow DOM)
130
- // - "fully-isolated": Inyecta SOLO estilos propios de la app
131
-
132
- if (styleMode === 'isolated') {
133
- // 🔒 MODO ISOLATED: Encapsulamiento nativo del Shadow DOM
134
- // No se inyectan estilos externos - la app debe manejar sus propios estilos
135
- logger.wuDebug(`Style mode "isolated" for ${appName}, using native Shadow DOM encapsulation`);
136
- sandbox.stylesReady = Promise.resolve(0);
137
- // No configurar observer de estilos - la app es responsable de sus propios estilos
138
-
139
- } else if (styleMode === 'fully-isolated') {
140
- logger.wuDebug(`Style mode "fully-isolated" detected for ${appName}, using enhanced style injection`);
141
- // Registrar esta app como fully-isolated en el style bridge para filtrar sus estilos
142
- const appUrl = options.appUrl || (options.manifest?.name ? `/${options.manifest.name}/` : `/${appName}/`);
143
- this.styleBridge.registerFullyIsolatedApp(appName, appUrl);
144
-
145
- // Guardar appUrl en sandbox para uso en reinjectStyles
146
- sandbox.appUrl = appUrl;
147
-
148
- // Para fully-isolated, inyectar SOLO los estilos propios de la app en su Shadow DOM
149
- // Guardamos referencia a this para usar en el observer
150
- const self = this;
151
-
152
- sandbox.stylesReady = new Promise((resolve) => {
153
- let resolved = false;
154
-
155
- const tryInject = async () => {
156
- const count = await self.injectOwnStylesToShadow(shadowRoot, appName, appUrl);
157
-
158
- if (count > 0) {
159
- logger.wuDebug(`Injected ${count} own styles for ${appName} (fully-isolated)`);
160
- if (!resolved) {
161
- resolved = true;
162
- resolve(count);
163
- }
164
- }
165
-
166
- return count;
167
- };
168
-
169
- // Usar MutationObserver PERSISTENTE para detectar cuando se inyectan estilos del app
170
- logger.wuDebug(`Setting up style observer for ${appName} (fully-isolated)`);
171
- const observer = new MutationObserver((mutations) => {
172
- let newStyleCount = 0;
173
- for (const m of mutations) {
174
- if (m.type === 'childList') {
175
- for (const n of m.addedNodes) {
176
- if (n.nodeName === 'STYLE' || n.nodeName === 'LINK') {
177
- newStyleCount++;
178
- const viteId = n.getAttribute ? n.getAttribute('data-vite-dev-id') : null;
179
- if (viteId && viteId.toLowerCase().includes(appName.toLowerCase())) {
180
- logger.wuDebug(`New ${appName} style detected: ${viteId.split('/').pop()}`);
181
- }
182
- }
183
- }
184
- }
185
- }
186
- if (newStyleCount > 0) {
187
- logger.wuDebug(`${newStyleCount} new styles detected in head, checking for ${appName}...`);
188
- tryInject();
189
- }
190
- });
191
-
192
- // Observar cambios en el head DE FORMA PERSISTENTE
193
- observer.observe(document.head, {
194
- childList: true,
195
- subtree: true
196
- });
197
-
198
- // Guardar referencia al observer para poder desconectarlo cuando se desmonte la app
199
- sandbox.styleObserver = observer;
200
-
201
- // Intento inicial con pequeño delay para que Vite procese los imports
202
- setTimeout(async () => {
203
- const count = await tryInject();
204
- // Si después de 3 segundos no hay estilos, usar fallback
205
- if (!resolved) {
206
- setTimeout(() => {
207
- if (!resolved) {
208
- logger.wuWarn(`No own styles found for ${appName} after timeout, using FALLBACK`);
209
- const fallbackCount = self.injectAllStylesToShadow(shadowRoot, appName);
210
- logger.wuDebug(`FALLBACK: Injected ${fallbackCount} styles for ${appName}`);
211
- resolved = true;
212
- resolve(fallbackCount);
213
- }
214
- }, 3000);
215
- }
216
- }, 50);
217
- });
218
- } else {
219
- // 🌐 MODO SHARED (default): Inyectar todos los estilos compartidos del documento
220
- logger.wuDebug(`Style mode "shared" for ${appName}, injecting all shared styles...`);
221
- sandbox.stylesReady = this.styleBridge.injectStylesIntoShadow(shadowRoot, appName, styleMode).then(count => {
222
- logger.wuDebug(`Shared ${count} styles with ${appName}`);
223
-
224
- // 🔄 Observar cambios dinámicos de estilos (HMR de Vite)
225
- this.styleBridge.observeStyleChanges(() => {
226
- logger.wuDebug(`Reinjecting styles for ${appName} due to changes`);
227
- this.styleBridge.injectStylesIntoShadow(shadowRoot, appName, styleMode).catch(err => {
228
- logger.wuWarn(`Failed to reinject styles: ${err}`);
229
- });
230
- });
231
-
232
- return count;
233
- }).catch(error => {
234
- logger.wuWarn(`Failed to inject styles: ${error}`);
235
- return 0;
236
- });
237
- }
238
-
239
- // 📊 Register in sandbox registry
240
- this.sandboxes.set(appName, sandbox);
241
-
242
- logger.wuDebug(`Enhanced sandbox created for ${appName}`);
243
- return sandbox;
244
-
245
- } catch (error) {
246
- logger.wuError(`Failed to create sandbox for ${appName}: ${error}`);
247
-
248
- // 🔧 FALLBACK RECOVERY: Create fallback sandbox when Shadow DOM fails
249
- if (error.message.includes('Shadow root cannot be created')) {
250
- return this.createFallbackSandbox(appName, hostContainer);
251
- }
252
-
253
- throw error;
254
- }
255
- }
256
-
257
- /**
258
- * 🔧 FALLBACK SANDBOX: Create fallback container when Shadow DOM is not available
259
- */
260
- createFallbackSandbox(appName, hostContainer) {
261
- logger.wuDebug(`Creating fallback sandbox for ${appName}...`);
262
-
263
- try {
264
- // 🛠️ Complete shadow DOM reset
265
- if (hostContainer.shadowRoot) {
266
- hostContainer.shadowRoot.innerHTML = '';
267
- }
268
-
269
- // 🌟 Create minimal container without shadow DOM if necessary
270
- const fallbackContainer = document.createElement('div');
271
- fallbackContainer.id = `wu-app-${appName}`;
272
- fallbackContainer.className = 'wu-app-root wu-fallback';
273
- fallbackContainer.style.cssText = `
274
- width: 100%;
275
- height: 100%;
276
- isolation: isolate;
277
- contain: layout style paint;
278
- `;
279
-
280
- // 🧹 Clear host container
281
- hostContainer.innerHTML = '';
282
- hostContainer.appendChild(fallbackContainer);
283
-
284
- const healedSandbox = {
285
- appName,
286
- shadowRoot: null, // No shadow DOM in fallback mode
287
- container: fallbackContainer,
288
- hostContainer,
289
- styles: null,
290
- created: Date.now(),
291
- sandbox_state: 'fallback_mode',
292
- recovery_count: 1,
293
- fallback_mode: true
294
- };
295
-
296
- this.sandboxes.set(appName, healedSandbox);
297
-
298
- logger.wuDebug(`Fallback sandbox created successfully for ${appName}`);
299
- return healedSandbox;
300
-
301
- } catch (healingError) {
302
- logger.wuError(`Fallback sandbox creation failed: ${healingError}`);
303
- throw healingError;
304
- }
305
- }
306
-
307
- /**
308
- * 🎨 SANDBOX STYLES: Generate CSS styles for Shadow DOM isolation
309
- */
310
- generateSandboxStyles(appName) {
311
- return `
312
- /* Wu Framework - Shadow DOM Isolation Styles */
313
- :host {
314
- display: block;
315
- width: 100%;
316
- height: 100%;
317
- box-sizing: border-box;
318
- contain: layout style paint;
319
- --wu-sandbox-active: true;
320
- --wu-isolation-state: stable;
321
- }
322
-
323
- .wu-app-root {
324
- width: 100%;
325
- height: 100%;
326
- box-sizing: border-box;
327
- isolation: isolate;
328
- position: relative;
329
- overflow: hidden;
330
- }
331
-
332
- /* Loading animation for sandbox initialization */
333
- .wu-app-root[data-wu-loading="true"] {
334
- background: linear-gradient(45deg,
335
- rgba(74, 144, 226, 0.1) 0%,
336
- rgba(80, 227, 194, 0.1) 100%);
337
- animation: sandboxPulse 2s ease-in-out infinite;
338
- }
339
-
340
- @keyframes sandboxPulse {
341
- 0%, 100% { opacity: 0.8; }
342
- 50% { opacity: 1; }
343
- }
344
-
345
- /* CSS reset for shadow DOM stability */
346
- * {
347
- box-sizing: border-box;
348
- }
349
-
350
- /* CSS custom properties for sandbox */
351
- :host {
352
- --wu-app-name: "${appName}";
353
- --wu-isolation: true;
354
- --wu-creation-timestamp: ${Date.now()};
355
- }
356
-
357
- /* 🛡️ Debug mode enhancements */
358
- :host([wu-debug]) {
359
- border: 2px dashed #4a90e2;
360
- background: rgba(74, 144, 226, 0.05);
361
- box-shadow: 0 0 10px rgba(74, 144, 226, 0.3);
362
- }
363
-
364
- :host([wu-debug])::before {
365
- content: "Wu Framework: " attr(wu-app);
366
- position: absolute;
367
- top: 0;
368
- left: 0;
369
- background: linear-gradient(45deg, #4a90e2, #50e3c2);
370
- color: white;
371
- padding: 4px 8px;
372
- font-size: 11px;
373
- font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
374
- z-index: 10000;
375
- border-radius: 0 0 4px 0;
376
- font-weight: 600;
377
- }
378
-
379
- /* Sandbox state indicators */
380
- :host([data-sandbox-state="stable"]) {
381
- --wu-isolation-state: stable;
382
- }
383
-
384
- :host([data-sandbox-state="healing"]) {
385
- --wu-dimensional-stability: healing;
386
- animation: sandboxHealing 1s ease-in-out infinite;
387
- }
388
-
389
- @keyframes sandboxHealing {
390
- 0%, 100% { filter: hue-rotate(0deg); }
391
- 50% { filter: hue-rotate(180deg); }
392
- }
393
- `;
394
- }
395
-
396
- /**
397
- * 🛡️ Crear JS Sandbox avanzado (ProxySandbox o SnapshotSandbox)
398
- * @param {string} appName - Nombre de la app
399
- * @returns {WuProxySandbox|WuSnapshotSandbox} Sandbox JS
400
- */
401
- createAdvancedJSSandbox(appName) {
402
- let jsSandbox;
403
-
404
- if (this.sandboxStrategy === 'proxy') {
405
- jsSandbox = new WuProxySandbox(appName);
406
- logger.wuDebug(`Created ProxySandbox for ${appName}`);
407
- } else {
408
- jsSandbox = new WuSnapshotSandbox(appName);
409
- logger.wuDebug(`Created SnapshotSandbox for ${appName}`);
410
- }
411
-
412
- // Registrar sandbox
413
- this.jsSandboxes.set(appName, jsSandbox);
414
-
415
- return jsSandbox;
416
- }
417
-
418
- /**
419
- * Agregar estilos personalizados al sandbox
420
- * @param {string} appName - Nombre de la aplicación
421
- * @param {string} css - CSS a agregar
422
- */
423
- addStyles(appName, css) {
424
- const sandbox = this.sandboxes.get(appName);
425
- if (!sandbox) {
426
- logger.wuWarn(`Sandbox not found for: ${appName}`);
427
- return;
428
- }
429
-
430
- const styleElement = document.createElement('style');
431
- styleElement.textContent = css;
432
- styleElement.setAttribute('wu-custom-styles', '');
433
-
434
- sandbox.shadowRoot.appendChild(styleElement);
435
- logger.wuDebug(`Custom styles added to ${appName}`);
436
- }
437
-
438
- /**
439
- * Cargar estilos externos en el sandbox
440
- * @param {string} appName - Nombre de la aplicación
441
- * @param {string} href - URL del CSS
442
- */
443
- loadExternalStyles(appName, href) {
444
- const sandbox = this.sandboxes.get(appName);
445
- if (!sandbox) {
446
- logger.wuWarn(`Sandbox not found for: ${appName}`);
447
- return;
448
- }
449
-
450
- const linkElement = document.createElement('link');
451
- linkElement.rel = 'stylesheet';
452
- linkElement.href = href;
453
- linkElement.setAttribute('wu-external-styles', '');
454
-
455
- sandbox.shadowRoot.appendChild(linkElement);
456
- logger.wuDebug(`External styles loaded in ${appName}: ${href}`);
457
- }
458
-
459
- /**
460
- * Establecer modo debug para un sandbox
461
- * @param {string} appName - Nombre de la aplicación
462
- * @param {boolean} enabled - Activar/desactivar debug
463
- */
464
- setDebugMode(appName, enabled = true) {
465
- const sandbox = this.sandboxes.get(appName);
466
- if (!sandbox) {
467
- logger.wuWarn(`Sandbox not found for: ${appName}`);
468
- return;
469
- }
470
-
471
- if (enabled) {
472
- sandbox.hostContainer.setAttribute('wu-debug', '');
473
- sandbox.hostContainer.setAttribute('wu-app', appName);
474
- } else {
475
- sandbox.hostContainer.removeAttribute('wu-debug');
476
- sandbox.hostContainer.removeAttribute('wu-app');
477
- }
478
-
479
- logger.wuDebug(`Debug mode ${enabled ? 'enabled' : 'disabled'} for ${appName}`);
480
- }
481
-
482
- /**
483
- * Limpiar y destruir sandbox
484
- * @param {Object} sandbox - Sandbox a limpiar
485
- */
486
- cleanup(sandbox) {
487
- if (!sandbox) return;
488
-
489
- const { appName, shadowRoot, hostContainer, jsSandbox } = sandbox;
490
-
491
- logger.wuDebug(`Cleaning up sandbox for: ${appName}`);
492
-
493
- try {
494
- // 🛡️ NUEVO: Desactivar JS Sandbox
495
- if (jsSandbox && jsSandbox.isActive()) {
496
- jsSandbox.deactivate();
497
- logger.wuDebug(`JS Sandbox deactivated for ${appName}`);
498
- }
499
-
500
- // Limpiar eventos y observers
501
- this.cleanupEventListeners(sandbox);
502
-
503
- // Limpiar contenido del Shadow DOM
504
- if (shadowRoot) {
505
- shadowRoot.innerHTML = '';
506
- }
507
-
508
- // Remover atributos del host
509
- if (hostContainer) {
510
- hostContainer.removeAttribute('wu-debug');
511
- hostContainer.removeAttribute('wu-app');
512
- hostContainer.removeAttribute('wu-no-scroll');
513
- }
514
-
515
- // Remover del registro
516
- this.sandboxes.delete(appName);
517
- this.jsSandboxes.delete(appName);
518
-
519
- logger.wuDebug(`Sandbox cleaned up: ${appName}`);
520
-
521
- } catch (error) {
522
- logger.wuError(`Error cleaning up sandbox ${appName}: ${error}`);
523
- }
524
- }
525
-
526
- /**
527
- * Limpiar event listeners del sandbox
528
- * @param {Object} sandbox - Sandbox a limpiar
529
- */
530
- cleanupEventListeners(sandbox) {
531
- // Remover todos los event listeners del Shadow DOM
532
- const { shadowRoot } = sandbox;
533
- if (!shadowRoot) return;
534
-
535
- // Clonar nodos para remover todos los event listeners
536
- const elements = shadowRoot.querySelectorAll('*');
537
- elements.forEach(element => {
538
- if (element.cloneNode) {
539
- const clone = element.cloneNode(true);
540
- element.parentNode?.replaceChild(clone, element);
541
- }
542
- });
543
- }
544
-
545
- /**
546
- * Obtener información de un sandbox
547
- * @param {string} appName - Nombre de la aplicación
548
- * @returns {Object} Información del sandbox
549
- */
550
- getSandboxInfo(appName) {
551
- const sandbox = this.sandboxes.get(appName);
552
- if (!sandbox) return null;
553
-
554
- return {
555
- appName: sandbox.appName,
556
- created: sandbox.created,
557
- hasContainer: !!sandbox.container,
558
- hasShadowRoot: !!sandbox.shadowRoot,
559
- elementCount: sandbox.shadowRoot?.children?.length || 0,
560
- uptime: Date.now() - sandbox.created
561
- };
562
- }
563
-
564
- /**
565
- * Obtener estadísticas de todos los sandboxes
566
- */
567
- getStats() {
568
- return {
569
- strategy: this.sandboxStrategy,
570
- total: this.sandboxes.size,
571
- sandboxes: Array.from(this.sandboxes.keys()),
572
- jsSandboxes: Array.from(this.jsSandboxes.keys()),
573
- details: Array.from(this.sandboxes.entries()).map(([name, sandbox]) => ({
574
- name,
575
- uptime: Date.now() - sandbox.created,
576
- elements: sandbox.shadowRoot?.children?.length || 0,
577
- hasJsSandbox: !!sandbox.jsSandbox,
578
- jsSandboxActive: sandbox.jsSandbox?.isActive() || false
579
- }))
580
- };
581
- }
582
-
583
- /**
584
- * Limpiar todos los sandboxes
585
- */
586
- cleanupAll() {
587
- logger.wuDebug(`Cleaning up all ${this.sandboxes.size} sandboxes...`);
588
-
589
- for (const [appName, sandbox] of this.sandboxes) {
590
- this.cleanup(sandbox);
591
- }
592
-
593
- // Limpiar StyleBridge
594
- if (this.styleBridge) {
595
- this.styleBridge.cleanup();
596
- }
597
-
598
- logger.wuDebug('All sandboxes cleaned up');
599
- }
600
-
601
- /**
602
- * 🎨 CONFIGURAR STYLE BRIDGE: Configura el sistema de compartición de estilos
603
- * @param {Object} config - Configuración del StyleBridge
604
- */
605
- configureStyleSharing(config) {
606
- if (this.styleBridge) {
607
- this.styleBridge.configure(config);
608
- logger.wuDebug('StyleBridge configured');
609
- }
610
- }
611
-
612
- /**
613
- * 📊 OBTENER ESTADÍSTICAS DE ESTILOS: Info sobre estilos compartidos
614
- * @returns {Object}
615
- */
616
- getStyleStats() {
617
- return this.styleBridge ? this.styleBridge.getStats() : null;
618
- }
619
-
620
- /**
621
- * 🔄 RE-INYECTAR ESTILOS: Vuelve a inyectar estilos en un sandbox
622
- * @param {string} appName - Nombre de la aplicación
623
- */
624
- async reinjectStyles(appName) {
625
- const sandbox = this.sandboxes.get(appName);
626
- if (!sandbox || !sandbox.shadowRoot) {
627
- logger.wuWarn(`Cannot reinject styles for ${appName}`);
628
- return;
629
- }
630
-
631
- const styleMode = sandbox.styleMode;
632
-
633
- // 🔒 MODO ISOLATED: No reinyectar estilos - la app maneja sus propios estilos
634
- if (styleMode === 'isolated') {
635
- logger.wuDebug(`Skipping reinject for ${appName} (isolated mode - app manages own styles)`);
636
- return;
637
- }
638
-
639
- // 🛡️ MODO FULLY-ISOLATED: Reinyectar SOLO estilos propios
640
- if (styleMode === 'fully-isolated') {
641
- logger.wuDebug(`Reinjecting OWN styles for ${appName} (fully-isolated)...`);
642
- const appUrl = sandbox.appUrl || sandbox.manifest?.name ? `/${sandbox.manifest.name}/` : `/${appName}/`;
643
- const count = await this.injectOwnStylesToShadow(sandbox.shadowRoot, appName, appUrl);
644
- logger.wuDebug(`Reinjected ${count} own styles for ${appName}`);
645
- return;
646
- }
647
-
648
- // 🌐 MODO SHARED: Reinyectar todos los estilos compartidos
649
- logger.wuDebug(`Reinjecting shared styles for ${appName}...`);
650
- const count = await this.styleBridge.injectStylesIntoShadow(
651
- sandbox.shadowRoot,
652
- appName,
653
- styleMode
654
- );
655
- logger.wuDebug(`Reinjected ${count} shared styles`);
656
- }
657
-
658
- /**
659
- * 🎨 INYECTAR ESTILOS PROPIOS: Inyecta SOLO los estilos propios de una app en su Shadow DOM
660
- * Usado para apps en modo fully-isolated
661
- * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
662
- * @param {string} appName - Nombre de la app
663
- * @param {string} appUrl - URL base de la app
664
- * @returns {Promise<number>} Número de estilos inyectados
665
- */
666
- async injectOwnStylesToShadow(shadowRoot, appName, appUrl) {
667
- if (!shadowRoot) return 0;
668
-
669
- let injectedCount = 0;
670
-
671
- // Buscar TODOS los estilos en el head
672
- const allStyles = document.querySelectorAll('style');
673
- const normalizedAppName = appName.toLowerCase();
674
-
675
- // Patrones para detectar estilos de esta app (Windows y Unix paths)
676
- // IMPORTANTE: Debe coincidir SOLO con packages/appName/ al inicio del path del paquete
677
- // NO debe coincidir con packages/shell/src/components/learning/ (eso es del shell)
678
- const appPatterns = [
679
- // Patrón específico: packages/learning/src o packages\learning\src
680
- // El src/ es clave para asegurar que es el MFE, no un subdirectorio de otro package
681
- new RegExp(`packages[/\\\\]${normalizedAppName}[/\\\\]src[/\\\\]`, 'i')
682
- ];
683
-
684
- logger.wuDebug(`Searching own styles for ${appName}, found ${allStyles.length} style tags in head`);
685
-
686
- // Log para debug: mostrar estilos que contienen el nombre de la app
687
- let matchingCount = 0;
688
- for (const s of allStyles) {
689
- const vid = s.getAttribute('data-vite-dev-id') || '';
690
- if (vid.toLowerCase().includes(normalizedAppName)) {
691
- matchingCount++;
692
- }
693
- }
694
- if (matchingCount > 0) {
695
- logger.wuDebug(`Found ${matchingCount} styles potentially matching ${appName}`);
696
- }
697
-
698
- for (const style of allStyles) {
699
- // NO saltar basándose en data-wu-injected del head - eso se pone en los clones del shadow DOM
700
-
701
- const viteId = style.getAttribute('data-vite-dev-id') || '';
702
- const normalizedViteId = viteId.replace(/\\/g, '/').toLowerCase();
703
-
704
- // Verificar si el estilo pertenece a esta app
705
- let belongsToApp = false;
706
-
707
- // 1. Por data-vite-dev-id (la forma más confiable)
708
- if (viteId) {
709
- // Revisar si el path contiene packages/appName/
710
- for (const pattern of appPatterns) {
711
- if (pattern instanceof RegExp) {
712
- if (pattern.test(viteId)) {
713
- belongsToApp = true;
714
- break;
715
- }
716
- } else {
717
- if (normalizedViteId.includes(pattern.toLowerCase())) {
718
- belongsToApp = true;
719
- break;
720
- }
721
- }
722
- }
723
- }
724
-
725
- if (belongsToApp) {
726
- // Verificar si ya existe en el Shadow DOM
727
- const existingStyle = shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`);
728
-
729
- if (!existingStyle) {
730
- const clonedStyle = style.cloneNode(true);
731
- clonedStyle.setAttribute('data-wu-injected', 'true');
732
- shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
733
- injectedCount++;
734
- const styleName = viteId.substring(viteId.lastIndexOf('/') + 1) || viteId.substring(viteId.lastIndexOf('\\') + 1);
735
- logger.wuDebug(`Injected own style for ${appName}: ${styleName}`);
736
- }
737
- }
738
- }
739
-
740
- logger.wuDebug(`Total own styles injected for ${appName}: ${injectedCount}`);
741
- return injectedCount;
742
- }
743
-
744
- /**
745
- * 🎨 FALLBACK: Inyectar estilos que contengan el nombre de la app
746
- * Usado como último recurso cuando no se encuentran los estilos con el patrón exacto
747
- * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
748
- * @param {string} appName - Nombre de la app (para logging)
749
- * @returns {number} Número de estilos inyectados
750
- */
751
- injectAllStylesToShadow(shadowRoot, appName) {
752
- if (!shadowRoot) return 0;
753
-
754
- let injectedCount = 0;
755
- const normalizedAppName = appName.toLowerCase();
756
-
757
- // Inyectar estilos que contengan el nombre de la app en su vite-id
758
- const allStyles = document.querySelectorAll('style');
759
- for (const style of allStyles) {
760
- const viteId = style.getAttribute('data-vite-dev-id') || '';
761
-
762
- // Solo inyectar si contiene el nombre de la app
763
- if (!viteId.toLowerCase().includes(normalizedAppName)) continue;
764
-
765
- // Verificar si ya existe en el shadow DOM
766
- if (shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`)) continue;
767
-
768
- const clonedStyle = style.cloneNode(true);
769
- clonedStyle.setAttribute('data-wu-fallback', 'true');
770
- shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
771
- injectedCount++;
772
- const styleName = viteId.split('/').pop() || viteId.split('\\').pop();
773
- logger.wuDebug(`FALLBACK injected: ${styleName}`);
774
- }
775
-
776
- logger.wuDebug(`FALLBACK: Total ${injectedCount} styles injected for ${appName}`);
777
- return injectedCount;
778
- }
779
- }
1
+ /**
2
+ * 🛡️ WU-SANDBOX: ADVANCED ISOLATION SYSTEM
3
+ * Shadow DOM + Proxy Sandbox + Script Execution + HTML Parsing
4
+ * Combina lo mejor de video-code con Shadow DOM nativo
5
+ */
6
+
7
+ import { WuStyleBridge } from './wu-style-bridge.js';
8
+ import { WuProxySandbox } from './wu-proxy-sandbox.js';
9
+ import { WuSnapshotSandbox } from './wu-snapshot-sandbox.js';
10
+ import { logger } from './wu-logger.js';
11
+
12
+ export class WuSandbox {
13
+ constructor() {
14
+ // Registros existentes
15
+ this.sandboxes = new Map();
16
+ this.styleBridge = new WuStyleBridge();
17
+
18
+ // 🚀 NUEVOS SISTEMAS INTEGRADOS
19
+ this.jsSandboxes = new Map(); // ProxySandbox o SnapshotSandbox por app
20
+ this.sandboxStrategy = this.detectSandboxStrategy();
21
+
22
+ logger.wuDebug(`Advanced isolation system initialized (strategy: ${this.sandboxStrategy})`);
23
+ }
24
+
25
+ /**
26
+ * Detectar estrategia de sandbox óptima
27
+ * @returns {'proxy' | 'snapshot'} Estrategia a usar
28
+ */
29
+ detectSandboxStrategy() {
30
+ // Verificar si Proxy está disponible
31
+ if (typeof Proxy !== 'undefined') {
32
+ try {
33
+ // Test básico de Proxy
34
+ const testProxy = new Proxy({}, {
35
+ get(target, prop) { return target[prop]; }
36
+ });
37
+ testProxy.test = 'value';
38
+
39
+ logger.wuDebug('Proxy available - using ProxySandbox strategy');
40
+ return 'proxy';
41
+ } catch (error) {
42
+ logger.wuWarn('Proxy not working - falling back to SnapshotSandbox');
43
+ return 'snapshot';
44
+ }
45
+ }
46
+
47
+ logger.wuWarn('Proxy not available - using SnapshotSandbox strategy');
48
+ return 'snapshot';
49
+ }
50
+
51
+ /**
52
+ * 🔧 SMART SANDBOX: Advanced Shadow DOM creation with error recovery
53
+ * @param {string} appName - Nombre de la aplicación
54
+ * @param {HTMLElement} hostContainer - Contenedor host
55
+ * @param {Object} options - Opciones adicionales (styleMode, manifest, etc.)
56
+ * @returns {Object} Sandbox con shadow root y container
57
+ */
58
+ create(appName, hostContainer, options = {}) {
59
+ logger.wuDebug(`Creating sandbox for: ${appName}`);
60
+
61
+ try {
62
+ // 🔧 SHADOW DOM VERIFICATION
63
+ if (!hostContainer.attachShadow) {
64
+ throw new Error('Shadow DOM not supported in this browser');
65
+ }
66
+
67
+ // 🛠️ SMART CLEANUP: Handle existing shadow roots
68
+ let shadowRoot;
69
+ if (hostContainer.shadowRoot) {
70
+ logger.wuDebug(`Existing shadow root detected for ${appName}, performing cleanup...`);
71
+
72
+ // Clear existing shadow root content
73
+ hostContainer.shadowRoot.innerHTML = '';
74
+ shadowRoot = hostContainer.shadowRoot;
75
+
76
+ logger.wuDebug(`Existing shadow root cleaned and reused for ${appName}`);
77
+ } else {
78
+ // Create new Shadow DOM
79
+ shadowRoot = hostContainer.attachShadow({
80
+ mode: 'open',
81
+ delegatesFocus: true
82
+ });
83
+
84
+ logger.wuDebug(`New shadow root created for ${appName}`);
85
+ }
86
+
87
+ // 🎯 Create app container with advanced features
88
+ const appContainer = document.createElement('div');
89
+ appContainer.id = `wu-app-${appName}`;
90
+ appContainer.className = 'wu-app-root';
91
+ appContainer.setAttribute('data-wu-enhanced', 'true');
92
+ appContainer.setAttribute('data-wu-timestamp', Date.now().toString());
93
+
94
+ // 🎨 Enhanced base styles with advanced properties
95
+ const baseStyles = document.createElement('style');
96
+ baseStyles.textContent = this.generateSandboxStyles(appName);
97
+
98
+ // 🌟 Assemble enhanced Shadow DOM
99
+ shadowRoot.appendChild(baseStyles);
100
+ shadowRoot.appendChild(appContainer);
101
+
102
+ // Create JS Sandbox with container reference for DOM/storage scoping
103
+ const jsSandbox = this.createAdvancedJSSandbox(appName);
104
+ if (jsSandbox.setContainer) {
105
+ jsSandbox.setContainer(appContainer, shadowRoot);
106
+ }
107
+ const jsProxy = jsSandbox.activate();
108
+
109
+ // Verificar styleMode del manifest antes de inyectar estilos
110
+ const styleMode = options.styleMode || options.manifest?.styleMode;
111
+
112
+ const sandbox = {
113
+ appName,
114
+ shadowRoot,
115
+ container: appContainer,
116
+ hostContainer,
117
+ jsSandbox, // NUEVO: ProxySandbox o SnapshotSandbox
118
+ jsProxy, // NUEVO: Proxy para ejecutar scripts
119
+ styles: baseStyles,
120
+ styleMode, // Guardar styleMode para uso futuro
121
+ manifest: options.manifest, // Guardar manifest completo
122
+ created: Date.now(),
123
+ sandbox_state: 'stable',
124
+ recovery_count: 0
125
+ };
126
+
127
+ // 🎨 INJECT STYLES: Comportamiento según styleMode
128
+ // - "shared": Inyecta todos los estilos del documento padre
129
+ // - "isolated": NO inyecta estilos externos (encapsulamiento nativo Shadow DOM)
130
+ // - "fully-isolated": Inyecta SOLO estilos propios de la app
131
+
132
+ if (styleMode === 'isolated') {
133
+ // 🔒 MODO ISOLATED: Encapsulamiento nativo del Shadow DOM
134
+ // No se inyectan estilos externos - la app debe manejar sus propios estilos
135
+ logger.wuDebug(`Style mode "isolated" for ${appName}, using native Shadow DOM encapsulation`);
136
+ sandbox.stylesReady = Promise.resolve(0);
137
+ // No configurar observer de estilos - la app es responsable de sus propios estilos
138
+
139
+ } else if (styleMode === 'fully-isolated') {
140
+ logger.wuDebug(`Style mode "fully-isolated" detected for ${appName}, using enhanced style injection`);
141
+ // Registrar esta app como fully-isolated en el style bridge para filtrar sus estilos
142
+ const appUrl = options.appUrl || (options.manifest?.name ? `/${options.manifest.name}/` : `/${appName}/`);
143
+ this.styleBridge.registerFullyIsolatedApp(appName, appUrl);
144
+
145
+ // Guardar appUrl en sandbox para uso en reinjectStyles
146
+ sandbox.appUrl = appUrl;
147
+
148
+ // Para fully-isolated, inyectar SOLO los estilos propios de la app en su Shadow DOM
149
+ // Guardamos referencia a this para usar en el observer
150
+ const self = this;
151
+
152
+ sandbox.stylesReady = new Promise((resolve) => {
153
+ let resolved = false;
154
+
155
+ const tryInject = async () => {
156
+ const count = await self.injectOwnStylesToShadow(shadowRoot, appName, appUrl);
157
+
158
+ if (count > 0) {
159
+ logger.wuDebug(`Injected ${count} own styles for ${appName} (fully-isolated)`);
160
+ if (!resolved) {
161
+ resolved = true;
162
+ resolve(count);
163
+ }
164
+ }
165
+
166
+ return count;
167
+ };
168
+
169
+ // Usar MutationObserver PERSISTENTE para detectar cuando se inyectan estilos del app
170
+ logger.wuDebug(`Setting up style observer for ${appName} (fully-isolated)`);
171
+ const observer = new MutationObserver((mutations) => {
172
+ let newStyleCount = 0;
173
+ for (const m of mutations) {
174
+ if (m.type === 'childList') {
175
+ for (const n of m.addedNodes) {
176
+ if (n.nodeName === 'STYLE' || n.nodeName === 'LINK') {
177
+ newStyleCount++;
178
+ const viteId = n.getAttribute ? n.getAttribute('data-vite-dev-id') : null;
179
+ if (viteId && viteId.toLowerCase().includes(appName.toLowerCase())) {
180
+ logger.wuDebug(`New ${appName} style detected: ${viteId.split('/').pop()}`);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ if (newStyleCount > 0) {
187
+ logger.wuDebug(`${newStyleCount} new styles detected in head, checking for ${appName}...`);
188
+ tryInject();
189
+ }
190
+ });
191
+
192
+ // Observar cambios en el head DE FORMA PERSISTENTE
193
+ observer.observe(document.head, {
194
+ childList: true,
195
+ subtree: true
196
+ });
197
+
198
+ // Guardar referencia al observer para poder desconectarlo cuando se desmonte la app
199
+ sandbox.styleObserver = observer;
200
+
201
+ // Intento inicial con pequeño delay para que Vite procese los imports
202
+ setTimeout(async () => {
203
+ const count = await tryInject();
204
+ // Si después de 3 segundos no hay estilos, usar fallback
205
+ if (!resolved) {
206
+ setTimeout(() => {
207
+ if (!resolved) {
208
+ logger.wuWarn(`No own styles found for ${appName} after timeout, using FALLBACK`);
209
+ const fallbackCount = self.injectAllStylesToShadow(shadowRoot, appName);
210
+ logger.wuDebug(`FALLBACK: Injected ${fallbackCount} styles for ${appName}`);
211
+ resolved = true;
212
+ resolve(fallbackCount);
213
+ }
214
+ }, 3000);
215
+ }
216
+ }, 50);
217
+ });
218
+ } else {
219
+ // 🌐 MODO SHARED (default): Inyectar todos los estilos compartidos del documento
220
+ logger.wuDebug(`Style mode "shared" for ${appName}, injecting all shared styles...`);
221
+ sandbox.stylesReady = this.styleBridge.injectStylesIntoShadow(shadowRoot, appName, styleMode).then(count => {
222
+ logger.wuDebug(`Shared ${count} styles with ${appName}`);
223
+
224
+ // 🔄 Observar cambios dinámicos de estilos (HMR de Vite)
225
+ this.styleBridge.observeStyleChanges(() => {
226
+ logger.wuDebug(`Reinjecting styles for ${appName} due to changes`);
227
+ this.styleBridge.injectStylesIntoShadow(shadowRoot, appName, styleMode).catch(err => {
228
+ logger.wuWarn(`Failed to reinject styles: ${err}`);
229
+ });
230
+ });
231
+
232
+ return count;
233
+ }).catch(error => {
234
+ logger.wuWarn(`Failed to inject styles: ${error}`);
235
+ return 0;
236
+ });
237
+ }
238
+
239
+ // 📊 Register in sandbox registry
240
+ this.sandboxes.set(appName, sandbox);
241
+
242
+ logger.wuDebug(`Enhanced sandbox created for ${appName}`);
243
+ return sandbox;
244
+
245
+ } catch (error) {
246
+ logger.wuError(`Failed to create sandbox for ${appName}: ${error}`);
247
+
248
+ // 🔧 FALLBACK RECOVERY: Create fallback sandbox when Shadow DOM fails
249
+ if (error.message.includes('Shadow root cannot be created')) {
250
+ return this.createFallbackSandbox(appName, hostContainer);
251
+ }
252
+
253
+ throw error;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * 🔧 FALLBACK SANDBOX: Create fallback container when Shadow DOM is not available
259
+ */
260
+ createFallbackSandbox(appName, hostContainer) {
261
+ logger.wuDebug(`Creating fallback sandbox for ${appName}...`);
262
+
263
+ try {
264
+ // 🛠️ Complete shadow DOM reset
265
+ if (hostContainer.shadowRoot) {
266
+ hostContainer.shadowRoot.innerHTML = '';
267
+ }
268
+
269
+ // 🌟 Create minimal container without shadow DOM if necessary
270
+ const fallbackContainer = document.createElement('div');
271
+ fallbackContainer.id = `wu-app-${appName}`;
272
+ fallbackContainer.className = 'wu-app-root wu-fallback';
273
+ fallbackContainer.style.cssText = `
274
+ width: 100%;
275
+ height: 100%;
276
+ isolation: isolate;
277
+ contain: layout style paint;
278
+ `;
279
+
280
+ // 🧹 Clear host container
281
+ hostContainer.innerHTML = '';
282
+ hostContainer.appendChild(fallbackContainer);
283
+
284
+ const healedSandbox = {
285
+ appName,
286
+ shadowRoot: null, // No shadow DOM in fallback mode
287
+ container: fallbackContainer,
288
+ hostContainer,
289
+ styles: null,
290
+ created: Date.now(),
291
+ sandbox_state: 'fallback_mode',
292
+ recovery_count: 1,
293
+ fallback_mode: true
294
+ };
295
+
296
+ this.sandboxes.set(appName, healedSandbox);
297
+
298
+ logger.wuDebug(`Fallback sandbox created successfully for ${appName}`);
299
+ return healedSandbox;
300
+
301
+ } catch (healingError) {
302
+ logger.wuError(`Fallback sandbox creation failed: ${healingError}`);
303
+ throw healingError;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * 🎨 SANDBOX STYLES: Generate CSS styles for Shadow DOM isolation
309
+ */
310
+ generateSandboxStyles(appName) {
311
+ return `
312
+ /* Wu Framework - Shadow DOM Isolation Styles */
313
+ :host {
314
+ display: block;
315
+ width: 100%;
316
+ height: 100%;
317
+ box-sizing: border-box;
318
+ contain: layout style paint;
319
+ --wu-sandbox-active: true;
320
+ --wu-isolation-state: stable;
321
+ }
322
+
323
+ .wu-app-root {
324
+ width: 100%;
325
+ height: 100%;
326
+ box-sizing: border-box;
327
+ isolation: isolate;
328
+ position: relative;
329
+ overflow: hidden;
330
+ }
331
+
332
+ /* Loading animation for sandbox initialization */
333
+ .wu-app-root[data-wu-loading="true"] {
334
+ background: linear-gradient(45deg,
335
+ rgba(74, 144, 226, 0.1) 0%,
336
+ rgba(80, 227, 194, 0.1) 100%);
337
+ animation: sandboxPulse 2s ease-in-out infinite;
338
+ }
339
+
340
+ @keyframes sandboxPulse {
341
+ 0%, 100% { opacity: 0.8; }
342
+ 50% { opacity: 1; }
343
+ }
344
+
345
+ /* CSS reset for shadow DOM stability */
346
+ * {
347
+ box-sizing: border-box;
348
+ }
349
+
350
+ /* CSS custom properties for sandbox */
351
+ :host {
352
+ --wu-app-name: "${appName}";
353
+ --wu-isolation: true;
354
+ --wu-creation-timestamp: ${Date.now()};
355
+ }
356
+
357
+ /* 🛡️ Debug mode enhancements */
358
+ :host([wu-debug]) {
359
+ border: 2px dashed #4a90e2;
360
+ background: rgba(74, 144, 226, 0.05);
361
+ box-shadow: 0 0 10px rgba(74, 144, 226, 0.3);
362
+ }
363
+
364
+ :host([wu-debug])::before {
365
+ content: "Wu Framework: " attr(wu-app);
366
+ position: absolute;
367
+ top: 0;
368
+ left: 0;
369
+ background: linear-gradient(45deg, #4a90e2, #50e3c2);
370
+ color: white;
371
+ padding: 4px 8px;
372
+ font-size: 11px;
373
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
374
+ z-index: 10000;
375
+ border-radius: 0 0 4px 0;
376
+ font-weight: 600;
377
+ }
378
+
379
+ /* Sandbox state indicators */
380
+ :host([data-sandbox-state="stable"]) {
381
+ --wu-isolation-state: stable;
382
+ }
383
+
384
+ :host([data-sandbox-state="healing"]) {
385
+ --wu-dimensional-stability: healing;
386
+ animation: sandboxHealing 1s ease-in-out infinite;
387
+ }
388
+
389
+ @keyframes sandboxHealing {
390
+ 0%, 100% { filter: hue-rotate(0deg); }
391
+ 50% { filter: hue-rotate(180deg); }
392
+ }
393
+ `;
394
+ }
395
+
396
+ /**
397
+ * 🛡️ Crear JS Sandbox avanzado (ProxySandbox o SnapshotSandbox)
398
+ * @param {string} appName - Nombre de la app
399
+ * @returns {WuProxySandbox|WuSnapshotSandbox} Sandbox JS
400
+ */
401
+ createAdvancedJSSandbox(appName) {
402
+ let jsSandbox;
403
+
404
+ if (this.sandboxStrategy === 'proxy') {
405
+ jsSandbox = new WuProxySandbox(appName);
406
+ logger.wuDebug(`Created ProxySandbox for ${appName}`);
407
+ } else {
408
+ jsSandbox = new WuSnapshotSandbox(appName);
409
+ logger.wuDebug(`Created SnapshotSandbox for ${appName}`);
410
+ }
411
+
412
+ // Registrar sandbox
413
+ this.jsSandboxes.set(appName, jsSandbox);
414
+
415
+ return jsSandbox;
416
+ }
417
+
418
+ /**
419
+ * Agregar estilos personalizados al sandbox
420
+ * @param {string} appName - Nombre de la aplicación
421
+ * @param {string} css - CSS a agregar
422
+ */
423
+ addStyles(appName, css) {
424
+ const sandbox = this.sandboxes.get(appName);
425
+ if (!sandbox) {
426
+ logger.wuWarn(`Sandbox not found for: ${appName}`);
427
+ return;
428
+ }
429
+
430
+ const styleElement = document.createElement('style');
431
+ styleElement.textContent = css;
432
+ styleElement.setAttribute('wu-custom-styles', '');
433
+
434
+ sandbox.shadowRoot.appendChild(styleElement);
435
+ logger.wuDebug(`Custom styles added to ${appName}`);
436
+ }
437
+
438
+ /**
439
+ * Cargar estilos externos en el sandbox
440
+ * @param {string} appName - Nombre de la aplicación
441
+ * @param {string} href - URL del CSS
442
+ */
443
+ loadExternalStyles(appName, href) {
444
+ const sandbox = this.sandboxes.get(appName);
445
+ if (!sandbox) {
446
+ logger.wuWarn(`Sandbox not found for: ${appName}`);
447
+ return;
448
+ }
449
+
450
+ const linkElement = document.createElement('link');
451
+ linkElement.rel = 'stylesheet';
452
+ linkElement.href = href;
453
+ linkElement.setAttribute('wu-external-styles', '');
454
+
455
+ sandbox.shadowRoot.appendChild(linkElement);
456
+ logger.wuDebug(`External styles loaded in ${appName}: ${href}`);
457
+ }
458
+
459
+ /**
460
+ * Establecer modo debug para un sandbox
461
+ * @param {string} appName - Nombre de la aplicación
462
+ * @param {boolean} enabled - Activar/desactivar debug
463
+ */
464
+ setDebugMode(appName, enabled = true) {
465
+ const sandbox = this.sandboxes.get(appName);
466
+ if (!sandbox) {
467
+ logger.wuWarn(`Sandbox not found for: ${appName}`);
468
+ return;
469
+ }
470
+
471
+ if (enabled) {
472
+ sandbox.hostContainer.setAttribute('wu-debug', '');
473
+ sandbox.hostContainer.setAttribute('wu-app', appName);
474
+ } else {
475
+ sandbox.hostContainer.removeAttribute('wu-debug');
476
+ sandbox.hostContainer.removeAttribute('wu-app');
477
+ }
478
+
479
+ logger.wuDebug(`Debug mode ${enabled ? 'enabled' : 'disabled'} for ${appName}`);
480
+ }
481
+
482
+ /**
483
+ * Limpiar y destruir sandbox
484
+ * @param {Object} sandbox - Sandbox a limpiar
485
+ */
486
+ cleanup(sandbox) {
487
+ if (!sandbox) return;
488
+
489
+ const { appName, shadowRoot, hostContainer, jsSandbox } = sandbox;
490
+
491
+ logger.wuDebug(`Cleaning up sandbox for: ${appName}`);
492
+
493
+ try {
494
+ // 🛡️ NUEVO: Desactivar JS Sandbox
495
+ if (jsSandbox && jsSandbox.isActive()) {
496
+ jsSandbox.deactivate();
497
+ logger.wuDebug(`JS Sandbox deactivated for ${appName}`);
498
+ }
499
+
500
+ // Limpiar eventos y observers
501
+ this.cleanupEventListeners(sandbox);
502
+
503
+ // Limpiar contenido del Shadow DOM
504
+ if (shadowRoot) {
505
+ shadowRoot.innerHTML = '';
506
+ }
507
+
508
+ // Remover atributos del host
509
+ if (hostContainer) {
510
+ hostContainer.removeAttribute('wu-debug');
511
+ hostContainer.removeAttribute('wu-app');
512
+ hostContainer.removeAttribute('wu-no-scroll');
513
+ }
514
+
515
+ // Remover del registro
516
+ this.sandboxes.delete(appName);
517
+ this.jsSandboxes.delete(appName);
518
+
519
+ logger.wuDebug(`Sandbox cleaned up: ${appName}`);
520
+
521
+ } catch (error) {
522
+ logger.wuError(`Error cleaning up sandbox ${appName}: ${error}`);
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Limpiar event listeners del sandbox
528
+ * @param {Object} sandbox - Sandbox a limpiar
529
+ */
530
+ cleanupEventListeners(sandbox) {
531
+ // Remover todos los event listeners del Shadow DOM
532
+ const { shadowRoot } = sandbox;
533
+ if (!shadowRoot) return;
534
+
535
+ // Clonar nodos para remover todos los event listeners
536
+ const elements = shadowRoot.querySelectorAll('*');
537
+ elements.forEach(element => {
538
+ if (element.cloneNode) {
539
+ const clone = element.cloneNode(true);
540
+ element.parentNode?.replaceChild(clone, element);
541
+ }
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Obtener información de un sandbox
547
+ * @param {string} appName - Nombre de la aplicación
548
+ * @returns {Object} Información del sandbox
549
+ */
550
+ getSandboxInfo(appName) {
551
+ const sandbox = this.sandboxes.get(appName);
552
+ if (!sandbox) return null;
553
+
554
+ return {
555
+ appName: sandbox.appName,
556
+ created: sandbox.created,
557
+ hasContainer: !!sandbox.container,
558
+ hasShadowRoot: !!sandbox.shadowRoot,
559
+ elementCount: sandbox.shadowRoot?.children?.length || 0,
560
+ uptime: Date.now() - sandbox.created
561
+ };
562
+ }
563
+
564
+ /**
565
+ * Obtener estadísticas de todos los sandboxes
566
+ */
567
+ getStats() {
568
+ return {
569
+ strategy: this.sandboxStrategy,
570
+ total: this.sandboxes.size,
571
+ sandboxes: Array.from(this.sandboxes.keys()),
572
+ jsSandboxes: Array.from(this.jsSandboxes.keys()),
573
+ details: Array.from(this.sandboxes.entries()).map(([name, sandbox]) => ({
574
+ name,
575
+ uptime: Date.now() - sandbox.created,
576
+ elements: sandbox.shadowRoot?.children?.length || 0,
577
+ hasJsSandbox: !!sandbox.jsSandbox,
578
+ jsSandboxActive: sandbox.jsSandbox?.isActive() || false
579
+ }))
580
+ };
581
+ }
582
+
583
+ /**
584
+ * Limpiar todos los sandboxes
585
+ */
586
+ cleanupAll() {
587
+ logger.wuDebug(`Cleaning up all ${this.sandboxes.size} sandboxes...`);
588
+
589
+ for (const [appName, sandbox] of this.sandboxes) {
590
+ this.cleanup(sandbox);
591
+ }
592
+
593
+ // Limpiar StyleBridge
594
+ if (this.styleBridge) {
595
+ this.styleBridge.cleanup();
596
+ }
597
+
598
+ logger.wuDebug('All sandboxes cleaned up');
599
+ }
600
+
601
+ /**
602
+ * 🎨 CONFIGURAR STYLE BRIDGE: Configura el sistema de compartición de estilos
603
+ * @param {Object} config - Configuración del StyleBridge
604
+ */
605
+ configureStyleSharing(config) {
606
+ if (this.styleBridge) {
607
+ this.styleBridge.configure(config);
608
+ logger.wuDebug('StyleBridge configured');
609
+ }
610
+ }
611
+
612
+ /**
613
+ * 📊 OBTENER ESTADÍSTICAS DE ESTILOS: Info sobre estilos compartidos
614
+ * @returns {Object}
615
+ */
616
+ getStyleStats() {
617
+ return this.styleBridge ? this.styleBridge.getStats() : null;
618
+ }
619
+
620
+ /**
621
+ * 🔄 RE-INYECTAR ESTILOS: Vuelve a inyectar estilos en un sandbox
622
+ * @param {string} appName - Nombre de la aplicación
623
+ */
624
+ async reinjectStyles(appName) {
625
+ const sandbox = this.sandboxes.get(appName);
626
+ if (!sandbox || !sandbox.shadowRoot) {
627
+ logger.wuWarn(`Cannot reinject styles for ${appName}`);
628
+ return;
629
+ }
630
+
631
+ const styleMode = sandbox.styleMode;
632
+
633
+ // 🔒 MODO ISOLATED: No reinyectar estilos - la app maneja sus propios estilos
634
+ if (styleMode === 'isolated') {
635
+ logger.wuDebug(`Skipping reinject for ${appName} (isolated mode - app manages own styles)`);
636
+ return;
637
+ }
638
+
639
+ // 🛡️ MODO FULLY-ISOLATED: Reinyectar SOLO estilos propios
640
+ if (styleMode === 'fully-isolated') {
641
+ logger.wuDebug(`Reinjecting OWN styles for ${appName} (fully-isolated)...`);
642
+ const appUrl = sandbox.appUrl || sandbox.manifest?.name ? `/${sandbox.manifest.name}/` : `/${appName}/`;
643
+ const count = await this.injectOwnStylesToShadow(sandbox.shadowRoot, appName, appUrl);
644
+ logger.wuDebug(`Reinjected ${count} own styles for ${appName}`);
645
+ return;
646
+ }
647
+
648
+ // 🌐 MODO SHARED: Reinyectar todos los estilos compartidos
649
+ logger.wuDebug(`Reinjecting shared styles for ${appName}...`);
650
+ const count = await this.styleBridge.injectStylesIntoShadow(
651
+ sandbox.shadowRoot,
652
+ appName,
653
+ styleMode
654
+ );
655
+ logger.wuDebug(`Reinjected ${count} shared styles`);
656
+ }
657
+
658
+ /**
659
+ * 🎨 INYECTAR ESTILOS PROPIOS: Inyecta SOLO los estilos propios de una app en su Shadow DOM
660
+ * Usado para apps en modo fully-isolated
661
+ * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
662
+ * @param {string} appName - Nombre de la app
663
+ * @param {string} appUrl - URL base de la app
664
+ * @returns {Promise<number>} Número de estilos inyectados
665
+ */
666
+ async injectOwnStylesToShadow(shadowRoot, appName, appUrl) {
667
+ if (!shadowRoot) return 0;
668
+
669
+ let injectedCount = 0;
670
+
671
+ // Buscar TODOS los estilos en el head
672
+ const allStyles = document.querySelectorAll('style');
673
+ const normalizedAppName = appName.toLowerCase();
674
+
675
+ // Patrones para detectar estilos de esta app (Windows y Unix paths)
676
+ // IMPORTANTE: Debe coincidir SOLO con packages/appName/ al inicio del path del paquete
677
+ // NO debe coincidir con packages/shell/src/components/learning/ (eso es del shell)
678
+ const appPatterns = [
679
+ // Patrón específico: packages/learning/src o packages\learning\src
680
+ // El src/ es clave para asegurar que es el MFE, no un subdirectorio de otro package
681
+ new RegExp(`packages[/\\\\]${normalizedAppName}[/\\\\]src[/\\\\]`, 'i')
682
+ ];
683
+
684
+ logger.wuDebug(`Searching own styles for ${appName}, found ${allStyles.length} style tags in head`);
685
+
686
+ // Log para debug: mostrar estilos que contienen el nombre de la app
687
+ let matchingCount = 0;
688
+ for (const s of allStyles) {
689
+ const vid = s.getAttribute('data-vite-dev-id') || '';
690
+ if (vid.toLowerCase().includes(normalizedAppName)) {
691
+ matchingCount++;
692
+ }
693
+ }
694
+ if (matchingCount > 0) {
695
+ logger.wuDebug(`Found ${matchingCount} styles potentially matching ${appName}`);
696
+ }
697
+
698
+ for (const style of allStyles) {
699
+ // NO saltar basándose en data-wu-injected del head - eso se pone en los clones del shadow DOM
700
+
701
+ const viteId = style.getAttribute('data-vite-dev-id') || '';
702
+ const normalizedViteId = viteId.replace(/\\/g, '/').toLowerCase();
703
+
704
+ // Verificar si el estilo pertenece a esta app
705
+ let belongsToApp = false;
706
+
707
+ // 1. Por data-vite-dev-id (la forma más confiable)
708
+ if (viteId) {
709
+ // Revisar si el path contiene packages/appName/
710
+ for (const pattern of appPatterns) {
711
+ if (pattern instanceof RegExp) {
712
+ if (pattern.test(viteId)) {
713
+ belongsToApp = true;
714
+ break;
715
+ }
716
+ } else {
717
+ if (normalizedViteId.includes(pattern.toLowerCase())) {
718
+ belongsToApp = true;
719
+ break;
720
+ }
721
+ }
722
+ }
723
+ }
724
+
725
+ if (belongsToApp) {
726
+ // Verificar si ya existe en el Shadow DOM
727
+ const existingStyle = shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`);
728
+
729
+ if (!existingStyle) {
730
+ const clonedStyle = style.cloneNode(true);
731
+ clonedStyle.setAttribute('data-wu-injected', 'true');
732
+ shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
733
+ injectedCount++;
734
+ const styleName = viteId.substring(viteId.lastIndexOf('/') + 1) || viteId.substring(viteId.lastIndexOf('\\') + 1);
735
+ logger.wuDebug(`Injected own style for ${appName}: ${styleName}`);
736
+ }
737
+ }
738
+ }
739
+
740
+ logger.wuDebug(`Total own styles injected for ${appName}: ${injectedCount}`);
741
+ return injectedCount;
742
+ }
743
+
744
+ /**
745
+ * 🎨 FALLBACK: Inyectar estilos que contengan el nombre de la app
746
+ * Usado como último recurso cuando no se encuentran los estilos con el patrón exacto
747
+ * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
748
+ * @param {string} appName - Nombre de la app (para logging)
749
+ * @returns {number} Número de estilos inyectados
750
+ */
751
+ injectAllStylesToShadow(shadowRoot, appName) {
752
+ if (!shadowRoot) return 0;
753
+
754
+ let injectedCount = 0;
755
+ const normalizedAppName = appName.toLowerCase();
756
+
757
+ // Inyectar estilos que contengan el nombre de la app en su vite-id
758
+ const allStyles = document.querySelectorAll('style');
759
+ for (const style of allStyles) {
760
+ const viteId = style.getAttribute('data-vite-dev-id') || '';
761
+
762
+ // Solo inyectar si contiene el nombre de la app
763
+ if (!viteId.toLowerCase().includes(normalizedAppName)) continue;
764
+
765
+ // Verificar si ya existe en el shadow DOM
766
+ if (shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`)) continue;
767
+
768
+ const clonedStyle = style.cloneNode(true);
769
+ clonedStyle.setAttribute('data-wu-fallback', 'true');
770
+ shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
771
+ injectedCount++;
772
+ const styleName = viteId.split('/').pop() || viteId.split('\\').pop();
773
+ logger.wuDebug(`FALLBACK injected: ${styleName}`);
774
+ }
775
+
776
+ logger.wuDebug(`FALLBACK: Total ${injectedCount} styles injected for ${appName}`);
777
+ return injectedCount;
778
+ }
779
+ }