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.
- package/README.md +52 -20
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15511 -15146
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/package.json +166 -161
- package/src/adapters/angular/ai.js +30 -30
- package/src/adapters/angular/index.d.ts +154 -154
- package/src/adapters/angular/index.js +932 -932
- package/src/adapters/angular.d.ts +3 -3
- package/src/adapters/angular.js +3 -3
- package/src/adapters/index.js +168 -168
- package/src/adapters/lit/ai.js +20 -20
- package/src/adapters/lit/index.d.ts +120 -120
- package/src/adapters/lit/index.js +721 -721
- package/src/adapters/lit.d.ts +3 -3
- package/src/adapters/lit.js +3 -3
- package/src/adapters/preact/ai.js +33 -33
- package/src/adapters/preact/index.d.ts +108 -108
- package/src/adapters/preact/index.js +661 -661
- package/src/adapters/preact.d.ts +3 -3
- package/src/adapters/preact.js +3 -3
- package/src/adapters/react/index.js +48 -54
- package/src/adapters/react.d.ts +3 -3
- package/src/adapters/react.js +3 -3
- package/src/adapters/shared.js +64 -64
- package/src/adapters/solid/ai.js +32 -32
- package/src/adapters/solid/index.d.ts +101 -101
- package/src/adapters/solid/index.js +586 -586
- package/src/adapters/solid.d.ts +3 -3
- package/src/adapters/solid.js +3 -3
- package/src/adapters/svelte/ai.js +31 -31
- package/src/adapters/svelte/index.d.ts +166 -166
- package/src/adapters/svelte/index.js +798 -798
- package/src/adapters/svelte.d.ts +3 -3
- package/src/adapters/svelte.js +3 -3
- package/src/adapters/vanilla/ai.js +30 -30
- package/src/adapters/vanilla/index.d.ts +179 -179
- package/src/adapters/vanilla/index.js +785 -785
- package/src/adapters/vanilla.d.ts +3 -3
- package/src/adapters/vanilla.js +3 -3
- package/src/adapters/vue/ai.js +52 -52
- package/src/adapters/vue/index.d.ts +299 -299
- package/src/adapters/vue/index.js +610 -610
- package/src/adapters/vue.d.ts +3 -3
- package/src/adapters/vue.js +3 -3
- package/src/ai/wu-ai-actions.js +261 -261
- package/src/ai/wu-ai-agent.js +546 -546
- package/src/ai/wu-ai-browser-primitives.js +354 -354
- package/src/ai/wu-ai-browser.js +380 -380
- package/src/ai/wu-ai-context.js +332 -332
- package/src/ai/wu-ai-conversation.js +613 -613
- package/src/ai/wu-ai-orchestrate.js +1021 -1021
- package/src/ai/wu-ai-permissions.js +381 -381
- package/src/ai/wu-ai-provider.js +700 -700
- package/src/ai/wu-ai-schema.js +225 -225
- package/src/ai/wu-ai-triggers.js +396 -396
- package/src/ai/wu-ai.js +804 -804
- package/src/core/wu-app.js +236 -236
- package/src/core/wu-cache.js +498 -477
- package/src/core/wu-core.js +1412 -1398
- package/src/core/wu-error-boundary.js +396 -382
- package/src/core/wu-event-bus.js +390 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +199 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +385 -273
- package/src/core/wu-logger.js +142 -134
- package/src/core/wu-manifest.js +532 -509
- package/src/core/wu-mcp-bridge.js +432 -432
- package/src/core/wu-overrides.js +510 -510
- package/src/core/wu-performance.js +228 -228
- package/src/core/wu-plugin.js +401 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +477 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +161 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-store.js +13 -3
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.d.ts +317 -0
- package/src/index.js +234 -224
- package/src/utils/dependency-resolver.js +327 -327
package/src/core/wu-sandbox.js
CHANGED
|
@@ -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
|
+
}
|