@syntropysoft/syntropyfront 0.1.0-alpha.1 → 0.2.2
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 +317 -219
- package/dist/index.cjs +308 -3234
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +309 -3231
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +14 -12
package/dist/index.js
CHANGED
|
@@ -1,3349 +1,427 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* BreadcrumbManager - Gestiona breadcrumbs
|
|
3
|
+
* Responsabilidad única: Almacenar y gestionar breadcrumbs
|
|
4
4
|
*/
|
|
5
|
-
class
|
|
6
|
-
constructor(
|
|
7
|
-
this.maxBreadcrumbs = maxBreadcrumbs;
|
|
5
|
+
class BreadcrumbManager {
|
|
6
|
+
constructor() {
|
|
8
7
|
this.breadcrumbs = [];
|
|
9
|
-
this.agent = null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Configura el agent para envío automático
|
|
14
|
-
* @param {Object} agent - Instancia del agent
|
|
15
|
-
*/
|
|
16
|
-
setAgent(agent) {
|
|
17
|
-
this.agent = agent;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Configura el tamaño máximo de breadcrumbs
|
|
22
|
-
* @param {number} maxBreadcrumbs - Nuevo tamaño máximo
|
|
23
|
-
*/
|
|
24
|
-
setMaxBreadcrumbs(maxBreadcrumbs) {
|
|
25
|
-
this.maxBreadcrumbs = maxBreadcrumbs;
|
|
26
|
-
|
|
27
|
-
// Si el nuevo tamaño es menor, eliminar breadcrumbs excedentes
|
|
28
|
-
if (this.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
29
|
-
this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Obtiene el tamaño máximo actual
|
|
35
|
-
* @returns {number} Tamaño máximo de breadcrumbs
|
|
36
|
-
*/
|
|
37
|
-
getMaxBreadcrumbs() {
|
|
38
|
-
return this.maxBreadcrumbs;
|
|
39
8
|
}
|
|
40
9
|
|
|
41
|
-
|
|
42
|
-
* Añade un breadcrumb a la lista
|
|
43
|
-
* @param {Object} crumb - El breadcrumb a añadir
|
|
44
|
-
* @param {string} crumb.category - Categoría del evento (ui, network, error, etc.)
|
|
45
|
-
* @param {string} crumb.message - Mensaje descriptivo
|
|
46
|
-
* @param {Object} [crumb.data] - Datos adicionales opcionales
|
|
47
|
-
*/
|
|
48
|
-
add(crumb) {
|
|
10
|
+
add(category, message, data = {}) {
|
|
49
11
|
const breadcrumb = {
|
|
50
|
-
|
|
51
|
-
|
|
12
|
+
category,
|
|
13
|
+
message,
|
|
14
|
+
data,
|
|
15
|
+
timestamp: new Date().toISOString()
|
|
52
16
|
};
|
|
53
|
-
|
|
54
|
-
if (this.breadcrumbs.length >= this.maxBreadcrumbs) {
|
|
55
|
-
this.breadcrumbs.shift(); // Elimina el más antiguo
|
|
56
|
-
}
|
|
57
17
|
|
|
58
18
|
this.breadcrumbs.push(breadcrumb);
|
|
59
|
-
|
|
60
|
-
// Callback opcional para logging
|
|
61
|
-
if (this.onBreadcrumbAdded) {
|
|
62
|
-
this.onBreadcrumbAdded(breadcrumb);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Enviar al agent si está configurado
|
|
66
|
-
if (this.agent) {
|
|
67
|
-
this.agent.sendBreadcrumbs([breadcrumb]);
|
|
68
|
-
}
|
|
19
|
+
return breadcrumb;
|
|
69
20
|
}
|
|
70
21
|
|
|
71
|
-
/**
|
|
72
|
-
* Devuelve todos los breadcrumbs
|
|
73
|
-
* @returns {Array} Copia de todos los breadcrumbs
|
|
74
|
-
*/
|
|
75
22
|
getAll() {
|
|
76
|
-
return
|
|
23
|
+
return this.breadcrumbs;
|
|
77
24
|
}
|
|
78
25
|
|
|
79
|
-
/**
|
|
80
|
-
* Limpia todos los breadcrumbs
|
|
81
|
-
*/
|
|
82
26
|
clear() {
|
|
83
27
|
this.breadcrumbs = [];
|
|
84
28
|
}
|
|
85
29
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* @param {string} category - Categoría a filtrar
|
|
89
|
-
* @returns {Array} Breadcrumbs de la categoría especificada
|
|
90
|
-
*/
|
|
91
|
-
getByCategory(category) {
|
|
92
|
-
return this.breadcrumbs.filter(b => b.category === category);
|
|
30
|
+
getCount() {
|
|
31
|
+
return this.breadcrumbs.length;
|
|
93
32
|
}
|
|
94
33
|
}
|
|
95
34
|
|
|
96
|
-
// Instancia singleton
|
|
97
|
-
const breadcrumbStore = new BreadcrumbStore();
|
|
98
|
-
|
|
99
35
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
36
|
+
* ErrorManager - Gestiona errores
|
|
37
|
+
* Responsabilidad única: Formatear y gestionar errores
|
|
102
38
|
*/
|
|
103
|
-
class
|
|
39
|
+
class ErrorManager {
|
|
104
40
|
constructor() {
|
|
105
|
-
this.
|
|
106
|
-
this.circularRefs = new Map();
|
|
107
|
-
this.refCounter = 0;
|
|
41
|
+
this.errors = [];
|
|
108
42
|
}
|
|
109
43
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
44
|
+
send(error, context = {}) {
|
|
45
|
+
const errorData = {
|
|
46
|
+
message: error.message,
|
|
47
|
+
stack: error.stack,
|
|
48
|
+
context,
|
|
49
|
+
timestamp: new Date().toISOString()
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.errors.push(errorData);
|
|
53
|
+
return errorData;
|
|
54
|
+
}
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// Convertir a JSON
|
|
126
|
-
return JSON.stringify(safeObj);
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error('SyntropyFront: Error en serialización robusta:', error);
|
|
129
|
-
|
|
130
|
-
// Fallback: intentar serialización básica con información de error
|
|
131
|
-
return JSON.stringify({
|
|
132
|
-
__serializationError: true,
|
|
133
|
-
error: error.message,
|
|
134
|
-
originalType: typeof obj,
|
|
135
|
-
isObject: obj !== null && typeof obj === 'object',
|
|
136
|
-
timestamp: new Date().toISOString()
|
|
137
|
-
});
|
|
138
|
-
}
|
|
56
|
+
getAll() {
|
|
57
|
+
return this.errors;
|
|
139
58
|
}
|
|
140
59
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* @param {string} path - Ruta actual en el objeto
|
|
145
|
-
* @returns {any} Objeto serializable
|
|
146
|
-
*/
|
|
147
|
-
makeSerializable(obj, path = '') {
|
|
148
|
-
// Casos primitivos
|
|
149
|
-
if (obj === null || obj === undefined) {
|
|
150
|
-
return obj;
|
|
151
|
-
}
|
|
60
|
+
clear() {
|
|
61
|
+
this.errors = [];
|
|
62
|
+
}
|
|
152
63
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
64
|
+
getCount() {
|
|
65
|
+
return this.errors.length;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
156
68
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Logger - Hace logging solo en errores
|
|
71
|
+
* Responsabilidad única: Mostrar mensajes solo cuando hay errores
|
|
72
|
+
*/
|
|
73
|
+
class Logger {
|
|
74
|
+
constructor() {
|
|
75
|
+
this.isSilent = true; // Por defecto silente
|
|
76
|
+
}
|
|
164
77
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
78
|
+
log(message, data = null) {
|
|
79
|
+
// No loggear nada en modo silente
|
|
80
|
+
if (this.isSilent) return;
|
|
81
|
+
|
|
82
|
+
if (data) {
|
|
83
|
+
console.log(message, data);
|
|
84
|
+
} else {
|
|
85
|
+
console.log(message);
|
|
173
86
|
}
|
|
87
|
+
}
|
|
174
88
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
89
|
+
error(message, data = null) {
|
|
90
|
+
// SIEMPRE loggear errores
|
|
91
|
+
if (data) {
|
|
92
|
+
console.error(message, data);
|
|
93
|
+
} else {
|
|
94
|
+
console.error(message);
|
|
181
95
|
}
|
|
96
|
+
}
|
|
182
97
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
__circular: true,
|
|
190
|
-
refId: refId
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
this.seen.add(obj);
|
|
195
|
-
const refId = `ref_${++this.refCounter}`;
|
|
196
|
-
this.circularRefs.set(obj, refId);
|
|
197
|
-
|
|
198
|
-
return obj.map((item, index) =>
|
|
199
|
-
this.makeSerializable(item, `${path}[${index}]`)
|
|
200
|
-
);
|
|
98
|
+
warn(message, data = null) {
|
|
99
|
+
// Solo warnings importantes
|
|
100
|
+
if (data) {
|
|
101
|
+
console.warn(message, data);
|
|
102
|
+
} else {
|
|
103
|
+
console.warn(message);
|
|
201
104
|
}
|
|
105
|
+
}
|
|
202
106
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const refId = this.circularRefs.get(obj);
|
|
208
|
-
return {
|
|
209
|
-
__circular: true,
|
|
210
|
-
refId: refId
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
this.seen.add(obj);
|
|
215
|
-
const refId = `ref_${++this.refCounter}`;
|
|
216
|
-
this.circularRefs.set(obj, refId);
|
|
217
|
-
|
|
218
|
-
const result = {};
|
|
219
|
-
|
|
220
|
-
// Procesar propiedades del objeto
|
|
221
|
-
for (const key in obj) {
|
|
222
|
-
if (obj.hasOwnProperty(key)) {
|
|
223
|
-
try {
|
|
224
|
-
const value = obj[key];
|
|
225
|
-
const safeValue = this.makeSerializable(value, `${path}.${key}`);
|
|
226
|
-
result[key] = safeValue;
|
|
227
|
-
} catch (error) {
|
|
228
|
-
// Si falla la serialización de una propiedad, la omitimos
|
|
229
|
-
result[key] = {
|
|
230
|
-
__serializationError: true,
|
|
231
|
-
error: error.message,
|
|
232
|
-
propertyName: key
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Procesar símbolos si están disponibles
|
|
239
|
-
if (Object.getOwnPropertySymbols) {
|
|
240
|
-
const symbols = Object.getOwnPropertySymbols(obj);
|
|
241
|
-
for (const symbol of symbols) {
|
|
242
|
-
try {
|
|
243
|
-
const value = obj[symbol];
|
|
244
|
-
const safeValue = this.makeSerializable(value, `${path}[Symbol(${symbol.description})]`);
|
|
245
|
-
result[`__symbol_${symbol.description || 'anonymous'}`] = safeValue;
|
|
246
|
-
} catch (error) {
|
|
247
|
-
result[`__symbol_${symbol.description || 'anonymous'}`] = {
|
|
248
|
-
__serializationError: true,
|
|
249
|
-
error: error.message,
|
|
250
|
-
symbolName: symbol.description || 'anonymous'
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
107
|
+
// Método para activar logging (solo para debug)
|
|
108
|
+
enableLogging() {
|
|
109
|
+
this.isSilent = false;
|
|
110
|
+
}
|
|
255
111
|
|
|
256
|
-
|
|
257
|
-
|
|
112
|
+
// Método para desactivar logging
|
|
113
|
+
disableLogging() {
|
|
114
|
+
this.isSilent = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
258
117
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
name: obj.name || 'anonymous',
|
|
264
|
-
length: obj.length,
|
|
265
|
-
toString: obj.toString().substring(0, 200) + '...'
|
|
266
|
-
};
|
|
267
|
-
}
|
|
118
|
+
/**
|
|
119
|
+
* SyntropyFront - Observability library with automatic capture
|
|
120
|
+
* Single responsibility: Automatically capture events and send errors with context
|
|
121
|
+
*/
|
|
268
122
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
123
|
+
class SyntropyFront {
|
|
124
|
+
constructor() {
|
|
125
|
+
// Basic managers
|
|
126
|
+
this.breadcrumbManager = new BreadcrumbManager();
|
|
127
|
+
this.errorManager = new ErrorManager();
|
|
128
|
+
this.logger = new Logger();
|
|
129
|
+
|
|
130
|
+
// Default configuration
|
|
131
|
+
this.maxEvents = 50;
|
|
132
|
+
this.fetchConfig = null; // Complete fetch configuration
|
|
133
|
+
this.onErrorCallback = null; // User-defined error handler
|
|
134
|
+
this.isActive = false;
|
|
135
|
+
|
|
136
|
+
// Automatic capture
|
|
137
|
+
this.originalHandlers = {};
|
|
138
|
+
|
|
139
|
+
// Auto-initialize
|
|
140
|
+
this.init();
|
|
275
141
|
}
|
|
276
142
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const parsed = JSON.parse(jsonString);
|
|
285
|
-
return this.restoreCircularRefs(parsed);
|
|
286
|
-
} catch (error) {
|
|
287
|
-
console.error('SyntropyFront: Error en deserialización:', error);
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
143
|
+
init() {
|
|
144
|
+
this.isActive = true;
|
|
145
|
+
|
|
146
|
+
// Configure automatic capture immediately
|
|
147
|
+
this.setupAutomaticCapture();
|
|
148
|
+
|
|
149
|
+
console.log('🚀 SyntropyFront: Initialized with automatic capture');
|
|
290
150
|
}
|
|
291
151
|
|
|
292
152
|
/**
|
|
293
|
-
*
|
|
294
|
-
* @param {
|
|
295
|
-
* @param {
|
|
296
|
-
* @
|
|
153
|
+
* Configure SyntropyFront
|
|
154
|
+
* @param {Object} config - Configuration
|
|
155
|
+
* @param {number} config.maxEvents - Maximum number of events to store
|
|
156
|
+
* @param {Object} config.fetch - Complete fetch configuration
|
|
157
|
+
* @param {string} config.fetch.url - Endpoint URL
|
|
158
|
+
* @param {Object} config.fetch.options - Fetch options (headers, method, etc.)
|
|
159
|
+
* @param {Function} config.onError - User-defined error handler callback
|
|
297
160
|
*/
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return new Date(obj.value);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (obj.__type === 'Error') {
|
|
313
|
-
const error = new Error(obj.message);
|
|
314
|
-
error.name = obj.name;
|
|
315
|
-
error.stack = obj.stack;
|
|
316
|
-
if (obj.cause) {
|
|
317
|
-
error.cause = this.restoreCircularRefs(obj.cause, refs);
|
|
318
|
-
}
|
|
319
|
-
return error;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (obj.__type === 'RegExp') {
|
|
323
|
-
return new RegExp(obj.source, obj.flags);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (obj.__type === 'Function') {
|
|
327
|
-
// No podemos restaurar funciones completamente, devolvemos info
|
|
328
|
-
return `[Function: ${obj.name}]`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Arrays
|
|
332
|
-
if (Array.isArray(obj)) {
|
|
333
|
-
const result = [];
|
|
334
|
-
refs.set(obj, result);
|
|
335
|
-
|
|
336
|
-
for (let i = 0; i < obj.length; i++) {
|
|
337
|
-
if (obj[i] && obj[i].__circular) {
|
|
338
|
-
const refId = obj[i].refId;
|
|
339
|
-
if (refs.has(refId)) {
|
|
340
|
-
result[i] = refs.get(refId);
|
|
341
|
-
} else {
|
|
342
|
-
result[i] = null; // Referencia no encontrada
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
result[i] = this.restoreCircularRefs(obj[i], refs);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Objetos
|
|
353
|
-
if (typeof obj === 'object') {
|
|
354
|
-
const result = {};
|
|
355
|
-
refs.set(obj, result);
|
|
356
|
-
|
|
357
|
-
for (const key in obj) {
|
|
358
|
-
if (obj.hasOwnProperty(key)) {
|
|
359
|
-
if (key.startsWith('__')) {
|
|
360
|
-
// Propiedades especiales
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const value = obj[key];
|
|
365
|
-
if (value && value.__circular) {
|
|
366
|
-
const refId = value.refId;
|
|
367
|
-
if (refs.has(refId)) {
|
|
368
|
-
result[key] = refs.get(refId);
|
|
369
|
-
} else {
|
|
370
|
-
result[key] = null; // Referencia no encontrada
|
|
371
|
-
}
|
|
372
|
-
} else {
|
|
373
|
-
result[key] = this.restoreCircularRefs(value, refs);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return result;
|
|
161
|
+
configure(config = {}) {
|
|
162
|
+
this.maxEvents = config.maxEvents || this.maxEvents;
|
|
163
|
+
this.fetchConfig = config.fetch;
|
|
164
|
+
this.onErrorCallback = config.onError;
|
|
165
|
+
|
|
166
|
+
if (this.onErrorCallback) {
|
|
167
|
+
console.log(`✅ SyntropyFront: Configured - maxEvents: ${this.maxEvents}, custom error handler`);
|
|
168
|
+
} else if (this.fetchConfig) {
|
|
169
|
+
console.log(`✅ SyntropyFront: Configured - maxEvents: ${this.maxEvents}, endpoint: ${this.fetchConfig.url}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`✅ SyntropyFront: Configured - maxEvents: ${this.maxEvents}, console only`);
|
|
379
172
|
}
|
|
380
|
-
|
|
381
|
-
return obj;
|
|
382
173
|
}
|
|
383
174
|
|
|
384
175
|
/**
|
|
385
|
-
*
|
|
386
|
-
* @param {any} obj - Objeto a serializar
|
|
387
|
-
* @returns {string} JSON string seguro para logs
|
|
176
|
+
* Configure automatic event capture
|
|
388
177
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return this.serialize(obj);
|
|
392
|
-
} catch (error) {
|
|
393
|
-
return JSON.stringify({
|
|
394
|
-
__logError: true,
|
|
395
|
-
message: 'Error serializando para logging',
|
|
396
|
-
originalError: error.message,
|
|
397
|
-
timestamp: new Date().toISOString()
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Instancia singleton
|
|
404
|
-
const robustSerializer = new RobustSerializer();
|
|
178
|
+
setupAutomaticCapture() {
|
|
179
|
+
if (typeof window === 'undefined') return;
|
|
405
180
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
* Implementa reintentos con backoff exponencial y buffer persistente
|
|
409
|
-
*/
|
|
410
|
-
class Agent {
|
|
411
|
-
constructor() {
|
|
412
|
-
this.endpoint = null;
|
|
413
|
-
this.headers = {
|
|
414
|
-
'Content-Type': 'application/json'
|
|
415
|
-
};
|
|
416
|
-
this.batchSize = 10;
|
|
417
|
-
this.batchTimeout = null; // Por defecto = solo errores
|
|
418
|
-
this.queue = [];
|
|
419
|
-
this.batchTimer = null;
|
|
420
|
-
this.isEnabled = false;
|
|
421
|
-
this.sendBreadcrumbs = false; // Por defecto = solo errores
|
|
422
|
-
this.encrypt = null; // Callback de encriptación opcional
|
|
181
|
+
// Capture clicks
|
|
182
|
+
this.setupClickCapture();
|
|
423
183
|
|
|
424
|
-
//
|
|
425
|
-
this.
|
|
426
|
-
this.retryTimer = null;
|
|
427
|
-
this.maxRetries = 5;
|
|
428
|
-
this.baseDelay = 1000; // 1 segundo
|
|
429
|
-
this.maxDelay = 30000; // 30 segundos
|
|
184
|
+
// Capture errors
|
|
185
|
+
this.setupErrorCapture();
|
|
430
186
|
|
|
431
|
-
//
|
|
432
|
-
this.
|
|
433
|
-
this.dbName = 'SyntropyFrontBuffer';
|
|
434
|
-
this.dbVersion = 1;
|
|
435
|
-
this.storeName = 'failedItems';
|
|
187
|
+
// Capture HTTP calls
|
|
188
|
+
this.setupHttpCapture();
|
|
436
189
|
|
|
437
|
-
//
|
|
438
|
-
this.
|
|
190
|
+
// Capture console logs
|
|
191
|
+
this.setupConsoleCapture();
|
|
439
192
|
}
|
|
440
193
|
|
|
441
194
|
/**
|
|
442
|
-
*
|
|
195
|
+
* Capture user clicks
|
|
443
196
|
*/
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
request.onupgradeneeded = (event) => {
|
|
458
|
-
const db = event.target.result;
|
|
459
|
-
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
460
|
-
db.createObjectStore(this.storeName, { keyPath: 'id', autoIncrement: true });
|
|
461
|
-
}
|
|
462
|
-
};
|
|
197
|
+
setupClickCapture() {
|
|
198
|
+
const clickHandler = (event) => {
|
|
199
|
+
const element = event.target;
|
|
200
|
+
this.addBreadcrumb('user', 'click', {
|
|
201
|
+
element: element.tagName,
|
|
202
|
+
id: element.id,
|
|
203
|
+
className: element.className,
|
|
204
|
+
x: event.clientX,
|
|
205
|
+
y: event.clientY
|
|
206
|
+
});
|
|
207
|
+
};
|
|
463
208
|
|
|
464
|
-
|
|
465
|
-
this.db = request.result;
|
|
466
|
-
this.usePersistentBuffer = true;
|
|
467
|
-
console.log('SyntropyFront: Buffer persistente inicializado');
|
|
468
|
-
|
|
469
|
-
// Intentar enviar items fallidos al inicializar
|
|
470
|
-
this.retryFailedItems();
|
|
471
|
-
};
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.warn('SyntropyFront: Error inicializando buffer persistente:', error);
|
|
474
|
-
}
|
|
209
|
+
document.addEventListener('click', clickHandler);
|
|
475
210
|
}
|
|
476
211
|
|
|
477
212
|
/**
|
|
478
|
-
*
|
|
213
|
+
* Automatically capture errors
|
|
479
214
|
*/
|
|
480
|
-
|
|
481
|
-
|
|
215
|
+
setupErrorCapture() {
|
|
216
|
+
// Save original handlers
|
|
217
|
+
this.originalHandlers.onerror = window.onerror;
|
|
218
|
+
this.originalHandlers.onunhandledrejection = window.onunhandledrejection;
|
|
219
|
+
|
|
220
|
+
// Intercept errors
|
|
221
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
222
|
+
const errorPayload = {
|
|
223
|
+
type: 'uncaught_exception',
|
|
224
|
+
error: { message, source, lineno, colno, stack: error?.stack },
|
|
225
|
+
breadcrumbs: this.getBreadcrumbs(),
|
|
226
|
+
timestamp: new Date().toISOString()
|
|
227
|
+
};
|
|
482
228
|
|
|
483
|
-
|
|
484
|
-
const transaction = this.db.transaction([this.storeName], 'readwrite');
|
|
485
|
-
const store = transaction.objectStore(this.storeName);
|
|
229
|
+
this.handleError(errorPayload);
|
|
486
230
|
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
serializedItems = robustSerializer.serialize(items);
|
|
491
|
-
} catch (error) {
|
|
492
|
-
console.error('SyntropyFront: Error serializando items para buffer:', error);
|
|
493
|
-
serializedItems = JSON.stringify({
|
|
494
|
-
__serializationError: true,
|
|
495
|
-
error: error.message,
|
|
496
|
-
timestamp: new Date().toISOString(),
|
|
497
|
-
fallbackData: 'Items no serializables'
|
|
498
|
-
});
|
|
231
|
+
// Call original handler
|
|
232
|
+
if (this.originalHandlers.onerror) {
|
|
233
|
+
return this.originalHandlers.onerror(message, source, lineno, colno, error);
|
|
499
234
|
}
|
|
500
235
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
236
|
+
return false;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Intercept rejected promises
|
|
240
|
+
window.onunhandledrejection = (event) => {
|
|
241
|
+
const errorPayload = {
|
|
242
|
+
type: 'unhandled_rejection',
|
|
243
|
+
error: {
|
|
244
|
+
message: event.reason?.message || 'Promise rejection without message',
|
|
245
|
+
stack: event.reason?.stack,
|
|
246
|
+
},
|
|
247
|
+
breadcrumbs: this.getBreadcrumbs(),
|
|
248
|
+
timestamp: new Date().toISOString()
|
|
505
249
|
};
|
|
250
|
+
|
|
251
|
+
this.handleError(errorPayload);
|
|
506
252
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
253
|
+
// Call original handler
|
|
254
|
+
if (this.originalHandlers.onunhandledrejection) {
|
|
255
|
+
this.originalHandlers.onunhandledrejection(event);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
512
258
|
}
|
|
513
259
|
|
|
514
260
|
/**
|
|
515
|
-
*
|
|
261
|
+
* Capture HTTP calls
|
|
516
262
|
*/
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
const store = transaction.objectStore(this.storeName);
|
|
523
|
-
const request = store.getAll();
|
|
263
|
+
setupHttpCapture() {
|
|
264
|
+
// Intercept fetch
|
|
265
|
+
const originalFetch = window.fetch;
|
|
266
|
+
window.fetch = (...args) => {
|
|
267
|
+
const [url, options] = args;
|
|
524
268
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
269
|
+
this.addBreadcrumb('http', 'fetch', {
|
|
270
|
+
url: url,
|
|
271
|
+
method: options?.method || 'GET'
|
|
528
272
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
273
|
+
|
|
274
|
+
return originalFetch(...args).then(response => {
|
|
275
|
+
this.addBreadcrumb('http', 'fetch_response', {
|
|
276
|
+
url: url,
|
|
277
|
+
status: response.status
|
|
278
|
+
});
|
|
279
|
+
return response;
|
|
280
|
+
}).catch(error => {
|
|
281
|
+
this.addBreadcrumb('http', 'fetch_error', {
|
|
282
|
+
url: url,
|
|
283
|
+
error: error.message
|
|
284
|
+
});
|
|
285
|
+
throw error;
|
|
286
|
+
});
|
|
287
|
+
};
|
|
533
288
|
}
|
|
534
289
|
|
|
535
290
|
/**
|
|
536
|
-
*
|
|
291
|
+
* Capture console logs
|
|
537
292
|
*/
|
|
538
|
-
|
|
539
|
-
|
|
293
|
+
setupConsoleCapture() {
|
|
294
|
+
const originalLog = console.log;
|
|
295
|
+
const originalError = console.error;
|
|
296
|
+
const originalWarn = console.warn;
|
|
540
297
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
298
|
+
console.log = (...args) => {
|
|
299
|
+
this.addBreadcrumb('console', 'log', { message: args.join(' ') });
|
|
300
|
+
originalLog.apply(console, args);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
console.error = (...args) => {
|
|
304
|
+
this.addBreadcrumb('console', 'error', { message: args.join(' ') });
|
|
305
|
+
originalError.apply(console, args);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
console.warn = (...args) => {
|
|
309
|
+
this.addBreadcrumb('console', 'warn', { message: args.join(' ') });
|
|
310
|
+
originalWarn.apply(console, args);
|
|
311
|
+
};
|
|
548
312
|
}
|
|
549
313
|
|
|
550
314
|
/**
|
|
551
|
-
*
|
|
315
|
+
* Handle errors - priority: onError callback > fetch > console
|
|
552
316
|
*/
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const failedItems = await this.getFromPersistentBuffer();
|
|
317
|
+
handleError(errorPayload) {
|
|
318
|
+
// Default log
|
|
319
|
+
this.logger.error('❌ Error:', errorPayload);
|
|
557
320
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
deserializedItems = robustSerializer.deserialize(item.items);
|
|
565
|
-
} else {
|
|
566
|
-
deserializedItems = item.items; // Ya deserializado
|
|
567
|
-
}
|
|
568
|
-
} catch (error) {
|
|
569
|
-
console.error('SyntropyFront: Error deserializando items del buffer:', error);
|
|
570
|
-
// Saltar este item y removerlo del buffer
|
|
571
|
-
await this.removeFromPersistentBuffer(item.id);
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
this.addToRetryQueue(deserializedItems, item.retryCount + 1, item.id);
|
|
576
|
-
} else {
|
|
577
|
-
console.warn('SyntropyFront: Item excedió máximo de reintentos, removiendo del buffer');
|
|
578
|
-
await this.removeFromPersistentBuffer(item.id);
|
|
321
|
+
// Priority 1: User-defined callback (maximum flexibility)
|
|
322
|
+
if (this.onErrorCallback) {
|
|
323
|
+
try {
|
|
324
|
+
this.onErrorCallback(errorPayload);
|
|
325
|
+
} catch (callbackError) {
|
|
326
|
+
console.warn('SyntropyFront: Error in user callback:', callbackError);
|
|
579
327
|
}
|
|
328
|
+
return;
|
|
580
329
|
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Configura el agent
|
|
585
|
-
* @param {Object} config - Configuración del agent
|
|
586
|
-
* @param {string} config.endpoint - URL del endpoint para enviar datos
|
|
587
|
-
* @param {Object} [config.headers] - Headers adicionales
|
|
588
|
-
* @param {number} [config.batchSize] - Tamaño del batch
|
|
589
|
-
* @param {number} [config.batchTimeout] - Timeout del batch en ms (si existe = modo completo)
|
|
590
|
-
* @param {Function} [config.encrypt] - Callback para encriptar datos antes de enviar
|
|
591
|
-
* @param {boolean} [config.usePersistentBuffer] - Usar buffer persistente (default: true)
|
|
592
|
-
* @param {number} [config.maxRetries] - Máximo número de reintentos (default: 5)
|
|
593
|
-
*/
|
|
594
|
-
configure(config) {
|
|
595
|
-
this.endpoint = config.endpoint;
|
|
596
|
-
this.headers = { ...this.headers, ...config.headers };
|
|
597
|
-
this.batchSize = config.batchSize || this.batchSize;
|
|
598
|
-
this.batchTimeout = config.batchTimeout; // Si existe = modo completo
|
|
599
|
-
this.isEnabled = !!config.endpoint;
|
|
600
|
-
this.encrypt = config.encrypt || null; // Callback de encriptación
|
|
601
|
-
this.usePersistentBuffer = config.usePersistentBuffer !== false; // Por defecto: true
|
|
602
|
-
this.maxRetries = config.maxRetries || this.maxRetries;
|
|
603
330
|
|
|
604
|
-
//
|
|
605
|
-
this.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Envía un error al backend
|
|
610
|
-
* @param {Object} errorPayload - Payload del error
|
|
611
|
-
* @param {Object} context - Contexto adicional (opcional)
|
|
612
|
-
*/
|
|
613
|
-
sendError(errorPayload, context = null) {
|
|
614
|
-
if (!this.isEnabled) {
|
|
615
|
-
console.warn('SyntropyFront Agent: No configurado, error no enviado');
|
|
331
|
+
// Priority 2: Fetch to endpoint
|
|
332
|
+
if (this.fetchConfig) {
|
|
333
|
+
this.postToEndpoint(errorPayload);
|
|
616
334
|
return;
|
|
617
335
|
}
|
|
618
|
-
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
...errorPayload,
|
|
622
|
-
context: context
|
|
623
|
-
} : errorPayload;
|
|
624
|
-
|
|
625
|
-
// Aplicar encriptación si está configurada
|
|
626
|
-
const dataToSend = this.encrypt ? this.encrypt(payloadWithContext) : payloadWithContext;
|
|
627
|
-
|
|
628
|
-
this.addToQueue({
|
|
629
|
-
type: 'error',
|
|
630
|
-
data: dataToSend,
|
|
631
|
-
timestamp: new Date().toISOString()
|
|
632
|
-
});
|
|
336
|
+
|
|
337
|
+
// Priority 3: Console only (default)
|
|
338
|
+
// Already logged above
|
|
633
339
|
}
|
|
634
340
|
|
|
635
341
|
/**
|
|
636
|
-
*
|
|
637
|
-
* @param {Array} breadcrumbs - Lista de breadcrumbs
|
|
342
|
+
* Post error object using fetch configuration
|
|
638
343
|
*/
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
344
|
+
postToEndpoint(errorPayload) {
|
|
345
|
+
const { url, options = {} } = this.fetchConfig;
|
|
346
|
+
|
|
347
|
+
// Default configuration
|
|
348
|
+
const defaultOptions = {
|
|
349
|
+
method: 'POST',
|
|
350
|
+
headers: {
|
|
351
|
+
'Content-Type': 'application/json',
|
|
352
|
+
...options.headers
|
|
353
|
+
},
|
|
354
|
+
body: JSON.stringify(errorPayload),
|
|
355
|
+
...options
|
|
356
|
+
};
|
|
647
357
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
data: dataToSend,
|
|
651
|
-
timestamp: new Date().toISOString()
|
|
358
|
+
fetch(url, defaultOptions).catch(error => {
|
|
359
|
+
console.warn('SyntropyFront: Error posting to endpoint:', error);
|
|
652
360
|
});
|
|
653
361
|
}
|
|
654
362
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (
|
|
664
|
-
this.
|
|
665
|
-
|
|
666
|
-
// Solo programar timeout si batchTimeout está configurado
|
|
667
|
-
this.batchTimer = setTimeout(() => {
|
|
668
|
-
this.flush();
|
|
669
|
-
}, this.batchTimeout);
|
|
363
|
+
// Public API
|
|
364
|
+
addBreadcrumb(category, message, data = {}) {
|
|
365
|
+
if (!this.isActive) return;
|
|
366
|
+
|
|
367
|
+
const breadcrumb = this.breadcrumbManager.add(category, message, data);
|
|
368
|
+
|
|
369
|
+
// Keep only the last maxEvents
|
|
370
|
+
const breadcrumbs = this.breadcrumbManager.getAll();
|
|
371
|
+
if (breadcrumbs.length > this.maxEvents) {
|
|
372
|
+
this.breadcrumbManager.clear();
|
|
373
|
+
breadcrumbs.slice(-this.maxEvents).forEach(b => this.breadcrumbManager.add(b.category, b.message, b.data));
|
|
670
374
|
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Añade items a la cola de reintentos
|
|
675
|
-
* @param {Array} items - Items a reintentar
|
|
676
|
-
* @param {number} retryCount - Número de reintento
|
|
677
|
-
* @param {number} persistentId - ID en buffer persistente (opcional)
|
|
678
|
-
*/
|
|
679
|
-
addToRetryQueue(items, retryCount = 1, persistentId = null) {
|
|
680
|
-
const delay = Math.min(this.baseDelay * Math.pow(2, retryCount - 1), this.maxDelay);
|
|
681
375
|
|
|
682
|
-
|
|
683
|
-
items,
|
|
684
|
-
retryCount,
|
|
685
|
-
persistentId,
|
|
686
|
-
nextRetry: Date.now() + delay
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
this.scheduleRetry();
|
|
376
|
+
return breadcrumb;
|
|
690
377
|
}
|
|
691
378
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
*/
|
|
695
|
-
scheduleRetry() {
|
|
696
|
-
if (this.retryTimer) return;
|
|
697
|
-
|
|
698
|
-
const nextItem = this.retryQueue.find(item => item.nextRetry <= Date.now());
|
|
699
|
-
if (!nextItem) return;
|
|
700
|
-
|
|
701
|
-
this.retryTimer = setTimeout(() => {
|
|
702
|
-
this.processRetryQueue();
|
|
703
|
-
}, Math.max(0, nextItem.nextRetry - Date.now()));
|
|
379
|
+
getBreadcrumbs() {
|
|
380
|
+
return this.breadcrumbManager.getAll();
|
|
704
381
|
}
|
|
705
382
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
async processRetryQueue() {
|
|
710
|
-
this.retryTimer = null;
|
|
383
|
+
clearBreadcrumbs() {
|
|
384
|
+
this.breadcrumbManager.clear();
|
|
385
|
+
}
|
|
711
386
|
|
|
712
|
-
|
|
713
|
-
|
|
387
|
+
sendError(error, context = {}) {
|
|
388
|
+
if (!this.isActive) return;
|
|
714
389
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
await this.removeFromPersistentBuffer(item.persistentId);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
console.log(`SyntropyFront: Reintento exitoso después de ${item.retryCount} intentos`);
|
|
728
|
-
} catch (error) {
|
|
729
|
-
console.warn(`SyntropyFront: Reintento ${item.retryCount} falló:`, error);
|
|
730
|
-
|
|
731
|
-
if (item.retryCount >= this.maxRetries) {
|
|
732
|
-
// ❌ Máximo de reintentos alcanzado
|
|
733
|
-
this.retryQueue = this.retryQueue.filter(q => q !== item);
|
|
734
|
-
console.error('SyntropyFront: Item excedió máximo de reintentos, datos perdidos');
|
|
735
|
-
} else {
|
|
736
|
-
// Programar próximo reintento
|
|
737
|
-
item.retryCount++;
|
|
738
|
-
item.nextRetry = Date.now() + Math.min(
|
|
739
|
-
this.baseDelay * Math.pow(2, item.retryCount - 1),
|
|
740
|
-
this.maxDelay
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Programar próximo reintento si quedan items
|
|
747
|
-
if (this.retryQueue.length > 0) {
|
|
748
|
-
this.scheduleRetry();
|
|
749
|
-
}
|
|
390
|
+
const errorData = this.errorManager.send(error, context);
|
|
391
|
+
const errorPayload = {
|
|
392
|
+
...errorData,
|
|
393
|
+
breadcrumbs: this.getBreadcrumbs(),
|
|
394
|
+
timestamp: new Date().toISOString()
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
this.handleError(errorPayload);
|
|
398
|
+
return errorData;
|
|
750
399
|
}
|
|
751
400
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
async flush() {
|
|
756
|
-
if (this.queue.length === 0) return;
|
|
757
|
-
|
|
758
|
-
const itemsToSend = [...this.queue];
|
|
759
|
-
this.queue = [];
|
|
760
|
-
|
|
761
|
-
if (this.batchTimer) {
|
|
762
|
-
clearTimeout(this.batchTimer);
|
|
763
|
-
this.batchTimer = null;
|
|
764
|
-
}
|
|
401
|
+
getErrors() {
|
|
402
|
+
return this.errorManager.getAll();
|
|
403
|
+
}
|
|
765
404
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
console.log('SyntropyFront: Datos enviados exitosamente');
|
|
769
|
-
} catch (error) {
|
|
770
|
-
console.error('SyntropyFront Agent: Error enviando datos:', error);
|
|
771
|
-
|
|
772
|
-
// ✅ REINTENTOS: Agregar a cola de reintentos
|
|
773
|
-
this.addToRetryQueue(itemsToSend);
|
|
774
|
-
|
|
775
|
-
// ✅ BUFFER PERSISTENTE: Guardar en IndexedDB
|
|
776
|
-
if (this.usePersistentBuffer) {
|
|
777
|
-
await this.saveToPersistentBuffer(itemsToSend);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
405
|
+
clearErrors() {
|
|
406
|
+
this.errorManager.clear();
|
|
780
407
|
}
|
|
781
408
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
409
|
+
// Utility methods
|
|
410
|
+
getStats() {
|
|
411
|
+
return {
|
|
412
|
+
breadcrumbs: this.breadcrumbManager.getCount(),
|
|
413
|
+
errors: this.errorManager.getCount(),
|
|
414
|
+
isActive: this.isActive,
|
|
415
|
+
maxEvents: this.maxEvents,
|
|
416
|
+
hasFetchConfig: !!this.fetchConfig,
|
|
417
|
+
hasErrorCallback: !!this.onErrorCallback,
|
|
418
|
+
endpoint: this.fetchConfig?.url || 'console'
|
|
790
419
|
};
|
|
791
|
-
|
|
792
|
-
// ✅ SERIALIZACIÓN ROBUSTA: Usar serializador que maneja referencias circulares
|
|
793
|
-
let serializedPayload;
|
|
794
|
-
try {
|
|
795
|
-
serializedPayload = robustSerializer.serialize(payload);
|
|
796
|
-
} catch (error) {
|
|
797
|
-
console.error('SyntropyFront: Error en serialización del payload:', error);
|
|
798
|
-
|
|
799
|
-
// Fallback: intentar serialización básica con información de error
|
|
800
|
-
serializedPayload = JSON.stringify({
|
|
801
|
-
__serializationError: true,
|
|
802
|
-
error: error.message,
|
|
803
|
-
timestamp: new Date().toISOString(),
|
|
804
|
-
itemsCount: items.length,
|
|
805
|
-
fallbackData: 'Serialización falló, datos no enviados'
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const response = await fetch(this.endpoint, {
|
|
810
|
-
method: 'POST',
|
|
811
|
-
headers: this.headers,
|
|
812
|
-
body: serializedPayload
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
if (!response.ok) {
|
|
816
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
return response.json();
|
|
820
420
|
}
|
|
421
|
+
}
|
|
821
422
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
*/
|
|
825
|
-
async forceFlush() {
|
|
826
|
-
await this.flush();
|
|
827
|
-
|
|
828
|
-
// También intentar enviar items en cola de reintentos
|
|
829
|
-
if (this.retryQueue.length > 0) {
|
|
830
|
-
console.log('SyntropyFront: Intentando enviar items en cola de reintentos...');
|
|
831
|
-
await this.processRetryQueue();
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
* Obtiene estadísticas del agent
|
|
837
|
-
* @returns {Object} Estadísticas
|
|
838
|
-
*/
|
|
839
|
-
getStats() {
|
|
840
|
-
return {
|
|
841
|
-
queueLength: this.queue.length,
|
|
842
|
-
retryQueueLength: this.retryQueue.length,
|
|
843
|
-
isEnabled: this.isEnabled,
|
|
844
|
-
usePersistentBuffer: this.usePersistentBuffer,
|
|
845
|
-
maxRetries: this.maxRetries
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Desactiva el agent
|
|
851
|
-
*/
|
|
852
|
-
disable() {
|
|
853
|
-
this.isEnabled = false;
|
|
854
|
-
this.queue = [];
|
|
855
|
-
this.retryQueue = [];
|
|
856
|
-
|
|
857
|
-
if (this.batchTimer) {
|
|
858
|
-
clearTimeout(this.batchTimer);
|
|
859
|
-
this.batchTimer = null;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
if (this.retryTimer) {
|
|
863
|
-
clearTimeout(this.retryTimer);
|
|
864
|
-
this.retryTimer = null;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Instancia singleton
|
|
870
|
-
const agent = new Agent();
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* ContextCollector - Recolector dinámico de contexto
|
|
874
|
-
* Sistema elegante para recolectar datos según lo que pida el usuario
|
|
875
|
-
* Por defecto: Sets curados y seguros
|
|
876
|
-
* Configuración específica: El usuario elige exactamente qué quiere
|
|
877
|
-
*/
|
|
878
|
-
class ContextCollector {
|
|
879
|
-
constructor() {
|
|
880
|
-
// Sets curados por defecto (seguros y útiles)
|
|
881
|
-
this.defaultContexts = {
|
|
882
|
-
device: {
|
|
883
|
-
userAgent: () => navigator.userAgent,
|
|
884
|
-
language: () => navigator.language,
|
|
885
|
-
screen: () => ({
|
|
886
|
-
width: window.screen.width,
|
|
887
|
-
height: window.screen.height
|
|
888
|
-
}),
|
|
889
|
-
timezone: () => Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
890
|
-
},
|
|
891
|
-
window: {
|
|
892
|
-
url: () => window.location.href,
|
|
893
|
-
viewport: () => ({
|
|
894
|
-
width: window.innerWidth,
|
|
895
|
-
height: window.innerHeight
|
|
896
|
-
}),
|
|
897
|
-
title: () => document.title
|
|
898
|
-
},
|
|
899
|
-
session: {
|
|
900
|
-
sessionId: () => this.generateSessionId(),
|
|
901
|
-
pageLoadTime: () => performance.now()
|
|
902
|
-
},
|
|
903
|
-
ui: {
|
|
904
|
-
visibility: () => document.visibilityState,
|
|
905
|
-
activeElement: () => document.activeElement ? {
|
|
906
|
-
tagName: document.activeElement.tagName
|
|
907
|
-
} : null
|
|
908
|
-
},
|
|
909
|
-
network: {
|
|
910
|
-
online: () => navigator.onLine,
|
|
911
|
-
connection: () => navigator.connection ? {
|
|
912
|
-
effectiveType: navigator.connection.effectiveType
|
|
913
|
-
} : null
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
// Mapeo completo de todos los campos disponibles
|
|
918
|
-
this.allFields = {
|
|
919
|
-
device: {
|
|
920
|
-
userAgent: () => navigator.userAgent,
|
|
921
|
-
language: () => navigator.language,
|
|
922
|
-
languages: () => navigator.languages,
|
|
923
|
-
screen: () => ({
|
|
924
|
-
width: window.screen.width,
|
|
925
|
-
height: window.screen.height,
|
|
926
|
-
availWidth: window.screen.availWidth,
|
|
927
|
-
availHeight: window.screen.availHeight,
|
|
928
|
-
colorDepth: window.screen.colorDepth,
|
|
929
|
-
pixelDepth: window.screen.pixelDepth
|
|
930
|
-
}),
|
|
931
|
-
timezone: () => Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
932
|
-
cookieEnabled: () => navigator.cookieEnabled,
|
|
933
|
-
doNotTrack: () => navigator.doNotTrack
|
|
934
|
-
},
|
|
935
|
-
window: {
|
|
936
|
-
url: () => window.location.href,
|
|
937
|
-
pathname: () => window.location.pathname,
|
|
938
|
-
search: () => window.location.search,
|
|
939
|
-
hash: () => window.location.hash,
|
|
940
|
-
referrer: () => document.referrer,
|
|
941
|
-
title: () => document.title,
|
|
942
|
-
viewport: () => ({
|
|
943
|
-
width: window.innerWidth,
|
|
944
|
-
height: window.innerHeight
|
|
945
|
-
})
|
|
946
|
-
},
|
|
947
|
-
storage: {
|
|
948
|
-
localStorage: () => {
|
|
949
|
-
const keys = Object.keys(localStorage);
|
|
950
|
-
return {
|
|
951
|
-
keys: keys.length,
|
|
952
|
-
size: JSON.stringify(localStorage).length,
|
|
953
|
-
keyNames: keys // Solo nombres, no valores
|
|
954
|
-
};
|
|
955
|
-
},
|
|
956
|
-
sessionStorage: () => {
|
|
957
|
-
const keys = Object.keys(sessionStorage);
|
|
958
|
-
return {
|
|
959
|
-
keys: keys.length,
|
|
960
|
-
size: JSON.stringify(sessionStorage).length,
|
|
961
|
-
keyNames: keys // Solo nombres, no valores
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
},
|
|
965
|
-
network: {
|
|
966
|
-
online: () => navigator.onLine,
|
|
967
|
-
connection: () => navigator.connection ? {
|
|
968
|
-
effectiveType: navigator.connection.effectiveType,
|
|
969
|
-
downlink: navigator.connection.downlink,
|
|
970
|
-
rtt: navigator.connection.rtt
|
|
971
|
-
} : null
|
|
972
|
-
},
|
|
973
|
-
ui: {
|
|
974
|
-
focused: () => document.hasFocus(),
|
|
975
|
-
visibility: () => document.visibilityState,
|
|
976
|
-
activeElement: () => document.activeElement ? {
|
|
977
|
-
tagName: document.activeElement.tagName,
|
|
978
|
-
id: document.activeElement.id,
|
|
979
|
-
className: document.activeElement.className
|
|
980
|
-
} : null
|
|
981
|
-
},
|
|
982
|
-
performance: {
|
|
983
|
-
memory: () => window.performance && window.performance.memory ? {
|
|
984
|
-
used: Math.round(window.performance.memory.usedJSHeapSize / 1048576),
|
|
985
|
-
total: Math.round(window.performance.memory.totalJSHeapSize / 1048576),
|
|
986
|
-
limit: Math.round(window.performance.memory.jsHeapSizeLimit / 1048576)
|
|
987
|
-
} : null,
|
|
988
|
-
timing: () => window.performance ? {
|
|
989
|
-
navigationStart: window.performance.timing.navigationStart,
|
|
990
|
-
loadEventEnd: window.performance.timing.loadEventEnd
|
|
991
|
-
} : null
|
|
992
|
-
},
|
|
993
|
-
session: {
|
|
994
|
-
sessionId: () => this.generateSessionId(),
|
|
995
|
-
startTime: () => new Date().toISOString(),
|
|
996
|
-
pageLoadTime: () => performance.now()
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Recolecta contexto según la configuración
|
|
1003
|
-
* @param {Object} contextConfig - Configuración de contexto
|
|
1004
|
-
* @returns {Object} Contexto recolectado
|
|
1005
|
-
*/
|
|
1006
|
-
collect(contextConfig = {}) {
|
|
1007
|
-
const context = {};
|
|
1008
|
-
|
|
1009
|
-
Object.entries(contextConfig).forEach(([contextType, config]) => {
|
|
1010
|
-
try {
|
|
1011
|
-
if (config === true) {
|
|
1012
|
-
// Usar set curado por defecto
|
|
1013
|
-
context[contextType] = this.collectDefaultContext(contextType);
|
|
1014
|
-
} else if (Array.isArray(config)) {
|
|
1015
|
-
// Configuración específica: array de campos
|
|
1016
|
-
context[contextType] = this.collectSpecificFields(contextType, config);
|
|
1017
|
-
} else if (config === false) {
|
|
1018
|
-
// Explícitamente deshabilitado
|
|
1019
|
-
// No hacer nada
|
|
1020
|
-
} else {
|
|
1021
|
-
console.warn(`SyntropyFront: Configuración de contexto inválida para ${contextType}:`, config);
|
|
1022
|
-
}
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
console.warn(`SyntropyFront: Error recolectando contexto ${contextType}:`, error);
|
|
1025
|
-
context[contextType] = { error: 'Failed to collect' };
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
|
-
|
|
1029
|
-
return context;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
/**
|
|
1033
|
-
* Recolecta el set curado por defecto
|
|
1034
|
-
* @param {string} contextType - Tipo de contexto
|
|
1035
|
-
* @returns {Object} Contexto por defecto
|
|
1036
|
-
*/
|
|
1037
|
-
collectDefaultContext(contextType) {
|
|
1038
|
-
const defaultContext = this.defaultContexts[contextType];
|
|
1039
|
-
if (!defaultContext) {
|
|
1040
|
-
console.warn(`SyntropyFront: No hay set por defecto para ${contextType}`);
|
|
1041
|
-
return {};
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
const result = {};
|
|
1045
|
-
Object.entries(defaultContext).forEach(([field, getter]) => {
|
|
1046
|
-
try {
|
|
1047
|
-
result[field] = getter();
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
console.warn(`SyntropyFront: Error recolectando campo ${field} de ${contextType}:`, error);
|
|
1050
|
-
result[field] = null;
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
return result;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* Recolecta campos específicos
|
|
1059
|
-
* @param {string} contextType - Tipo de contexto
|
|
1060
|
-
* @param {Array} fields - Campos específicos a recolectar
|
|
1061
|
-
* @returns {Object} Contexto específico
|
|
1062
|
-
*/
|
|
1063
|
-
collectSpecificFields(contextType, fields) {
|
|
1064
|
-
const allFields = this.allFields[contextType];
|
|
1065
|
-
if (!allFields) {
|
|
1066
|
-
console.warn(`SyntropyFront: Tipo de contexto desconocido: ${contextType}`);
|
|
1067
|
-
return {};
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
const result = {};
|
|
1071
|
-
fields.forEach(field => {
|
|
1072
|
-
try {
|
|
1073
|
-
if (allFields[field]) {
|
|
1074
|
-
result[field] = allFields[field]();
|
|
1075
|
-
} else {
|
|
1076
|
-
console.warn(`SyntropyFront: Campo ${field} no disponible en ${contextType}`);
|
|
1077
|
-
}
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
console.warn(`SyntropyFront: Error recolectando campo ${field} de ${contextType}:`, error);
|
|
1080
|
-
result[field] = null;
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
return result;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
/**
|
|
1088
|
-
* Genera un ID de sesión simple
|
|
1089
|
-
*/
|
|
1090
|
-
generateSessionId() {
|
|
1091
|
-
if (!this._sessionId) {
|
|
1092
|
-
this._sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
1093
|
-
}
|
|
1094
|
-
return this._sessionId;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Obtiene la lista de tipos de contexto disponibles
|
|
1099
|
-
* @returns {Array} Tipos disponibles
|
|
1100
|
-
*/
|
|
1101
|
-
getAvailableTypes() {
|
|
1102
|
-
return Object.keys(this.allFields);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Obtiene la lista de campos disponibles para un tipo de contexto
|
|
1107
|
-
* @param {string} contextType - Tipo de contexto
|
|
1108
|
-
* @returns {Array} Campos disponibles
|
|
1109
|
-
*/
|
|
1110
|
-
getAvailableFields(contextType) {
|
|
1111
|
-
const fields = this.allFields[contextType];
|
|
1112
|
-
return fields ? Object.keys(fields) : [];
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
/**
|
|
1116
|
-
* Obtiene información sobre los sets por defecto
|
|
1117
|
-
* @returns {Object} Información de sets por defecto
|
|
1118
|
-
*/
|
|
1119
|
-
getDefaultContextsInfo() {
|
|
1120
|
-
const info = {};
|
|
1121
|
-
Object.entries(this.defaultContexts).forEach(([type, fields]) => {
|
|
1122
|
-
info[type] = Object.keys(fields);
|
|
1123
|
-
});
|
|
1124
|
-
return info;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// Instancia singleton
|
|
1129
|
-
const contextCollector = new ContextCollector();
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* Interceptors - Observadores que capturan eventos automáticamente
|
|
1133
|
-
* Implementa Chaining Pattern para coexistir con otros APMs
|
|
1134
|
-
*/
|
|
1135
|
-
class Interceptors {
|
|
1136
|
-
constructor() {
|
|
1137
|
-
this.isInitialized = false;
|
|
1138
|
-
this.config = {
|
|
1139
|
-
captureClicks: true,
|
|
1140
|
-
captureFetch: true,
|
|
1141
|
-
captureErrors: true,
|
|
1142
|
-
captureUnhandledRejections: true
|
|
1143
|
-
};
|
|
1144
|
-
this.contextTypes = [];
|
|
1145
|
-
|
|
1146
|
-
// Referencias originales para restaurar en destroy()
|
|
1147
|
-
this.originalHandlers = {
|
|
1148
|
-
fetch: null,
|
|
1149
|
-
onerror: null,
|
|
1150
|
-
onunhandledrejection: null
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
// Event listeners para limpiar
|
|
1154
|
-
this.eventListeners = new Map();
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* Configura los interceptores
|
|
1159
|
-
* @param {Object} config - Configuración de interceptores
|
|
1160
|
-
*/
|
|
1161
|
-
configure(config) {
|
|
1162
|
-
this.config = { ...this.config, ...config };
|
|
1163
|
-
this.contextTypes = config.context || [];
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Inicializa todos los interceptores
|
|
1168
|
-
*/
|
|
1169
|
-
init() {
|
|
1170
|
-
if (this.isInitialized) {
|
|
1171
|
-
console.warn('SyntropyFront: Interceptors ya están inicializados');
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
if (this.config.captureClicks) {
|
|
1176
|
-
this.setupClickInterceptor();
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
if (this.config.captureFetch) {
|
|
1180
|
-
this.setupFetchInterceptor();
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
if (this.config.captureErrors || this.config.captureUnhandledRejections) {
|
|
1184
|
-
this.setupErrorInterceptors();
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
this.isInitialized = true;
|
|
1188
|
-
console.log('SyntropyFront: Interceptors inicializados con Chaining Pattern');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
/**
|
|
1192
|
-
* Intercepta clics de usuario
|
|
1193
|
-
*/
|
|
1194
|
-
setupClickInterceptor() {
|
|
1195
|
-
const clickHandler = (event) => {
|
|
1196
|
-
const el = event.target;
|
|
1197
|
-
if (!el) return;
|
|
1198
|
-
|
|
1199
|
-
// Genera un selector CSS simple para identificar el elemento
|
|
1200
|
-
let selector = el.tagName.toLowerCase();
|
|
1201
|
-
if (el.id) {
|
|
1202
|
-
selector += `#${el.id}`;
|
|
1203
|
-
} else if (el.className && typeof el.className === 'string') {
|
|
1204
|
-
selector += `.${el.className.split(' ').filter(Boolean).join('.')}`;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
breadcrumbStore.add({
|
|
1208
|
-
category: 'ui',
|
|
1209
|
-
message: `Usuario hizo click en '${selector}'`,
|
|
1210
|
-
data: {
|
|
1211
|
-
selector,
|
|
1212
|
-
tagName: el.tagName,
|
|
1213
|
-
id: el.id,
|
|
1214
|
-
className: el.className
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
// Guardar referencia para limpiar después
|
|
1220
|
-
this.eventListeners.set('click', clickHandler);
|
|
1221
|
-
document.addEventListener('click', clickHandler, true);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
/**
|
|
1225
|
-
* Intercepta llamadas de red (fetch) con Chaining
|
|
1226
|
-
*/
|
|
1227
|
-
setupFetchInterceptor() {
|
|
1228
|
-
// Guardar referencia original
|
|
1229
|
-
this.originalHandlers.fetch = window.fetch;
|
|
1230
|
-
|
|
1231
|
-
// Crear nuevo handler que encadena con el original
|
|
1232
|
-
const syntropyFetchHandler = (...args) => {
|
|
1233
|
-
const url = args[0] instanceof Request ? args[0].url : args[0];
|
|
1234
|
-
const method = args[0] instanceof Request ? args[0].method : (args[1]?.method || 'GET');
|
|
1235
|
-
|
|
1236
|
-
breadcrumbStore.add({
|
|
1237
|
-
category: 'network',
|
|
1238
|
-
message: `Request: ${method} ${url}`,
|
|
1239
|
-
data: {
|
|
1240
|
-
url,
|
|
1241
|
-
method,
|
|
1242
|
-
timestamp: Date.now()
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
|
|
1246
|
-
// ✅ CHAINING: Llamar al handler original
|
|
1247
|
-
return this.originalHandlers.fetch.apply(window, args);
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
// Sobrescribir con el nuevo handler
|
|
1251
|
-
window.fetch = syntropyFetchHandler;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
/**
|
|
1255
|
-
* Intercepta errores globales con Chaining
|
|
1256
|
-
*/
|
|
1257
|
-
setupErrorInterceptors() {
|
|
1258
|
-
if (this.config.captureErrors) {
|
|
1259
|
-
// Guardar referencia original
|
|
1260
|
-
this.originalHandlers.onerror = window.onerror;
|
|
1261
|
-
|
|
1262
|
-
// Crear nuevo handler que encadena con el original
|
|
1263
|
-
const syntropyErrorHandler = (message, source, lineno, colno, error) => {
|
|
1264
|
-
const errorPayload = {
|
|
1265
|
-
type: 'uncaught_exception',
|
|
1266
|
-
error: {
|
|
1267
|
-
message,
|
|
1268
|
-
source,
|
|
1269
|
-
lineno,
|
|
1270
|
-
colno,
|
|
1271
|
-
stack: error?.stack
|
|
1272
|
-
},
|
|
1273
|
-
breadcrumbs: breadcrumbStore.getAll(),
|
|
1274
|
-
timestamp: new Date().toISOString()
|
|
1275
|
-
};
|
|
1276
|
-
|
|
1277
|
-
this.handleError(errorPayload);
|
|
1278
|
-
|
|
1279
|
-
// ✅ CHAINING: Llamar al handler original si existe
|
|
1280
|
-
if (this.originalHandlers.onerror) {
|
|
1281
|
-
try {
|
|
1282
|
-
return this.originalHandlers.onerror(message, source, lineno, colno, error);
|
|
1283
|
-
} catch (originalError) {
|
|
1284
|
-
console.warn('SyntropyFront: Error en handler original:', originalError);
|
|
1285
|
-
return false;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
return false; // No prevenir el error por defecto
|
|
1290
|
-
};
|
|
1291
|
-
|
|
1292
|
-
// Sobrescribir con el nuevo handler
|
|
1293
|
-
window.onerror = syntropyErrorHandler;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
if (this.config.captureUnhandledRejections) {
|
|
1297
|
-
// Guardar referencia original
|
|
1298
|
-
this.originalHandlers.onunhandledrejection = window.onunhandledrejection;
|
|
1299
|
-
|
|
1300
|
-
// Crear nuevo handler que encadena con el original
|
|
1301
|
-
const syntropyRejectionHandler = (event) => {
|
|
1302
|
-
const errorPayload = {
|
|
1303
|
-
type: 'unhandled_rejection',
|
|
1304
|
-
error: {
|
|
1305
|
-
message: event.reason?.message || 'Rechazo de promesa sin mensaje',
|
|
1306
|
-
stack: event.reason?.stack,
|
|
1307
|
-
},
|
|
1308
|
-
breadcrumbs: breadcrumbStore.getAll(),
|
|
1309
|
-
timestamp: new Date().toISOString()
|
|
1310
|
-
};
|
|
1311
|
-
|
|
1312
|
-
this.handleError(errorPayload);
|
|
1313
|
-
|
|
1314
|
-
// ✅ CHAINING: Llamar al handler original si existe
|
|
1315
|
-
if (this.originalHandlers.onunhandledrejection) {
|
|
1316
|
-
try {
|
|
1317
|
-
this.originalHandlers.onunhandledrejection(event);
|
|
1318
|
-
} catch (originalError) {
|
|
1319
|
-
console.warn('SyntropyFront: Error en handler original de rejection:', originalError);
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
// Sobrescribir con el nuevo handler
|
|
1325
|
-
window.onunhandledrejection = syntropyRejectionHandler;
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* Maneja los errores capturados
|
|
1331
|
-
* @param {Object} errorPayload - Payload del error
|
|
1332
|
-
*/
|
|
1333
|
-
handleError(errorPayload) {
|
|
1334
|
-
// Recolectar contexto si está configurado
|
|
1335
|
-
const context = this.contextTypes.length > 0 ? contextCollector.collect(this.contextTypes) : null;
|
|
1336
|
-
|
|
1337
|
-
// Enviar al agent si está configurado
|
|
1338
|
-
agent.sendError(errorPayload, context);
|
|
1339
|
-
|
|
1340
|
-
// Callback para manejo personalizado de errores
|
|
1341
|
-
if (this.onError) {
|
|
1342
|
-
this.onError(errorPayload);
|
|
1343
|
-
} else {
|
|
1344
|
-
// Comportamiento por defecto: log a consola
|
|
1345
|
-
console.error('SyntropyFront - Error detectado:', errorPayload);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
* Desactiva todos los interceptores y restaura handlers originales
|
|
1351
|
-
*/
|
|
1352
|
-
destroy() {
|
|
1353
|
-
if (!this.isInitialized) return;
|
|
1354
|
-
|
|
1355
|
-
console.log('SyntropyFront: Limpiando interceptores...');
|
|
1356
|
-
|
|
1357
|
-
// ✅ RESTAURAR: Handlers originales
|
|
1358
|
-
if (this.originalHandlers.fetch) {
|
|
1359
|
-
window.fetch = this.originalHandlers.fetch;
|
|
1360
|
-
console.log('SyntropyFront: fetch original restaurado');
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
if (this.originalHandlers.onerror) {
|
|
1364
|
-
window.onerror = this.originalHandlers.onerror;
|
|
1365
|
-
console.log('SyntropyFront: onerror original restaurado');
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
if (this.originalHandlers.onunhandledrejection) {
|
|
1369
|
-
window.onunhandledrejection = this.originalHandlers.onunhandledrejection;
|
|
1370
|
-
console.log('SyntropyFront: onunhandledrejection original restaurado');
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
// ✅ LIMPIAR: Event listeners
|
|
1374
|
-
this.eventListeners.forEach((handler, eventType) => {
|
|
1375
|
-
document.removeEventListener(eventType, handler, true);
|
|
1376
|
-
console.log(`SyntropyFront: Event listener ${eventType} removido`);
|
|
1377
|
-
});
|
|
1378
|
-
|
|
1379
|
-
// Limpiar referencias
|
|
1380
|
-
this.originalHandlers = {
|
|
1381
|
-
fetch: null,
|
|
1382
|
-
onerror: null,
|
|
1383
|
-
onunhandledrejection: null
|
|
1384
|
-
};
|
|
1385
|
-
this.eventListeners.clear();
|
|
1386
|
-
this.isInitialized = false;
|
|
1387
|
-
|
|
1388
|
-
console.log('SyntropyFront: Interceptors destruidos y handlers restaurados');
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
/**
|
|
1392
|
-
* Obtiene información sobre los handlers originales
|
|
1393
|
-
* @returns {Object} Información de handlers
|
|
1394
|
-
*/
|
|
1395
|
-
getHandlerInfo() {
|
|
1396
|
-
return {
|
|
1397
|
-
isInitialized: this.isInitialized,
|
|
1398
|
-
hasOriginalFetch: !!this.originalHandlers.fetch,
|
|
1399
|
-
hasOriginalOnError: !!this.originalHandlers.onerror,
|
|
1400
|
-
hasOriginalOnUnhandledRejection: !!this.originalHandlers.onunhandledrejection,
|
|
1401
|
-
eventListenersCount: this.eventListeners.size
|
|
1402
|
-
};
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
// Instancia singleton
|
|
1407
|
-
const interceptors = new Interceptors();
|
|
1408
|
-
|
|
1409
|
-
/**
|
|
1410
|
-
* Presets de configuración para SyntropyFront
|
|
1411
|
-
* Recetas pre-configuradas para diferentes casos de uso
|
|
1412
|
-
*
|
|
1413
|
-
* @author SyntropyFront Team
|
|
1414
|
-
* @version 1.0.0
|
|
1415
|
-
*/
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* Preset 'safe' - Modo solo emergencias
|
|
1419
|
-
* Ideal para: Producción, aplicaciones críticas, GDPR estricto
|
|
1420
|
-
*/
|
|
1421
|
-
const SAFE_PRESET = {
|
|
1422
|
-
name: 'safe',
|
|
1423
|
-
description: 'Modo solo emergencias - Mínimo impacto, máxima privacidad',
|
|
1424
|
-
|
|
1425
|
-
// Configuración del agent
|
|
1426
|
-
agent: {
|
|
1427
|
-
batchTimeout: null, // Solo errores
|
|
1428
|
-
batchSize: 5,
|
|
1429
|
-
encrypt: null // Sin encriptación por defecto
|
|
1430
|
-
},
|
|
1431
|
-
|
|
1432
|
-
// Breadcrumbs limitados
|
|
1433
|
-
maxBreadcrumbs: 10,
|
|
1434
|
-
|
|
1435
|
-
// Contexto mínimo
|
|
1436
|
-
context: {
|
|
1437
|
-
device: true, // Solo información básica del dispositivo
|
|
1438
|
-
window: false, // No URL ni viewport
|
|
1439
|
-
session: true, // Solo sessionId
|
|
1440
|
-
ui: false, // No información de UI
|
|
1441
|
-
network: false // No información de red
|
|
1442
|
-
},
|
|
1443
|
-
|
|
1444
|
-
// Sin tracking de objetos
|
|
1445
|
-
customObjects: {},
|
|
1446
|
-
proxyTracking: false,
|
|
1447
|
-
|
|
1448
|
-
// Interceptores básicos
|
|
1449
|
-
captureClicks: false,
|
|
1450
|
-
captureFetch: false,
|
|
1451
|
-
captureErrors: true,
|
|
1452
|
-
captureUnhandledRejections: true,
|
|
1453
|
-
|
|
1454
|
-
// Worker opcional
|
|
1455
|
-
useWorker: false,
|
|
1456
|
-
|
|
1457
|
-
// Callbacks
|
|
1458
|
-
onError: null,
|
|
1459
|
-
onBreadcrumbAdded: null
|
|
1460
|
-
};
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
* Preset 'balanced' - Modo equilibrado
|
|
1464
|
-
* Ideal para: Desarrollo, testing, aplicaciones generales
|
|
1465
|
-
*/
|
|
1466
|
-
const BALANCED_PRESET = {
|
|
1467
|
-
name: 'balanced',
|
|
1468
|
-
description: 'Modo equilibrado - Balance entre información y performance',
|
|
1469
|
-
|
|
1470
|
-
// Configuración del agent
|
|
1471
|
-
agent: {
|
|
1472
|
-
batchTimeout: 10000, // Envío cada 10 segundos
|
|
1473
|
-
batchSize: 20,
|
|
1474
|
-
encrypt: null
|
|
1475
|
-
},
|
|
1476
|
-
|
|
1477
|
-
// Breadcrumbs moderados
|
|
1478
|
-
maxBreadcrumbs: 50,
|
|
1479
|
-
|
|
1480
|
-
// Contexto curado
|
|
1481
|
-
context: {
|
|
1482
|
-
device: true, // Información completa del dispositivo
|
|
1483
|
-
window: true, // URL y viewport
|
|
1484
|
-
session: true, // Información de sesión
|
|
1485
|
-
ui: true, // Estado básico de UI
|
|
1486
|
-
network: true // Estado de conectividad
|
|
1487
|
-
},
|
|
1488
|
-
|
|
1489
|
-
// Tracking de objetos moderado
|
|
1490
|
-
customObjects: {},
|
|
1491
|
-
proxyTracking: {
|
|
1492
|
-
enabled: true,
|
|
1493
|
-
maxStates: 10,
|
|
1494
|
-
trackNested: true,
|
|
1495
|
-
trackArrays: false
|
|
1496
|
-
},
|
|
1497
|
-
|
|
1498
|
-
// Interceptores completos
|
|
1499
|
-
captureClicks: true,
|
|
1500
|
-
captureFetch: true,
|
|
1501
|
-
captureErrors: true,
|
|
1502
|
-
captureUnhandledRejections: true,
|
|
1503
|
-
|
|
1504
|
-
// Worker habilitado
|
|
1505
|
-
useWorker: true,
|
|
1506
|
-
|
|
1507
|
-
// Callbacks
|
|
1508
|
-
onError: null,
|
|
1509
|
-
onBreadcrumbAdded: null
|
|
1510
|
-
};
|
|
1511
|
-
|
|
1512
|
-
/**
|
|
1513
|
-
* Preset 'debug' - Modo debug completo
|
|
1514
|
-
* Ideal para: Desarrollo, debugging, análisis profundo
|
|
1515
|
-
*/
|
|
1516
|
-
const DEBUG_PRESET = {
|
|
1517
|
-
name: 'debug',
|
|
1518
|
-
description: 'Modo debug completo - Máxima información para desarrollo',
|
|
1519
|
-
|
|
1520
|
-
// Configuración del agent
|
|
1521
|
-
agent: {
|
|
1522
|
-
batchTimeout: 5000, // Envío cada 5 segundos
|
|
1523
|
-
batchSize: 50,
|
|
1524
|
-
encrypt: null
|
|
1525
|
-
},
|
|
1526
|
-
|
|
1527
|
-
// Breadcrumbs completos
|
|
1528
|
-
maxBreadcrumbs: 100,
|
|
1529
|
-
|
|
1530
|
-
// Contexto completo
|
|
1531
|
-
context: {
|
|
1532
|
-
device: true, // Todo del dispositivo
|
|
1533
|
-
window: true, // Todo de la ventana
|
|
1534
|
-
session: true, // Todo de la sesión
|
|
1535
|
-
ui: true, // Todo de la UI
|
|
1536
|
-
network: true // Todo de la red
|
|
1537
|
-
},
|
|
1538
|
-
|
|
1539
|
-
// Tracking de objetos completo
|
|
1540
|
-
customObjects: {},
|
|
1541
|
-
proxyTracking: {
|
|
1542
|
-
enabled: true,
|
|
1543
|
-
maxStates: 20,
|
|
1544
|
-
trackNested: true,
|
|
1545
|
-
trackArrays: true,
|
|
1546
|
-
trackFunctions: true
|
|
1547
|
-
},
|
|
1548
|
-
|
|
1549
|
-
// Todos los interceptores
|
|
1550
|
-
captureClicks: true,
|
|
1551
|
-
captureFetch: true,
|
|
1552
|
-
captureErrors: true,
|
|
1553
|
-
captureUnhandledRejections: true,
|
|
1554
|
-
|
|
1555
|
-
// Worker habilitado
|
|
1556
|
-
useWorker: true,
|
|
1557
|
-
|
|
1558
|
-
// Callbacks para debugging
|
|
1559
|
-
onError: (error) => {
|
|
1560
|
-
console.error('SyntropyFront Error:', error);
|
|
1561
|
-
},
|
|
1562
|
-
onBreadcrumbAdded: (breadcrumb) => {
|
|
1563
|
-
console.log('SyntropyFront Breadcrumb:', breadcrumb);
|
|
1564
|
-
}
|
|
1565
|
-
};
|
|
1566
|
-
|
|
1567
|
-
/**
|
|
1568
|
-
* Preset 'performance' - Modo optimizado para performance
|
|
1569
|
-
* Ideal para: Aplicaciones de alta performance, gaming, real-time
|
|
1570
|
-
*/
|
|
1571
|
-
const PERFORMANCE_PRESET = {
|
|
1572
|
-
name: 'performance',
|
|
1573
|
-
description: 'Modo performance - Máxima velocidad, información mínima',
|
|
1574
|
-
|
|
1575
|
-
// Configuración del agent
|
|
1576
|
-
agent: {
|
|
1577
|
-
batchTimeout: null, // Solo errores críticos
|
|
1578
|
-
batchSize: 3,
|
|
1579
|
-
encrypt: null
|
|
1580
|
-
},
|
|
1581
|
-
|
|
1582
|
-
// Breadcrumbs mínimos
|
|
1583
|
-
maxBreadcrumbs: 5,
|
|
1584
|
-
|
|
1585
|
-
// Contexto mínimo
|
|
1586
|
-
context: {
|
|
1587
|
-
device: false, // Sin información de dispositivo
|
|
1588
|
-
window: false, // Sin información de ventana
|
|
1589
|
-
session: true, // Solo sessionId
|
|
1590
|
-
ui: false, // Sin información de UI
|
|
1591
|
-
network: false // Sin información de red
|
|
1592
|
-
},
|
|
1593
|
-
|
|
1594
|
-
// Sin tracking de objetos
|
|
1595
|
-
customObjects: {},
|
|
1596
|
-
proxyTracking: false,
|
|
1597
|
-
|
|
1598
|
-
// Solo errores críticos
|
|
1599
|
-
captureClicks: false,
|
|
1600
|
-
captureFetch: false,
|
|
1601
|
-
captureErrors: true,
|
|
1602
|
-
captureUnhandledRejections: true,
|
|
1603
|
-
|
|
1604
|
-
// Sin worker para máxima velocidad
|
|
1605
|
-
useWorker: false,
|
|
1606
|
-
|
|
1607
|
-
// Sin callbacks
|
|
1608
|
-
onError: null,
|
|
1609
|
-
onBreadcrumbAdded: null
|
|
1610
|
-
};
|
|
1611
|
-
|
|
1612
|
-
/**
|
|
1613
|
-
* Mapa de presets disponibles
|
|
1614
|
-
*/
|
|
1615
|
-
const PRESETS = {
|
|
1616
|
-
safe: SAFE_PRESET,
|
|
1617
|
-
balanced: BALANCED_PRESET,
|
|
1618
|
-
debug: DEBUG_PRESET,
|
|
1619
|
-
performance: PERFORMANCE_PRESET
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
/**
|
|
1623
|
-
* Obtiene un preset por nombre
|
|
1624
|
-
*/
|
|
1625
|
-
function getPreset(name) {
|
|
1626
|
-
const preset = PRESETS[name];
|
|
1627
|
-
if (!preset) {
|
|
1628
|
-
throw new Error(`Preset '${name}' no encontrado. Presets disponibles: ${Object.keys(PRESETS).join(', ')}`);
|
|
1629
|
-
}
|
|
1630
|
-
return preset;
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
/**
|
|
1634
|
-
* Lista todos los presets disponibles
|
|
1635
|
-
*/
|
|
1636
|
-
function getAvailablePresets() {
|
|
1637
|
-
return Object.keys(PRESETS).map(name => ({
|
|
1638
|
-
name,
|
|
1639
|
-
...PRESETS[name]
|
|
1640
|
-
}));
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* Obtiene información de un preset
|
|
1645
|
-
*/
|
|
1646
|
-
function getPresetInfo(name) {
|
|
1647
|
-
const preset = getPreset(name);
|
|
1648
|
-
return {
|
|
1649
|
-
name: preset.name,
|
|
1650
|
-
description: preset.description,
|
|
1651
|
-
features: {
|
|
1652
|
-
breadcrumbs: preset.maxBreadcrumbs,
|
|
1653
|
-
context: Object.keys(preset.context).filter(key => preset.context[key]).length,
|
|
1654
|
-
worker: preset.useWorker,
|
|
1655
|
-
proxyTracking: preset.proxyTracking?.enabled || false,
|
|
1656
|
-
agentMode: preset.agent.batchTimeout ? 'completo' : 'solo emergencias'
|
|
1657
|
-
}
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
/**
|
|
1662
|
-
* SyntropyFront - Sistema de trazabilidad para frontend
|
|
1663
|
-
* API principal para inicializar y configurar el sistema
|
|
1664
|
-
*/
|
|
1665
|
-
class SyntropyFront {
|
|
1666
|
-
constructor() {
|
|
1667
|
-
this.isInitialized = false;
|
|
1668
|
-
this.currentPreset = null;
|
|
1669
|
-
|
|
1670
|
-
// Lazy-loaded modules
|
|
1671
|
-
this.proxyObjectTracker = null;
|
|
1672
|
-
this.interceptorRegistry = null;
|
|
1673
|
-
this.workerManager = null;
|
|
1674
|
-
|
|
1675
|
-
// Configuración por defecto (balanced preset)
|
|
1676
|
-
this.config = {
|
|
1677
|
-
preset: 'balanced', // Preset por defecto
|
|
1678
|
-
maxBreadcrumbs: 50,
|
|
1679
|
-
captureClicks: true,
|
|
1680
|
-
captureFetch: true,
|
|
1681
|
-
captureErrors: true,
|
|
1682
|
-
captureUnhandledRejections: true,
|
|
1683
|
-
onError: null,
|
|
1684
|
-
onBreadcrumbAdded: null,
|
|
1685
|
-
// Configuración del agent
|
|
1686
|
-
agent: {
|
|
1687
|
-
endpoint: null,
|
|
1688
|
-
headers: {},
|
|
1689
|
-
batchSize: 20,
|
|
1690
|
-
batchTimeout: 10000, // 10 segundos por defecto
|
|
1691
|
-
encrypt: null // Callback de encriptación opcional
|
|
1692
|
-
},
|
|
1693
|
-
// Configuración de contexto (nueva arquitectura granular)
|
|
1694
|
-
context: {
|
|
1695
|
-
device: true, // Set curado por defecto
|
|
1696
|
-
window: true, // Set curado por defecto
|
|
1697
|
-
session: true, // Set curado por defecto
|
|
1698
|
-
ui: true, // Set curado por defecto
|
|
1699
|
-
network: true // Set curado por defecto
|
|
1700
|
-
},
|
|
1701
|
-
// Configuración de proxy tracking
|
|
1702
|
-
proxyTracking: {
|
|
1703
|
-
enabled: true,
|
|
1704
|
-
maxStates: 10,
|
|
1705
|
-
trackNested: true,
|
|
1706
|
-
trackArrays: false
|
|
1707
|
-
},
|
|
1708
|
-
// Worker habilitado por defecto
|
|
1709
|
-
useWorker: true
|
|
1710
|
-
};
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
/**
|
|
1714
|
-
* Inicializa el sistema de trazabilidad
|
|
1715
|
-
* @param {Object} options - Opciones de configuración
|
|
1716
|
-
*/
|
|
1717
|
-
async init(options = {}) {
|
|
1718
|
-
if (this.isInitialized) {
|
|
1719
|
-
console.warn('SyntropyFront ya está inicializado');
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// Manejar preset si se especifica
|
|
1724
|
-
if (options.preset) {
|
|
1725
|
-
try {
|
|
1726
|
-
const preset = getPreset(options.preset);
|
|
1727
|
-
this.currentPreset = options.preset;
|
|
1728
|
-
|
|
1729
|
-
// Aplicar configuración del preset
|
|
1730
|
-
this.config = { ...this.config, ...preset };
|
|
1731
|
-
|
|
1732
|
-
console.log(`🎯 SyntropyFront: Aplicando preset '${options.preset}' - ${preset.description}`);
|
|
1733
|
-
} catch (error) {
|
|
1734
|
-
console.error(`❌ SyntropyFront: Error aplicando preset '${options.preset}':`, error.message);
|
|
1735
|
-
throw error;
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
// Aplicar configuración personalizada (sobrescribe preset)
|
|
1740
|
-
this.config = { ...this.config, ...options };
|
|
1741
|
-
|
|
1742
|
-
// Configurar agent primero
|
|
1743
|
-
if (this.config.agent.endpoint) {
|
|
1744
|
-
agent.configure(this.config.agent);
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
// Configurar worker manager
|
|
1748
|
-
if (this.config.useWorker !== false) {
|
|
1749
|
-
await this.workerManager.init({
|
|
1750
|
-
maxBreadcrumbs: this.config.maxBreadcrumbs,
|
|
1751
|
-
agent: this.config.agent
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// Configurar breadcrumb store
|
|
1756
|
-
breadcrumbStore.setMaxBreadcrumbs(this.config.maxBreadcrumbs);
|
|
1757
|
-
breadcrumbStore.onBreadcrumbAdded = this.config.onBreadcrumbAdded;
|
|
1758
|
-
breadcrumbStore.setAgent(agent);
|
|
1759
|
-
|
|
1760
|
-
// Configurar contexto (nueva arquitectura granular)
|
|
1761
|
-
this.contextConfig = this.config.context || {
|
|
1762
|
-
device: true,
|
|
1763
|
-
window: true,
|
|
1764
|
-
session: true,
|
|
1765
|
-
ui: true,
|
|
1766
|
-
network: true
|
|
1767
|
-
};
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
// Lazy load modules based on configuration
|
|
1772
|
-
await this.loadModules();
|
|
1773
|
-
|
|
1774
|
-
// Configurar proxy object tracker (si está habilitado)
|
|
1775
|
-
if (this.config.proxyTracking?.enabled && this.proxyObjectTracker) {
|
|
1776
|
-
this.proxyObjectTracker.configure(this.config.proxyTracking);
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
// Configurar interceptores
|
|
1780
|
-
interceptors.configure({
|
|
1781
|
-
captureClicks: this.config.captureClicks,
|
|
1782
|
-
captureFetch: this.config.captureFetch,
|
|
1783
|
-
captureErrors: this.config.captureErrors,
|
|
1784
|
-
captureUnhandledRejections: this.config.captureUnhandledRejections
|
|
1785
|
-
});
|
|
1786
|
-
|
|
1787
|
-
interceptors.onError = this.config.onError;
|
|
1788
|
-
|
|
1789
|
-
// Inicializar interceptores
|
|
1790
|
-
interceptors.init();
|
|
1791
|
-
|
|
1792
|
-
// Inicializar interceptores personalizados (si están habilitados)
|
|
1793
|
-
if (this.interceptorRegistry) {
|
|
1794
|
-
this.interceptorRegistry.init({
|
|
1795
|
-
breadcrumbStore,
|
|
1796
|
-
agent,
|
|
1797
|
-
contextCollector
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
this.isInitialized = true;
|
|
1802
|
-
console.log('SyntropyFront inicializado correctamente');
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
/**
|
|
1806
|
-
* Carga módulos dinámicamente basado en la configuración
|
|
1807
|
-
*/
|
|
1808
|
-
async loadModules() {
|
|
1809
|
-
const loadPromises = [];
|
|
1810
|
-
|
|
1811
|
-
// Cargar ProxyObjectTracker si está habilitado
|
|
1812
|
-
if (this.config.proxyTracking?.enabled) {
|
|
1813
|
-
loadPromises.push(
|
|
1814
|
-
Promise.resolve().then(function () { return ProxyObjectTracker$1; })
|
|
1815
|
-
.then(module => {
|
|
1816
|
-
this.proxyObjectTracker = module.proxyObjectTracker;
|
|
1817
|
-
console.log('🔄 ProxyObjectTracker cargado dinámicamente');
|
|
1818
|
-
})
|
|
1819
|
-
.catch(error => {
|
|
1820
|
-
console.warn('⚠️ Error cargando ProxyObjectTracker:', error);
|
|
1821
|
-
})
|
|
1822
|
-
);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
// Cargar InterceptorRegistry si hay interceptores personalizados
|
|
1826
|
-
if (this.config.useInterceptors !== false) {
|
|
1827
|
-
loadPromises.push(
|
|
1828
|
-
Promise.resolve().then(function () { return InterceptorRegistry$1; })
|
|
1829
|
-
.then(module => {
|
|
1830
|
-
this.interceptorRegistry = module.interceptorRegistry;
|
|
1831
|
-
console.log('🔄 InterceptorRegistry cargado dinámicamente');
|
|
1832
|
-
})
|
|
1833
|
-
.catch(error => {
|
|
1834
|
-
console.warn('⚠️ Error cargando InterceptorRegistry:', error);
|
|
1835
|
-
})
|
|
1836
|
-
);
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// Cargar WorkerManager si está habilitado
|
|
1840
|
-
if (this.config.useWorker !== false) {
|
|
1841
|
-
loadPromises.push(
|
|
1842
|
-
Promise.resolve().then(function () { return WorkerManager$1; })
|
|
1843
|
-
.then(module => {
|
|
1844
|
-
this.workerManager = new module.default();
|
|
1845
|
-
console.log('🔄 WorkerManager cargado dinámicamente');
|
|
1846
|
-
})
|
|
1847
|
-
.catch(error => {
|
|
1848
|
-
console.warn('⚠️ Error cargando WorkerManager:', error);
|
|
1849
|
-
})
|
|
1850
|
-
);
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
// Esperar a que todos los módulos se carguen
|
|
1854
|
-
await Promise.all(loadPromises);
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
/**
|
|
1858
|
-
* Añade un breadcrumb manualmente
|
|
1859
|
-
* @param {string} category - Categoría del evento
|
|
1860
|
-
* @param {string} message - Mensaje descriptivo
|
|
1861
|
-
* @param {Object} data - Datos adicionales
|
|
1862
|
-
*/
|
|
1863
|
-
addBreadcrumb(category, message, data = {}) {
|
|
1864
|
-
breadcrumbStore.add({ category, message, data });
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
/**
|
|
1868
|
-
* Obtiene todos los breadcrumbs
|
|
1869
|
-
* @returns {Array} Lista de breadcrumbs
|
|
1870
|
-
*/
|
|
1871
|
-
getBreadcrumbs() {
|
|
1872
|
-
return breadcrumbStore.getAll();
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
/**
|
|
1876
|
-
* Obtiene breadcrumbs por categoría
|
|
1877
|
-
* @param {string} category - Categoría a filtrar
|
|
1878
|
-
* @returns {Array} Breadcrumbs de la categoría
|
|
1879
|
-
*/
|
|
1880
|
-
getBreadcrumbsByCategory(category) {
|
|
1881
|
-
return breadcrumbStore.getByCategory(category);
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
/**
|
|
1885
|
-
* Limpia todos los breadcrumbs
|
|
1886
|
-
*/
|
|
1887
|
-
clearBreadcrumbs() {
|
|
1888
|
-
breadcrumbStore.clear();
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
/**
|
|
1892
|
-
* Desactiva el sistema de trazabilidad
|
|
1893
|
-
*/
|
|
1894
|
-
destroy() {
|
|
1895
|
-
if (!this.isInitialized) return;
|
|
1896
|
-
|
|
1897
|
-
interceptors.destroy();
|
|
1898
|
-
|
|
1899
|
-
if (this.interceptorRegistry) {
|
|
1900
|
-
this.interceptorRegistry.destroy();
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
breadcrumbStore.clear();
|
|
1904
|
-
agent.disable();
|
|
1905
|
-
|
|
1906
|
-
if (this.workerManager) {
|
|
1907
|
-
this.workerManager.destroy();
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
this.isInitialized = false;
|
|
1911
|
-
console.log('SyntropyFront desactivado');
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
/**
|
|
1915
|
-
* Configura el tamaño máximo de breadcrumbs
|
|
1916
|
-
* @param {number} maxBreadcrumbs - Nuevo tamaño máximo
|
|
1917
|
-
*/
|
|
1918
|
-
setMaxBreadcrumbs(maxBreadcrumbs) {
|
|
1919
|
-
breadcrumbStore.setMaxBreadcrumbs(maxBreadcrumbs);
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
/**
|
|
1923
|
-
* Obtiene el tamaño máximo actual de breadcrumbs
|
|
1924
|
-
* @returns {number} Tamaño máximo
|
|
1925
|
-
*/
|
|
1926
|
-
getMaxBreadcrumbs() {
|
|
1927
|
-
return breadcrumbStore.getMaxBreadcrumbs();
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
/**
|
|
1931
|
-
* Fuerza el envío de datos pendientes al backend
|
|
1932
|
-
*/
|
|
1933
|
-
async flush() {
|
|
1934
|
-
await agent.forceFlush();
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
/**
|
|
1938
|
-
* Obtiene el contexto actual según la configuración
|
|
1939
|
-
* @returns {Object} Contexto recolectado
|
|
1940
|
-
*/
|
|
1941
|
-
getContext() {
|
|
1942
|
-
const context = contextCollector.collect(this.contextConfig);
|
|
1943
|
-
|
|
1944
|
-
// Agregar objetos personalizados
|
|
1945
|
-
const customObjects = customObjectCollector.collectCustomObjects();
|
|
1946
|
-
if (Object.keys(customObjects).length > 0) {
|
|
1947
|
-
context.customObjects = customObjects;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
return context;
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
/**
|
|
1954
|
-
* Obtiene todos los tipos de contexto disponibles
|
|
1955
|
-
* @returns {Array} Tipos disponibles
|
|
1956
|
-
*/
|
|
1957
|
-
getAvailableContextTypes() {
|
|
1958
|
-
return contextCollector.getAvailableTypes();
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
/**
|
|
1962
|
-
* Obtiene los campos disponibles para un tipo de contexto
|
|
1963
|
-
* @param {string} contextType - Tipo de contexto
|
|
1964
|
-
* @returns {Array} Campos disponibles
|
|
1965
|
-
*/
|
|
1966
|
-
getAvailableContextFields(contextType) {
|
|
1967
|
-
return contextCollector.getAvailableFields(contextType);
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
/**
|
|
1971
|
-
* Obtiene información sobre los sets por defecto
|
|
1972
|
-
* @returns {Object} Información de sets por defecto
|
|
1973
|
-
*/
|
|
1974
|
-
getDefaultContextsInfo() {
|
|
1975
|
-
return contextCollector.getDefaultContextsInfo();
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
/**
|
|
1979
|
-
* Configura el contexto a recolectar
|
|
1980
|
-
* @param {Object} contextConfig - Configuración de contexto
|
|
1981
|
-
*/
|
|
1982
|
-
setContext(contextConfig) {
|
|
1983
|
-
if (typeof contextConfig !== 'object') {
|
|
1984
|
-
console.warn('SyntropyFront: contextConfig debe ser un objeto');
|
|
1985
|
-
return;
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
this.contextConfig = contextConfig;
|
|
1989
|
-
console.log('SyntropyFront: Configuración de contexto actualizada:', contextConfig);
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
/**
|
|
1993
|
-
* Configura los tipos de contexto a recolectar (método legacy)
|
|
1994
|
-
* @param {Array} contextTypes - Tipos de contexto
|
|
1995
|
-
*/
|
|
1996
|
-
setContextTypes(contextTypes) {
|
|
1997
|
-
if (!Array.isArray(contextTypes)) {
|
|
1998
|
-
console.warn('SyntropyFront: contextTypes debe ser un array');
|
|
1999
|
-
return;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
// Convertir array a configuración por defecto
|
|
2003
|
-
const contextConfig = {};
|
|
2004
|
-
contextTypes.forEach(type => {
|
|
2005
|
-
contextConfig[type] = true; // Usar set por defecto
|
|
2006
|
-
});
|
|
2007
|
-
|
|
2008
|
-
this.setContext(contextConfig);
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
// ===== DEPRECATED: CUSTOM OBJECT METHODS =====
|
|
2012
|
-
// Estos métodos están deprecados. Usa ProxyObjectTracker en su lugar.
|
|
2013
|
-
|
|
2014
|
-
/**
|
|
2015
|
-
* @deprecated Usa addProxyObject() en su lugar
|
|
2016
|
-
*/
|
|
2017
|
-
addCustomObject(name, source, maxStates = 10) {
|
|
2018
|
-
console.warn('SyntropyFront: addCustomObject() está deprecado. Usa addProxyObject() en su lugar.');
|
|
2019
|
-
throw new Error('addCustomObject() está deprecado. Usa addProxyObject() en su lugar.');
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
/**
|
|
2023
|
-
* @deprecated Usa removeProxyObject() en su lugar
|
|
2024
|
-
*/
|
|
2025
|
-
removeCustomObject(name) {
|
|
2026
|
-
console.warn('SyntropyFront: removeCustomObject() está deprecado. Usa removeProxyObject() en su lugar.');
|
|
2027
|
-
throw new Error('removeCustomObject() está deprecado. Usa removeProxyObject() en su lugar.');
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
/**
|
|
2031
|
-
* @deprecated Usa getProxyObjectState() en su lugar
|
|
2032
|
-
*/
|
|
2033
|
-
getCustomObjectValue(name) {
|
|
2034
|
-
console.warn('SyntropyFront: getCustomObjectValue() está deprecado. Usa getProxyObjectState() en su lugar.');
|
|
2035
|
-
throw new Error('getCustomObjectValue() está deprecado. Usa getProxyObjectState() en su lugar.');
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
/**
|
|
2039
|
-
* @deprecated Usa getProxyObjectHistory() en su lugar
|
|
2040
|
-
*/
|
|
2041
|
-
getCustomObjectHistory(name) {
|
|
2042
|
-
console.warn('SyntropyFront: getCustomObjectHistory() está deprecado. Usa getProxyObjectHistory() en su lugar.');
|
|
2043
|
-
throw new Error('getCustomObjectHistory() está deprecado. Usa getProxyObjectHistory() en su lugar.');
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
/**
|
|
2047
|
-
* @deprecated Usa getProxyTrackedObjects() en su lugar
|
|
2048
|
-
*/
|
|
2049
|
-
getCustomObjectNames() {
|
|
2050
|
-
console.warn('SyntropyFront: getCustomObjectNames() está deprecado. Usa getProxyTrackedObjects() en su lugar.');
|
|
2051
|
-
throw new Error('getCustomObjectNames() está deprecado. Usa getProxyTrackedObjects() en su lugar.');
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
/**
|
|
2055
|
-
* Inyecta un interceptor personalizado
|
|
2056
|
-
* @param {string} name - Nombre del interceptor
|
|
2057
|
-
* @param {Object} interceptor - Objeto interceptor con métodos init/destroy
|
|
2058
|
-
* @returns {SyntropyFront} Instancia para chaining
|
|
2059
|
-
*/
|
|
2060
|
-
inject(name, interceptor) {
|
|
2061
|
-
if (!this.interceptorRegistry) {
|
|
2062
|
-
console.warn('SyntropyFront: InterceptorRegistry no está cargado. Asegúrate de que useInterceptors no esté en false.');
|
|
2063
|
-
return this;
|
|
2064
|
-
}
|
|
2065
|
-
this.interceptorRegistry.register(name, interceptor);
|
|
2066
|
-
return this; // Para chaining
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
/**
|
|
2070
|
-
* Remueve un interceptor personalizado
|
|
2071
|
-
* @param {string} name - Nombre del interceptor
|
|
2072
|
-
*/
|
|
2073
|
-
removeInterceptor(name) {
|
|
2074
|
-
if (!this.interceptorRegistry) {
|
|
2075
|
-
console.warn('SyntropyFront: InterceptorRegistry no está cargado.');
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
this.interceptorRegistry.unregister(name);
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
/**
|
|
2082
|
-
* Obtiene la lista de interceptores registrados
|
|
2083
|
-
* @returns {Array} Lista de nombres de interceptores
|
|
2084
|
-
*/
|
|
2085
|
-
getRegisteredInterceptors() {
|
|
2086
|
-
if (!this.interceptorRegistry) {
|
|
2087
|
-
return [];
|
|
2088
|
-
}
|
|
2089
|
-
return this.interceptorRegistry.getRegisteredInterceptors();
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
/**
|
|
2093
|
-
* Obtiene información de un interceptor específico
|
|
2094
|
-
* @param {string} name - Nombre del interceptor
|
|
2095
|
-
* @returns {Object|null} Información del interceptor
|
|
2096
|
-
*/
|
|
2097
|
-
getInterceptorInfo(name) {
|
|
2098
|
-
if (!this.interceptorRegistry) {
|
|
2099
|
-
return null;
|
|
2100
|
-
}
|
|
2101
|
-
return this.interceptorRegistry.getInterceptorInfo(name);
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
/**
|
|
2105
|
-
* Verifica si está inicializado
|
|
2106
|
-
* @returns {boolean} Estado de inicialización
|
|
2107
|
-
*/
|
|
2108
|
-
isActive() {
|
|
2109
|
-
return this.isInitialized;
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
// ===== PROXY OBJECT TRACKER METHODS =====
|
|
2113
|
-
|
|
2114
|
-
/**
|
|
2115
|
-
* Agrega un objeto para tracking reactivo con Proxy
|
|
2116
|
-
* @param {string} objectPath - Ruta/nombre del objeto
|
|
2117
|
-
* @param {Object} targetObject - Objeto a trackear
|
|
2118
|
-
* @param {Object} options - Opciones de tracking
|
|
2119
|
-
* @returns {Object} Proxy del objeto original
|
|
2120
|
-
*/
|
|
2121
|
-
addProxyObject(objectPath, targetObject, options = {}) {
|
|
2122
|
-
if (!this.proxyObjectTracker) {
|
|
2123
|
-
console.warn('SyntropyFront: ProxyObjectTracker no está cargado. Asegúrate de que proxyTracking.enabled esté en true.');
|
|
2124
|
-
return targetObject;
|
|
2125
|
-
}
|
|
2126
|
-
return this.proxyObjectTracker.addObject(objectPath, targetObject, options);
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
/**
|
|
2130
|
-
* Obtiene el historial de estados de un objeto trackeado
|
|
2131
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2132
|
-
* @returns {Array} Historial de estados
|
|
2133
|
-
*/
|
|
2134
|
-
getProxyObjectHistory(objectPath) {
|
|
2135
|
-
if (!this.proxyObjectTracker) {
|
|
2136
|
-
return [];
|
|
2137
|
-
}
|
|
2138
|
-
return this.proxyObjectTracker.getObjectHistory(objectPath);
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
/**
|
|
2142
|
-
* Obtiene el estado actual de un objeto trackeado
|
|
2143
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2144
|
-
* @returns {Object|null} Estado actual
|
|
2145
|
-
*/
|
|
2146
|
-
getProxyObjectState(objectPath) {
|
|
2147
|
-
if (!this.proxyObjectTracker) {
|
|
2148
|
-
return null;
|
|
2149
|
-
}
|
|
2150
|
-
return this.proxyObjectTracker.getCurrentState(objectPath);
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
/**
|
|
2154
|
-
* Obtiene todos los objetos trackeados con Proxy
|
|
2155
|
-
* @returns {Array} Lista de objetos trackeados
|
|
2156
|
-
*/
|
|
2157
|
-
getProxyTrackedObjects() {
|
|
2158
|
-
if (!this.proxyObjectTracker) {
|
|
2159
|
-
return [];
|
|
2160
|
-
}
|
|
2161
|
-
return this.proxyObjectTracker.getTrackedObjects();
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
/**
|
|
2165
|
-
* Remueve un objeto del tracking con Proxy
|
|
2166
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2167
|
-
* @returns {Object|null} Objeto original (sin proxy)
|
|
2168
|
-
*/
|
|
2169
|
-
removeProxyObject(objectPath) {
|
|
2170
|
-
if (!this.proxyObjectTracker) {
|
|
2171
|
-
return null;
|
|
2172
|
-
}
|
|
2173
|
-
return this.proxyObjectTracker.removeObject(objectPath);
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
/**
|
|
2177
|
-
* Limpia todos los objetos trackeados con Proxy
|
|
2178
|
-
*/
|
|
2179
|
-
clearProxyObjects() {
|
|
2180
|
-
if (!this.proxyObjectTracker) {
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
this.proxyObjectTracker.clear();
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
/**
|
|
2187
|
-
* Obtiene estadísticas del ProxyObjectTracker
|
|
2188
|
-
* @returns {Object} Estadísticas
|
|
2189
|
-
*/
|
|
2190
|
-
getProxyTrackerStats() {
|
|
2191
|
-
if (!this.proxyObjectTracker) {
|
|
2192
|
-
return { enabled: false, trackedObjects: 0 };
|
|
2193
|
-
}
|
|
2194
|
-
return this.proxyObjectTracker.getStats();
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
// Worker Manager Methods
|
|
2198
|
-
async addBreadcrumbToWorker(type, message, data = {}) {
|
|
2199
|
-
if (!this.workerManager) {
|
|
2200
|
-
console.warn('SyntropyFront: WorkerManager no está cargado. Asegúrate de que useWorker no esté en false.');
|
|
2201
|
-
return this.addBreadcrumb(type, message, data);
|
|
2202
|
-
}
|
|
2203
|
-
if (this.workerManager.isWorkerAvailable()) {
|
|
2204
|
-
return await this.workerManager.addBreadcrumb(type, message, data);
|
|
2205
|
-
} else {
|
|
2206
|
-
// Fallback al método normal
|
|
2207
|
-
return this.addBreadcrumb(type, message, data);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
async getBreadcrumbsFromWorker() {
|
|
2212
|
-
if (!this.workerManager) {
|
|
2213
|
-
return this.getBreadcrumbs();
|
|
2214
|
-
}
|
|
2215
|
-
if (this.workerManager.isWorkerAvailable()) {
|
|
2216
|
-
return await this.workerManager.getBreadcrumbs();
|
|
2217
|
-
} else {
|
|
2218
|
-
return this.getBreadcrumbs();
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
async clearBreadcrumbsFromWorker() {
|
|
2223
|
-
if (!this.workerManager) {
|
|
2224
|
-
return this.clearBreadcrumbs();
|
|
2225
|
-
}
|
|
2226
|
-
if (this.workerManager.isWorkerAvailable()) {
|
|
2227
|
-
return await this.workerManager.clearBreadcrumbs();
|
|
2228
|
-
} else {
|
|
2229
|
-
return this.clearBreadcrumbs();
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
async sendErrorToWorker(error, context = {}) {
|
|
2234
|
-
if (!this.workerManager) {
|
|
2235
|
-
console.warn('SyntropyFront: WorkerManager no está cargado. Asegúrate de que useWorker no esté en false.');
|
|
2236
|
-
return this.sendError(error, context);
|
|
2237
|
-
}
|
|
2238
|
-
if (this.workerManager.isWorkerAvailable()) {
|
|
2239
|
-
return await this.workerManager.sendError(error, context);
|
|
2240
|
-
} else {
|
|
2241
|
-
// Fallback al método normal
|
|
2242
|
-
return this.sendError(error, context);
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
async pingWorker() {
|
|
2247
|
-
if (!this.workerManager) {
|
|
2248
|
-
return { success: false, message: 'Worker no cargado' };
|
|
2249
|
-
}
|
|
2250
|
-
if (this.workerManager.isWorkerAvailable()) {
|
|
2251
|
-
return await this.workerManager.ping();
|
|
2252
|
-
} else {
|
|
2253
|
-
return { success: false, message: 'Worker no disponible' };
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
getWorkerStatus() {
|
|
2258
|
-
if (!this.workerManager) {
|
|
2259
|
-
return { isAvailable: false, isInitialized: false, pendingRequests: 0 };
|
|
2260
|
-
}
|
|
2261
|
-
return this.workerManager.getStatus();
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
isWorkerAvailable() {
|
|
2265
|
-
if (!this.workerManager) {
|
|
2266
|
-
return false;
|
|
2267
|
-
}
|
|
2268
|
-
return this.workerManager.isWorkerAvailable();
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
// Preset Methods
|
|
2272
|
-
getCurrentPreset() {
|
|
2273
|
-
return this.currentPreset;
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
getPresetInfo(presetName = null) {
|
|
2277
|
-
const name = presetName || this.currentPreset;
|
|
2278
|
-
if (!name) {
|
|
2279
|
-
return null;
|
|
2280
|
-
}
|
|
2281
|
-
return getPresetInfo(name);
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
getAvailablePresets() {
|
|
2285
|
-
return getAvailablePresets();
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
async changePreset(presetName, options = {}) {
|
|
2289
|
-
if (this.isInitialized) {
|
|
2290
|
-
console.warn('SyntropyFront: No se puede cambiar preset después de la inicialización');
|
|
2291
|
-
return false;
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
try {
|
|
2295
|
-
const preset = getPreset(presetName);
|
|
2296
|
-
this.currentPreset = presetName;
|
|
2297
|
-
|
|
2298
|
-
// Aplicar preset
|
|
2299
|
-
this.config = { ...this.config, ...preset };
|
|
2300
|
-
|
|
2301
|
-
// Aplicar opciones adicionales
|
|
2302
|
-
this.config = { ...this.config, ...options };
|
|
2303
|
-
|
|
2304
|
-
console.log(`🎯 SyntropyFront: Preset cambiado a '${presetName}' - ${preset.description}`);
|
|
2305
|
-
return true;
|
|
2306
|
-
} catch (error) {
|
|
2307
|
-
console.error(`❌ SyntropyFront: Error cambiando preset a '${presetName}':`, error.message);
|
|
2308
|
-
return false;
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
|
-
getConfiguration() {
|
|
2313
|
-
return {
|
|
2314
|
-
currentPreset: this.currentPreset,
|
|
2315
|
-
config: this.config,
|
|
2316
|
-
isInitialized: this.isInitialized
|
|
2317
|
-
};
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
// Instancia singleton principal
|
|
2322
|
-
const syntropyFront = new SyntropyFront();
|
|
2323
|
-
|
|
2324
|
-
/**
|
|
2325
|
-
* ProxyObjectTracker - Tracking reactivo de objetos usando Proxy
|
|
2326
|
-
* Captura cambios en tiempo real sin necesidad de polling
|
|
2327
|
-
*/
|
|
2328
|
-
class ProxyObjectTracker {
|
|
2329
|
-
constructor() {
|
|
2330
|
-
this.trackedObjects = new Map(); // Map<objectPath, ProxyInfo>
|
|
2331
|
-
this.maxStates = 10; // Estados máximos por objeto
|
|
2332
|
-
this.isEnabled = true;
|
|
2333
|
-
this.onChangeCallback = null; // Callback cuando cambia un objeto
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
/**
|
|
2337
|
-
* Configura el tracker
|
|
2338
|
-
* @param {Object} config - Configuración
|
|
2339
|
-
* @param {number} [config.maxStates] - Máximo número de estados por objeto
|
|
2340
|
-
* @param {Function} [config.onChange] - Callback cuando cambia un objeto
|
|
2341
|
-
*/
|
|
2342
|
-
configure(config = {}) {
|
|
2343
|
-
this.maxStates = config.maxStates || this.maxStates;
|
|
2344
|
-
this.onChangeCallback = config.onChange || null;
|
|
2345
|
-
this.isEnabled = config.enabled !== false;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
/**
|
|
2349
|
-
* Agrega un objeto para tracking reactivo
|
|
2350
|
-
* @param {string} objectPath - Ruta/nombre del objeto
|
|
2351
|
-
* @param {Object} targetObject - Objeto a trackear
|
|
2352
|
-
* @param {Object} options - Opciones de tracking
|
|
2353
|
-
* @returns {Object} Proxy del objeto original
|
|
2354
|
-
*/
|
|
2355
|
-
addObject(objectPath, targetObject, options = {}) {
|
|
2356
|
-
if (!this.isEnabled) {
|
|
2357
|
-
console.warn('SyntropyFront: ProxyObjectTracker deshabilitado');
|
|
2358
|
-
return targetObject;
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
if (!targetObject || typeof targetObject !== 'object') {
|
|
2362
|
-
console.warn(`SyntropyFront: Objeto inválido para tracking: ${objectPath}`);
|
|
2363
|
-
return targetObject;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
// Verificar si ya está siendo trackeado
|
|
2367
|
-
if (this.trackedObjects.has(objectPath)) {
|
|
2368
|
-
console.warn(`SyntropyFront: Objeto ya está siendo trackeado: ${objectPath}`);
|
|
2369
|
-
return this.trackedObjects.get(objectPath).proxy;
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
try {
|
|
2373
|
-
// Crear estado inicial
|
|
2374
|
-
const initialState = {
|
|
2375
|
-
value: this.deepClone(targetObject),
|
|
2376
|
-
timestamp: new Date().toISOString(),
|
|
2377
|
-
changeType: 'initial'
|
|
2378
|
-
};
|
|
2379
|
-
|
|
2380
|
-
// Crear info del objeto trackeado
|
|
2381
|
-
const proxyInfo = {
|
|
2382
|
-
objectPath,
|
|
2383
|
-
originalObject: targetObject,
|
|
2384
|
-
states: [initialState],
|
|
2385
|
-
proxy: null,
|
|
2386
|
-
options: {
|
|
2387
|
-
trackNested: options.trackNested !== false,
|
|
2388
|
-
trackArrays: options.trackArrays !== false,
|
|
2389
|
-
trackFunctions: options.trackFunctions !== false,
|
|
2390
|
-
maxDepth: options.maxDepth || 5
|
|
2391
|
-
}
|
|
2392
|
-
};
|
|
2393
|
-
|
|
2394
|
-
// Crear Proxy
|
|
2395
|
-
const proxy = this.createProxy(targetObject, proxyInfo);
|
|
2396
|
-
proxyInfo.proxy = proxy;
|
|
2397
|
-
|
|
2398
|
-
// Guardar en el mapa
|
|
2399
|
-
this.trackedObjects.set(objectPath, proxyInfo);
|
|
2400
|
-
|
|
2401
|
-
console.log(`SyntropyFront: Objeto agregado para tracking reactivo: ${objectPath}`);
|
|
2402
|
-
return proxy;
|
|
2403
|
-
|
|
2404
|
-
} catch (error) {
|
|
2405
|
-
console.error(`SyntropyFront: Error creando proxy para ${objectPath}:`, error);
|
|
2406
|
-
return targetObject;
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
/**
|
|
2411
|
-
* Crea un Proxy que intercepta cambios
|
|
2412
|
-
* @param {Object} target - Objeto objetivo
|
|
2413
|
-
* @param {Object} proxyInfo - Información del proxy
|
|
2414
|
-
* @param {number} depth - Profundidad actual
|
|
2415
|
-
* @returns {Proxy} Proxy del objeto
|
|
2416
|
-
*/
|
|
2417
|
-
createProxy(target, proxyInfo, depth = 0) {
|
|
2418
|
-
const { objectPath, options } = proxyInfo;
|
|
2419
|
-
|
|
2420
|
-
return new Proxy(target, {
|
|
2421
|
-
get: (obj, prop) => {
|
|
2422
|
-
const value = obj[prop];
|
|
2423
|
-
|
|
2424
|
-
// Si es un objeto/array y queremos trackear anidados
|
|
2425
|
-
if (options.trackNested &&
|
|
2426
|
-
depth < options.maxDepth &&
|
|
2427
|
-
value &&
|
|
2428
|
-
typeof value === 'object' &&
|
|
2429
|
-
!(value instanceof Date) &&
|
|
2430
|
-
!(value instanceof RegExp) &&
|
|
2431
|
-
!(value instanceof Error)) {
|
|
2432
|
-
|
|
2433
|
-
// Crear proxy para objetos anidados
|
|
2434
|
-
if (Array.isArray(value) && options.trackArrays) {
|
|
2435
|
-
return this.createArrayProxy(value, proxyInfo, depth + 1);
|
|
2436
|
-
} else if (!Array.isArray(value)) {
|
|
2437
|
-
return this.createProxy(value, proxyInfo, depth + 1);
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
return value;
|
|
2442
|
-
},
|
|
2443
|
-
|
|
2444
|
-
set: (obj, prop, value) => {
|
|
2445
|
-
const oldValue = obj[prop];
|
|
2446
|
-
|
|
2447
|
-
// Solo registrar si realmente cambió
|
|
2448
|
-
if (!this.isEqual(oldValue, value)) {
|
|
2449
|
-
// Guardar estado anterior antes del cambio
|
|
2450
|
-
this.saveState(proxyInfo, 'property_change', {
|
|
2451
|
-
property: prop,
|
|
2452
|
-
oldValue: this.deepClone(oldValue),
|
|
2453
|
-
newValue: this.deepClone(value),
|
|
2454
|
-
path: `${objectPath}.${prop}`
|
|
2455
|
-
});
|
|
2456
|
-
|
|
2457
|
-
// Aplicar el cambio
|
|
2458
|
-
obj[prop] = value;
|
|
2459
|
-
|
|
2460
|
-
// Notificar cambio
|
|
2461
|
-
this.notifyChange(proxyInfo, prop, oldValue, value);
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
return true;
|
|
2465
|
-
},
|
|
2466
|
-
|
|
2467
|
-
deleteProperty: (obj, prop) => {
|
|
2468
|
-
const oldValue = obj[prop];
|
|
2469
|
-
|
|
2470
|
-
// Guardar estado antes de eliminar
|
|
2471
|
-
this.saveState(proxyInfo, 'property_deleted', {
|
|
2472
|
-
property: prop,
|
|
2473
|
-
oldValue: this.deepClone(oldValue),
|
|
2474
|
-
path: `${objectPath}.${prop}`
|
|
2475
|
-
});
|
|
2476
|
-
|
|
2477
|
-
// Eliminar la propiedad
|
|
2478
|
-
const result = delete obj[prop];
|
|
2479
|
-
|
|
2480
|
-
// Notificar cambio
|
|
2481
|
-
this.notifyChange(proxyInfo, prop, oldValue, undefined);
|
|
2482
|
-
|
|
2483
|
-
return result;
|
|
2484
|
-
}
|
|
2485
|
-
});
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
/**
|
|
2489
|
-
* Crea un Proxy especial para arrays
|
|
2490
|
-
* @param {Array} target - Array objetivo
|
|
2491
|
-
* @param {Object} proxyInfo - Información del proxy
|
|
2492
|
-
* @param {number} depth - Profundidad actual
|
|
2493
|
-
* @returns {Proxy} Proxy del array
|
|
2494
|
-
*/
|
|
2495
|
-
createArrayProxy(target, proxyInfo, depth = 0) {
|
|
2496
|
-
const { objectPath, options } = proxyInfo;
|
|
2497
|
-
|
|
2498
|
-
return new Proxy(target, {
|
|
2499
|
-
get: (obj, prop) => {
|
|
2500
|
-
const value = obj[prop];
|
|
2501
|
-
|
|
2502
|
-
// Si es un método de array que modifica
|
|
2503
|
-
if (typeof value === 'function' && this.isArrayMutator(prop)) {
|
|
2504
|
-
return (...args) => {
|
|
2505
|
-
// Guardar estado antes de la mutación
|
|
2506
|
-
this.saveState(proxyInfo, 'array_mutation', {
|
|
2507
|
-
method: prop,
|
|
2508
|
-
arguments: args,
|
|
2509
|
-
oldArray: this.deepClone(obj),
|
|
2510
|
-
path: objectPath
|
|
2511
|
-
});
|
|
2512
|
-
|
|
2513
|
-
// Ejecutar el método
|
|
2514
|
-
const result = value.apply(obj, args);
|
|
2515
|
-
|
|
2516
|
-
// Notificar cambio
|
|
2517
|
-
this.notifyChange(proxyInfo, prop, null, obj);
|
|
2518
|
-
|
|
2519
|
-
return result;
|
|
2520
|
-
};
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
// Si es un elemento del array y es un objeto
|
|
2524
|
-
if (options.trackNested &&
|
|
2525
|
-
depth < options.maxDepth &&
|
|
2526
|
-
value &&
|
|
2527
|
-
typeof value === 'object' &&
|
|
2528
|
-
!Array.isArray(value) &&
|
|
2529
|
-
!(value instanceof Date) &&
|
|
2530
|
-
!(value instanceof RegExp) &&
|
|
2531
|
-
!(value instanceof Error)) {
|
|
2532
|
-
|
|
2533
|
-
return this.createProxy(value, proxyInfo, depth + 1);
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
return value;
|
|
2537
|
-
},
|
|
2538
|
-
|
|
2539
|
-
set: (obj, prop, value) => {
|
|
2540
|
-
const oldValue = obj[prop];
|
|
2541
|
-
|
|
2542
|
-
// Solo registrar si realmente cambió
|
|
2543
|
-
if (!this.isEqual(oldValue, value)) {
|
|
2544
|
-
// Guardar estado anterior
|
|
2545
|
-
this.saveState(proxyInfo, 'array_element_change', {
|
|
2546
|
-
index: prop,
|
|
2547
|
-
oldValue: this.deepClone(oldValue),
|
|
2548
|
-
newValue: this.deepClone(value),
|
|
2549
|
-
path: `${objectPath}[${prop}]`
|
|
2550
|
-
});
|
|
2551
|
-
|
|
2552
|
-
// Aplicar el cambio
|
|
2553
|
-
obj[prop] = value;
|
|
2554
|
-
|
|
2555
|
-
// Notificar cambio
|
|
2556
|
-
this.notifyChange(proxyInfo, prop, oldValue, value);
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
return true;
|
|
2560
|
-
}
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
|
-
/**
|
|
2565
|
-
* Verifica si un método de array es mutador
|
|
2566
|
-
* @param {string} method - Nombre del método
|
|
2567
|
-
* @returns {boolean} True si es mutador
|
|
2568
|
-
*/
|
|
2569
|
-
isArrayMutator(method) {
|
|
2570
|
-
const mutators = [
|
|
2571
|
-
'push', 'pop', 'shift', 'unshift', 'splice',
|
|
2572
|
-
'reverse', 'sort', 'fill', 'copyWithin'
|
|
2573
|
-
];
|
|
2574
|
-
return mutators.includes(method);
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
/**
|
|
2578
|
-
* Guarda un estado en el historial
|
|
2579
|
-
* @param {Object} proxyInfo - Información del proxy
|
|
2580
|
-
* @param {string} changeType - Tipo de cambio
|
|
2581
|
-
* @param {Object} changeData - Datos del cambio
|
|
2582
|
-
*/
|
|
2583
|
-
saveState(proxyInfo, changeType, changeData = {}) {
|
|
2584
|
-
const state = {
|
|
2585
|
-
value: this.deepClone(proxyInfo.originalObject),
|
|
2586
|
-
timestamp: new Date().toISOString(),
|
|
2587
|
-
changeType,
|
|
2588
|
-
changeData
|
|
2589
|
-
};
|
|
2590
|
-
|
|
2591
|
-
// Agregar al historial
|
|
2592
|
-
proxyInfo.states.push(state);
|
|
2593
|
-
|
|
2594
|
-
// Mantener solo los últimos maxStates
|
|
2595
|
-
if (proxyInfo.states.length > this.maxStates) {
|
|
2596
|
-
proxyInfo.states.shift();
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
/**
|
|
2601
|
-
* Notifica un cambio
|
|
2602
|
-
* @param {Object} proxyInfo - Información del proxy
|
|
2603
|
-
* @param {string} property - Propiedad que cambió
|
|
2604
|
-
* @param {any} oldValue - Valor anterior
|
|
2605
|
-
* @param {any} newValue - Valor nuevo
|
|
2606
|
-
*/
|
|
2607
|
-
notifyChange(proxyInfo, property, oldValue, newValue) {
|
|
2608
|
-
if (this.onChangeCallback) {
|
|
2609
|
-
try {
|
|
2610
|
-
this.onChangeCallback({
|
|
2611
|
-
objectPath: proxyInfo.objectPath,
|
|
2612
|
-
property,
|
|
2613
|
-
oldValue,
|
|
2614
|
-
newValue,
|
|
2615
|
-
timestamp: new Date().toISOString(),
|
|
2616
|
-
states: proxyInfo.states.length
|
|
2617
|
-
});
|
|
2618
|
-
} catch (error) {
|
|
2619
|
-
console.error('SyntropyFront: Error en callback de cambio:', error);
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
/**
|
|
2625
|
-
* Obtiene el historial de estados de un objeto
|
|
2626
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2627
|
-
* @returns {Array} Historial de estados
|
|
2628
|
-
*/
|
|
2629
|
-
getObjectHistory(objectPath) {
|
|
2630
|
-
const proxyInfo = this.trackedObjects.get(objectPath);
|
|
2631
|
-
if (!proxyInfo) {
|
|
2632
|
-
console.warn(`SyntropyFront: Objeto no encontrado: ${objectPath}`);
|
|
2633
|
-
return [];
|
|
2634
|
-
}
|
|
2635
|
-
|
|
2636
|
-
return [...proxyInfo.states];
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
/**
|
|
2640
|
-
* Obtiene el estado actual de un objeto
|
|
2641
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2642
|
-
* @returns {Object|null} Estado actual
|
|
2643
|
-
*/
|
|
2644
|
-
getCurrentState(objectPath) {
|
|
2645
|
-
const proxyInfo = this.trackedObjects.get(objectPath);
|
|
2646
|
-
if (!proxyInfo) {
|
|
2647
|
-
return null;
|
|
2648
|
-
}
|
|
2649
|
-
|
|
2650
|
-
return {
|
|
2651
|
-
value: this.deepClone(proxyInfo.originalObject),
|
|
2652
|
-
timestamp: new Date().toISOString(),
|
|
2653
|
-
statesCount: proxyInfo.states.length
|
|
2654
|
-
};
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
/**
|
|
2658
|
-
* Obtiene todos los objetos trackeados
|
|
2659
|
-
* @returns {Array} Lista de objetos trackeados
|
|
2660
|
-
*/
|
|
2661
|
-
getTrackedObjects() {
|
|
2662
|
-
return Array.from(this.trackedObjects.keys());
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
/**
|
|
2666
|
-
* Remueve un objeto del tracking
|
|
2667
|
-
* @param {string} objectPath - Ruta del objeto
|
|
2668
|
-
* @returns {Object|null} Objeto original (sin proxy)
|
|
2669
|
-
*/
|
|
2670
|
-
removeObject(objectPath) {
|
|
2671
|
-
const proxyInfo = this.trackedObjects.get(objectPath);
|
|
2672
|
-
if (!proxyInfo) {
|
|
2673
|
-
return null;
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
|
-
this.trackedObjects.delete(objectPath);
|
|
2677
|
-
console.log(`SyntropyFront: Objeto removido del tracking: ${objectPath}`);
|
|
2678
|
-
|
|
2679
|
-
return proxyInfo.originalObject;
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
/**
|
|
2683
|
-
* Limpia todos los objetos trackeados
|
|
2684
|
-
*/
|
|
2685
|
-
clear() {
|
|
2686
|
-
this.trackedObjects.clear();
|
|
2687
|
-
console.log('SyntropyFront: Todos los objetos removidos del tracking');
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
/**
|
|
2691
|
-
* Obtiene estadísticas del tracker
|
|
2692
|
-
* @returns {Object} Estadísticas
|
|
2693
|
-
*/
|
|
2694
|
-
getStats() {
|
|
2695
|
-
const stats = {
|
|
2696
|
-
trackedObjects: this.trackedObjects.size,
|
|
2697
|
-
totalStates: 0,
|
|
2698
|
-
isEnabled: this.isEnabled,
|
|
2699
|
-
maxStates: this.maxStates
|
|
2700
|
-
};
|
|
2701
|
-
|
|
2702
|
-
for (const proxyInfo of this.trackedObjects.values()) {
|
|
2703
|
-
stats.totalStates += proxyInfo.states.length;
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
return stats;
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
/**
|
|
2710
|
-
* Clona profundamente un objeto
|
|
2711
|
-
* @param {any} obj - Objeto a clonar
|
|
2712
|
-
* @returns {any} Objeto clonado
|
|
2713
|
-
*/
|
|
2714
|
-
deepClone(obj) {
|
|
2715
|
-
if (obj === null || obj === undefined) {
|
|
2716
|
-
return obj;
|
|
2717
|
-
}
|
|
2718
|
-
|
|
2719
|
-
if (typeof obj !== 'object') {
|
|
2720
|
-
return obj;
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
if (obj instanceof Date) {
|
|
2724
|
-
return new Date(obj.getTime());
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
if (obj instanceof RegExp) {
|
|
2728
|
-
return new RegExp(obj.source, obj.flags);
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
if (obj instanceof Error) {
|
|
2732
|
-
const error = new Error(obj.message);
|
|
2733
|
-
error.name = obj.name;
|
|
2734
|
-
error.stack = obj.stack;
|
|
2735
|
-
if (obj.cause) {
|
|
2736
|
-
error.cause = this.deepClone(obj.cause);
|
|
2737
|
-
}
|
|
2738
|
-
return error;
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
if (Array.isArray(obj)) {
|
|
2742
|
-
return obj.map(item => this.deepClone(item));
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
const cloned = {};
|
|
2746
|
-
for (const key in obj) {
|
|
2747
|
-
if (obj.hasOwnProperty(key)) {
|
|
2748
|
-
cloned[key] = this.deepClone(obj[key]);
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
|
|
2752
|
-
return cloned;
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
/**
|
|
2756
|
-
* Compara dos valores para verificar si son iguales
|
|
2757
|
-
* @param {any} a - Primer valor
|
|
2758
|
-
* @param {any} b - Segundo valor
|
|
2759
|
-
* @returns {boolean} True si son iguales
|
|
2760
|
-
*/
|
|
2761
|
-
isEqual(a, b) {
|
|
2762
|
-
if (a === b) return true;
|
|
2763
|
-
if (a === null || b === null) return a === b;
|
|
2764
|
-
if (typeof a !== typeof b) return false;
|
|
2765
|
-
if (typeof a !== 'object') return a === b;
|
|
2766
|
-
|
|
2767
|
-
// Para objetos, comparación superficial
|
|
2768
|
-
const keysA = Object.keys(a);
|
|
2769
|
-
const keysB = Object.keys(b);
|
|
2770
|
-
|
|
2771
|
-
if (keysA.length !== keysB.length) return false;
|
|
2772
|
-
|
|
2773
|
-
for (const key of keysA) {
|
|
2774
|
-
if (!keysB.includes(key)) return false;
|
|
2775
|
-
if (a[key] !== b[key]) return false;
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
return true;
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
|
-
// Instancia singleton
|
|
2783
|
-
const proxyObjectTracker = new ProxyObjectTracker();
|
|
2784
|
-
|
|
2785
|
-
var ProxyObjectTracker$1 = /*#__PURE__*/Object.freeze({
|
|
2786
|
-
__proto__: null,
|
|
2787
|
-
ProxyObjectTracker: ProxyObjectTracker,
|
|
2788
|
-
proxyObjectTracker: proxyObjectTracker
|
|
2789
|
-
});
|
|
2790
|
-
|
|
2791
|
-
/**
|
|
2792
|
-
* InterceptorRegistry - Registro de interceptores personalizados
|
|
2793
|
-
* Permite al usuario inyectar sus propios interceptores sin modificar el código base
|
|
2794
|
-
* Usa una Facade para exponer solo métodos seguros a los interceptores
|
|
2795
|
-
*/
|
|
2796
|
-
class InterceptorRegistry {
|
|
2797
|
-
constructor() {
|
|
2798
|
-
this.customInterceptors = new Map();
|
|
2799
|
-
this.isInitialized = false;
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
/**
|
|
2803
|
-
* Crea una API segura para los interceptores
|
|
2804
|
-
* Solo expone métodos públicos y seguros
|
|
2805
|
-
* @param {Object} config - Configuración con instancias internas
|
|
2806
|
-
* @returns {Object} API segura para interceptores
|
|
2807
|
-
*/
|
|
2808
|
-
createInterceptorApi(config) {
|
|
2809
|
-
const { breadcrumbStore, agent, contextCollector } = config;
|
|
2810
|
-
|
|
2811
|
-
return {
|
|
2812
|
-
// Métodos para breadcrumbs
|
|
2813
|
-
addBreadcrumb: (category, message, data = {}) => {
|
|
2814
|
-
breadcrumbStore.add({ category, message, data, timestamp: new Date().toISOString() });
|
|
2815
|
-
},
|
|
2816
|
-
|
|
2817
|
-
// Métodos para enviar datos
|
|
2818
|
-
sendError: (errorPayload, context = null) => {
|
|
2819
|
-
agent.sendError(errorPayload, context);
|
|
2820
|
-
},
|
|
2821
|
-
|
|
2822
|
-
sendBreadcrumbs: (breadcrumbs) => {
|
|
2823
|
-
agent.sendBreadcrumbs(breadcrumbs);
|
|
2824
|
-
},
|
|
2825
|
-
|
|
2826
|
-
// Métodos para contexto
|
|
2827
|
-
getContext: (contextConfig = {}) => {
|
|
2828
|
-
return contextCollector.collect(contextConfig);
|
|
2829
|
-
},
|
|
2830
|
-
|
|
2831
|
-
// Métodos de utilidad
|
|
2832
|
-
getTimestamp: () => new Date().toISOString(),
|
|
2833
|
-
|
|
2834
|
-
// Información de la API (solo lectura)
|
|
2835
|
-
apiVersion: '1.0.0',
|
|
2836
|
-
availableMethods: [
|
|
2837
|
-
'addBreadcrumb',
|
|
2838
|
-
'sendError',
|
|
2839
|
-
'sendBreadcrumbs',
|
|
2840
|
-
'getContext',
|
|
2841
|
-
'getTimestamp'
|
|
2842
|
-
]
|
|
2843
|
-
};
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
/**
|
|
2847
|
-
* Registra un interceptor personalizado
|
|
2848
|
-
* @param {string} name - Nombre del interceptor
|
|
2849
|
-
* @param {Object} interceptor - Objeto interceptor con métodos init/destroy
|
|
2850
|
-
*/
|
|
2851
|
-
register(name, interceptor) {
|
|
2852
|
-
if (!interceptor || typeof interceptor.init !== 'function') {
|
|
2853
|
-
throw new Error(`Interceptor ${name} debe tener un método init()`);
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
this.customInterceptors.set(name, {
|
|
2857
|
-
name,
|
|
2858
|
-
interceptor,
|
|
2859
|
-
enabled: true
|
|
2860
|
-
});
|
|
2861
|
-
|
|
2862
|
-
console.log(`SyntropyFront: Interceptor personalizado registrado: ${name}`);
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
/**
|
|
2866
|
-
* Remueve un interceptor personalizado
|
|
2867
|
-
* @param {string} name - Nombre del interceptor
|
|
2868
|
-
*/
|
|
2869
|
-
unregister(name) {
|
|
2870
|
-
const registered = this.customInterceptors.get(name);
|
|
2871
|
-
if (registered) {
|
|
2872
|
-
// Destruir el interceptor si está inicializado
|
|
2873
|
-
if (this.isInitialized && registered.interceptor.destroy) {
|
|
2874
|
-
try {
|
|
2875
|
-
registered.interceptor.destroy();
|
|
2876
|
-
} catch (error) {
|
|
2877
|
-
console.warn(`SyntropyFront: Error destruyendo interceptor ${name}:`, error);
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
this.customInterceptors.delete(name);
|
|
2882
|
-
console.log(`SyntropyFront: Interceptor personalizado removido: ${name}`);
|
|
2883
|
-
}
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
/**
|
|
2887
|
-
* Inicializa todos los interceptores personalizados
|
|
2888
|
-
* @param {Object} config - Configuración con instancias internas
|
|
2889
|
-
*/
|
|
2890
|
-
init(config = {}) {
|
|
2891
|
-
if (this.isInitialized) {
|
|
2892
|
-
console.warn('SyntropyFront: InterceptorRegistry ya está inicializado');
|
|
2893
|
-
return;
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// Crear API segura para interceptores
|
|
2897
|
-
const interceptorApi = this.createInterceptorApi(config);
|
|
2898
|
-
|
|
2899
|
-
for (const [name, registered] of this.customInterceptors) {
|
|
2900
|
-
if (registered.enabled) {
|
|
2901
|
-
try {
|
|
2902
|
-
// ✅ SEGURO: Pasar solo la API, no el config crudo
|
|
2903
|
-
registered.interceptor.init(interceptorApi);
|
|
2904
|
-
console.log(`SyntropyFront: Interceptor ${name} inicializado`);
|
|
2905
|
-
} catch (error) {
|
|
2906
|
-
console.error(`SyntropyFront: Error inicializando interceptor ${name}:`, error);
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
this.isInitialized = true;
|
|
2912
|
-
}
|
|
2913
|
-
|
|
2914
|
-
/**
|
|
2915
|
-
* Destruye todos los interceptores personalizados
|
|
2916
|
-
*/
|
|
2917
|
-
destroy() {
|
|
2918
|
-
if (!this.isInitialized) return;
|
|
2919
|
-
|
|
2920
|
-
for (const [name, registered] of this.customInterceptors) {
|
|
2921
|
-
if (registered.interceptor.destroy) {
|
|
2922
|
-
try {
|
|
2923
|
-
registered.interceptor.destroy();
|
|
2924
|
-
console.log(`SyntropyFront: Interceptor ${name} destruido`);
|
|
2925
|
-
} catch (error) {
|
|
2926
|
-
console.warn(`SyntropyFront: Error destruyendo interceptor ${name}:`, error);
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
this.isInitialized = false;
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
/**
|
|
2935
|
-
* Habilita/deshabilita un interceptor personalizado
|
|
2936
|
-
* @param {string} name - Nombre del interceptor
|
|
2937
|
-
* @param {boolean} enabled - Si está habilitado
|
|
2938
|
-
*/
|
|
2939
|
-
setEnabled(name, enabled) {
|
|
2940
|
-
const registered = this.customInterceptors.get(name);
|
|
2941
|
-
if (registered) {
|
|
2942
|
-
registered.enabled = enabled;
|
|
2943
|
-
|
|
2944
|
-
if (this.isInitialized) {
|
|
2945
|
-
if (enabled && registered.interceptor.init) {
|
|
2946
|
-
try {
|
|
2947
|
-
// Crear API segura para el interceptor
|
|
2948
|
-
const interceptorApi = this.createInterceptorApi({
|
|
2949
|
-
breadcrumbStore: this.breadcrumbStore,
|
|
2950
|
-
agent: this.agent,
|
|
2951
|
-
contextCollector: this.contextCollector
|
|
2952
|
-
});
|
|
2953
|
-
registered.interceptor.init(interceptorApi);
|
|
2954
|
-
console.log(`SyntropyFront: Interceptor ${name} habilitado`);
|
|
2955
|
-
} catch (error) {
|
|
2956
|
-
console.error(`SyntropyFront: Error habilitando interceptor ${name}:`, error);
|
|
2957
|
-
}
|
|
2958
|
-
} else if (!enabled && registered.interceptor.destroy) {
|
|
2959
|
-
try {
|
|
2960
|
-
registered.interceptor.destroy();
|
|
2961
|
-
console.log(`SyntropyFront: Interceptor ${name} deshabilitado`);
|
|
2962
|
-
} catch (error) {
|
|
2963
|
-
console.warn(`SyntropyFront: Error deshabilitando interceptor ${name}:`, error);
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
|
|
2970
|
-
/**
|
|
2971
|
-
* Obtiene la lista de interceptores registrados
|
|
2972
|
-
* @returns {Array} Lista de nombres de interceptores
|
|
2973
|
-
*/
|
|
2974
|
-
getRegisteredInterceptors() {
|
|
2975
|
-
return Array.from(this.customInterceptors.keys());
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
/**
|
|
2979
|
-
* Obtiene información de un interceptor específico
|
|
2980
|
-
* @param {string} name - Nombre del interceptor
|
|
2981
|
-
* @returns {Object|null} Información del interceptor
|
|
2982
|
-
*/
|
|
2983
|
-
getInterceptorInfo(name) {
|
|
2984
|
-
const registered = this.customInterceptors.get(name);
|
|
2985
|
-
if (registered) {
|
|
2986
|
-
return {
|
|
2987
|
-
name: registered.name,
|
|
2988
|
-
enabled: registered.enabled,
|
|
2989
|
-
hasInit: typeof registered.interceptor.init === 'function',
|
|
2990
|
-
hasDestroy: typeof registered.interceptor.destroy === 'function'
|
|
2991
|
-
};
|
|
2992
|
-
}
|
|
2993
|
-
return null;
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
|
-
/**
|
|
2997
|
-
* Obtiene la documentación de la API para interceptores
|
|
2998
|
-
* @returns {Object} Documentación de la API
|
|
2999
|
-
*/
|
|
3000
|
-
getApiDocumentation() {
|
|
3001
|
-
return {
|
|
3002
|
-
version: '1.0.0',
|
|
3003
|
-
methods: {
|
|
3004
|
-
addBreadcrumb: {
|
|
3005
|
-
description: 'Agrega un breadcrumb al historial',
|
|
3006
|
-
signature: 'addBreadcrumb(category, message, data?)',
|
|
3007
|
-
example: 'api.addBreadcrumb("ui", "Usuario hizo click", { element: "button" })'
|
|
3008
|
-
},
|
|
3009
|
-
sendError: {
|
|
3010
|
-
description: 'Envía un error al backend',
|
|
3011
|
-
signature: 'sendError(errorPayload, context?)',
|
|
3012
|
-
example: 'api.sendError({ message: "Error crítico" }, { device: true })'
|
|
3013
|
-
},
|
|
3014
|
-
sendBreadcrumbs: {
|
|
3015
|
-
description: 'Envía breadcrumbs al backend',
|
|
3016
|
-
signature: 'sendBreadcrumbs(breadcrumbs)',
|
|
3017
|
-
example: 'api.sendBreadcrumbs([{ category: "ui", message: "Click" }])'
|
|
3018
|
-
},
|
|
3019
|
-
getContext: {
|
|
3020
|
-
description: 'Obtiene contexto del navegador',
|
|
3021
|
-
signature: 'getContext(contextConfig?)',
|
|
3022
|
-
example: 'api.getContext({ device: true, window: ["url"] })'
|
|
3023
|
-
},
|
|
3024
|
-
getTimestamp: {
|
|
3025
|
-
description: 'Obtiene timestamp actual en formato ISO',
|
|
3026
|
-
signature: 'getTimestamp()',
|
|
3027
|
-
example: 'const now = api.getTimestamp()'
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3033
|
-
/**
|
|
3034
|
-
* Limpia todos los interceptores registrados
|
|
3035
|
-
*/
|
|
3036
|
-
clear() {
|
|
3037
|
-
this.destroy();
|
|
3038
|
-
this.customInterceptors.clear();
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
// Instancia singleton
|
|
3043
|
-
const interceptorRegistry = new InterceptorRegistry();
|
|
3044
|
-
|
|
3045
|
-
var InterceptorRegistry$1 = /*#__PURE__*/Object.freeze({
|
|
3046
|
-
__proto__: null,
|
|
3047
|
-
InterceptorRegistry: InterceptorRegistry,
|
|
3048
|
-
interceptorRegistry: interceptorRegistry
|
|
3049
|
-
});
|
|
3050
|
-
|
|
3051
|
-
/**
|
|
3052
|
-
* WorkerManager - Maneja comunicación con SyntropyWorker
|
|
3053
|
-
* Proporciona API para interactuar con el worker desde el main thread
|
|
3054
|
-
*
|
|
3055
|
-
* @author SyntropyFront Team
|
|
3056
|
-
* @version 1.0.0
|
|
3057
|
-
*/
|
|
3058
|
-
|
|
3059
|
-
class WorkerManager {
|
|
3060
|
-
constructor() {
|
|
3061
|
-
this.worker = null;
|
|
3062
|
-
this.pendingRequests = new Map();
|
|
3063
|
-
this.requestId = 0;
|
|
3064
|
-
this.isInitialized = false;
|
|
3065
|
-
this.config = {};
|
|
3066
|
-
|
|
3067
|
-
// Setup worker communication
|
|
3068
|
-
this.setupWorker();
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
/**
|
|
3072
|
-
* Inicializa el worker
|
|
3073
|
-
*/
|
|
3074
|
-
setupWorker() {
|
|
3075
|
-
try {
|
|
3076
|
-
// Crear worker
|
|
3077
|
-
this.worker = new Worker('./src/workers/SyntropyWorker.js');
|
|
3078
|
-
|
|
3079
|
-
// Setup message handling
|
|
3080
|
-
this.worker.addEventListener('message', (event) => {
|
|
3081
|
-
this.handleWorkerMessage(event.data);
|
|
3082
|
-
});
|
|
3083
|
-
|
|
3084
|
-
// Setup error handling
|
|
3085
|
-
this.worker.addEventListener('error', (error) => {
|
|
3086
|
-
console.error('SyntropyWorker error:', error);
|
|
3087
|
-
this.handleWorkerError(error);
|
|
3088
|
-
});
|
|
3089
|
-
|
|
3090
|
-
console.log('🔄 WorkerManager: Worker inicializado');
|
|
3091
|
-
} catch (error) {
|
|
3092
|
-
console.error('WorkerManager: Error inicializando worker:', error);
|
|
3093
|
-
this.handleWorkerUnavailable();
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
/**
|
|
3098
|
-
* Inicializa el worker con configuración
|
|
3099
|
-
*/
|
|
3100
|
-
async init(config) {
|
|
3101
|
-
try {
|
|
3102
|
-
this.config = config;
|
|
3103
|
-
|
|
3104
|
-
const response = await this.sendMessage('INIT', config);
|
|
3105
|
-
|
|
3106
|
-
if (response.success) {
|
|
3107
|
-
this.isInitialized = true;
|
|
3108
|
-
console.log('✅ WorkerManager: Worker inicializado correctamente');
|
|
3109
|
-
return true;
|
|
3110
|
-
} else {
|
|
3111
|
-
throw new Error(response.error || 'Error inicializando worker');
|
|
3112
|
-
}
|
|
3113
|
-
} catch (error) {
|
|
3114
|
-
console.error('WorkerManager: Error en init:', error);
|
|
3115
|
-
return false;
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
|
|
3119
|
-
/**
|
|
3120
|
-
* Envía mensaje al worker y espera respuesta
|
|
3121
|
-
*/
|
|
3122
|
-
sendMessage(type, payload = {}) {
|
|
3123
|
-
return new Promise((resolve, reject) => {
|
|
3124
|
-
if (!this.worker) {
|
|
3125
|
-
reject(new Error('Worker no disponible'));
|
|
3126
|
-
return;
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
const id = this.generateRequestId();
|
|
3130
|
-
|
|
3131
|
-
// Guardar callback para la respuesta
|
|
3132
|
-
this.pendingRequests.set(id, { resolve, reject });
|
|
3133
|
-
|
|
3134
|
-
// Enviar mensaje al worker
|
|
3135
|
-
this.worker.postMessage({
|
|
3136
|
-
type,
|
|
3137
|
-
payload,
|
|
3138
|
-
id
|
|
3139
|
-
});
|
|
3140
|
-
|
|
3141
|
-
// Timeout para evitar requests colgados
|
|
3142
|
-
setTimeout(() => {
|
|
3143
|
-
if (this.pendingRequests.has(id)) {
|
|
3144
|
-
this.pendingRequests.delete(id);
|
|
3145
|
-
reject(new Error(`Timeout en request: ${type}`));
|
|
3146
|
-
}
|
|
3147
|
-
}, 5000); // 5 segundos timeout
|
|
3148
|
-
});
|
|
3149
|
-
}
|
|
3150
|
-
|
|
3151
|
-
/**
|
|
3152
|
-
* Maneja mensajes del worker
|
|
3153
|
-
*/
|
|
3154
|
-
handleWorkerMessage(data) {
|
|
3155
|
-
const { id, success, error, ...response } = data;
|
|
3156
|
-
|
|
3157
|
-
const request = this.pendingRequests.get(id);
|
|
3158
|
-
if (request) {
|
|
3159
|
-
this.pendingRequests.delete(id);
|
|
3160
|
-
|
|
3161
|
-
if (success) {
|
|
3162
|
-
request.resolve(response);
|
|
3163
|
-
} else {
|
|
3164
|
-
request.reject(new Error(error || 'Error en worker'));
|
|
3165
|
-
}
|
|
3166
|
-
} else {
|
|
3167
|
-
console.warn('WorkerManager: Respuesta sin request pendiente:', id);
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
/**
|
|
3172
|
-
* Maneja errores del worker
|
|
3173
|
-
*/
|
|
3174
|
-
handleWorkerError(error) {
|
|
3175
|
-
console.error('WorkerManager: Error del worker:', error);
|
|
3176
|
-
|
|
3177
|
-
// Limpiar requests pendientes
|
|
3178
|
-
this.pendingRequests.forEach((request) => {
|
|
3179
|
-
request.reject(new Error('Worker error'));
|
|
3180
|
-
});
|
|
3181
|
-
this.pendingRequests.clear();
|
|
3182
|
-
|
|
3183
|
-
// Fallback a modo sin worker
|
|
3184
|
-
this.handleWorkerUnavailable();
|
|
3185
|
-
}
|
|
3186
|
-
|
|
3187
|
-
/**
|
|
3188
|
-
* Maneja cuando el worker no está disponible
|
|
3189
|
-
*/
|
|
3190
|
-
handleWorkerUnavailable() {
|
|
3191
|
-
console.warn('WorkerManager: Worker no disponible, usando fallback');
|
|
3192
|
-
|
|
3193
|
-
// Aquí podríamos implementar fallback al modo main thread
|
|
3194
|
-
// Por ahora solo loggeamos
|
|
3195
|
-
}
|
|
3196
|
-
|
|
3197
|
-
/**
|
|
3198
|
-
* Agrega breadcrumb al worker
|
|
3199
|
-
*/
|
|
3200
|
-
async addBreadcrumb(type, message, data = {}) {
|
|
3201
|
-
try {
|
|
3202
|
-
const response = await this.sendMessage('ADD_BREADCRUMB', {
|
|
3203
|
-
type,
|
|
3204
|
-
message,
|
|
3205
|
-
data
|
|
3206
|
-
});
|
|
3207
|
-
|
|
3208
|
-
return response;
|
|
3209
|
-
} catch (error) {
|
|
3210
|
-
console.error('WorkerManager: Error agregando breadcrumb:', error);
|
|
3211
|
-
throw error;
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
/**
|
|
3216
|
-
* Obtiene breadcrumbs del worker
|
|
3217
|
-
*/
|
|
3218
|
-
async getBreadcrumbs() {
|
|
3219
|
-
try {
|
|
3220
|
-
const response = await this.sendMessage('GET_BREADCRUMBS');
|
|
3221
|
-
return response.breadcrumbs || [];
|
|
3222
|
-
} catch (error) {
|
|
3223
|
-
console.error('WorkerManager: Error obteniendo breadcrumbs:', error);
|
|
3224
|
-
return [];
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
|
-
/**
|
|
3229
|
-
* Limpia breadcrumbs del worker
|
|
3230
|
-
*/
|
|
3231
|
-
async clearBreadcrumbs() {
|
|
3232
|
-
try {
|
|
3233
|
-
const response = await this.sendMessage('CLEAR_BREADCRUMBS');
|
|
3234
|
-
return response;
|
|
3235
|
-
} catch (error) {
|
|
3236
|
-
console.error('WorkerManager: Error limpiando breadcrumbs:', error);
|
|
3237
|
-
throw error;
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3241
|
-
/**
|
|
3242
|
-
* Envía error al worker
|
|
3243
|
-
*/
|
|
3244
|
-
async sendError(error, context = {}) {
|
|
3245
|
-
try {
|
|
3246
|
-
const response = await this.sendMessage('SEND_ERROR', {
|
|
3247
|
-
error,
|
|
3248
|
-
context
|
|
3249
|
-
});
|
|
3250
|
-
|
|
3251
|
-
return response;
|
|
3252
|
-
} catch (error) {
|
|
3253
|
-
console.error('WorkerManager: Error enviando error:', error);
|
|
3254
|
-
throw error;
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
|
-
/**
|
|
3259
|
-
* Actualiza contexto del worker
|
|
3260
|
-
*/
|
|
3261
|
-
async updateContext(context) {
|
|
3262
|
-
try {
|
|
3263
|
-
const response = await this.sendMessage('UPDATE_CONTEXT', context);
|
|
3264
|
-
return response;
|
|
3265
|
-
} catch (error) {
|
|
3266
|
-
console.error('WorkerManager: Error actualizando contexto:', error);
|
|
3267
|
-
throw error;
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
|
-
/**
|
|
3272
|
-
* Ping al worker para verificar conectividad
|
|
3273
|
-
*/
|
|
3274
|
-
async ping() {
|
|
3275
|
-
try {
|
|
3276
|
-
const response = await this.sendMessage('PING');
|
|
3277
|
-
return response;
|
|
3278
|
-
} catch (error) {
|
|
3279
|
-
console.error('WorkerManager: Error en ping:', error);
|
|
3280
|
-
throw error;
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
/**
|
|
3285
|
-
* Obtiene estadísticas del worker
|
|
3286
|
-
*/
|
|
3287
|
-
async getWorkerStats() {
|
|
3288
|
-
try {
|
|
3289
|
-
const response = await this.sendMessage('GET_STATS');
|
|
3290
|
-
return response;
|
|
3291
|
-
} catch (error) {
|
|
3292
|
-
console.error('WorkerManager: Error obteniendo stats:', error);
|
|
3293
|
-
return null;
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
/**
|
|
3298
|
-
* Destruye el worker
|
|
3299
|
-
*/
|
|
3300
|
-
destroy() {
|
|
3301
|
-
if (this.worker) {
|
|
3302
|
-
// Limpiar requests pendientes
|
|
3303
|
-
this.pendingRequests.forEach((request) => {
|
|
3304
|
-
request.reject(new Error('Worker destroyed'));
|
|
3305
|
-
});
|
|
3306
|
-
this.pendingRequests.clear();
|
|
3307
|
-
|
|
3308
|
-
// Terminar worker
|
|
3309
|
-
this.worker.terminate();
|
|
3310
|
-
this.worker = null;
|
|
3311
|
-
|
|
3312
|
-
console.log('🔄 WorkerManager: Worker destruido');
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
/**
|
|
3317
|
-
* Genera ID único para requests
|
|
3318
|
-
*/
|
|
3319
|
-
generateRequestId() {
|
|
3320
|
-
return `req_${++this.requestId}_${Date.now()}`;
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
/**
|
|
3324
|
-
* Verifica si el worker está disponible
|
|
3325
|
-
*/
|
|
3326
|
-
isWorkerAvailable() {
|
|
3327
|
-
return this.worker !== null && this.isInitialized;
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
|
-
/**
|
|
3331
|
-
* Obtiene estado del worker
|
|
3332
|
-
*/
|
|
3333
|
-
getStatus() {
|
|
3334
|
-
return {
|
|
3335
|
-
isAvailable: this.isWorkerAvailable(),
|
|
3336
|
-
isInitialized: this.isInitialized,
|
|
3337
|
-
pendingRequests: this.pendingRequests.size,
|
|
3338
|
-
config: this.config
|
|
3339
|
-
};
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
var WorkerManager$1 = /*#__PURE__*/Object.freeze({
|
|
3344
|
-
__proto__: null,
|
|
3345
|
-
default: WorkerManager
|
|
3346
|
-
});
|
|
423
|
+
// Single instance - auto-initializes
|
|
424
|
+
const syntropyFront = new SyntropyFront();
|
|
3347
425
|
|
|
3348
|
-
export {
|
|
426
|
+
export { syntropyFront as default };
|
|
3349
427
|
//# sourceMappingURL=index.js.map
|