wu-framework 1.1.16 → 1.1.18
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/package.json +48 -3
- package/src/adapters/alpine/index.d.ts +60 -0
- package/src/adapters/alpine/index.js +231 -0
- package/src/adapters/alpine.d.ts +3 -0
- package/src/adapters/alpine.js +3 -0
- package/src/adapters/htmx/index.d.ts +60 -0
- package/src/adapters/htmx/index.js +242 -0
- package/src/adapters/htmx.d.ts +3 -0
- package/src/adapters/htmx.js +3 -0
- package/src/adapters/index.js +60 -3
- package/src/adapters/qwik/index.d.ts +52 -0
- package/src/adapters/qwik/index.js +214 -0
- package/src/adapters/qwik.d.ts +3 -0
- package/src/adapters/qwik.js +3 -0
- package/src/adapters/react/ai.js +135 -135
- package/src/adapters/react/index.d.ts +246 -246
- package/src/adapters/react/index.js +695 -695
- package/src/adapters/stencil/index.d.ts +54 -0
- package/src/adapters/stencil/index.js +228 -0
- package/src/adapters/stencil.d.ts +3 -0
- package/src/adapters/stencil.js +3 -0
- package/src/adapters/stimulus/index.d.ts +60 -0
- package/src/adapters/stimulus/index.js +255 -0
- package/src/adapters/stimulus.d.ts +3 -0
- package/src/adapters/stimulus.js +3 -0
- package/src/adapters/svelte/index.js +1 -1
- package/src/adapters/vanilla/index.js +1 -1
- package/src/adapters/vue/index.js +8 -0
- package/src/core/wu-cache.js +24 -3
- package/src/core/wu-core.js +15 -1
- package/src/core/wu-error-boundary.js +17 -3
- package/src/core/wu-event-bus.js +43 -1
- package/src/core/wu-html-parser.js +13 -4
- package/src/core/wu-loader.js +162 -50
- package/src/core/wu-logger.js +21 -13
- package/src/core/wu-manifest.js +23 -0
- package/src/core/wu-plugin.js +57 -4
- package/src/core/wu-proxy-sandbox.js +2 -1
- package/src/core/wu-script-executor.js +48 -0
- package/src/core/wu-store.js +13 -3
- package/src/index.d.ts +317 -0
- package/src/index.js +11 -1
- package/dist/wu-framework.cjs.js +0 -3
- package/dist/wu-framework.cjs.js.map +0 -1
- package/dist/wu-framework.dev.js +0 -15302
- package/dist/wu-framework.dev.js.map +0 -1
- package/dist/wu-framework.esm.js +0 -3
- package/dist/wu-framework.esm.js.map +0 -1
- package/dist/wu-framework.umd.js +0 -3
- package/dist/wu-framework.umd.js.map +0 -1
package/src/core/wu-cache.js
CHANGED
|
@@ -32,6 +32,9 @@ export class WuCache {
|
|
|
32
32
|
cooldownUntil: 0
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
// Rate limit notification flag (log only once per cooldown)
|
|
36
|
+
this._rateLimitNotified = false;
|
|
37
|
+
|
|
35
38
|
// Memory cache
|
|
36
39
|
this.memoryCache = new Map();
|
|
37
40
|
|
|
@@ -66,6 +69,7 @@ export class WuCache {
|
|
|
66
69
|
}
|
|
67
70
|
// Cooldown terminado
|
|
68
71
|
this.rateLimiting.inCooldown = false;
|
|
72
|
+
this._rateLimitNotified = false;
|
|
69
73
|
this.rateLimiting.operations = [];
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -91,7 +95,22 @@ export class WuCache {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
94
|
-
*
|
|
98
|
+
* Handle rate-limited operations: log once per cooldown, return diagnostic object
|
|
99
|
+
* @param {string} operation - The operation that was rate limited ('get' or 'set')
|
|
100
|
+
* @param {string} key - The cache key that was rejected
|
|
101
|
+
* @returns {{ rateLimited: true, operation: string, key: string }}
|
|
102
|
+
*/
|
|
103
|
+
_onRateLimited(operation, key) {
|
|
104
|
+
if (!this._rateLimitNotified) {
|
|
105
|
+
const cooldownRemaining = Math.max(0, this.rateLimiting.cooldownUntil - Date.now());
|
|
106
|
+
logger.warn(`[WuCache] Rate limited: ${operation} for key "${key}" rejected. ${cooldownRemaining}ms remaining in cooldown.`);
|
|
107
|
+
this._rateLimitNotified = true;
|
|
108
|
+
}
|
|
109
|
+
return { rateLimited: true, operation, key };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* GET RATE LIMIT STATUS
|
|
95
114
|
*/
|
|
96
115
|
getRateLimitStatus() {
|
|
97
116
|
const now = Date.now();
|
|
@@ -115,7 +134,8 @@ export class WuCache {
|
|
|
115
134
|
get(key) {
|
|
116
135
|
// 🔐 Check rate limit
|
|
117
136
|
if (!this._checkRateLimit()) {
|
|
118
|
-
|
|
137
|
+
this._onRateLimited('get', key);
|
|
138
|
+
return null;
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
// 1. Buscar en memoria
|
|
@@ -162,7 +182,8 @@ export class WuCache {
|
|
|
162
182
|
set(key, value, ttl) {
|
|
163
183
|
// 🔐 Check rate limit
|
|
164
184
|
if (!this._checkRateLimit()) {
|
|
165
|
-
|
|
185
|
+
this._onRateLimited('set', key);
|
|
186
|
+
return false;
|
|
166
187
|
}
|
|
167
188
|
|
|
168
189
|
try {
|
package/src/core/wu-core.js
CHANGED
|
@@ -333,6 +333,20 @@ export class WuCore {
|
|
|
333
333
|
} catch (error) {
|
|
334
334
|
logger.wuError(`Mount attempt ${attempt + 1} failed for ${appName}:`, error);
|
|
335
335
|
|
|
336
|
+
// Cleanup sandbox to prevent orphaned shadow DOMs
|
|
337
|
+
try {
|
|
338
|
+
if (this.sandbox && this.sandbox.sandboxes && this.sandbox.sandboxes.has(appName)) {
|
|
339
|
+
const sb = this.sandbox.sandboxes.get(appName);
|
|
340
|
+
if (sb && sb.proxySandbox) {
|
|
341
|
+
sb.proxySandbox.deactivate();
|
|
342
|
+
}
|
|
343
|
+
this.sandbox.sandboxes.delete(appName);
|
|
344
|
+
logger.wuDebug(`Sandbox cleaned up after mount failure for ${appName}`);
|
|
345
|
+
}
|
|
346
|
+
} catch (cleanupError) {
|
|
347
|
+
logger.wuWarn(`Sandbox cleanup failed for ${appName}:`, cleanupError);
|
|
348
|
+
}
|
|
349
|
+
|
|
336
350
|
// Use error boundary for intelligent error handling
|
|
337
351
|
const errorResult = await this.errorBoundary.handle(error, {
|
|
338
352
|
appName,
|
|
@@ -671,7 +685,7 @@ export class WuCore {
|
|
|
671
685
|
* Intelligently resolves module paths with real-time validation
|
|
672
686
|
*/
|
|
673
687
|
async resolveModulePath(app) {
|
|
674
|
-
|
|
688
|
+
const entryFile = app.manifest?.entry || 'main.js';
|
|
675
689
|
const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
|
|
676
690
|
|
|
677
691
|
// Normalize path: Remove duplicated directories
|
|
@@ -314,19 +314,33 @@ export class WuErrorBoundary {
|
|
|
314
314
|
* @param {Object} context - Contexto
|
|
315
315
|
*/
|
|
316
316
|
logError(error, context) {
|
|
317
|
+
// Truncate stack to first 5 lines to prevent retaining large object references
|
|
318
|
+
const stack = error.stack ? error.stack.split('\n').slice(0, 5).join('\n') : '';
|
|
319
|
+
|
|
320
|
+
// Shallow-copy context to avoid retaining references to live objects
|
|
321
|
+
const safeContext = {};
|
|
322
|
+
for (const key of Object.keys(context)) {
|
|
323
|
+
const val = context[key];
|
|
324
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val === null) {
|
|
325
|
+
safeContext[key] = val;
|
|
326
|
+
} else {
|
|
327
|
+
safeContext[key] = String(val);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
317
331
|
const errorEntry = {
|
|
318
332
|
error: {
|
|
319
333
|
name: error.name,
|
|
320
334
|
message: error.message,
|
|
321
|
-
stack
|
|
335
|
+
stack
|
|
322
336
|
},
|
|
323
|
-
context,
|
|
337
|
+
context: safeContext,
|
|
324
338
|
timestamp: Date.now()
|
|
325
339
|
};
|
|
326
340
|
|
|
327
341
|
this.errorLog.push(errorEntry);
|
|
328
342
|
|
|
329
|
-
//
|
|
343
|
+
// Maintain log limit
|
|
330
344
|
if (this.errorLog.length > this.maxErrorLog) {
|
|
331
345
|
this.errorLog.shift();
|
|
332
346
|
}
|
package/src/core/wu-event-bus.js
CHANGED
|
@@ -10,6 +10,25 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { logger } from './wu-logger.js';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} WuEvent
|
|
15
|
+
* @property {string} name - Event name
|
|
16
|
+
* @property {*} data - Event payload
|
|
17
|
+
* @property {number} timestamp - Event timestamp
|
|
18
|
+
* @property {string} appName - Source app name
|
|
19
|
+
* @property {Object} meta - Additional metadata
|
|
20
|
+
* @property {boolean} verified - Whether origin was verified
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} WuEventBusConfig
|
|
25
|
+
* @property {number} [maxHistory=100] - Maximum events in history
|
|
26
|
+
* @property {boolean} [enableReplay=true] - Enable event replay
|
|
27
|
+
* @property {boolean} [enableWildcards=true] - Enable wildcard matching
|
|
28
|
+
* @property {boolean} [logEvents=false] - Log all events
|
|
29
|
+
* @property {boolean} [strictMode=false] - Reject unauthorized events
|
|
30
|
+
* @property {boolean} [validateOrigin=true] - Validate event origins
|
|
31
|
+
*/
|
|
13
32
|
|
|
14
33
|
export class WuEventBus {
|
|
15
34
|
constructor() {
|
|
@@ -20,16 +39,21 @@ export class WuEventBus {
|
|
|
20
39
|
this.authorizedApps = new Map(); // appName -> { token, permissions }
|
|
21
40
|
this.trustedEvents = new Set(['wu:*', 'system:*']); // Eventos del sistema
|
|
22
41
|
|
|
42
|
+
// Auto-detect production environment for strictMode default
|
|
43
|
+
const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
|
|
44
|
+
|
|
23
45
|
this.config = {
|
|
24
46
|
maxHistory: 100,
|
|
25
47
|
enableReplay: true,
|
|
26
48
|
enableWildcards: true,
|
|
27
49
|
logEvents: false,
|
|
28
50
|
// 🔐 Opciones de seguridad
|
|
29
|
-
strictMode:
|
|
51
|
+
strictMode: isProduction, // Auto-enabled in production, permissive in development
|
|
30
52
|
validateOrigin: true // Valida que appName sea una app registrada
|
|
31
53
|
};
|
|
32
54
|
|
|
55
|
+
this._permissiveWarned = false;
|
|
56
|
+
|
|
33
57
|
this.stats = {
|
|
34
58
|
emitted: 0,
|
|
35
59
|
subscriptions: 0,
|
|
@@ -127,6 +151,19 @@ export class WuEventBus {
|
|
|
127
151
|
return `wu_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
128
152
|
}
|
|
129
153
|
|
|
154
|
+
/**
|
|
155
|
+
* WARN PERMISSIVE MODE: Log a one-time warning when strictMode is off
|
|
156
|
+
* Alerts developers that events are flowing without authorization checks
|
|
157
|
+
*/
|
|
158
|
+
_warnPermissiveMode() {
|
|
159
|
+
if (this._permissiveWarned) return;
|
|
160
|
+
this._permissiveWarned = true;
|
|
161
|
+
logger.warn(
|
|
162
|
+
'[WuEventBus] strictMode is disabled. Events are emitted without authorization checks. ' +
|
|
163
|
+
'Enable strictMode for production by calling enableStrictMode() or setting NODE_ENV=production.'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
130
167
|
/**
|
|
131
168
|
* 📢 EMIT: Emitir evento con validación de origen
|
|
132
169
|
* @param {string} eventName - Nombre del evento
|
|
@@ -136,6 +173,11 @@ export class WuEventBus {
|
|
|
136
173
|
emit(eventName, data, options = {}) {
|
|
137
174
|
const appName = options.appName || 'unknown';
|
|
138
175
|
|
|
176
|
+
// Warn once if running in permissive mode (strictMode off)
|
|
177
|
+
if (!this.config.strictMode) {
|
|
178
|
+
this._warnPermissiveMode();
|
|
179
|
+
}
|
|
180
|
+
|
|
139
181
|
// 🔐 Validar origen si está habilitado
|
|
140
182
|
if (this.config.validateOrigin && this.config.strictMode) {
|
|
141
183
|
if (!this._validateOrigin(eventName, appName, options.token)) {
|
|
@@ -62,19 +62,28 @@ export class WuHtmlParser {
|
|
|
62
62
|
return this._cache.get(cacheKey);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
|
|
65
|
+
const parser = new DOMParser();
|
|
66
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
67
67
|
|
|
68
68
|
const inlineScripts = [];
|
|
69
69
|
const externalScripts = [];
|
|
70
70
|
const inlineStyles = [];
|
|
71
71
|
const externalStyles = [];
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
const ctx = {
|
|
74
74
|
inlineScripts, externalScripts,
|
|
75
75
|
inlineStyles, externalStyles,
|
|
76
76
|
baseUrl
|
|
77
|
-
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// DOMParser moves <style>, <link>, and some <script> tags to <head>.
|
|
80
|
+
// Extract resources from both head and body to capture everything.
|
|
81
|
+
if (doc.head) {
|
|
82
|
+
this._extractResources(doc.head, ctx);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const temp = doc.body || doc.documentElement;
|
|
86
|
+
this._extractResources(temp, ctx);
|
|
78
87
|
|
|
79
88
|
const result = {
|
|
80
89
|
dom: temp.innerHTML,
|
package/src/core/wu-loader.js
CHANGED
|
@@ -1,68 +1,177 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WU-LOADER: SISTEMA DE CARGA DINAMICA UNIVERSAL
|
|
3
3
|
* Carga aplicaciones y componentes sin depender del framework
|
|
4
|
+
*
|
|
5
|
+
* Cache strategy: LRU with TTL eviction.
|
|
6
|
+
* Entries track lastAccess time. When the cache reaches maxCacheSize,
|
|
7
|
+
* the least recently accessed entry is evicted. Entries older than
|
|
8
|
+
* cacheTTL are treated as stale and removed on access or eviction.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
import { logger } from './wu-logger.js';
|
|
7
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} WuLoaderOptions
|
|
15
|
+
* @property {number} [maxCacheSize=50] - Maximum cache entries
|
|
16
|
+
* @property {number} [cacheTTL=1800000] - Cache TTL in ms (default 30min)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} WuLoaderStats
|
|
21
|
+
* @property {number} cached - Number of cached entries
|
|
22
|
+
* @property {number} loading - Number of in-flight loads
|
|
23
|
+
* @property {number} maxCacheSize - Max cache size setting
|
|
24
|
+
* @property {number} cacheTTL - Cache TTL setting
|
|
25
|
+
* @property {string[]} cacheKeys - Cached URL keys
|
|
26
|
+
*/
|
|
27
|
+
|
|
8
28
|
export class WuLoader {
|
|
9
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @param {number} [options.maxCacheSize=50] - Maximum number of entries in the cache
|
|
32
|
+
* @param {number} [options.cacheTTL=1800000] - Time-to-live for cache entries in ms (default 30 minutes)
|
|
33
|
+
*/
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
this.maxCacheSize = options.maxCacheSize ?? 50;
|
|
36
|
+
this.cacheTTL = options.cacheTTL ?? 1800000;
|
|
10
37
|
this.cache = new Map();
|
|
11
38
|
this.loadingPromises = new Map();
|
|
12
39
|
|
|
13
|
-
logger.debug('[WuLoader]
|
|
40
|
+
logger.debug('[WuLoader] Dynamic loader initialized');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read from cache with TTL validation and LRU access tracking.
|
|
45
|
+
* Returns undefined if the entry does not exist or has expired.
|
|
46
|
+
* @param {string} key
|
|
47
|
+
* @returns {string|undefined}
|
|
48
|
+
*/
|
|
49
|
+
_cacheGet(key) {
|
|
50
|
+
if (!this.cache.has(key)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const entry = this.cache.get(key);
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
|
|
57
|
+
if (now - entry.timestamp > this.cacheTTL) {
|
|
58
|
+
this.cache.delete(key);
|
|
59
|
+
logger.debug(`[WuLoader] Cache expired for: ${key}`);
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Promote: delete and re-insert so iteration order reflects recency.
|
|
64
|
+
// Map iteration order in JS follows insertion order, so the oldest
|
|
65
|
+
// inserted entry is always first -- exactly what we need for LRU eviction.
|
|
66
|
+
this.cache.delete(key);
|
|
67
|
+
entry.lastAccess = now;
|
|
68
|
+
this.cache.set(key, entry);
|
|
69
|
+
|
|
70
|
+
return entry.code;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write to cache. Evicts stale and LRU entries before inserting.
|
|
75
|
+
* @param {string} key
|
|
76
|
+
* @param {string} code
|
|
77
|
+
*/
|
|
78
|
+
_cacheSet(key, code) {
|
|
79
|
+
// If the key already exists, remove it first so re-insertion
|
|
80
|
+
// moves it to the end (most-recently-used position).
|
|
81
|
+
if (this.cache.has(key)) {
|
|
82
|
+
this.cache.delete(key);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this._evictIfNeeded();
|
|
86
|
+
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
this.cache.set(key, {
|
|
89
|
+
code,
|
|
90
|
+
timestamp: now,
|
|
91
|
+
lastAccess: now
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Evict entries until cache is below maxCacheSize.
|
|
97
|
+
*
|
|
98
|
+
* Two-pass strategy:
|
|
99
|
+
* 1. Remove all expired entries (TTL exceeded).
|
|
100
|
+
* 2. If still at capacity, remove the least recently accessed entry.
|
|
101
|
+
* Because Map preserves insertion order and _cacheGet promotes on
|
|
102
|
+
* access, the first key from the iterator is always the LRU entry.
|
|
103
|
+
*/
|
|
104
|
+
_evictIfNeeded() {
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
|
|
107
|
+
// Pass 1: purge expired entries
|
|
108
|
+
for (const [key, entry] of this.cache) {
|
|
109
|
+
if (now - entry.timestamp > this.cacheTTL) {
|
|
110
|
+
this.cache.delete(key);
|
|
111
|
+
logger.debug(`[WuLoader] Evicted expired entry: ${key}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Pass 2: evict LRU entries until we are under the limit
|
|
116
|
+
while (this.cache.size >= this.maxCacheSize) {
|
|
117
|
+
// Map.keys().next() gives us the oldest-inserted key (LRU)
|
|
118
|
+
const oldestKey = this.cache.keys().next().value;
|
|
119
|
+
this.cache.delete(oldestKey);
|
|
120
|
+
logger.debug(`[WuLoader] Evicted LRU entry: ${oldestKey}`);
|
|
121
|
+
}
|
|
14
122
|
}
|
|
15
123
|
|
|
16
124
|
/**
|
|
17
|
-
* Cargar
|
|
18
|
-
* @param {string} appUrl - URL base de la
|
|
19
|
-
* @param {Object} manifest - Manifest de la
|
|
20
|
-
* @returns {string}
|
|
125
|
+
* Cargar aplicacion completa
|
|
126
|
+
* @param {string} appUrl - URL base de la aplicacion
|
|
127
|
+
* @param {Object} manifest - Manifest de la aplicacion
|
|
128
|
+
* @returns {string} Codigo JavaScript de la aplicacion
|
|
21
129
|
*/
|
|
22
130
|
async loadApp(appUrl, manifest) {
|
|
23
131
|
const entryFile = manifest?.entry || 'index.js';
|
|
24
132
|
const fullUrl = `${appUrl}/${entryFile}`;
|
|
25
133
|
|
|
26
|
-
logger.debug(`[WuLoader]
|
|
134
|
+
logger.debug(`[WuLoader] Loading app from: ${fullUrl}`);
|
|
27
135
|
|
|
28
136
|
try {
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
137
|
+
// Check cache with TTL and LRU tracking
|
|
138
|
+
const cached = this._cacheGet(fullUrl);
|
|
139
|
+
if (cached !== undefined) {
|
|
140
|
+
logger.debug(`[WuLoader] Cache hit for: ${fullUrl}`);
|
|
141
|
+
return cached;
|
|
33
142
|
}
|
|
34
143
|
|
|
35
|
-
//
|
|
144
|
+
// Check if already loading
|
|
36
145
|
if (this.loadingPromises.has(fullUrl)) {
|
|
37
|
-
logger.debug(`[WuLoader]
|
|
146
|
+
logger.debug(`[WuLoader] Loading in progress for: ${fullUrl}`);
|
|
38
147
|
return await this.loadingPromises.get(fullUrl);
|
|
39
148
|
}
|
|
40
149
|
|
|
41
|
-
//
|
|
150
|
+
// Create loading promise
|
|
42
151
|
const loadingPromise = this.fetchCode(fullUrl);
|
|
43
152
|
this.loadingPromises.set(fullUrl, loadingPromise);
|
|
44
153
|
|
|
45
154
|
const code = await loadingPromise;
|
|
46
155
|
|
|
47
|
-
//
|
|
156
|
+
// Clean up loading promise and cache result
|
|
48
157
|
this.loadingPromises.delete(fullUrl);
|
|
49
|
-
this.
|
|
158
|
+
this._cacheSet(fullUrl, code);
|
|
50
159
|
|
|
51
|
-
logger.debug(`[WuLoader]
|
|
160
|
+
logger.debug(`[WuLoader] App loaded successfully: ${fullUrl}`);
|
|
52
161
|
return code;
|
|
53
162
|
|
|
54
163
|
} catch (error) {
|
|
55
164
|
this.loadingPromises.delete(fullUrl);
|
|
56
|
-
console.error(`[WuLoader]
|
|
165
|
+
console.error(`[WuLoader] Failed to load app: ${fullUrl}`, error);
|
|
57
166
|
throw new Error(`Failed to load app from ${fullUrl}: ${error.message}`);
|
|
58
167
|
}
|
|
59
168
|
}
|
|
60
169
|
|
|
61
170
|
/**
|
|
62
|
-
* Cargar componente
|
|
63
|
-
* @param {string} appUrl - URL base de la
|
|
171
|
+
* Cargar componente especifico
|
|
172
|
+
* @param {string} appUrl - URL base de la aplicacion
|
|
64
173
|
* @param {string} componentPath - Ruta del componente
|
|
65
|
-
* @returns {Function}
|
|
174
|
+
* @returns {Function} Funcion del componente
|
|
66
175
|
*/
|
|
67
176
|
async loadComponent(appUrl, componentPath) {
|
|
68
177
|
// Normalizar ruta del componente
|
|
@@ -76,13 +185,13 @@ export class WuLoader {
|
|
|
76
185
|
|
|
77
186
|
const fullUrl = `${appUrl}/${normalizedPath}`;
|
|
78
187
|
|
|
79
|
-
logger.debug(`[WuLoader]
|
|
188
|
+
logger.debug(`[WuLoader] Loading component from: ${fullUrl}`);
|
|
80
189
|
|
|
81
190
|
try {
|
|
82
|
-
// Cargar
|
|
191
|
+
// Cargar codigo del componente
|
|
83
192
|
const code = await this.loadCode(fullUrl);
|
|
84
193
|
|
|
85
|
-
// Crear
|
|
194
|
+
// Crear funcion que retorna el componente
|
|
86
195
|
const componentFunction = new Function('require', 'module', 'exports', `
|
|
87
196
|
${code}
|
|
88
197
|
return typeof module.exports === 'function' ? module.exports :
|
|
@@ -99,39 +208,40 @@ export class WuLoader {
|
|
|
99
208
|
|
|
100
209
|
const component = componentFunction(fakeRequire, fakeModule, fakeModule.exports);
|
|
101
210
|
|
|
102
|
-
logger.debug(`[WuLoader]
|
|
211
|
+
logger.debug(`[WuLoader] Component loaded: ${componentPath}`);
|
|
103
212
|
return component;
|
|
104
213
|
|
|
105
214
|
} catch (error) {
|
|
106
|
-
console.error(`[WuLoader]
|
|
215
|
+
console.error(`[WuLoader] Failed to load component: ${componentPath}`, error);
|
|
107
216
|
throw new Error(`Failed to load component ${componentPath}: ${error.message}`);
|
|
108
217
|
}
|
|
109
218
|
}
|
|
110
219
|
|
|
111
220
|
/**
|
|
112
|
-
* Cargar
|
|
221
|
+
* Cargar codigo con cache
|
|
113
222
|
* @param {string} url - URL del archivo
|
|
114
|
-
* @returns {string}
|
|
223
|
+
* @returns {string} Codigo JavaScript
|
|
115
224
|
*/
|
|
116
225
|
async loadCode(url) {
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
226
|
+
// Check cache with TTL and LRU tracking
|
|
227
|
+
const cached = this._cacheGet(url);
|
|
228
|
+
if (cached !== undefined) {
|
|
229
|
+
return cached;
|
|
120
230
|
}
|
|
121
231
|
|
|
122
|
-
//
|
|
232
|
+
// Check if already loading
|
|
123
233
|
if (this.loadingPromises.has(url)) {
|
|
124
234
|
return await this.loadingPromises.get(url);
|
|
125
235
|
}
|
|
126
236
|
|
|
127
|
-
//
|
|
237
|
+
// Create loading promise
|
|
128
238
|
const loadingPromise = this.fetchCode(url);
|
|
129
239
|
this.loadingPromises.set(url, loadingPromise);
|
|
130
240
|
|
|
131
241
|
try {
|
|
132
242
|
const code = await loadingPromise;
|
|
133
243
|
this.loadingPromises.delete(url);
|
|
134
|
-
this.
|
|
244
|
+
this._cacheSet(url, code);
|
|
135
245
|
return code;
|
|
136
246
|
} catch (error) {
|
|
137
247
|
this.loadingPromises.delete(url);
|
|
@@ -140,9 +250,9 @@ export class WuLoader {
|
|
|
140
250
|
}
|
|
141
251
|
|
|
142
252
|
/**
|
|
143
|
-
* Realizar fetch del
|
|
253
|
+
* Realizar fetch del codigo
|
|
144
254
|
* @param {string} url - URL del archivo
|
|
145
|
-
* @returns {string}
|
|
255
|
+
* @returns {string} Codigo JavaScript
|
|
146
256
|
*/
|
|
147
257
|
async fetchCode(url) {
|
|
148
258
|
const response = await fetch(url, {
|
|
@@ -170,25 +280,25 @@ export class WuLoader {
|
|
|
170
280
|
* @param {Array} appConfigs - Configuraciones de aplicaciones
|
|
171
281
|
*/
|
|
172
282
|
async preload(appConfigs) {
|
|
173
|
-
logger.debug(`[WuLoader]
|
|
283
|
+
logger.debug(`[WuLoader] Preloading ${appConfigs.length} apps...`);
|
|
174
284
|
|
|
175
285
|
const preloadPromises = appConfigs.map(async (config) => {
|
|
176
286
|
try {
|
|
177
287
|
await this.loadApp(config.url, config.manifest);
|
|
178
|
-
logger.debug(`[WuLoader]
|
|
288
|
+
logger.debug(`[WuLoader] Preloaded: ${config.name}`);
|
|
179
289
|
} catch (error) {
|
|
180
|
-
logger.warn(`[WuLoader]
|
|
290
|
+
logger.warn(`[WuLoader] Failed to preload ${config.name}:`, error.message);
|
|
181
291
|
}
|
|
182
292
|
});
|
|
183
293
|
|
|
184
294
|
await Promise.allSettled(preloadPromises);
|
|
185
|
-
logger.debug(`[WuLoader]
|
|
295
|
+
logger.debug(`[WuLoader] Preload completed`);
|
|
186
296
|
}
|
|
187
297
|
|
|
188
298
|
/**
|
|
189
|
-
* Verificar si una URL
|
|
299
|
+
* Verificar si una URL esta disponible
|
|
190
300
|
* @param {string} url - URL a verificar
|
|
191
|
-
* @returns {boolean} True si
|
|
301
|
+
* @returns {boolean} True si esta disponible
|
|
192
302
|
*/
|
|
193
303
|
async isAvailable(url) {
|
|
194
304
|
try {
|
|
@@ -232,9 +342,9 @@ export class WuLoader {
|
|
|
232
342
|
try {
|
|
233
343
|
const component = await this.loadComponent(app.url, exportPath);
|
|
234
344
|
resolved.set(importPath, component);
|
|
235
|
-
logger.debug(`[WuLoader]
|
|
345
|
+
logger.debug(`[WuLoader] Resolved dependency: ${importPath}`);
|
|
236
346
|
} catch (error) {
|
|
237
|
-
console.error(`[WuLoader]
|
|
347
|
+
console.error(`[WuLoader] Failed to resolve: ${importPath}`, error);
|
|
238
348
|
}
|
|
239
349
|
}
|
|
240
350
|
|
|
@@ -243,7 +353,7 @@ export class WuLoader {
|
|
|
243
353
|
|
|
244
354
|
/**
|
|
245
355
|
* Limpiar cache
|
|
246
|
-
* @param {string} pattern -
|
|
356
|
+
* @param {string} pattern - Patron opcional para limpiar URLs especificas
|
|
247
357
|
*/
|
|
248
358
|
clearCache(pattern) {
|
|
249
359
|
if (pattern) {
|
|
@@ -251,23 +361,25 @@ export class WuLoader {
|
|
|
251
361
|
for (const [url] of this.cache) {
|
|
252
362
|
if (regex.test(url)) {
|
|
253
363
|
this.cache.delete(url);
|
|
254
|
-
logger.debug(`[WuLoader]
|
|
364
|
+
logger.debug(`[WuLoader] Cleared cache for: ${url}`);
|
|
255
365
|
}
|
|
256
366
|
}
|
|
257
367
|
} else {
|
|
258
368
|
this.cache.clear();
|
|
259
|
-
logger.debug(`[WuLoader]
|
|
369
|
+
logger.debug(`[WuLoader] Cache cleared completely`);
|
|
260
370
|
}
|
|
261
371
|
}
|
|
262
372
|
|
|
263
373
|
/**
|
|
264
|
-
* Obtener
|
|
374
|
+
* Obtener estadisticas del loader
|
|
265
375
|
*/
|
|
266
376
|
getStats() {
|
|
267
377
|
return {
|
|
268
378
|
cached: this.cache.size,
|
|
379
|
+
maxCacheSize: this.maxCacheSize,
|
|
380
|
+
cacheTTL: this.cacheTTL,
|
|
269
381
|
loading: this.loadingPromises.size,
|
|
270
382
|
cacheKeys: Array.from(this.cache.keys())
|
|
271
383
|
};
|
|
272
384
|
}
|
|
273
|
-
}
|
|
385
|
+
}
|
package/src/core/wu-logger.js
CHANGED
|
@@ -23,19 +23,27 @@ export class WuLogger {
|
|
|
23
23
|
* Detectar si estamos en desarrollo
|
|
24
24
|
*/
|
|
25
25
|
detectEnvironment() {
|
|
26
|
-
//
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
// 1. Explicit flag takes priority
|
|
27
|
+
if (typeof window !== 'undefined' && window.WU_DEBUG === true) return true;
|
|
28
|
+
if (typeof window !== 'undefined' && window.WU_DEBUG === false) return false;
|
|
29
|
+
|
|
30
|
+
// 2. NODE_ENV check (works in bundlers and Node)
|
|
31
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return false;
|
|
32
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') return true;
|
|
33
|
+
|
|
34
|
+
// 3. Browser heuristics (only if window exists)
|
|
35
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
36
|
+
const hostname = window.location.hostname;
|
|
37
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') return true;
|
|
38
|
+
|
|
39
|
+
// URL param override
|
|
40
|
+
try {
|
|
41
|
+
if (new URLSearchParams(window.location.search).has('wu-debug')) return true;
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 4. Default: assume production
|
|
46
|
+
return false;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
/**
|
package/src/core/wu-manifest.js
CHANGED
|
@@ -302,6 +302,29 @@ export class WuManifest {
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
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
|
+
|
|
305
328
|
// Normalizar y limpiar manifest
|
|
306
329
|
return this.normalize(manifest);
|
|
307
330
|
}
|