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.
- package/LICENSE +39 -39
- package/README.md +408 -408
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15151 -15151
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/integrations/astro/README.md +127 -127
- package/integrations/astro/WuApp.astro +63 -63
- package/integrations/astro/WuShell.astro +39 -39
- package/integrations/astro/index.js +68 -68
- package/integrations/astro/package.json +38 -38
- package/integrations/astro/types.d.ts +53 -53
- package/package.json +161 -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 +477 -477
- package/src/core/wu-core.js +1398 -1398
- package/src/core/wu-error-boundary.js +382 -382
- package/src/core/wu-event-bus.js +348 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +190 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +272 -272
- package/src/core/wu-logger.js +134 -134
- package/src/core/wu-manifest.js +509 -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 +348 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +476 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +113 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.js +224 -224
- package/src/utils/dependency-resolver.js +327 -327
|
@@ -1,382 +1,382 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🛡️ WU-ERROR-BOUNDARY: ADVANCED ERROR HANDLING
|
|
3
|
-
*
|
|
4
|
-
* Sistema de error boundaries con:
|
|
5
|
-
* - Chain of Responsibility pattern
|
|
6
|
-
* - Recovery strategies
|
|
7
|
-
* - Error classification
|
|
8
|
-
* - Fallback rendering
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { logger } from './wu-logger.js';
|
|
12
|
-
|
|
13
|
-
export class WuErrorBoundary {
|
|
14
|
-
constructor(core) {
|
|
15
|
-
this.core = core;
|
|
16
|
-
this.handlers = [];
|
|
17
|
-
this.errorLog = [];
|
|
18
|
-
this.maxErrorLog = 100;
|
|
19
|
-
|
|
20
|
-
this.config = {
|
|
21
|
-
maxRetries: 3,
|
|
22
|
-
retryDelay: 1000,
|
|
23
|
-
showErrorUI: true
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
this.registerDefaultHandlers();
|
|
27
|
-
|
|
28
|
-
logger.debug('[WuErrorBoundary] 🛡️ Error boundary initialized');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 📋 REGISTER DEFAULT HANDLERS: Chain of responsibility
|
|
33
|
-
*/
|
|
34
|
-
registerDefaultHandlers() {
|
|
35
|
-
// 1. Network Error Handler
|
|
36
|
-
this.register({
|
|
37
|
-
name: 'network',
|
|
38
|
-
canHandle: (error) => {
|
|
39
|
-
return error.name === 'TypeError' &&
|
|
40
|
-
(error.message.includes('fetch') || error.message.includes('network'));
|
|
41
|
-
},
|
|
42
|
-
handle: async (error, context) => {
|
|
43
|
-
logger.debug('[ErrorHandler:Network] Handling network error');
|
|
44
|
-
|
|
45
|
-
// Retry con backoff
|
|
46
|
-
if (context.retryCount < this.config.maxRetries) {
|
|
47
|
-
const delay = this.config.retryDelay * Math.pow(2, context.retryCount);
|
|
48
|
-
logger.debug(`[ErrorHandler:Network] Retrying in ${delay}ms...`);
|
|
49
|
-
|
|
50
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
recovered: true,
|
|
54
|
-
action: 'retry',
|
|
55
|
-
retryCount: context.retryCount + 1
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
recovered: false,
|
|
61
|
-
action: 'fallback',
|
|
62
|
-
message: 'Network error: Please check your connection'
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// 2. Script Load Error Handler
|
|
68
|
-
this.register({
|
|
69
|
-
name: 'script-load',
|
|
70
|
-
canHandle: (error) => {
|
|
71
|
-
return error.name === 'Error' &&
|
|
72
|
-
(error.message.includes('Loading') ||
|
|
73
|
-
error.message.includes('Failed to fetch'));
|
|
74
|
-
},
|
|
75
|
-
handle: async (error, context) => {
|
|
76
|
-
logger.debug('[ErrorHandler:ScriptLoad] Handling script load error');
|
|
77
|
-
|
|
78
|
-
// Intentar URL alternativa si existe
|
|
79
|
-
if (context.fallbackUrl) {
|
|
80
|
-
logger.debug('[ErrorHandler:ScriptLoad] Trying fallback URL');
|
|
81
|
-
return {
|
|
82
|
-
recovered: true,
|
|
83
|
-
action: 'use-fallback-url',
|
|
84
|
-
url: context.fallbackUrl
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
recovered: false,
|
|
90
|
-
action: 'fallback',
|
|
91
|
-
message: 'Failed to load microfrontend'
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// 3. Mount Error Handler
|
|
97
|
-
this.register({
|
|
98
|
-
name: 'mount',
|
|
99
|
-
canHandle: (error) => {
|
|
100
|
-
return error.message && error.message.includes('mount');
|
|
101
|
-
},
|
|
102
|
-
handle: async (error, context) => {
|
|
103
|
-
logger.debug('[ErrorHandler:Mount] Handling mount error');
|
|
104
|
-
|
|
105
|
-
// Limpiar y reintentar
|
|
106
|
-
if (context.retryCount < 2) {
|
|
107
|
-
logger.debug('[ErrorHandler:Mount] Cleaning up and retrying...');
|
|
108
|
-
|
|
109
|
-
// Cleanup
|
|
110
|
-
try {
|
|
111
|
-
await this.core.unmount(context.appName);
|
|
112
|
-
} catch (cleanupError) {
|
|
113
|
-
logger.warn('[ErrorHandler:Mount] Cleanup failed:', cleanupError);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
recovered: true,
|
|
120
|
-
action: 'retry',
|
|
121
|
-
retryCount: context.retryCount + 1
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
recovered: false,
|
|
127
|
-
action: 'fallback',
|
|
128
|
-
message: 'Failed to mount application'
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// 4. Timeout Error Handler
|
|
134
|
-
this.register({
|
|
135
|
-
name: 'timeout',
|
|
136
|
-
canHandle: (error) => {
|
|
137
|
-
return error.name === 'TimeoutError' ||
|
|
138
|
-
error.message.includes('timeout');
|
|
139
|
-
},
|
|
140
|
-
handle: async (error, context) => {
|
|
141
|
-
logger.debug('[ErrorHandler:Timeout] Handling timeout error');
|
|
142
|
-
|
|
143
|
-
// Aumentar timeout y reintentar
|
|
144
|
-
if (context.retryCount < 2) {
|
|
145
|
-
return {
|
|
146
|
-
recovered: true,
|
|
147
|
-
action: 'retry-with-longer-timeout',
|
|
148
|
-
timeout: (context.timeout || 5000) * 2,
|
|
149
|
-
retryCount: context.retryCount + 1
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
recovered: false,
|
|
155
|
-
action: 'fallback',
|
|
156
|
-
message: 'Operation timed out'
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// 5. Generic Error Handler (fallback)
|
|
162
|
-
this.register({
|
|
163
|
-
name: 'generic',
|
|
164
|
-
canHandle: () => true, // Maneja todo
|
|
165
|
-
handle: async (error, context) => {
|
|
166
|
-
logger.debug('[ErrorHandler:Generic] Handling generic error');
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
recovered: false,
|
|
170
|
-
action: 'fallback',
|
|
171
|
-
message: error.message || 'An unexpected error occurred'
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 📦 REGISTER: Registrar error handler
|
|
179
|
-
* @param {Object} handler - Error handler { name, canHandle, handle }
|
|
180
|
-
*/
|
|
181
|
-
register(handler) {
|
|
182
|
-
if (!handler.name || !handler.canHandle || !handler.handle) {
|
|
183
|
-
throw new Error('[WuErrorBoundary] Handler must have name, canHandle, and handle');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.handlers.push(handler);
|
|
187
|
-
logger.debug(`[WuErrorBoundary] Handler "${handler.name}" registered`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* 🎯 HANDLE: Manejar error con chain of responsibility
|
|
192
|
-
* @param {Error} error - Error a manejar
|
|
193
|
-
* @param {Object} context - Contexto del error
|
|
194
|
-
* @returns {Promise<Object>} Recovery result
|
|
195
|
-
*/
|
|
196
|
-
async handle(error, context = {}) {
|
|
197
|
-
// Agregar valores por defecto
|
|
198
|
-
context = {
|
|
199
|
-
retryCount: 0,
|
|
200
|
-
timestamp: Date.now(),
|
|
201
|
-
...context
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// Log error
|
|
205
|
-
this.logError(error, context);
|
|
206
|
-
|
|
207
|
-
// Buscar handler que pueda manejar este error
|
|
208
|
-
for (const handler of this.handlers) {
|
|
209
|
-
try {
|
|
210
|
-
if (handler.canHandle(error, context)) {
|
|
211
|
-
logger.debug(`[WuErrorBoundary] Using handler: ${handler.name}`);
|
|
212
|
-
|
|
213
|
-
const result = await handler.handle(error, context);
|
|
214
|
-
|
|
215
|
-
if (result.recovered) {
|
|
216
|
-
logger.debug(`[WuErrorBoundary] ✅ Error recovered by ${handler.name}`);
|
|
217
|
-
return result;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Si no se recuperó, renderizar fallback
|
|
221
|
-
if (result.action === 'fallback' && this.config.showErrorUI) {
|
|
222
|
-
this.renderFallback(context, result);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
} catch (handlerError) {
|
|
228
|
-
console.error(`[WuErrorBoundary] Handler "${handler.name}" failed:`, handlerError);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// No handler pudo manejar el error
|
|
233
|
-
console.error('[WuErrorBoundary] ❌ No handler could handle the error');
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
recovered: false,
|
|
237
|
-
action: 'unhandled',
|
|
238
|
-
message: 'Unhandled error'
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 🎨 RENDER FALLBACK: Renderizar UI de error
|
|
244
|
-
* @param {Object} context - Contexto del error
|
|
245
|
-
* @param {Object} result - Resultado del handler
|
|
246
|
-
*/
|
|
247
|
-
renderFallback(context, result) {
|
|
248
|
-
if (!context.container) {
|
|
249
|
-
logger.warn('[WuErrorBoundary] No container to render fallback');
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const container = typeof context.container === 'string'
|
|
254
|
-
? document.querySelector(context.container)
|
|
255
|
-
: context.container;
|
|
256
|
-
|
|
257
|
-
if (!container) return;
|
|
258
|
-
|
|
259
|
-
// Limpiar container
|
|
260
|
-
container.innerHTML = '';
|
|
261
|
-
|
|
262
|
-
// Crear UI de error
|
|
263
|
-
const errorUI = document.createElement('div');
|
|
264
|
-
errorUI.className = 'wu-error-boundary';
|
|
265
|
-
|
|
266
|
-
Object.assign(errorUI.style, {
|
|
267
|
-
padding: '2rem',
|
|
268
|
-
borderRadius: '8px',
|
|
269
|
-
background: '#fff3cd',
|
|
270
|
-
border: '1px solid #ffc107',
|
|
271
|
-
color: '#856404',
|
|
272
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
273
|
-
textAlign: 'center'
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const icon = document.createElement('div');
|
|
277
|
-
icon.textContent = '⚠️';
|
|
278
|
-
icon.style.fontSize = '3rem';
|
|
279
|
-
icon.style.marginBottom = '1rem';
|
|
280
|
-
|
|
281
|
-
const title = document.createElement('h3');
|
|
282
|
-
title.textContent = 'Application Error';
|
|
283
|
-
title.style.margin = '0 0 0.5rem 0';
|
|
284
|
-
|
|
285
|
-
const message = document.createElement('p');
|
|
286
|
-
message.textContent = result.message || 'An error occurred';
|
|
287
|
-
message.style.margin = '0 0 1rem 0';
|
|
288
|
-
|
|
289
|
-
const button = document.createElement('button');
|
|
290
|
-
button.textContent = '🔄 Reload';
|
|
291
|
-
Object.assign(button.style, {
|
|
292
|
-
padding: '0.5rem 1rem',
|
|
293
|
-
background: '#ffc107',
|
|
294
|
-
border: 'none',
|
|
295
|
-
borderRadius: '4px',
|
|
296
|
-
cursor: 'pointer',
|
|
297
|
-
fontWeight: 'bold',
|
|
298
|
-
color: '#000'
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
button.addEventListener('click', () => window.location.reload());
|
|
302
|
-
|
|
303
|
-
errorUI.appendChild(icon);
|
|
304
|
-
errorUI.appendChild(title);
|
|
305
|
-
errorUI.appendChild(message);
|
|
306
|
-
errorUI.appendChild(button);
|
|
307
|
-
|
|
308
|
-
container.appendChild(errorUI);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* 📝 LOG ERROR: Registrar error
|
|
313
|
-
* @param {Error} error - Error
|
|
314
|
-
* @param {Object} context - Contexto
|
|
315
|
-
*/
|
|
316
|
-
logError(error, context) {
|
|
317
|
-
const errorEntry = {
|
|
318
|
-
error: {
|
|
319
|
-
name: error.name,
|
|
320
|
-
message: error.message,
|
|
321
|
-
stack: error.stack
|
|
322
|
-
},
|
|
323
|
-
context,
|
|
324
|
-
timestamp: Date.now()
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
this.errorLog.push(errorEntry);
|
|
328
|
-
|
|
329
|
-
// Mantener límite de log
|
|
330
|
-
if (this.errorLog.length > this.maxErrorLog) {
|
|
331
|
-
this.errorLog.shift();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 📋 GET ERROR LOG: Obtener log de errores
|
|
337
|
-
* @param {number} limit - Límite de errores a retornar
|
|
338
|
-
* @returns {Array}
|
|
339
|
-
*/
|
|
340
|
-
getErrorLog(limit = 10) {
|
|
341
|
-
return this.errorLog.slice(-limit);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* 📊 GET STATS: Estadísticas de errores
|
|
346
|
-
* @returns {Object}
|
|
347
|
-
*/
|
|
348
|
-
getStats() {
|
|
349
|
-
const errorsByType = {};
|
|
350
|
-
|
|
351
|
-
this.errorLog.forEach(entry => {
|
|
352
|
-
const type = entry.error.name || 'Unknown';
|
|
353
|
-
errorsByType[type] = (errorsByType[type] || 0) + 1;
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
totalErrors: this.errorLog.length,
|
|
358
|
-
handlers: this.handlers.length,
|
|
359
|
-
errorsByType,
|
|
360
|
-
recentErrors: this.getErrorLog(5)
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* ⚙️ CONFIGURE: Configurar error boundary
|
|
366
|
-
* @param {Object} config - Nueva configuración
|
|
367
|
-
*/
|
|
368
|
-
configure(config) {
|
|
369
|
-
this.config = {
|
|
370
|
-
...this.config,
|
|
371
|
-
...config
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* 🧹 CLEANUP: Limpiar error boundary
|
|
377
|
-
*/
|
|
378
|
-
cleanup() {
|
|
379
|
-
this.errorLog = [];
|
|
380
|
-
logger.debug('[WuErrorBoundary] 🧹 Error boundary cleaned up');
|
|
381
|
-
}
|
|
382
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 🛡️ WU-ERROR-BOUNDARY: ADVANCED ERROR HANDLING
|
|
3
|
+
*
|
|
4
|
+
* Sistema de error boundaries con:
|
|
5
|
+
* - Chain of Responsibility pattern
|
|
6
|
+
* - Recovery strategies
|
|
7
|
+
* - Error classification
|
|
8
|
+
* - Fallback rendering
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { logger } from './wu-logger.js';
|
|
12
|
+
|
|
13
|
+
export class WuErrorBoundary {
|
|
14
|
+
constructor(core) {
|
|
15
|
+
this.core = core;
|
|
16
|
+
this.handlers = [];
|
|
17
|
+
this.errorLog = [];
|
|
18
|
+
this.maxErrorLog = 100;
|
|
19
|
+
|
|
20
|
+
this.config = {
|
|
21
|
+
maxRetries: 3,
|
|
22
|
+
retryDelay: 1000,
|
|
23
|
+
showErrorUI: true
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.registerDefaultHandlers();
|
|
27
|
+
|
|
28
|
+
logger.debug('[WuErrorBoundary] 🛡️ Error boundary initialized');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 📋 REGISTER DEFAULT HANDLERS: Chain of responsibility
|
|
33
|
+
*/
|
|
34
|
+
registerDefaultHandlers() {
|
|
35
|
+
// 1. Network Error Handler
|
|
36
|
+
this.register({
|
|
37
|
+
name: 'network',
|
|
38
|
+
canHandle: (error) => {
|
|
39
|
+
return error.name === 'TypeError' &&
|
|
40
|
+
(error.message.includes('fetch') || error.message.includes('network'));
|
|
41
|
+
},
|
|
42
|
+
handle: async (error, context) => {
|
|
43
|
+
logger.debug('[ErrorHandler:Network] Handling network error');
|
|
44
|
+
|
|
45
|
+
// Retry con backoff
|
|
46
|
+
if (context.retryCount < this.config.maxRetries) {
|
|
47
|
+
const delay = this.config.retryDelay * Math.pow(2, context.retryCount);
|
|
48
|
+
logger.debug(`[ErrorHandler:Network] Retrying in ${delay}ms...`);
|
|
49
|
+
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
recovered: true,
|
|
54
|
+
action: 'retry',
|
|
55
|
+
retryCount: context.retryCount + 1
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
recovered: false,
|
|
61
|
+
action: 'fallback',
|
|
62
|
+
message: 'Network error: Please check your connection'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 2. Script Load Error Handler
|
|
68
|
+
this.register({
|
|
69
|
+
name: 'script-load',
|
|
70
|
+
canHandle: (error) => {
|
|
71
|
+
return error.name === 'Error' &&
|
|
72
|
+
(error.message.includes('Loading') ||
|
|
73
|
+
error.message.includes('Failed to fetch'));
|
|
74
|
+
},
|
|
75
|
+
handle: async (error, context) => {
|
|
76
|
+
logger.debug('[ErrorHandler:ScriptLoad] Handling script load error');
|
|
77
|
+
|
|
78
|
+
// Intentar URL alternativa si existe
|
|
79
|
+
if (context.fallbackUrl) {
|
|
80
|
+
logger.debug('[ErrorHandler:ScriptLoad] Trying fallback URL');
|
|
81
|
+
return {
|
|
82
|
+
recovered: true,
|
|
83
|
+
action: 'use-fallback-url',
|
|
84
|
+
url: context.fallbackUrl
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
recovered: false,
|
|
90
|
+
action: 'fallback',
|
|
91
|
+
message: 'Failed to load microfrontend'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 3. Mount Error Handler
|
|
97
|
+
this.register({
|
|
98
|
+
name: 'mount',
|
|
99
|
+
canHandle: (error) => {
|
|
100
|
+
return error.message && error.message.includes('mount');
|
|
101
|
+
},
|
|
102
|
+
handle: async (error, context) => {
|
|
103
|
+
logger.debug('[ErrorHandler:Mount] Handling mount error');
|
|
104
|
+
|
|
105
|
+
// Limpiar y reintentar
|
|
106
|
+
if (context.retryCount < 2) {
|
|
107
|
+
logger.debug('[ErrorHandler:Mount] Cleaning up and retrying...');
|
|
108
|
+
|
|
109
|
+
// Cleanup
|
|
110
|
+
try {
|
|
111
|
+
await this.core.unmount(context.appName);
|
|
112
|
+
} catch (cleanupError) {
|
|
113
|
+
logger.warn('[ErrorHandler:Mount] Cleanup failed:', cleanupError);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
recovered: true,
|
|
120
|
+
action: 'retry',
|
|
121
|
+
retryCount: context.retryCount + 1
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
recovered: false,
|
|
127
|
+
action: 'fallback',
|
|
128
|
+
message: 'Failed to mount application'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 4. Timeout Error Handler
|
|
134
|
+
this.register({
|
|
135
|
+
name: 'timeout',
|
|
136
|
+
canHandle: (error) => {
|
|
137
|
+
return error.name === 'TimeoutError' ||
|
|
138
|
+
error.message.includes('timeout');
|
|
139
|
+
},
|
|
140
|
+
handle: async (error, context) => {
|
|
141
|
+
logger.debug('[ErrorHandler:Timeout] Handling timeout error');
|
|
142
|
+
|
|
143
|
+
// Aumentar timeout y reintentar
|
|
144
|
+
if (context.retryCount < 2) {
|
|
145
|
+
return {
|
|
146
|
+
recovered: true,
|
|
147
|
+
action: 'retry-with-longer-timeout',
|
|
148
|
+
timeout: (context.timeout || 5000) * 2,
|
|
149
|
+
retryCount: context.retryCount + 1
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
recovered: false,
|
|
155
|
+
action: 'fallback',
|
|
156
|
+
message: 'Operation timed out'
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 5. Generic Error Handler (fallback)
|
|
162
|
+
this.register({
|
|
163
|
+
name: 'generic',
|
|
164
|
+
canHandle: () => true, // Maneja todo
|
|
165
|
+
handle: async (error, context) => {
|
|
166
|
+
logger.debug('[ErrorHandler:Generic] Handling generic error');
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
recovered: false,
|
|
170
|
+
action: 'fallback',
|
|
171
|
+
message: error.message || 'An unexpected error occurred'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 📦 REGISTER: Registrar error handler
|
|
179
|
+
* @param {Object} handler - Error handler { name, canHandle, handle }
|
|
180
|
+
*/
|
|
181
|
+
register(handler) {
|
|
182
|
+
if (!handler.name || !handler.canHandle || !handler.handle) {
|
|
183
|
+
throw new Error('[WuErrorBoundary] Handler must have name, canHandle, and handle');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.handlers.push(handler);
|
|
187
|
+
logger.debug(`[WuErrorBoundary] Handler "${handler.name}" registered`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 🎯 HANDLE: Manejar error con chain of responsibility
|
|
192
|
+
* @param {Error} error - Error a manejar
|
|
193
|
+
* @param {Object} context - Contexto del error
|
|
194
|
+
* @returns {Promise<Object>} Recovery result
|
|
195
|
+
*/
|
|
196
|
+
async handle(error, context = {}) {
|
|
197
|
+
// Agregar valores por defecto
|
|
198
|
+
context = {
|
|
199
|
+
retryCount: 0,
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
...context
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Log error
|
|
205
|
+
this.logError(error, context);
|
|
206
|
+
|
|
207
|
+
// Buscar handler que pueda manejar este error
|
|
208
|
+
for (const handler of this.handlers) {
|
|
209
|
+
try {
|
|
210
|
+
if (handler.canHandle(error, context)) {
|
|
211
|
+
logger.debug(`[WuErrorBoundary] Using handler: ${handler.name}`);
|
|
212
|
+
|
|
213
|
+
const result = await handler.handle(error, context);
|
|
214
|
+
|
|
215
|
+
if (result.recovered) {
|
|
216
|
+
logger.debug(`[WuErrorBoundary] ✅ Error recovered by ${handler.name}`);
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Si no se recuperó, renderizar fallback
|
|
221
|
+
if (result.action === 'fallback' && this.config.showErrorUI) {
|
|
222
|
+
this.renderFallback(context, result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
} catch (handlerError) {
|
|
228
|
+
console.error(`[WuErrorBoundary] Handler "${handler.name}" failed:`, handlerError);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// No handler pudo manejar el error
|
|
233
|
+
console.error('[WuErrorBoundary] ❌ No handler could handle the error');
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
recovered: false,
|
|
237
|
+
action: 'unhandled',
|
|
238
|
+
message: 'Unhandled error'
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 🎨 RENDER FALLBACK: Renderizar UI de error
|
|
244
|
+
* @param {Object} context - Contexto del error
|
|
245
|
+
* @param {Object} result - Resultado del handler
|
|
246
|
+
*/
|
|
247
|
+
renderFallback(context, result) {
|
|
248
|
+
if (!context.container) {
|
|
249
|
+
logger.warn('[WuErrorBoundary] No container to render fallback');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const container = typeof context.container === 'string'
|
|
254
|
+
? document.querySelector(context.container)
|
|
255
|
+
: context.container;
|
|
256
|
+
|
|
257
|
+
if (!container) return;
|
|
258
|
+
|
|
259
|
+
// Limpiar container
|
|
260
|
+
container.innerHTML = '';
|
|
261
|
+
|
|
262
|
+
// Crear UI de error
|
|
263
|
+
const errorUI = document.createElement('div');
|
|
264
|
+
errorUI.className = 'wu-error-boundary';
|
|
265
|
+
|
|
266
|
+
Object.assign(errorUI.style, {
|
|
267
|
+
padding: '2rem',
|
|
268
|
+
borderRadius: '8px',
|
|
269
|
+
background: '#fff3cd',
|
|
270
|
+
border: '1px solid #ffc107',
|
|
271
|
+
color: '#856404',
|
|
272
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
273
|
+
textAlign: 'center'
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const icon = document.createElement('div');
|
|
277
|
+
icon.textContent = '⚠️';
|
|
278
|
+
icon.style.fontSize = '3rem';
|
|
279
|
+
icon.style.marginBottom = '1rem';
|
|
280
|
+
|
|
281
|
+
const title = document.createElement('h3');
|
|
282
|
+
title.textContent = 'Application Error';
|
|
283
|
+
title.style.margin = '0 0 0.5rem 0';
|
|
284
|
+
|
|
285
|
+
const message = document.createElement('p');
|
|
286
|
+
message.textContent = result.message || 'An error occurred';
|
|
287
|
+
message.style.margin = '0 0 1rem 0';
|
|
288
|
+
|
|
289
|
+
const button = document.createElement('button');
|
|
290
|
+
button.textContent = '🔄 Reload';
|
|
291
|
+
Object.assign(button.style, {
|
|
292
|
+
padding: '0.5rem 1rem',
|
|
293
|
+
background: '#ffc107',
|
|
294
|
+
border: 'none',
|
|
295
|
+
borderRadius: '4px',
|
|
296
|
+
cursor: 'pointer',
|
|
297
|
+
fontWeight: 'bold',
|
|
298
|
+
color: '#000'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
button.addEventListener('click', () => window.location.reload());
|
|
302
|
+
|
|
303
|
+
errorUI.appendChild(icon);
|
|
304
|
+
errorUI.appendChild(title);
|
|
305
|
+
errorUI.appendChild(message);
|
|
306
|
+
errorUI.appendChild(button);
|
|
307
|
+
|
|
308
|
+
container.appendChild(errorUI);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 📝 LOG ERROR: Registrar error
|
|
313
|
+
* @param {Error} error - Error
|
|
314
|
+
* @param {Object} context - Contexto
|
|
315
|
+
*/
|
|
316
|
+
logError(error, context) {
|
|
317
|
+
const errorEntry = {
|
|
318
|
+
error: {
|
|
319
|
+
name: error.name,
|
|
320
|
+
message: error.message,
|
|
321
|
+
stack: error.stack
|
|
322
|
+
},
|
|
323
|
+
context,
|
|
324
|
+
timestamp: Date.now()
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
this.errorLog.push(errorEntry);
|
|
328
|
+
|
|
329
|
+
// Mantener límite de log
|
|
330
|
+
if (this.errorLog.length > this.maxErrorLog) {
|
|
331
|
+
this.errorLog.shift();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 📋 GET ERROR LOG: Obtener log de errores
|
|
337
|
+
* @param {number} limit - Límite de errores a retornar
|
|
338
|
+
* @returns {Array}
|
|
339
|
+
*/
|
|
340
|
+
getErrorLog(limit = 10) {
|
|
341
|
+
return this.errorLog.slice(-limit);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 📊 GET STATS: Estadísticas de errores
|
|
346
|
+
* @returns {Object}
|
|
347
|
+
*/
|
|
348
|
+
getStats() {
|
|
349
|
+
const errorsByType = {};
|
|
350
|
+
|
|
351
|
+
this.errorLog.forEach(entry => {
|
|
352
|
+
const type = entry.error.name || 'Unknown';
|
|
353
|
+
errorsByType[type] = (errorsByType[type] || 0) + 1;
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
totalErrors: this.errorLog.length,
|
|
358
|
+
handlers: this.handlers.length,
|
|
359
|
+
errorsByType,
|
|
360
|
+
recentErrors: this.getErrorLog(5)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* ⚙️ CONFIGURE: Configurar error boundary
|
|
366
|
+
* @param {Object} config - Nueva configuración
|
|
367
|
+
*/
|
|
368
|
+
configure(config) {
|
|
369
|
+
this.config = {
|
|
370
|
+
...this.config,
|
|
371
|
+
...config
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 🧹 CLEANUP: Limpiar error boundary
|
|
377
|
+
*/
|
|
378
|
+
cleanup() {
|
|
379
|
+
this.errorLog = [];
|
|
380
|
+
logger.debug('[WuErrorBoundary] 🧹 Error boundary cleaned up');
|
|
381
|
+
}
|
|
382
|
+
}
|