wu-framework 1.1.15 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +52 -20
  2. package/dist/wu-framework.cjs.js +1 -1
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15511 -15146
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +1 -1
  7. package/dist/wu-framework.esm.js.map +1 -1
  8. package/dist/wu-framework.umd.js +1 -1
  9. package/dist/wu-framework.umd.js.map +1 -1
  10. package/package.json +166 -161
  11. package/src/adapters/angular/ai.js +30 -30
  12. package/src/adapters/angular/index.d.ts +154 -154
  13. package/src/adapters/angular/index.js +932 -932
  14. package/src/adapters/angular.d.ts +3 -3
  15. package/src/adapters/angular.js +3 -3
  16. package/src/adapters/index.js +168 -168
  17. package/src/adapters/lit/ai.js +20 -20
  18. package/src/adapters/lit/index.d.ts +120 -120
  19. package/src/adapters/lit/index.js +721 -721
  20. package/src/adapters/lit.d.ts +3 -3
  21. package/src/adapters/lit.js +3 -3
  22. package/src/adapters/preact/ai.js +33 -33
  23. package/src/adapters/preact/index.d.ts +108 -108
  24. package/src/adapters/preact/index.js +661 -661
  25. package/src/adapters/preact.d.ts +3 -3
  26. package/src/adapters/preact.js +3 -3
  27. package/src/adapters/react/index.js +48 -54
  28. package/src/adapters/react.d.ts +3 -3
  29. package/src/adapters/react.js +3 -3
  30. package/src/adapters/shared.js +64 -64
  31. package/src/adapters/solid/ai.js +32 -32
  32. package/src/adapters/solid/index.d.ts +101 -101
  33. package/src/adapters/solid/index.js +586 -586
  34. package/src/adapters/solid.d.ts +3 -3
  35. package/src/adapters/solid.js +3 -3
  36. package/src/adapters/svelte/ai.js +31 -31
  37. package/src/adapters/svelte/index.d.ts +166 -166
  38. package/src/adapters/svelte/index.js +798 -798
  39. package/src/adapters/svelte.d.ts +3 -3
  40. package/src/adapters/svelte.js +3 -3
  41. package/src/adapters/vanilla/ai.js +30 -30
  42. package/src/adapters/vanilla/index.d.ts +179 -179
  43. package/src/adapters/vanilla/index.js +785 -785
  44. package/src/adapters/vanilla.d.ts +3 -3
  45. package/src/adapters/vanilla.js +3 -3
  46. package/src/adapters/vue/ai.js +52 -52
  47. package/src/adapters/vue/index.d.ts +299 -299
  48. package/src/adapters/vue/index.js +610 -610
  49. package/src/adapters/vue.d.ts +3 -3
  50. package/src/adapters/vue.js +3 -3
  51. package/src/ai/wu-ai-actions.js +261 -261
  52. package/src/ai/wu-ai-agent.js +546 -546
  53. package/src/ai/wu-ai-browser-primitives.js +354 -354
  54. package/src/ai/wu-ai-browser.js +380 -380
  55. package/src/ai/wu-ai-context.js +332 -332
  56. package/src/ai/wu-ai-conversation.js +613 -613
  57. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  58. package/src/ai/wu-ai-permissions.js +381 -381
  59. package/src/ai/wu-ai-provider.js +700 -700
  60. package/src/ai/wu-ai-schema.js +225 -225
  61. package/src/ai/wu-ai-triggers.js +396 -396
  62. package/src/ai/wu-ai.js +804 -804
  63. package/src/core/wu-app.js +236 -236
  64. package/src/core/wu-cache.js +498 -477
  65. package/src/core/wu-core.js +1412 -1398
  66. package/src/core/wu-error-boundary.js +396 -382
  67. package/src/core/wu-event-bus.js +390 -348
  68. package/src/core/wu-hooks.js +350 -350
  69. package/src/core/wu-html-parser.js +199 -190
  70. package/src/core/wu-iframe-sandbox.js +328 -328
  71. package/src/core/wu-loader.js +385 -273
  72. package/src/core/wu-logger.js +142 -134
  73. package/src/core/wu-manifest.js +532 -509
  74. package/src/core/wu-mcp-bridge.js +432 -432
  75. package/src/core/wu-overrides.js +510 -510
  76. package/src/core/wu-performance.js +228 -228
  77. package/src/core/wu-plugin.js +401 -348
  78. package/src/core/wu-prefetch.js +414 -414
  79. package/src/core/wu-proxy-sandbox.js +477 -476
  80. package/src/core/wu-sandbox.js +779 -779
  81. package/src/core/wu-script-executor.js +161 -113
  82. package/src/core/wu-snapshot-sandbox.js +227 -227
  83. package/src/core/wu-store.js +13 -3
  84. package/src/core/wu-strategies.js +256 -256
  85. package/src/core/wu-style-bridge.js +477 -477
  86. package/src/index.d.ts +317 -0
  87. package/src/index.js +234 -224
  88. package/src/utils/dependency-resolver.js +327 -327
