wu-framework 1.1.14 → 1.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +39 -39
  2. package/README.md +408 -408
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15151 -15151
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js.map +1 -1
  7. package/dist/wu-framework.umd.js.map +1 -1
  8. package/integrations/astro/README.md +127 -127
  9. package/integrations/astro/WuApp.astro +63 -63
  10. package/integrations/astro/WuShell.astro +39 -39
  11. package/integrations/astro/index.js +68 -68
  12. package/integrations/astro/package.json +38 -38
  13. package/integrations/astro/types.d.ts +53 -53
  14. package/package.json +161 -161
  15. package/src/adapters/angular/ai.js +30 -30
  16. package/src/adapters/angular/index.d.ts +154 -154
  17. package/src/adapters/angular/index.js +932 -932
  18. package/src/adapters/angular.d.ts +3 -3
  19. package/src/adapters/angular.js +3 -3
  20. package/src/adapters/index.js +168 -168
  21. package/src/adapters/lit/ai.js +20 -20
  22. package/src/adapters/lit/index.d.ts +120 -120
  23. package/src/adapters/lit/index.js +721 -721
  24. package/src/adapters/lit.d.ts +3 -3
  25. package/src/adapters/lit.js +3 -3
  26. package/src/adapters/preact/ai.js +33 -33
  27. package/src/adapters/preact/index.d.ts +108 -108
  28. package/src/adapters/preact/index.js +661 -661
  29. package/src/adapters/preact.d.ts +3 -3
  30. package/src/adapters/preact.js +3 -3
  31. package/src/adapters/react/index.js +48 -54
  32. package/src/adapters/react.d.ts +3 -3
  33. package/src/adapters/react.js +3 -3
  34. package/src/adapters/shared.js +64 -64
  35. package/src/adapters/solid/ai.js +32 -32
  36. package/src/adapters/solid/index.d.ts +101 -101
  37. package/src/adapters/solid/index.js +586 -586
  38. package/src/adapters/solid.d.ts +3 -3
  39. package/src/adapters/solid.js +3 -3
  40. package/src/adapters/svelte/ai.js +31 -31
  41. package/src/adapters/svelte/index.d.ts +166 -166
  42. package/src/adapters/svelte/index.js +798 -798
  43. package/src/adapters/svelte.d.ts +3 -3
  44. package/src/adapters/svelte.js +3 -3
  45. package/src/adapters/vanilla/ai.js +30 -30
  46. package/src/adapters/vanilla/index.d.ts +179 -179
  47. package/src/adapters/vanilla/index.js +785 -785
  48. package/src/adapters/vanilla.d.ts +3 -3
  49. package/src/adapters/vanilla.js +3 -3
  50. package/src/adapters/vue/ai.js +52 -52
  51. package/src/adapters/vue/index.d.ts +299 -299
  52. package/src/adapters/vue/index.js +610 -610
  53. package/src/adapters/vue.d.ts +3 -3
  54. package/src/adapters/vue.js +3 -3
  55. package/src/ai/wu-ai-actions.js +261 -261
  56. package/src/ai/wu-ai-agent.js +546 -546
  57. package/src/ai/wu-ai-browser-primitives.js +354 -354
  58. package/src/ai/wu-ai-browser.js +380 -380
  59. package/src/ai/wu-ai-context.js +332 -332
  60. package/src/ai/wu-ai-conversation.js +613 -613
  61. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  62. package/src/ai/wu-ai-permissions.js +381 -381
  63. package/src/ai/wu-ai-provider.js +700 -700
  64. package/src/ai/wu-ai-schema.js +225 -225
  65. package/src/ai/wu-ai-triggers.js +396 -396
  66. package/src/ai/wu-ai.js +804 -804
  67. package/src/core/wu-app.js +236 -236
  68. package/src/core/wu-cache.js +477 -477
  69. package/src/core/wu-core.js +1398 -1398
  70. package/src/core/wu-error-boundary.js +382 -382
  71. package/src/core/wu-event-bus.js +348 -348
  72. package/src/core/wu-hooks.js +350 -350
  73. package/src/core/wu-html-parser.js +190 -190
  74. package/src/core/wu-iframe-sandbox.js +328 -328
  75. package/src/core/wu-loader.js +272 -272
  76. package/src/core/wu-logger.js +134 -134
  77. package/src/core/wu-manifest.js +509 -509
  78. package/src/core/wu-mcp-bridge.js +432 -432
  79. package/src/core/wu-overrides.js +510 -510
  80. package/src/core/wu-performance.js +228 -228
  81. package/src/core/wu-plugin.js +348 -348
  82. package/src/core/wu-prefetch.js +414 -414
  83. package/src/core/wu-proxy-sandbox.js +476 -476
  84. package/src/core/wu-sandbox.js +779 -779
  85. package/src/core/wu-script-executor.js +113 -113
  86. package/src/core/wu-snapshot-sandbox.js +227 -227
  87. package/src/core/wu-strategies.js +256 -256
  88. package/src/core/wu-style-bridge.js +477 -477
  89. package/src/index.js +224 -224
  90. package/src/utils/dependency-resolver.js +327 -327
@@ -1,510 +1,510 @@
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
+ // 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
+ }
510
510
  }