wu-framework 1.0.0
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/LICENSE +21 -0
- package/README.md +559 -0
- package/package.json +84 -0
- package/src/api/wu-simple.js +316 -0
- package/src/core/wu-app.js +192 -0
- package/src/core/wu-cache.js +374 -0
- package/src/core/wu-core.js +1296 -0
- package/src/core/wu-error-boundary.js +380 -0
- package/src/core/wu-event-bus.js +257 -0
- package/src/core/wu-hooks.js +348 -0
- package/src/core/wu-html-parser.js +280 -0
- package/src/core/wu-loader.js +271 -0
- package/src/core/wu-logger.js +119 -0
- package/src/core/wu-manifest.js +366 -0
- package/src/core/wu-performance.js +226 -0
- package/src/core/wu-plugin.js +213 -0
- package/src/core/wu-proxy-sandbox.js +153 -0
- package/src/core/wu-registry.js +130 -0
- package/src/core/wu-sandbox-pool.js +390 -0
- package/src/core/wu-sandbox.js +720 -0
- package/src/core/wu-script-executor.js +216 -0
- package/src/core/wu-snapshot-sandbox.js +184 -0
- package/src/core/wu-store.js +297 -0
- package/src/core/wu-strategies.js +241 -0
- package/src/core/wu-style-bridge.js +357 -0
- package/src/index.js +690 -0
- package/src/utils/dependency-resolver.js +326 -0
|
@@ -0,0 +1,720 @@
|
|
|
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 { WuScriptExecutor } from './wu-script-executor.js';
|
|
11
|
+
import { WuHtmlParser } from './wu-html-parser.js';
|
|
12
|
+
|
|
13
|
+
export class WuSandbox {
|
|
14
|
+
constructor() {
|
|
15
|
+
// Registros existentes
|
|
16
|
+
this.sandboxes = new Map();
|
|
17
|
+
this.globalState = new Map();
|
|
18
|
+
this.styleBridge = new WuStyleBridge();
|
|
19
|
+
|
|
20
|
+
// 🚀 NUEVOS SISTEMAS INTEGRADOS
|
|
21
|
+
this.jsSandboxes = new Map(); // ProxySandbox o SnapshotSandbox por app
|
|
22
|
+
this.scriptExecutor = new WuScriptExecutor();
|
|
23
|
+
this.htmlParser = new WuHtmlParser();
|
|
24
|
+
this.sandboxStrategy = this.detectSandboxStrategy();
|
|
25
|
+
|
|
26
|
+
console.log(`[WuSandbox] 🛡️ Advanced isolation system initialized (strategy: ${this.sandboxStrategy})`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detectar estrategia de sandbox óptima
|
|
31
|
+
* @returns {'proxy' | 'snapshot'} Estrategia a usar
|
|
32
|
+
*/
|
|
33
|
+
detectSandboxStrategy() {
|
|
34
|
+
// Verificar si Proxy está disponible
|
|
35
|
+
if (typeof Proxy !== 'undefined') {
|
|
36
|
+
try {
|
|
37
|
+
// Test básico de Proxy
|
|
38
|
+
const testProxy = new Proxy({}, {
|
|
39
|
+
get(target, prop) { return target[prop]; }
|
|
40
|
+
});
|
|
41
|
+
testProxy.test = 'value';
|
|
42
|
+
|
|
43
|
+
console.log('[WuSandbox] ✅ Proxy available - using ProxySandbox strategy');
|
|
44
|
+
return 'proxy';
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn('[WuSandbox] ⚠️ Proxy not working - falling back to SnapshotSandbox');
|
|
47
|
+
return 'snapshot';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.warn('[WuSandbox] ⚠️ Proxy not available - using SnapshotSandbox strategy');
|
|
52
|
+
return 'snapshot';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 🔧 SMART SANDBOX: Advanced Shadow DOM creation with error recovery
|
|
57
|
+
* @param {string} appName - Nombre de la aplicación
|
|
58
|
+
* @param {HTMLElement} hostContainer - Contenedor host
|
|
59
|
+
* @returns {Object} Sandbox con shadow root y container
|
|
60
|
+
*/
|
|
61
|
+
create(appName, hostContainer) {
|
|
62
|
+
console.log(`[WuSandbox] 🔨 Creating sandbox for: ${appName}`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// 🔧 SHADOW DOM VERIFICATION
|
|
66
|
+
if (!hostContainer.attachShadow) {
|
|
67
|
+
throw new Error('Shadow DOM not supported in this browser');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 🛠️ SMART CLEANUP: Handle existing shadow roots
|
|
71
|
+
let shadowRoot;
|
|
72
|
+
if (hostContainer.shadowRoot) {
|
|
73
|
+
console.log(`[WuSandbox] 🔄 Existing shadow root detected, performing cleanup...`);
|
|
74
|
+
|
|
75
|
+
// Clear existing shadow root content
|
|
76
|
+
hostContainer.shadowRoot.innerHTML = '';
|
|
77
|
+
shadowRoot = hostContainer.shadowRoot;
|
|
78
|
+
|
|
79
|
+
console.log(`[WuSandbox] ✨ Existing shadow root cleaned and reused`);
|
|
80
|
+
} else {
|
|
81
|
+
// Create new Shadow DOM
|
|
82
|
+
shadowRoot = hostContainer.attachShadow({
|
|
83
|
+
mode: 'open',
|
|
84
|
+
delegatesFocus: true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log(`[WuSandbox] 🌟 New shadow root created`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 🎯 Create app container with advanced features
|
|
91
|
+
const appContainer = document.createElement('div');
|
|
92
|
+
appContainer.id = `wu-app-${appName}`;
|
|
93
|
+
appContainer.className = 'wu-app-root';
|
|
94
|
+
appContainer.setAttribute('data-wu-enhanced', 'true');
|
|
95
|
+
appContainer.setAttribute('data-wu-timestamp', Date.now().toString());
|
|
96
|
+
|
|
97
|
+
// 🎨 Enhanced base styles with advanced properties
|
|
98
|
+
const baseStyles = document.createElement('style');
|
|
99
|
+
baseStyles.textContent = this.generateSandboxStyles(appName);
|
|
100
|
+
|
|
101
|
+
// 🌟 Assemble enhanced Shadow DOM
|
|
102
|
+
shadowRoot.appendChild(baseStyles);
|
|
103
|
+
shadowRoot.appendChild(appContainer);
|
|
104
|
+
|
|
105
|
+
// 🚀 Create isolated JavaScript scope (LEGACY - backward compatibility)
|
|
106
|
+
const jsScope = this.createJSScope(appName);
|
|
107
|
+
|
|
108
|
+
// 🛡️ NUEVO: Crear JS Sandbox avanzado (ProxySandbox o SnapshotSandbox)
|
|
109
|
+
const jsSandbox = this.createAdvancedJSSandbox(appName);
|
|
110
|
+
const jsProxy = jsSandbox.activate(); // Activar sandbox y obtener proxy
|
|
111
|
+
|
|
112
|
+
const sandbox = {
|
|
113
|
+
appName,
|
|
114
|
+
shadowRoot,
|
|
115
|
+
container: appContainer,
|
|
116
|
+
hostContainer,
|
|
117
|
+
jsScope, // LEGACY: mantener para compatibilidad
|
|
118
|
+
jsSandbox, // NUEVO: ProxySandbox o SnapshotSandbox
|
|
119
|
+
jsProxy, // NUEVO: Proxy para ejecutar scripts
|
|
120
|
+
styles: baseStyles,
|
|
121
|
+
created: Date.now(),
|
|
122
|
+
sandbox_state: 'stable',
|
|
123
|
+
recovery_count: 0
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// 🎨 INJECT SHARED STYLES: Store promise for await in mount
|
|
127
|
+
sandbox.stylesReady = this.styleBridge.injectStylesIntoShadow(shadowRoot, appName).then(count => {
|
|
128
|
+
console.log(`[WuSandbox] 🎨 Shared ${count} styles with ${appName}`);
|
|
129
|
+
|
|
130
|
+
// 🔄 Observar cambios dinámicos de estilos (HMR de Vite)
|
|
131
|
+
this.styleBridge.observeStyleChanges(() => {
|
|
132
|
+
console.log(`[WuSandbox] 🔄 Reinjecting styles for ${appName} due to changes`);
|
|
133
|
+
this.styleBridge.injectStylesIntoShadow(shadowRoot, appName).catch(err => {
|
|
134
|
+
console.warn(`[WuSandbox] ⚠️ Failed to reinject styles:`, err);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return count;
|
|
139
|
+
}).catch(error => {
|
|
140
|
+
console.warn(`[WuSandbox] ⚠️ Failed to inject styles:`, error);
|
|
141
|
+
return 0;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 📊 Register in sandbox registry
|
|
145
|
+
this.sandboxes.set(appName, sandbox);
|
|
146
|
+
|
|
147
|
+
console.log(`[WuSandbox] ✅ Enhanced sandbox created for ${appName}`);
|
|
148
|
+
return sandbox;
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`[WuSandbox] ❌ Failed to create sandbox for ${appName}:`, error);
|
|
152
|
+
|
|
153
|
+
// 🔧 FALLBACK RECOVERY: Create fallback sandbox when Shadow DOM fails
|
|
154
|
+
if (error.message.includes('Shadow root cannot be created')) {
|
|
155
|
+
return this.createFallbackSandbox(appName, hostContainer);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 🔧 FALLBACK SANDBOX: Create fallback container when Shadow DOM is not available
|
|
164
|
+
*/
|
|
165
|
+
createFallbackSandbox(appName, hostContainer) {
|
|
166
|
+
console.log(`[WuSandbox] 🔧 Creating fallback sandbox for ${appName}...`);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// 🛠️ Complete shadow DOM reset
|
|
170
|
+
if (hostContainer.shadowRoot) {
|
|
171
|
+
hostContainer.shadowRoot.innerHTML = '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 🌟 Create minimal container without shadow DOM if necessary
|
|
175
|
+
const fallbackContainer = document.createElement('div');
|
|
176
|
+
fallbackContainer.id = `wu-app-${appName}`;
|
|
177
|
+
fallbackContainer.className = 'wu-app-root wu-fallback';
|
|
178
|
+
fallbackContainer.style.cssText = `
|
|
179
|
+
width: 100%;
|
|
180
|
+
height: 100%;
|
|
181
|
+
isolation: isolate;
|
|
182
|
+
contain: layout style paint;
|
|
183
|
+
`;
|
|
184
|
+
|
|
185
|
+
// 🧹 Clear host container
|
|
186
|
+
hostContainer.innerHTML = '';
|
|
187
|
+
hostContainer.appendChild(fallbackContainer);
|
|
188
|
+
|
|
189
|
+
const healedSandbox = {
|
|
190
|
+
appName,
|
|
191
|
+
shadowRoot: null, // No shadow DOM in fallback mode
|
|
192
|
+
container: fallbackContainer,
|
|
193
|
+
hostContainer,
|
|
194
|
+
jsScope: this.createJSScope(appName),
|
|
195
|
+
styles: null,
|
|
196
|
+
created: Date.now(),
|
|
197
|
+
sandbox_state: 'fallback_mode',
|
|
198
|
+
recovery_count: 1,
|
|
199
|
+
fallback_mode: true
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
this.sandboxes.set(appName, healedSandbox);
|
|
203
|
+
|
|
204
|
+
console.log(`[WuSandbox] ✨ Fallback sandbox created successfully for ${appName}`);
|
|
205
|
+
return healedSandbox;
|
|
206
|
+
|
|
207
|
+
} catch (healingError) {
|
|
208
|
+
console.error(`[WuSandbox] 💥 Fallback sandbox creation failed:`, healingError);
|
|
209
|
+
throw healingError;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 🎨 SANDBOX STYLES: Generate CSS styles for Shadow DOM isolation
|
|
215
|
+
*/
|
|
216
|
+
generateSandboxStyles(appName) {
|
|
217
|
+
return `
|
|
218
|
+
/* Wu Framework - Shadow DOM Isolation Styles */
|
|
219
|
+
:host {
|
|
220
|
+
display: block;
|
|
221
|
+
width: 100%;
|
|
222
|
+
height: 100%;
|
|
223
|
+
box-sizing: border-box;
|
|
224
|
+
contain: layout style paint;
|
|
225
|
+
--wu-sandbox-active: true;
|
|
226
|
+
--wu-isolation-state: stable;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.wu-app-root {
|
|
230
|
+
width: 100%;
|
|
231
|
+
height: 100%;
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
isolation: isolate;
|
|
234
|
+
position: relative;
|
|
235
|
+
overflow: hidden;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Loading animation for sandbox initialization */
|
|
239
|
+
.wu-app-root[data-wu-loading="true"] {
|
|
240
|
+
background: linear-gradient(45deg,
|
|
241
|
+
rgba(74, 144, 226, 0.1) 0%,
|
|
242
|
+
rgba(80, 227, 194, 0.1) 100%);
|
|
243
|
+
animation: sandboxPulse 2s ease-in-out infinite;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@keyframes sandboxPulse {
|
|
247
|
+
0%, 100% { opacity: 0.8; }
|
|
248
|
+
50% { opacity: 1; }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* CSS reset for shadow DOM stability */
|
|
252
|
+
* {
|
|
253
|
+
box-sizing: border-box;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* CSS custom properties for sandbox */
|
|
257
|
+
:host {
|
|
258
|
+
--wu-app-name: "${appName}";
|
|
259
|
+
--wu-isolation: true;
|
|
260
|
+
--wu-creation-timestamp: ${Date.now()};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* 🛡️ Debug mode enhancements */
|
|
264
|
+
:host([wu-debug]) {
|
|
265
|
+
border: 2px dashed #4a90e2;
|
|
266
|
+
background: rgba(74, 144, 226, 0.05);
|
|
267
|
+
box-shadow: 0 0 10px rgba(74, 144, 226, 0.3);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:host([wu-debug])::before {
|
|
271
|
+
content: "Wu Framework: " attr(wu-app);
|
|
272
|
+
position: absolute;
|
|
273
|
+
top: 0;
|
|
274
|
+
left: 0;
|
|
275
|
+
background: linear-gradient(45deg, #4a90e2, #50e3c2);
|
|
276
|
+
color: white;
|
|
277
|
+
padding: 4px 8px;
|
|
278
|
+
font-size: 11px;
|
|
279
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
280
|
+
z-index: 10000;
|
|
281
|
+
border-radius: 0 0 4px 0;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Sandbox state indicators */
|
|
286
|
+
:host([data-sandbox-state="stable"]) {
|
|
287
|
+
--wu-isolation-state: stable;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
:host([data-sandbox-state="healing"]) {
|
|
291
|
+
--wu-dimensional-stability: healing;
|
|
292
|
+
animation: sandboxHealing 1s ease-in-out infinite;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@keyframes sandboxHealing {
|
|
296
|
+
0%, 100% { filter: hue-rotate(0deg); }
|
|
297
|
+
50% { filter: hue-rotate(180deg); }
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 🛡️ Crear JS Sandbox avanzado (ProxySandbox o SnapshotSandbox)
|
|
304
|
+
* @param {string} appName - Nombre de la app
|
|
305
|
+
* @returns {WuProxySandbox|WuSnapshotSandbox} Sandbox JS
|
|
306
|
+
*/
|
|
307
|
+
createAdvancedJSSandbox(appName) {
|
|
308
|
+
let jsSandbox;
|
|
309
|
+
|
|
310
|
+
if (this.sandboxStrategy === 'proxy') {
|
|
311
|
+
jsSandbox = new WuProxySandbox(appName);
|
|
312
|
+
console.log(`[WuSandbox] 🛡️ Created ProxySandbox for ${appName}`);
|
|
313
|
+
} else {
|
|
314
|
+
jsSandbox = new WuSnapshotSandbox(appName);
|
|
315
|
+
console.log(`[WuSandbox] 📸 Created SnapshotSandbox for ${appName}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Registrar sandbox
|
|
319
|
+
this.jsSandboxes.set(appName, jsSandbox);
|
|
320
|
+
|
|
321
|
+
return jsSandbox;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 📜 Ejecutar script en sandbox aislado
|
|
326
|
+
* @param {string} script - Código JavaScript
|
|
327
|
+
* @param {string} appName - Nombre de la app
|
|
328
|
+
* @param {string} strategy - 'function' | 'eval' | 'auto'
|
|
329
|
+
* @returns {*} Resultado de la ejecución
|
|
330
|
+
*/
|
|
331
|
+
executeScript(script, appName, strategy = 'function') {
|
|
332
|
+
const sandbox = this.sandboxes.get(appName);
|
|
333
|
+
if (!sandbox || !sandbox.jsProxy) {
|
|
334
|
+
throw new Error(`Sandbox not found for ${appName}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
console.log(`[WuSandbox] 📜 Executing script for ${appName}`);
|
|
338
|
+
return this.scriptExecutor.execute(script, appName, sandbox.jsProxy, strategy);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 📄 Parsear HTML de micro-app
|
|
343
|
+
* @param {string} html - HTML a parsear
|
|
344
|
+
* @param {string} appName - Nombre de la app
|
|
345
|
+
* @param {string} baseUrl - URL base
|
|
346
|
+
* @returns {Object} Resultado del parsing
|
|
347
|
+
*/
|
|
348
|
+
parseHtml(html, appName, baseUrl) {
|
|
349
|
+
console.log(`[WuSandbox] 📄 Parsing HTML for ${appName}`);
|
|
350
|
+
return this.htmlParser.parse(html, appName, baseUrl);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 📄 Parsear HTML desde URL
|
|
355
|
+
* @param {string} url - URL de la app
|
|
356
|
+
* @param {string} appName - Nombre de la app
|
|
357
|
+
* @returns {Promise<Object>} Resultado del parsing
|
|
358
|
+
*/
|
|
359
|
+
async parseHtmlFromUrl(url, appName) {
|
|
360
|
+
console.log(`[WuSandbox] 🌐 Parsing HTML from URL for ${appName}`);
|
|
361
|
+
return await this.htmlParser.parseFromUrl(url, appName);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Obtener estilos base para el sandbox
|
|
366
|
+
* @param {string} appName - Nombre de la aplicación
|
|
367
|
+
* @returns {string} CSS base
|
|
368
|
+
*/
|
|
369
|
+
getBaseStyles(appName) {
|
|
370
|
+
return `
|
|
371
|
+
/* Wu Framework - Shadow DOM Base Styles */
|
|
372
|
+
:host {
|
|
373
|
+
display: block;
|
|
374
|
+
width: 100%;
|
|
375
|
+
height: 100%;
|
|
376
|
+
box-sizing: border-box;
|
|
377
|
+
contain: layout style paint;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.wu-app-root {
|
|
381
|
+
width: 100%;
|
|
382
|
+
height: 100%;
|
|
383
|
+
box-sizing: border-box;
|
|
384
|
+
isolation: isolate;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Reset básico para evitar conflictos */
|
|
388
|
+
* {
|
|
389
|
+
box-sizing: border-box;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* Variables CSS aisladas para la app */
|
|
393
|
+
:host {
|
|
394
|
+
--wu-app-name: "${appName}";
|
|
395
|
+
--wu-isolation: true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* Prevenir scroll del host cuando no es necesario */
|
|
399
|
+
:host([wu-no-scroll]) {
|
|
400
|
+
overflow: hidden;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Estilos para debugging en desarrollo */
|
|
404
|
+
:host([wu-debug]) {
|
|
405
|
+
border: 2px dashed #ff6b6b;
|
|
406
|
+
background: rgba(255, 107, 107, 0.1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
:host([wu-debug])::before {
|
|
410
|
+
content: "Wu App: " attr(wu-app);
|
|
411
|
+
position: absolute;
|
|
412
|
+
top: 0;
|
|
413
|
+
left: 0;
|
|
414
|
+
background: #ff6b6b;
|
|
415
|
+
color: white;
|
|
416
|
+
padding: 2px 6px;
|
|
417
|
+
font-size: 10px;
|
|
418
|
+
font-family: monospace;
|
|
419
|
+
z-index: 10000;
|
|
420
|
+
}
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Crear scope aislado para JavaScript
|
|
426
|
+
* @param {string} appName - Nombre de la aplicación
|
|
427
|
+
* @returns {Proxy} Proxy que aísla el scope global
|
|
428
|
+
*/
|
|
429
|
+
createJSScope(appName) {
|
|
430
|
+
// Crear namespace aislado para la app
|
|
431
|
+
const appGlobals = Object.create(null);
|
|
432
|
+
|
|
433
|
+
// Proxy que intercepta acceso a variables globales
|
|
434
|
+
return new Proxy(window, {
|
|
435
|
+
set(target, prop, value) {
|
|
436
|
+
// Variables específicas de la app van al namespace aislado
|
|
437
|
+
if (this.isAppSpecific(prop)) {
|
|
438
|
+
appGlobals[prop] = value;
|
|
439
|
+
console.log(`[WuSandbox] 📝 App ${appName} set isolated global: ${String(prop)}`);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Variables seguras pueden ir al global real
|
|
444
|
+
if (this.isSafeGlobal(prop)) {
|
|
445
|
+
target[prop] = value;
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Por defecto, aislar en el namespace de la app
|
|
450
|
+
appGlobals[prop] = value;
|
|
451
|
+
return true;
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
get(target, prop) {
|
|
455
|
+
// Primero buscar en namespace aislado
|
|
456
|
+
if (prop in appGlobals) {
|
|
457
|
+
return appGlobals[prop];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Luego en el global real
|
|
461
|
+
return target[prop];
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
has(target, prop) {
|
|
465
|
+
return prop in appGlobals || prop in target;
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
ownKeys(target) {
|
|
469
|
+
return [...Object.keys(appGlobals), ...Object.keys(target)];
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// Métodos auxiliares
|
|
473
|
+
isAppSpecific(prop) {
|
|
474
|
+
const appSpecificPatterns = [
|
|
475
|
+
/^app[A-Z]/, // appData, appConfig, etc.
|
|
476
|
+
/^[a-z]+App$/, // myApp, headerApp, etc.
|
|
477
|
+
/^__[a-zA-Z]/, // variables privadas
|
|
478
|
+
/^_[A-Z]/ // variables de librerías
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
return appSpecificPatterns.some(pattern => pattern.test(String(prop)));
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
isSafeGlobal(prop) {
|
|
485
|
+
const safeGlobals = new Set([
|
|
486
|
+
'console', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
487
|
+
'fetch', 'URL', 'URLSearchParams', 'FormData', 'Blob',
|
|
488
|
+
'Date', 'Math', 'JSON', 'Promise', 'Array', 'Object', 'String', 'Number'
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
return safeGlobals.has(String(prop));
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Agregar estilos personalizados al sandbox
|
|
498
|
+
* @param {string} appName - Nombre de la aplicación
|
|
499
|
+
* @param {string} css - CSS a agregar
|
|
500
|
+
*/
|
|
501
|
+
addStyles(appName, css) {
|
|
502
|
+
const sandbox = this.sandboxes.get(appName);
|
|
503
|
+
if (!sandbox) {
|
|
504
|
+
console.warn(`[WuSandbox] Sandbox not found for: ${appName}`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const styleElement = document.createElement('style');
|
|
509
|
+
styleElement.textContent = css;
|
|
510
|
+
styleElement.setAttribute('wu-custom-styles', '');
|
|
511
|
+
|
|
512
|
+
sandbox.shadowRoot.appendChild(styleElement);
|
|
513
|
+
console.log(`[WuSandbox] 🎨 Custom styles added to ${appName}`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Cargar estilos externos en el sandbox
|
|
518
|
+
* @param {string} appName - Nombre de la aplicación
|
|
519
|
+
* @param {string} href - URL del CSS
|
|
520
|
+
*/
|
|
521
|
+
loadExternalStyles(appName, href) {
|
|
522
|
+
const sandbox = this.sandboxes.get(appName);
|
|
523
|
+
if (!sandbox) {
|
|
524
|
+
console.warn(`[WuSandbox] Sandbox not found for: ${appName}`);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const linkElement = document.createElement('link');
|
|
529
|
+
linkElement.rel = 'stylesheet';
|
|
530
|
+
linkElement.href = href;
|
|
531
|
+
linkElement.setAttribute('wu-external-styles', '');
|
|
532
|
+
|
|
533
|
+
sandbox.shadowRoot.appendChild(linkElement);
|
|
534
|
+
console.log(`[WuSandbox] 🔗 External styles loaded in ${appName}: ${href}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Establecer modo debug para un sandbox
|
|
539
|
+
* @param {string} appName - Nombre de la aplicación
|
|
540
|
+
* @param {boolean} enabled - Activar/desactivar debug
|
|
541
|
+
*/
|
|
542
|
+
setDebugMode(appName, enabled = true) {
|
|
543
|
+
const sandbox = this.sandboxes.get(appName);
|
|
544
|
+
if (!sandbox) {
|
|
545
|
+
console.warn(`[WuSandbox] Sandbox not found for: ${appName}`);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (enabled) {
|
|
550
|
+
sandbox.hostContainer.setAttribute('wu-debug', '');
|
|
551
|
+
sandbox.hostContainer.setAttribute('wu-app', appName);
|
|
552
|
+
} else {
|
|
553
|
+
sandbox.hostContainer.removeAttribute('wu-debug');
|
|
554
|
+
sandbox.hostContainer.removeAttribute('wu-app');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
console.log(`[WuSandbox] 🐛 Debug mode ${enabled ? 'enabled' : 'disabled'} for ${appName}`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Limpiar y destruir sandbox
|
|
562
|
+
* @param {Object} sandbox - Sandbox a limpiar
|
|
563
|
+
*/
|
|
564
|
+
cleanup(sandbox) {
|
|
565
|
+
if (!sandbox) return;
|
|
566
|
+
|
|
567
|
+
const { appName, shadowRoot, hostContainer, jsSandbox } = sandbox;
|
|
568
|
+
|
|
569
|
+
console.log(`[WuSandbox] 🧹 Cleaning up sandbox for: ${appName}`);
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
// 🛡️ NUEVO: Desactivar JS Sandbox
|
|
573
|
+
if (jsSandbox && jsSandbox.isActive()) {
|
|
574
|
+
jsSandbox.deactivate();
|
|
575
|
+
console.log(`[WuSandbox] 🛡️ JS Sandbox deactivated for ${appName}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Limpiar eventos y observers
|
|
579
|
+
this.cleanupEventListeners(sandbox);
|
|
580
|
+
|
|
581
|
+
// Limpiar contenido del Shadow DOM
|
|
582
|
+
if (shadowRoot) {
|
|
583
|
+
shadowRoot.innerHTML = '';
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Remover atributos del host
|
|
587
|
+
if (hostContainer) {
|
|
588
|
+
hostContainer.removeAttribute('wu-debug');
|
|
589
|
+
hostContainer.removeAttribute('wu-app');
|
|
590
|
+
hostContainer.removeAttribute('wu-no-scroll');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Remover del registro
|
|
594
|
+
this.sandboxes.delete(appName);
|
|
595
|
+
this.jsSandboxes.delete(appName);
|
|
596
|
+
|
|
597
|
+
console.log(`[WuSandbox] ✅ Sandbox cleaned up: ${appName}`);
|
|
598
|
+
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error(`[WuSandbox] ❌ Error cleaning up sandbox ${appName}:`, error);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Limpiar event listeners del sandbox
|
|
606
|
+
* @param {Object} sandbox - Sandbox a limpiar
|
|
607
|
+
*/
|
|
608
|
+
cleanupEventListeners(sandbox) {
|
|
609
|
+
// Remover todos los event listeners del Shadow DOM
|
|
610
|
+
const { shadowRoot } = sandbox;
|
|
611
|
+
if (!shadowRoot) return;
|
|
612
|
+
|
|
613
|
+
// Clonar nodos para remover todos los event listeners
|
|
614
|
+
const elements = shadowRoot.querySelectorAll('*');
|
|
615
|
+
elements.forEach(element => {
|
|
616
|
+
if (element.cloneNode) {
|
|
617
|
+
const clone = element.cloneNode(true);
|
|
618
|
+
element.parentNode?.replaceChild(clone, element);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Obtener información de un sandbox
|
|
625
|
+
* @param {string} appName - Nombre de la aplicación
|
|
626
|
+
* @returns {Object} Información del sandbox
|
|
627
|
+
*/
|
|
628
|
+
getSandboxInfo(appName) {
|
|
629
|
+
const sandbox = this.sandboxes.get(appName);
|
|
630
|
+
if (!sandbox) return null;
|
|
631
|
+
|
|
632
|
+
return {
|
|
633
|
+
appName: sandbox.appName,
|
|
634
|
+
created: sandbox.created,
|
|
635
|
+
hasContainer: !!sandbox.container,
|
|
636
|
+
hasShadowRoot: !!sandbox.shadowRoot,
|
|
637
|
+
elementCount: sandbox.shadowRoot?.children?.length || 0,
|
|
638
|
+
uptime: Date.now() - sandbox.created
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Obtener estadísticas de todos los sandboxes
|
|
644
|
+
*/
|
|
645
|
+
getStats() {
|
|
646
|
+
return {
|
|
647
|
+
strategy: this.sandboxStrategy,
|
|
648
|
+
total: this.sandboxes.size,
|
|
649
|
+
sandboxes: Array.from(this.sandboxes.keys()),
|
|
650
|
+
jsSandboxes: Array.from(this.jsSandboxes.keys()),
|
|
651
|
+
htmlCacheSize: this.htmlParser.getStats().cacheSize,
|
|
652
|
+
scriptExecutor: this.scriptExecutor.getStats(),
|
|
653
|
+
details: Array.from(this.sandboxes.entries()).map(([name, sandbox]) => ({
|
|
654
|
+
name,
|
|
655
|
+
uptime: Date.now() - sandbox.created,
|
|
656
|
+
elements: sandbox.shadowRoot?.children?.length || 0,
|
|
657
|
+
hasJsSandbox: !!sandbox.jsSandbox,
|
|
658
|
+
jsSandboxActive: sandbox.jsSandbox?.isActive() || false
|
|
659
|
+
}))
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Limpiar todos los sandboxes
|
|
665
|
+
*/
|
|
666
|
+
cleanupAll() {
|
|
667
|
+
console.log(`[WuSandbox] 🧹 Cleaning up all ${this.sandboxes.size} sandboxes...`);
|
|
668
|
+
|
|
669
|
+
for (const [appName, sandbox] of this.sandboxes) {
|
|
670
|
+
this.cleanup(sandbox);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
this.globalState.clear();
|
|
674
|
+
|
|
675
|
+
// Limpiar StyleBridge
|
|
676
|
+
if (this.styleBridge) {
|
|
677
|
+
this.styleBridge.cleanup();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
console.log(`[WuSandbox] ✅ All sandboxes cleaned up`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* 🎨 CONFIGURAR STYLE BRIDGE: Configura el sistema de compartición de estilos
|
|
685
|
+
* @param {Object} config - Configuración del StyleBridge
|
|
686
|
+
*/
|
|
687
|
+
configureStyleSharing(config) {
|
|
688
|
+
if (this.styleBridge) {
|
|
689
|
+
this.styleBridge.configure(config);
|
|
690
|
+
console.log(`[WuSandbox] 🎨 StyleBridge configured`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* 📊 OBTENER ESTADÍSTICAS DE ESTILOS: Info sobre estilos compartidos
|
|
696
|
+
* @returns {Object}
|
|
697
|
+
*/
|
|
698
|
+
getStyleStats() {
|
|
699
|
+
return this.styleBridge ? this.styleBridge.getStats() : null;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* 🔄 RE-INYECTAR ESTILOS: Vuelve a inyectar estilos en un sandbox
|
|
704
|
+
* @param {string} appName - Nombre de la aplicación
|
|
705
|
+
*/
|
|
706
|
+
async reinjectStyles(appName) {
|
|
707
|
+
const sandbox = this.sandboxes.get(appName);
|
|
708
|
+
if (!sandbox || !sandbox.shadowRoot) {
|
|
709
|
+
console.warn(`[WuSandbox] Cannot reinject styles for ${appName}`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
console.log(`[WuSandbox] 🔄 Reinjecting styles for ${appName}...`);
|
|
714
|
+
const count = await this.styleBridge.injectStylesIntoShadow(
|
|
715
|
+
sandbox.shadowRoot,
|
|
716
|
+
appName
|
|
717
|
+
);
|
|
718
|
+
console.log(`[WuSandbox] ✅ Reinjected ${count} styles`);
|
|
719
|
+
}
|
|
720
|
+
}
|