wu-framework 1.1.15 → 1.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -20
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15511 -15146
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/package.json +166 -161
- package/src/adapters/angular/ai.js +30 -30
- package/src/adapters/angular/index.d.ts +154 -154
- package/src/adapters/angular/index.js +932 -932
- package/src/adapters/angular.d.ts +3 -3
- package/src/adapters/angular.js +3 -3
- package/src/adapters/index.js +168 -168
- package/src/adapters/lit/ai.js +20 -20
- package/src/adapters/lit/index.d.ts +120 -120
- package/src/adapters/lit/index.js +721 -721
- package/src/adapters/lit.d.ts +3 -3
- package/src/adapters/lit.js +3 -3
- package/src/adapters/preact/ai.js +33 -33
- package/src/adapters/preact/index.d.ts +108 -108
- package/src/adapters/preact/index.js +661 -661
- package/src/adapters/preact.d.ts +3 -3
- package/src/adapters/preact.js +3 -3
- package/src/adapters/react/index.js +48 -54
- package/src/adapters/react.d.ts +3 -3
- package/src/adapters/react.js +3 -3
- package/src/adapters/shared.js +64 -64
- package/src/adapters/solid/ai.js +32 -32
- package/src/adapters/solid/index.d.ts +101 -101
- package/src/adapters/solid/index.js +586 -586
- package/src/adapters/solid.d.ts +3 -3
- package/src/adapters/solid.js +3 -3
- package/src/adapters/svelte/ai.js +31 -31
- package/src/adapters/svelte/index.d.ts +166 -166
- package/src/adapters/svelte/index.js +798 -798
- package/src/adapters/svelte.d.ts +3 -3
- package/src/adapters/svelte.js +3 -3
- package/src/adapters/vanilla/ai.js +30 -30
- package/src/adapters/vanilla/index.d.ts +179 -179
- package/src/adapters/vanilla/index.js +785 -785
- package/src/adapters/vanilla.d.ts +3 -3
- package/src/adapters/vanilla.js +3 -3
- package/src/adapters/vue/ai.js +52 -52
- package/src/adapters/vue/index.d.ts +299 -299
- package/src/adapters/vue/index.js +610 -610
- package/src/adapters/vue.d.ts +3 -3
- package/src/adapters/vue.js +3 -3
- package/src/ai/wu-ai-actions.js +261 -261
- package/src/ai/wu-ai-agent.js +546 -546
- package/src/ai/wu-ai-browser-primitives.js +354 -354
- package/src/ai/wu-ai-browser.js +380 -380
- package/src/ai/wu-ai-context.js +332 -332
- package/src/ai/wu-ai-conversation.js +613 -613
- package/src/ai/wu-ai-orchestrate.js +1021 -1021
- package/src/ai/wu-ai-permissions.js +381 -381
- package/src/ai/wu-ai-provider.js +700 -700
- package/src/ai/wu-ai-schema.js +225 -225
- package/src/ai/wu-ai-triggers.js +396 -396
- package/src/ai/wu-ai.js +804 -804
- package/src/core/wu-app.js +236 -236
- package/src/core/wu-cache.js +498 -477
- package/src/core/wu-core.js +1412 -1398
- package/src/core/wu-error-boundary.js +396 -382
- package/src/core/wu-event-bus.js +390 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +199 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +385 -273
- package/src/core/wu-logger.js +142 -134
- package/src/core/wu-manifest.js +532 -509
- package/src/core/wu-mcp-bridge.js +432 -432
- package/src/core/wu-overrides.js +510 -510
- package/src/core/wu-performance.js +228 -228
- package/src/core/wu-plugin.js +401 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +477 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +161 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-store.js +13 -3
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.d.ts +317 -0
- package/src/index.js +234 -224
- package/src/utils/dependency-resolver.js +327 -327
package/src/core/wu-manifest.js
CHANGED
|
@@ -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
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return normalized;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Normalizar path
|
|
383
|
-
* @param {string}
|
|
384
|
-
* @returns {string}
|
|
385
|
-
*/
|
|
386
|
-
|
|
387
|
-
if (!
|
|
388
|
-
|
|
389
|
-
let normalized =
|
|
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
|
-
*
|
|
406
|
-
* @param {
|
|
407
|
-
* @
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
}
|