@@ -1,510 +1,533 @@
1
- /**
2
- * 📋 WU-MANIFEST: SECURE MANIFEST SYSTEM
3
- * Validación estricta de wu.json para seguridad
4
- */
5
-
6
- import { logger } from './wu-logger.js';
7
-
8
- export class WuManifest {
9
- constructor() {
10
- this.cache = new Map();
11
- this.schemas = new Map();
12
-
13
- // 🔐 Configuración de seguridad
14
- this.security = {
15
- maxManifestSize: 100 * 1024, // 100KB máximo
16
- maxNameLength: 50,
17
- maxEntryLength: 200,
18
- maxExports: 100,
19
- maxImports: 50,
20
- maxRoutes: 100,
21
- // Patrones peligrosos en paths
22
- dangerousPatterns: [
23
- /\.\./, // Path traversal
24
- /^\/etc\//, // System paths
25
- /^\/proc\//,
26
- /^file:\/\//, // File protocol
27
- /javascript:/i, // JS injection
28
- /data:/i, // Data URLs
29
- /<script/i, // Script tags
30
- /on\w+\s*=/i // Event handlers
31
- ],
32
- // Dominios bloqueados
33
- blockedDomains: [
34
- 'evil.com',
35
- 'malware.com'
36
- ]
37
- };
38
-
39
- this.defineSchema();
40
- }
41
-
42
- /**
43
- * Definir schema de validación para wu.json
44
- */
45
- defineSchema() {
46
- this.schemas.set('wu.json', {
47
- required: ['name', 'entry'],
48
- optional: ['wu'],
49
- wu: {
50
- optional: ['exports', 'imports', 'routes', 'permissions'],
51
- exports: 'object',
52
- imports: 'array',
53
- routes: 'array',
54
- permissions: 'array'
55
- }
56
- });
57
- }
58
-
59
- /**
60
- * Cargar manifest desde URL
61
- * @param {string} appUrl - URL base de la aplicación
62
- * @returns {Object} Manifest parseado y validado
63
- */
64
- async load(appUrl) {
65
- const manifestUrl = `${appUrl}/wu.json`;
66
-
67
- logger.debug(`[WuManifest] 📥 Loading manifest: ${manifestUrl}`);
68
-
69
- try {
70
- // Verificar cache
71
- if (this.cache.has(manifestUrl)) {
72
- logger.debug(`[WuManifest] ⚡ Cache hit: ${manifestUrl}`);
73
- return this.cache.get(manifestUrl);
74
- }
75
-
76
- // Cargar manifest
77
- const response = await fetch(manifestUrl, {
78
- cache: 'no-cache',
79
- headers: {
80
- 'Accept': 'application/json'
81
- }
82
- });
83
-
84
- if (!response.ok) {
85
- // Si no hay manifest, crear uno básico
86
- if (response.status === 404) {
87
- logger.debug(`[WuManifest] 📄 No manifest found, creating default for: ${appUrl}`);
88
- return this.createDefaultManifest(appUrl);
89
- }
90
-
91
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
92
- }
93
-
94
- const manifestText = await response.text();
95
-
96
- // 🔐 Validar tamaño del manifest
97
- if (manifestText.length > this.security.maxManifestSize) {
98
- throw new Error(`Manifest too large (${manifestText.length} bytes, max ${this.security.maxManifestSize})`);
99
- }
100
-
101
- // 🔐 Intentar parsear JSON de forma segura
102
- let manifest;
103
- try {
104
- manifest = JSON.parse(manifestText);
105
- } catch (parseError) {
106
- throw new Error(`Invalid JSON in manifest: ${parseError.message}`);
107
- }
108
-
109
- // Validar manifest
110
- const validatedManifest = this.validate(manifest);
111
-
112
- // Cachear resultado
113
- this.cache.set(manifestUrl, validatedManifest);
114
-
115
- logger.debug(`[WuManifest] ✅ Manifest loaded: ${manifest.name}`);
116
- return validatedManifest;
117
-
118
- } catch (error) {
119
- console.error(`[WuManifest] ❌ Failed to load manifest: ${manifestUrl}`, error);
120
-
121
- // En caso de error, intentar crear manifest por defecto
122
- try {
123
- return this.createDefaultManifest(appUrl);
124
- } catch (defaultError) {
125
- throw new Error(`Failed to load manifest from ${manifestUrl}: ${error.message}`);
126
- }
127
- }
128
- }
129
-
130
- /**
131
- * Crear manifest por defecto cuando no existe wu.json
132
- * @param {string} appUrl - URL de la aplicación
133
- * @returns {Object} Manifest por defecto
134
- */
135
- createDefaultManifest(appUrl) {
136
- // Extraer nombre de la app de la URL
137
- const appName = this.extractAppNameFromUrl(appUrl);
138
-
139
- const defaultManifest = {
140
- name: appName,
141
- entry: 'index.js',
142
- wu: {
143
- exports: {},
144
- imports: [],
145
- routes: [],
146
- permissions: []
147
- }
148
- };
149
-
150
- logger.debug(`[WuManifest] 🔧 Created default manifest for: ${appName}`);
151
- return defaultManifest;
152
- }
153
-
154
- /**
155
- * Extraer nombre de app desde URL
156
- * @param {string} url - URL de la aplicación
157
- * @returns {string} Nombre de la aplicación
158
- */
159
- extractAppNameFromUrl(url) {
160
- try {
161
- const urlObj = new URL(url);
162
- const pathSegments = urlObj.pathname.split('/').filter(Boolean);
163
-
164
- // Usar el último segmento como nombre de la app
165
- return pathSegments[pathSegments.length - 1] || 'unknown-app';
166
- } catch {
167
- // Si no es una URL válida, usar como está
168
- return url.replace(/[^a-zA-Z0-9-]/g, '') || 'unknown-app';
169
- }
170
- }
171
-
172
- /**
173
- * 🔐 SANITIZE STRING: Limpiar string de caracteres peligrosos
174
- */
175
- _sanitizeString(str) {
176
- if (typeof str !== 'string') return '';
177
- return str
178
- .replace(/[<>'"]/g, '') // Remove HTML chars
179
- .replace(/[\x00-\x1F\x7F]/g, '') // Remove control chars
180
- .trim();
181
- }
182
-
183
- /**
184
- * 🔐 CHECK DANGEROUS PATTERNS: Verificar patrones peligrosos
185
- */
186
- _hasDangerousPatterns(str) {
187
- if (typeof str !== 'string') return false;
188
- return this.security.dangerousPatterns.some(pattern => pattern.test(str));
189
- }
190
-
191
- /**
192
- * 🔐 VALIDATE URL: Verificar que URL es segura
193
- */
194
- _isUrlSafe(url) {
195
- if (typeof url !== 'string') return false;
196
-
197
- // Verificar patrones peligrosos
198
- if (this._hasDangerousPatterns(url)) {
199
- return false;
200
- }
201
-
202
- // Verificar dominios bloqueados
203
- try {
204
- const urlObj = new URL(url, 'http://localhost');
205
- if (this.security.blockedDomains.some(d => urlObj.hostname.includes(d))) {
206
- return false;
207
- }
208
- } catch {
209
- // Si no es URL válida, verificar como path
210
- if (this._hasDangerousPatterns(url)) {
211
- return false;
212
- }
213
- }
214
-
215
- return true;
216
- }
217
-
218
- /**
219
- * Validar manifest contra schema con validación de seguridad
220
- * @param {Object} manifest - Manifest a validar
221
- * @returns {Object} Manifest validado
222
- */
223
- validate(manifest) {
224
- const schema = this.schemas.get('wu.json');
225
-
226
- // 🔐 Verificar que manifest es un objeto
227
- if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
228
- throw new Error('Manifest must be a valid object');
229
- }
230
-
231
- // Verificar campos requeridos
232
- for (const field of schema.required) {
233
- if (!manifest[field]) {
234
- throw new Error(`Required field missing: ${field}`);
235
- }
236
- }
237
-
238
- // 🔐 Validar nombre
239
- if (typeof manifest.name !== 'string') {
240
- throw new Error('name must be a string');
241
- }
242
- if (manifest.name.length > this.security.maxNameLength) {
243
- throw new Error(`name too long (max ${this.security.maxNameLength} chars)`);
244
- }
245
- if (this._hasDangerousPatterns(manifest.name)) {
246
- throw new Error('name contains dangerous patterns');
247
- }
248
-
249
- // 🔐 Validar entry
250
- if (typeof manifest.entry !== 'string') {
251
- throw new Error('entry must be a string');
252
- }
253
- if (manifest.entry.length > this.security.maxEntryLength) {
254
- throw new Error(`entry too long (max ${this.security.maxEntryLength} chars)`);
255
- }
256
- if (!this._isUrlSafe(manifest.entry)) {
257
- throw new Error('entry contains dangerous patterns');
258
- }
259
-
260
- // Verificar tipos en sección wu
261
- if (manifest.wu) {
262
- const wu = manifest.wu;
263
-
264
- if (wu.exports && typeof wu.exports !== 'object') {
265
- throw new Error('wu.exports must be an object');
266
- }
267
-
268
- // 🔐 Validar límites de exports
269
- if (wu.exports && Object.keys(wu.exports).length > this.security.maxExports) {
270
- throw new Error(`Too many exports (max ${this.security.maxExports})`);
271
- }
272
-
273
- // 🔐 Validar cada export path
274
- if (wu.exports) {
275
- for (const [key, path] of Object.entries(wu.exports)) {
276
- if (!this._isUrlSafe(path)) {
277
- throw new Error(`Dangerous export path: ${key}`);
278
- }
279
- }
280
- }
281
-
282
- if (wu.imports && !Array.isArray(wu.imports)) {
283
- throw new Error('wu.imports must be an array');
284
- }
285
-
286
- // 🔐 Validar límites de imports
287
- if (wu.imports && wu.imports.length > this.security.maxImports) {
288
- throw new Error(`Too many imports (max ${this.security.maxImports})`);
289
- }
290
-
291
- if (wu.routes && !Array.isArray(wu.routes)) {
292
- throw new Error('wu.routes must be an array');
293
- }
294
-
295
- // 🔐 Validar límites de routes
296
- if (wu.routes && wu.routes.length > this.security.maxRoutes) {
297
- throw new Error(`Too many routes (max ${this.security.maxRoutes})`);
298
- }
299
-
300
- if (wu.permissions && !Array.isArray(wu.permissions)) {
301
- throw new Error('wu.permissions must be an array');
302
- }
303
- }
304
-
305
- // Normalizar y limpiar manifest
306
- return this.normalize(manifest);
307
- }
308
-
309
- /**
310
- * Normalizar manifest
311
- * @param {Object} manifest - Manifest a normalizar
312
- * @returns {Object} Manifest normalizado
313
- */
314
- normalize(manifest) {
315
- const normalized = {
316
- name: manifest.name.trim(),
317
- entry: this.normalizeEntry(manifest.entry),
318
- wu: {
319
- exports: manifest.wu?.exports || {},
320
- imports: manifest.wu?.imports || [],
321
- routes: manifest.wu?.routes || [],
322
- permissions: manifest.wu?.permissions || []
323
- }
324
- };
325
-
326
- // Preservar campos opcionales del manifest (styleMode, version, folder, etc.)
327
- if (manifest.styleMode) {
328
- normalized.styleMode = manifest.styleMode;
329
- }
330
- if (manifest.version) {
331
- normalized.version = manifest.version;
332
- }
333
- if (manifest.folder) {
334
- normalized.folder = manifest.folder;
335
- }
336
-
337
- // Normalizar exports
338
- if (normalized.wu.exports) {
339
- const normalizedExports = {};
340
- for (const [key, path] of Object.entries(normalized.wu.exports)) {
341
- normalizedExports[key] = this.normalizeComponentPath(path);
342
- }
343
- normalized.wu.exports = normalizedExports;
344
- }
345
-
346
- // Validar imports
347
- normalized.wu.imports = normalized.wu.imports.filter(imp => {
348
- if (typeof imp !== 'string' || !imp.includes('.')) {
349
- logger.warn(`[WuManifest] Invalid import format: ${imp}`);
350
- return false;
351
- }
352
- return true;
353
- });
354
-
355
- return normalized;
356
- }
357
-
358
- /**
359
- * Normalizar entry path
360
- * @param {string} entry - Entry path
361
- * @returns {string} Entry normalizado
362
- */
363
- normalizeEntry(entry) {
364
- if (!entry) return 'index.js';
365
-
366
- let normalized = entry.trim();
367
-
368
- // Remover ./ inicial si está presente
369
- if (normalized.startsWith('./')) {
370
- normalized = normalized.substring(2);
371
- }
372
-
373
- // Agregar extensión si no la tiene
374
- if (!normalized.includes('.')) {
375
- normalized += '.js';
376
- }
377
-
378
- return normalized;
379
- }
380
-
381
- /**
382
- * Normalizar path de componente
383
- * @param {string} path - Path del componente
384
- * @returns {string} Path normalizado
385
- */
386
- normalizeComponentPath(path) {
387
- if (!path) return '';
388
-
389
- let normalized = path.trim();
390
-
391
- // Remover ./ inicial si está presente
392
- if (normalized.startsWith('./')) {
393
- normalized = normalized.substring(2);
394
- }
395
-
396
- // Agregar extensión si no la tiene
397
- if (!normalized.includes('.')) {
398
- normalized += '.js';
399
- }
400
-
401
- return normalized;
402
- }
403
-
404
- /**
405
- * Validar dependencias de imports
406
- * @param {Array} imports - Lista de imports
407
- * @param {Map} availableApps - Apps disponibles
408
- * @returns {Object} Resultado de validación
409
- */
410
- validateDependencies(imports, availableApps) {
411
- const result = {
412
- valid: [],
413
- invalid: [],
414
- missing: []
415
- };
416
-
417
- for (const importPath of imports) {
418
- const [appName, componentName] = importPath.split('.');
419
-
420
- if (!appName || !componentName) {
421
- result.invalid.push({
422
- import: importPath,
423
- reason: 'Invalid format. Use "app.component"'
424
- });
425
- continue;
426
- }
427
-
428
- const app = availableApps.get(appName);
429
- if (!app) {
430
- result.missing.push({
431
- import: importPath,
432
- app: appName,
433
- reason: 'App not registered'
434
- });
435
- continue;
436
- }
437
-
438
- const manifest = app.manifest;
439
- const exportExists = manifest?.wu?.exports?.[componentName];
440
-
441
- if (!exportExists) {
442
- result.invalid.push({
443
- import: importPath,
444
- reason: `Component ${componentName} not exported by ${appName}`
445
- });
446
- continue;
447
- }
448
-
449
- result.valid.push({
450
- import: importPath,
451
- app: appName,
452
- component: componentName,
453
- path: exportExists
454
- });
455
- }
456
-
457
- return result;
458
- }
459
-
460
- /**
461
- * Crear manifest programáticamente
462
- * @param {string} name - Nombre de la app
463
- * @param {Object} config - Configuración
464
- * @returns {Object} Manifest creado
465
- */
466
- create(name, config = {}) {
467
- const manifest = {
468
- name: name,
469
- entry: config.entry || 'index.js',
470
- wu: {
471
- exports: config.exports || {},
472
- imports: config.imports || [],
473
- routes: config.routes || [],
474
- permissions: config.permissions || []
475
- }
476
- };
477
-
478
- return this.normalize(manifest);
479
- }
480
-
481
- /**
482
- * Limpiar cache de manifests
483
- * @param {string} pattern - Patrón opcional para limpiar URLs específicas
484
- */
485
- clearCache(pattern) {
486
- if (pattern) {
487
- const regex = new RegExp(pattern);
488
- for (const [url] of this.cache) {
489
- if (regex.test(url)) {
490
- this.cache.delete(url);
491
- logger.debug(`[WuManifest] 🗑️ Cleared cache for: ${url}`);
492
- }
493
- }
494
- } else {
495
- this.cache.clear();
496
- logger.debug(`[WuManifest] 🗑️ Manifest cache cleared completely`);
497
- }
498
- }
499
-
500
- /**
501
- * Obtener estadísticas del sistema de manifests
502
- */
503
- getStats() {
504
- return {
505
- cached: this.cache.size,
506
- schemas: this.schemas.size,
507
- cacheKeys: Array.from(this.cache.keys())
508
- };
509
- }
1
+ /**
2
+ * 📋 WU-MANIFEST: SECURE MANIFEST SYSTEM
3
+ * Validación estricta de wu.json para seguridad
4
+ */
5
+
6
+ import { logger } from './wu-logger.js';
7
+
8
+ export class WuManifest {
9
+ constructor() {
10
+ this.cache = new Map();
11
+ this.schemas = new Map();
12
+
13
+ // 🔐 Configuración de seguridad
14
+ this.security = {
15
+ maxManifestSize: 100 * 1024, // 100KB máximo
16
+ maxNameLength: 50,
17
+ maxEntryLength: 200,
18
+ maxExports: 100,
19
+ maxImports: 50,
20
+ maxRoutes: 100,
21
+ // Patrones peligrosos en paths
22
+ dangerousPatterns: [
23
+ /\.\./, // Path traversal
24
+ /^\/etc\//, // System paths
25
+ /^\/proc\//,
26
+ /^file:\/\//, // File protocol
27
+ /javascript:/i, // JS injection
28
+ /data:/i, // Data URLs
29
+ /<script/i, // Script tags
30
+ /on\w+\s*=/i // Event handlers
31
+ ],
32
+ // Dominios bloqueados
33
+ blockedDomains: [
34
+ 'evil.com',
35
+ 'malware.com'
36
+ ]
37
+ };
38
+
39
+ this.defineSchema();
40
+ }
41
+
42
+ /**
43
+ * Definir schema de validación para wu.json
44
+ */
45
+ defineSchema() {
46
+ this.schemas.set('wu.json', {
47
+ required: ['name', 'entry'],
48
+ optional: ['wu'],
49
+ wu: {
50
+ optional: ['exports', 'imports', 'routes', 'permissions'],
51
+ exports: 'object',
52
+ imports: 'array',
53
+ routes: 'array',
54
+ permissions: 'array'
55
+ }
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Cargar manifest desde URL
61
+ * @param {string} appUrl - URL base de la aplicación
62
+ * @returns {Object} Manifest parseado y validado
63
+ */
64
+ async load(appUrl) {
65
+ const manifestUrl = `${appUrl}/wu.json`;
66
+
67
+ logger.debug(`[WuManifest] 📥 Loading manifest: ${manifestUrl}`);
68
+
69
+ try {
70
+ // Verificar cache
71
+ if (this.cache.has(manifestUrl)) {
72
+ logger.debug(`[WuManifest] ⚡ Cache hit: ${manifestUrl}`);
73
+ return this.cache.get(manifestUrl);
74
+ }
75
+
76
+ // Cargar manifest
77
+ const response = await fetch(manifestUrl, {
78
+ cache: 'no-cache',
79
+ headers: {
80
+ 'Accept': 'application/json'
81
+ }
82
+ });
83
+
84
+ if (!response.ok) {
85
+ // Si no hay manifest, crear uno básico
86
+ if (response.status === 404) {
87
+ logger.debug(`[WuManifest] 📄 No manifest found, creating default for: ${appUrl}`);
88
+ return this.createDefaultManifest(appUrl);
89
+ }
90
+
91
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
92
+ }
93
+
94
+ const manifestText = await response.text();
95
+
96
+ // 🔐 Validar tamaño del manifest
97
+ if (manifestText.length > this.security.maxManifestSize) {
98
+ throw new Error(`Manifest too large (${manifestText.length} bytes, max ${this.security.maxManifestSize})`);
99
+ }
100
+
101
+ // 🔐 Intentar parsear JSON de forma segura
102
+ let manifest;
103
+ try {
104
+ manifest = JSON.parse(manifestText);
105
+ } catch (parseError) {
106
+ throw new Error(`Invalid JSON in manifest: ${parseError.message}`);
107
+ }
108
+
109
+ // Validar manifest
110
+ const validatedManifest = this.validate(manifest);
111
+
112
+ // Cachear resultado
113
+ this.cache.set(manifestUrl, validatedManifest);
114
+
115
+ logger.debug(`[WuManifest] ✅ Manifest loaded: ${manifest.name}`);
116
+ return validatedManifest;
117
+
118
+ } catch (error) {
119
+ console.error(`[WuManifest] ❌ Failed to load manifest: ${manifestUrl}`, error);
120
+
121
+ // En caso de error, intentar crear manifest por defecto
122
+ try {
123
+ return this.createDefaultManifest(appUrl);
124
+ } catch (defaultError) {
125
+ throw new Error(`Failed to load manifest from ${manifestUrl}: ${error.message}`);
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Crear manifest por defecto cuando no existe wu.json
132
+ * @param {string} appUrl - URL de la aplicación
133
+ * @returns {Object} Manifest por defecto
134
+ */
135
+ createDefaultManifest(appUrl) {
136
+ // Extraer nombre de la app de la URL
137
+ const appName = this.extractAppNameFromUrl(appUrl);
138
+
139
+ const defaultManifest = {
140
+ name: appName,
141
+ entry: 'index.js',
142
+ wu: {
143
+ exports: {},
144
+ imports: [],
145
+ routes: [],
146
+ permissions: []
147
+ }
148
+ };
149
+
150
+ logger.debug(`[WuManifest] 🔧 Created default manifest for: ${appName}`);
151
+ return defaultManifest;
152
+ }
153
+
154
+ /**
155
+ * Extraer nombre de app desde URL
156
+ * @param {string} url - URL de la aplicación
157
+ * @returns {string} Nombre de la aplicación
158
+ */
159
+ extractAppNameFromUrl(url) {
160
+ try {
161
+ const urlObj = new URL(url);
162
+ const pathSegments = urlObj.pathname.split('/').filter(Boolean);
163
+
164
+ // Usar el último segmento como nombre de la app
165
+ return pathSegments[pathSegments.length - 1] || 'unknown-app';
166
+ } catch {
167
+ // Si no es una URL válida, usar como está
168
+ return url.replace(/[^a-zA-Z0-9-]/g, '') || 'unknown-app';
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 🔐 SANITIZE STRING: Limpiar string de caracteres peligrosos
174
+ */
175
+ _sanitizeString(str) {
176
+ if (typeof str !== 'string') return '';
177
+ return str
178
+ .replace(/[<>'"]/g, '') // Remove HTML chars
179
+ .replace(/[\x00-\x1F\x7F]/g, '') // Remove control chars
180
+ .trim();
181
+ }
182
+
183
+ /**
184
+ * 🔐 CHECK DANGEROUS PATTERNS: Verificar patrones peligrosos
185
+ */
186
+ _hasDangerousPatterns(str) {
187
+ if (typeof str !== 'string') return false;
188
+ return this.security.dangerousPatterns.some(pattern => pattern.test(str));
189
+ }
190
+
191
+ /**
192
+ * 🔐 VALIDATE URL: Verificar que URL es segura
193
+ */
194
+ _isUrlSafe(url) {
195
+ if (typeof url !== 'string') return false;
196
+
197
+ // Verificar patrones peligrosos
198
+ if (this._hasDangerousPatterns(url)) {
199
+ return false;
200
+ }
201
+
202
+ // Verificar dominios bloqueados
203
+ try {
204
+ const urlObj = new URL(url, 'http://localhost');
205
+ if (this.security.blockedDomains.some(d => urlObj.hostname.includes(d))) {
206
+ return false;
207
+ }
208
+ } catch {
209
+ // Si no es URL válida, verificar como path
210
+ if (this._hasDangerousPatterns(url)) {
211
+ return false;
212
+ }
213
+ }
214
+
215
+ return true;
216
+ }
217
+
218
+ /**
219
+ * Validar manifest contra schema con validación de seguridad
220
+ * @param {Object} manifest - Manifest a validar
221
+ * @returns {Object} Manifest validado
222
+ */
223
+ validate(manifest) {
224
+ const schema = this.schemas.get('wu.json');
225
+
226
+ // 🔐 Verificar que manifest es un objeto
227
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
228
+ throw new Error('Manifest must be a valid object');
229
+ }
230
+
231
+ // Verificar campos requeridos
232
+ for (const field of schema.required) {
233
+ if (!manifest[field]) {
234
+ throw new Error(`Required field missing: ${field}`);
235
+ }
236
+ }
237
+
238
+ // 🔐 Validar nombre
239
+ if (typeof manifest.name !== 'string') {
240
+ throw new Error('name must be a string');
241
+ }
242
+ if (manifest.name.length > this.security.maxNameLength) {
243
+ throw new Error(`name too long (max ${this.security.maxNameLength} chars)`);
244
+ }
245
+ if (this._hasDangerousPatterns(manifest.name)) {
246
+ throw new Error('name contains dangerous patterns');
247
+ }
248
+
249
+ // 🔐 Validar entry
250
+ if (typeof manifest.entry !== 'string') {
251
+ throw new Error('entry must be a string');
252
+ }
253
+ if (manifest.entry.length > this.security.maxEntryLength) {
254
+ throw new Error(`entry too long (max ${this.security.maxEntryLength} chars)`);
255
+ }
256
+ if (!this._isUrlSafe(manifest.entry)) {
257
+ throw new Error('entry contains dangerous patterns');
258
+ }
259
+
260
+ // Verificar tipos en sección wu
261
+ if (manifest.wu) {
262
+ const wu = manifest.wu;
263
+
264
+ if (wu.exports && typeof wu.exports !== 'object') {
265
+ throw new Error('wu.exports must be an object');
266
+ }
267
+
268
+ // 🔐 Validar límites de exports
269
+ if (wu.exports && Object.keys(wu.exports).length > this.security.maxExports) {
270
+ throw new Error(`Too many exports (max ${this.security.maxExports})`);
271
+ }
272
+
273
+ // 🔐 Validar cada export path
274
+ if (wu.exports) {
275
+ for (const [key, path] of Object.entries(wu.exports)) {
276
+ if (!this._isUrlSafe(path)) {
277
+ throw new Error(`Dangerous export path: ${key}`);
278
+ }
279
+ }
280
+ }
281
+
282
+ if (wu.imports && !Array.isArray(wu.imports)) {
283
+ throw new Error('wu.imports must be an array');
284
+ }
285
+
286
+ // 🔐 Validar límites de imports
287
+ if (wu.imports && wu.imports.length > this.security.maxImports) {
288
+ throw new Error(`Too many imports (max ${this.security.maxImports})`);
289
+ }
290
+
291
+ if (wu.routes && !Array.isArray(wu.routes)) {
292
+ throw new Error('wu.routes must be an array');
293
+ }
294
+
295
+ // 🔐 Validar límites de routes
296
+ if (wu.routes && wu.routes.length > this.security.maxRoutes) {
297
+ throw new Error(`Too many routes (max ${this.security.maxRoutes})`);
298
+ }
299
+
300
+ if (wu.permissions && !Array.isArray(wu.permissions)) {
301
+ throw new Error('wu.permissions must be an array');
302
+ }
303
+ }
304
+
305
+ // Validate optional fields
306
+ if (manifest.styleMode !== undefined) {
307
+ const validModes = ['shared', 'isolated', 'fully-isolated'];
308
+ if (!validModes.includes(manifest.styleMode)) {
309
+ logger.warn(`[WuManifest] Invalid styleMode "${manifest.styleMode}", defaulting to "shared". Valid: ${validModes.join(', ')}`);
310
+ manifest.styleMode = 'shared';
311
+ }
312
+ }
313
+
314
+ if (manifest.version !== undefined && typeof manifest.version !== 'string') {
315
+ logger.warn('[WuManifest] version must be a string, ignoring');
316
+ delete manifest.version;
317
+ }
318
+
319
+ if (manifest.folder !== undefined) {
320
+ if (typeof manifest.folder !== 'string') {
321
+ logger.warn('[WuManifest] folder must be a string, ignoring');
322
+ delete manifest.folder;
323
+ } else if (this._hasDangerousPatterns(manifest.folder)) {
324
+ throw new Error('folder contains dangerous patterns');
325
+ }
326
+ }
327
+
328
+ // Normalizar y limpiar manifest
329
+ return this.normalize(manifest);
330
+ }
331
+
332
+ /**
333
+ * Normalizar manifest
334
+ * @param {Object} manifest - Manifest a normalizar
335
+ * @returns {Object} Manifest normalizado
336
+ */
337
+ normalize(manifest) {
338
+ const normalized = {
339
+ name: manifest.name.trim(),
340
+ entry: this.normalizeEntry(manifest.entry),
341
+ wu: {
342
+ exports: manifest.wu?.exports || {},
343
+ imports: manifest.wu?.imports || [],
344
+ routes: manifest.wu?.routes || [],
345
+ permissions: manifest.wu?.permissions || []
346
+ }
347
+ };
348
+
349
+ // Preservar campos opcionales del manifest (styleMode, version, folder, etc.)
350
+ if (manifest.styleMode) {
351
+ normalized.styleMode = manifest.styleMode;
352
+ }
353
+ if (manifest.version) {
354
+ normalized.version = manifest.version;
355
+ }
356
+ if (manifest.folder) {
357
+ normalized.folder = manifest.folder;
358
+ }
359
+
360
+ // Normalizar exports
361
+ if (normalized.wu.exports) {
362
+ const normalizedExports = {};
363
+ for (const [key, path] of Object.entries(normalized.wu.exports)) {
364
+ normalizedExports[key] = this.normalizeComponentPath(path);
365
+ }
366
+ normalized.wu.exports = normalizedExports;
367
+ }
368
+
369
+ // Validar imports
370
+ normalized.wu.imports = normalized.wu.imports.filter(imp => {
371
+ if (typeof imp !== 'string' || !imp.includes('.')) {
372
+ logger.warn(`[WuManifest] Invalid import format: ${imp}`);
373
+ return false;
374
+ }
375
+ return true;
376
+ });
377
+
378
+ return normalized;
379
+ }
380
+
381
+ /**
382
+ * Normalizar entry path
383
+ * @param {string} entry - Entry path
384
+ * @returns {string} Entry normalizado
385
+ */
386
+ normalizeEntry(entry) {
387
+ if (!entry) return 'index.js';
388
+
389
+ let normalized = entry.trim();
390
+
391
+ // Remover ./ inicial si está presente
392
+ if (normalized.startsWith('./')) {
393
+ normalized = normalized.substring(2);
394
+ }
395
+
396
+ // Agregar extensión si no la tiene
397
+ if (!normalized.includes('.')) {
398
+ normalized += '.js';
399
+ }
400
+
401
+ return normalized;
402
+ }
403
+
404
+ /**
405
+ * Normalizar path de componente
406
+ * @param {string} path - Path del componente
407
+ * @returns {string} Path normalizado
408
+ */
409
+ normalizeComponentPath(path) {
410
+ if (!path) return '';
411
+
412
+ let normalized = path.trim();
413
+
414
+ // Remover ./ inicial si está presente
415
+ if (normalized.startsWith('./')) {
416
+ normalized = normalized.substring(2);
417
+ }
418
+
419
+ // Agregar extensión si no la tiene
420
+ if (!normalized.includes('.')) {
421
+ normalized += '.js';
422
+ }
423
+
424
+ return normalized;
425
+ }
426
+
427
+ /**
428
+ * Validar dependencias de imports
429
+ * @param {Array} imports - Lista de imports
430
+ * @param {Map} availableApps - Apps disponibles
431
+ * @returns {Object} Resultado de validación
432
+ */
433
+ validateDependencies(imports, availableApps) {
434
+ const result = {
435
+ valid: [],
436
+ invalid: [],
437
+ missing: []
438
+ };
439
+
440
+ for (const importPath of imports) {
441
+ const [appName, componentName] = importPath.split('.');
442
+
443
+ if (!appName || !componentName) {
444
+ result.invalid.push({
445
+ import: importPath,
446
+ reason: 'Invalid format. Use "app.component"'
447
+ });
448
+ continue;
449
+ }
450
+
451
+ const app = availableApps.get(appName);
452
+ if (!app) {
453
+ result.missing.push({
454
+ import: importPath,
455
+ app: appName,
456
+ reason: 'App not registered'
457
+ });
458
+ continue;
459
+ }
460
+
461
+ const manifest = app.manifest;
462
+ const exportExists = manifest?.wu?.exports?.[componentName];
463
+
464
+ if (!exportExists) {
465
+ result.invalid.push({
466
+ import: importPath,
467
+ reason: `Component ${componentName} not exported by ${appName}`
468
+ });
469
+ continue;
470
+ }
471
+
472
+ result.valid.push({
473
+ import: importPath,
474
+ app: appName,
475
+ component: componentName,
476
+ path: exportExists
477
+ });
478
+ }
479
+
480
+ return result;
481
+ }
482
+
483
+ /**
484
+ * Crear manifest programáticamente
485
+ * @param {string} name - Nombre de la app
486
+ * @param {Object} config - Configuración
487
+ * @returns {Object} Manifest creado
488
+ */
489
+ create(name, config = {}) {
490
+ const manifest = {
491
+ name: name,
492
+ entry: config.entry || 'index.js',
493
+ wu: {
494
+ exports: config.exports || {},
495
+ imports: config.imports || [],
496
+ routes: config.routes || [],
497
+ permissions: config.permissions || []
498
+ }
499
+ };
500
+
501
+ return this.normalize(manifest);
502
+ }
503
+
504
+ /**
505
+ * Limpiar cache de manifests
506
+ * @param {string} pattern - Patrón opcional para limpiar URLs específicas
507
+ */
508
+ clearCache(pattern) {
509
+ if (pattern) {
510
+ const regex = new RegExp(pattern);
511
+ for (const [url] of this.cache) {
512
+ if (regex.test(url)) {
513
+ this.cache.delete(url);
514
+ logger.debug(`[WuManifest] 🗑️ Cleared cache for: ${url}`);
515
+ }
516
+ }
517
+ } else {
518
+ this.cache.clear();
519
+ logger.debug(`[WuManifest] 🗑️ Manifest cache cleared completely`);
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Obtener estadísticas del sistema de manifests
525
+ */
526
+ getStats() {
527
+ return {
528
+ cached: this.cache.size,
529
+ schemas: this.schemas.size,
530
+ cacheKeys: Array.from(this.cache.keys())
531
+ };
532
+ }
510
533
  }