react-native-qalink 0.1.0
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 +342 -0
- package/dist/core/classifier.d.ts +9 -0
- package/dist/core/classifier.d.ts.map +1 -0
- package/dist/core/classifier.js +51 -0
- package/dist/core/classifier.js.map +1 -0
- package/dist/core/session.d.ts +8 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +60 -0
- package/dist/core/session.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/axios.d.ts +4 -0
- package/dist/interceptors/axios.d.ts.map +1 -0
- package/dist/interceptors/axios.js +78 -0
- package/dist/interceptors/axios.js.map +1 -0
- package/dist/interceptors/console.d.ts +4 -0
- package/dist/interceptors/console.d.ts.map +1 -0
- package/dist/interceptors/console.js +159 -0
- package/dist/interceptors/console.js.map +1 -0
- package/dist/interceptors/errors.d.ts +4 -0
- package/dist/interceptors/errors.d.ts.map +1 -0
- package/dist/interceptors/errors.js +49 -0
- package/dist/interceptors/errors.js.map +1 -0
- package/dist/interceptors/fetch.d.ts +4 -0
- package/dist/interceptors/fetch.d.ts.map +1 -0
- package/dist/interceptors/fetch.js +83 -0
- package/dist/interceptors/fetch.js.map +1 -0
- package/dist/interceptors/runtime.d.ts +4 -0
- package/dist/interceptors/runtime.d.ts.map +1 -0
- package/dist/interceptors/runtime.js +141 -0
- package/dist/interceptors/runtime.js.map +1 -0
- package/dist/transport/websocket.d.ts +23 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +104 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/types/index.d.ts +139 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# react-native-qalink 🔍
|
|
2
|
+
|
|
3
|
+
SDK de captura de errores en tiempo real para React Native. Se instala en la app y transmite al servidor QALink cada request de red, error JS, log de consola y error del runtime — permitiendo al equipo de QA ver en tiempo real si un bug es del **frontend** o del **backend**, sin necesidad de Android Studio ni logs técnicos.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ¿Cómo identifica los requests a la API?
|
|
8
|
+
|
|
9
|
+
La librería usa **dos estrategias de interceptación simultáneas**:
|
|
10
|
+
|
|
11
|
+
### 1. Monkey-patch de `fetch` global
|
|
12
|
+
Al llamar `QALink.init()`, la librería reemplaza el `fetch` nativo del entorno JavaScript por su propia versión instrumentada. Cualquier llamada a `fetch(...)` en toda la app pasa primero por QALink, que registra el request y la respuesta antes de devolver el control al código original.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Tu código → fetch('/api/users')
|
|
16
|
+
↓
|
|
17
|
+
QALink intercepta → registra método, URL, tiempo de inicio
|
|
18
|
+
↓
|
|
19
|
+
Ejecuta fetch real → espera respuesta del servidor
|
|
20
|
+
↓
|
|
21
|
+
QALink registra → status code, body, duración, clasificación
|
|
22
|
+
↓
|
|
23
|
+
Devuelve al código → response (sin modificarla)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Interceptores de Axios
|
|
27
|
+
Axios expone un sistema oficial de interceptores (`.interceptors.request` y `.interceptors.response`). QALink se engancha en ambos para capturar cada request antes de que salga y cada respuesta antes de que llegue al código de la app. Funciona con la instancia global y con instancias personalizadas.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ¿Cómo captura los logs de Metro y el runtime de React Native?
|
|
32
|
+
|
|
33
|
+
Hay tres capas adicionales que se activan automáticamente con `init()`:
|
|
34
|
+
|
|
35
|
+
### 3. Monkey-patch de `console`
|
|
36
|
+
QALink reemplaza `console.log`, `console.warn` y `console.error` con versiones instrumentadas. **Metro sigue mostrando todo normalmente** (se llama al original primero), pero cada mensaje se envía también al dashboard. Los mensajes se clasifican automáticamente:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
console.log('Productos cargados', data) → 🔵 user_log
|
|
40
|
+
console.warn('componentWillMount deprecated') → 🟡 rn_warning
|
|
41
|
+
console.error('Invariant Violation: ...') → 🔴 rn_error
|
|
42
|
+
console.error('Unable to resolve module') → 🔴 rn_error (Metro)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 4. `ErrorUtils.setGlobalHandler` — Pantalla roja (Red Screen)
|
|
46
|
+
React Native expone `ErrorUtils` para capturar errores **antes** de que la app crashee y muestre la pantalla roja. QALink se registra ahí para enviar el error al dashboard con todo el stack trace, y luego llama al handler original para que RN siga comportándose normalmente.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Error fatal en JS
|
|
50
|
+
↓
|
|
51
|
+
QALink captura → envía al dashboard con stack trace completo
|
|
52
|
+
↓
|
|
53
|
+
ErrorUtils original → React Native muestra la pantalla roja
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 5. LogBox — Cajas amarillas (Yellow Box)
|
|
57
|
+
En React Native ≥ 0.64, los warnings se manejan internamente via `LogBox`. QALink se engancha al método interno `__warn` de LogBox para capturar cada warning antes de que aparezca en pantalla.
|
|
58
|
+
|
|
59
|
+
### Clasificación de errores de runtime
|
|
60
|
+
|
|
61
|
+
| Tipo | Categoría | Ejemplo |
|
|
62
|
+
|--------------------------|----------------|----------------------------------------------|
|
|
63
|
+
| Error fatal | `red_screen` | `Invariant Violation`, `undefined is not...` |
|
|
64
|
+
| Warning de RN | `yellow_box` | `componentWillMount is deprecated` |
|
|
65
|
+
| Módulo nativo | `native_module`| `NativeModule X is null` |
|
|
66
|
+
| Bridge JS↔Native | `bridge` | `RCTBridge error` |
|
|
67
|
+
| Promise sin .catch() | `unknown` | `Unhandled Promise Rejection` |
|
|
68
|
+
| Error de Metro/bundler | `rn_error` | `Unable to resolve module`, `SyntaxError` |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Instalación
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install react-native-qalink
|
|
76
|
+
# o
|
|
77
|
+
yarn add react-native-qalink
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Ejemplos de uso
|
|
83
|
+
|
|
84
|
+
### Ejemplo 1 — Setup básico con captura de consola
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
// App.tsx
|
|
88
|
+
import { useEffect } from 'react';
|
|
89
|
+
import { QALink } from 'react-native-qalink';
|
|
90
|
+
import axios from 'axios';
|
|
91
|
+
|
|
92
|
+
export default function App() {
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
QALink.init({
|
|
95
|
+
serverUrl: 'ws://192.168.1.100:3000',
|
|
96
|
+
appVersion: '1.2.3',
|
|
97
|
+
captureRuntimeErrors: true, // pantalla roja, yellow box
|
|
98
|
+
console: {
|
|
99
|
+
captureLogs: true, // console.log
|
|
100
|
+
captureWarnings: true, // console.warn
|
|
101
|
+
captureErrors: true, // console.error + errores de Metro
|
|
102
|
+
},
|
|
103
|
+
debug: __DEV__,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
QALink.interceptAxios(axios);
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
return <RootNavigator />;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Ejemplo 2 — Filtrar logs muy verbosos de librerías
|
|
116
|
+
|
|
117
|
+
Si usas librerías que loguean mucho (react-query, redux, etc.), puedes filtrarlas
|
|
118
|
+
para que el dashboard no se llene de ruido.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
QALink.init({
|
|
122
|
+
serverUrl: 'ws://192.168.1.100:3000',
|
|
123
|
+
appVersion: '1.2.3',
|
|
124
|
+
console: {
|
|
125
|
+
captureLogs: true,
|
|
126
|
+
captureWarnings: true,
|
|
127
|
+
captureErrors: true,
|
|
128
|
+
// Ignorar logs de estas librerías
|
|
129
|
+
ignorePatterns: [
|
|
130
|
+
'[react-query]',
|
|
131
|
+
'[redux]',
|
|
132
|
+
'Reanimated',
|
|
133
|
+
'Gesture Handler',
|
|
134
|
+
'[MMKV]',
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Ejemplo 3 — Capturar solo logs relevantes para QA
|
|
143
|
+
|
|
144
|
+
Si quieres que el QA vea únicamente los logs que tú marcas explícitamente:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
QALink.init({
|
|
148
|
+
serverUrl: 'ws://192.168.1.100:3000',
|
|
149
|
+
appVersion: '1.2.3',
|
|
150
|
+
console: {
|
|
151
|
+
captureLogs: true,
|
|
152
|
+
captureWarnings: true,
|
|
153
|
+
captureErrors: true,
|
|
154
|
+
// Solo capturar logs que contengan estas palabras
|
|
155
|
+
includePatterns: ['[QA]', '[FLOW]', '[ERROR]'],
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// En tu código, prefija los logs importantes:
|
|
160
|
+
console.log('[QA] Usuario llegó al checkout', { userId, cartTotal });
|
|
161
|
+
console.log('[FLOW] Pago procesado', { orderId });
|
|
162
|
+
console.error('[ERROR] Falló validación', { campo, valor });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### Ejemplo 4 — App con múltiples instancias de Axios
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// services/api.ts
|
|
171
|
+
export const apiPrincipal = axios.create({ baseURL: 'https://api.miapp.com/v1' });
|
|
172
|
+
export const apiPagos = axios.create({ baseURL: 'https://pagos.miapp.com' });
|
|
173
|
+
|
|
174
|
+
// App.tsx
|
|
175
|
+
await QALink.init({ serverUrl: 'ws://192.168.1.100:3000', appVersion: '1.2.3' });
|
|
176
|
+
|
|
177
|
+
QALink.interceptAxios(apiPrincipal);
|
|
178
|
+
QALink.interceptAxios(apiPagos);
|
|
179
|
+
// fetch nativo ya se intercepta automáticamente con init()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Ejemplo 5 — Integración con React Navigation
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
<NavigationContainer
|
|
188
|
+
ref={navigationRef}
|
|
189
|
+
onStateChange={() => {
|
|
190
|
+
const routeName = navigationRef.current?.getCurrentRoute()?.name ?? '';
|
|
191
|
+
QALink.setScreen(routeName);
|
|
192
|
+
// Genera automáticamente: breadcrumb "NAVIGATE → CheckoutScreen"
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### Ejemplo 6 — Breadcrumbs en acciones críticas
|
|
200
|
+
|
|
201
|
+
Los breadcrumbs + logs de consola juntos dan la línea de tiempo completa al QA:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
const handleConfirmarCompra = async () => {
|
|
205
|
+
QALink.addBreadcrumb('TAP → Confirmar Compra', {
|
|
206
|
+
cartId: cart.id,
|
|
207
|
+
items: cart.items.length,
|
|
208
|
+
total: cart.total,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
console.log('[QA] Iniciando checkout', { cartId: cart.id });
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const result = await apiPagos.post('/checkout', { cartId: cart.id });
|
|
215
|
+
console.log('[QA] Checkout exitoso', { orderId: result.data.orderId });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// QALink ya capturó el error de red automáticamente
|
|
218
|
+
// console.error aquí también se envía al dashboard
|
|
219
|
+
console.error('[QA] Checkout falló', error);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
El QA verá en el dashboard esta línea de tiempo completa:
|
|
225
|
+
```
|
|
226
|
+
14:33:40 NAVIGATE → CheckoutScreen
|
|
227
|
+
14:33:55 TAP → Confirmar Compra { cartId: "abc", items: 3, total: 150 }
|
|
228
|
+
14:33:55 🔵 [QA] Iniciando checkout { cartId: "abc" }
|
|
229
|
+
14:33:55 ❌ POST /checkout → 500 🔴 ERROR DE BACKEND
|
|
230
|
+
14:33:55 🔴 [QA] Checkout falló Error: Internal Server Error
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Ejemplo 7 — Configuración para build de QA (sin exponer en producción)
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
// config/qalink.ts
|
|
239
|
+
import Config from 'react-native-config';
|
|
240
|
+
|
|
241
|
+
export async function initQALink() {
|
|
242
|
+
if (Config.QALINK_ENABLED !== 'true') return;
|
|
243
|
+
|
|
244
|
+
await QALink.init({
|
|
245
|
+
serverUrl: Config.QALINK_SERVER,
|
|
246
|
+
appVersion: Config.VERSION_NAME ?? '0.0.0',
|
|
247
|
+
logNetworkBodies: true,
|
|
248
|
+
captureRuntimeErrors: true,
|
|
249
|
+
console: {
|
|
250
|
+
captureLogs: true,
|
|
251
|
+
captureWarnings: true,
|
|
252
|
+
captureErrors: true,
|
|
253
|
+
ignorePatterns: ['[react-query]'],
|
|
254
|
+
},
|
|
255
|
+
sensitiveHeaders: ['Authorization'],
|
|
256
|
+
sensitiveUrlPatterns: ['/auth/refresh'],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
QALink.interceptAxios(axios);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### Ejemplo 8 — Verificar estado de conexión
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
function QAStatusBadge() {
|
|
269
|
+
if (QALink.getStatus() !== 'connected') return null;
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<View style={styles.badge}>
|
|
273
|
+
<Text>🔴 QALink grabando</Text>
|
|
274
|
+
</View>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Qué ve el QA en el dashboard
|
|
282
|
+
|
|
283
|
+
Cada sesión incluye una línea de tiempo unificada con todos los eventos:
|
|
284
|
+
|
|
285
|
+
| Ícono | Tipo | Fuente |
|
|
286
|
+
|-------|-------------------|---------------------|
|
|
287
|
+
| ✅ | Request exitoso | fetch / axios |
|
|
288
|
+
| ❌ | Error de red | fetch / axios |
|
|
289
|
+
| 🔴 | Error fatal (RN) | ErrorUtils / LogBox |
|
|
290
|
+
| 🟡 | Warning (RN) | LogBox / console |
|
|
291
|
+
| 🔵 | Log de consola | console.log |
|
|
292
|
+
| 📍 | Breadcrumb | QALink.addBreadcrumb|
|
|
293
|
+
| 📱 | Pantalla | QALink.setScreen |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Configuración completa
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
QALink.init({
|
|
301
|
+
// — Requeridos —
|
|
302
|
+
serverUrl: 'ws://192.168.1.100:3000',
|
|
303
|
+
appVersion: '1.0.0',
|
|
304
|
+
|
|
305
|
+
// — Red —
|
|
306
|
+
logNetworkBodies: false,
|
|
307
|
+
sensitiveHeaders: ['Authorization'],
|
|
308
|
+
sensitiveUrlPatterns: ['/auth/refresh'],
|
|
309
|
+
|
|
310
|
+
// — Runtime de React Native —
|
|
311
|
+
captureRuntimeErrors: true, // pantalla roja, yellow box, promises
|
|
312
|
+
|
|
313
|
+
// — Consola y Metro —
|
|
314
|
+
console: {
|
|
315
|
+
captureLogs: true,
|
|
316
|
+
captureWarnings: true,
|
|
317
|
+
captureErrors: true,
|
|
318
|
+
ignorePatterns: ['[redux]'], // patrones a ignorar
|
|
319
|
+
includePatterns: [], // si se define, solo captura estos
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// — General —
|
|
323
|
+
enabled: true,
|
|
324
|
+
debug: false,
|
|
325
|
+
onEvent: (event) => {
|
|
326
|
+
console.log(event.type, event);
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## API completa
|
|
334
|
+
|
|
335
|
+
| Método | Descripción |
|
|
336
|
+
|---------------------------------|-----------------------------------------------------------|
|
|
337
|
+
| `QALink.init(config)` | Inicializa el SDK, intercepta fetch, consola y runtime |
|
|
338
|
+
| `QALink.interceptAxios(instance)`| Registra una instancia de axios |
|
|
339
|
+
| `QALink.setScreen(name)` | Registra la pantalla actual (genera breadcrumb) |
|
|
340
|
+
| `QALink.addBreadcrumb(action, data?)` | Registra una acción del usuario |
|
|
341
|
+
| `QALink.getStatus()` | Estado de la conexión WS |
|
|
342
|
+
| `QALink.destroy()` | Limpia interceptores y desconecta |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ErrorSource } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Clasifica el origen del error basado en el status code y contexto.
|
|
4
|
+
* Esta es la lógica core que le dice al QA quién tiene la culpa.
|
|
5
|
+
*/
|
|
6
|
+
export declare function classifyErrorSource(statusCode?: number, error?: unknown): ErrorSource;
|
|
7
|
+
export declare function getSourceLabel(source: ErrorSource): string;
|
|
8
|
+
export declare function isError(statusCode?: number): boolean;
|
|
9
|
+
//# sourceMappingURL=classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../src/core/classifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW,CAqBrF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAS1D;AAED,wBAAgB,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyErrorSource = classifyErrorSource;
|
|
4
|
+
exports.getSourceLabel = getSourceLabel;
|
|
5
|
+
exports.isError = isError;
|
|
6
|
+
/**
|
|
7
|
+
* Clasifica el origen del error basado en el status code y contexto.
|
|
8
|
+
* Esta es la lógica core que le dice al QA quién tiene la culpa.
|
|
9
|
+
*/
|
|
10
|
+
function classifyErrorSource(statusCode, error) {
|
|
11
|
+
if (!statusCode) {
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
const msg = error.message.toLowerCase();
|
|
14
|
+
if (msg.includes('network') || msg.includes('timeout') || msg.includes('connection')) {
|
|
15
|
+
return 'NETWORK';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return 'NETWORK';
|
|
19
|
+
}
|
|
20
|
+
if (statusCode >= 200 && statusCode < 300)
|
|
21
|
+
return 'UNKNOWN';
|
|
22
|
+
if (statusCode >= 300 && statusCode < 400)
|
|
23
|
+
return 'BACKEND';
|
|
24
|
+
if (statusCode === 401 || statusCode === 403)
|
|
25
|
+
return 'AUTH';
|
|
26
|
+
if (statusCode === 404)
|
|
27
|
+
return 'BACKEND';
|
|
28
|
+
if (statusCode === 422)
|
|
29
|
+
return 'FRONTEND';
|
|
30
|
+
if (statusCode === 429)
|
|
31
|
+
return 'BACKEND';
|
|
32
|
+
if (statusCode >= 400 && statusCode < 500)
|
|
33
|
+
return 'FRONTEND';
|
|
34
|
+
if (statusCode >= 500)
|
|
35
|
+
return 'BACKEND';
|
|
36
|
+
return 'UNKNOWN';
|
|
37
|
+
}
|
|
38
|
+
function getSourceLabel(source) {
|
|
39
|
+
const labels = {
|
|
40
|
+
BACKEND: '🔴 Error de Backend',
|
|
41
|
+
FRONTEND: '🟠 Error de Frontend',
|
|
42
|
+
NETWORK: '🟡 Error de Red/Conectividad',
|
|
43
|
+
AUTH: '🔐 Error de Autenticación',
|
|
44
|
+
UNKNOWN: '⚪ Origen desconocido',
|
|
45
|
+
};
|
|
46
|
+
return labels[source];
|
|
47
|
+
}
|
|
48
|
+
function isError(statusCode) {
|
|
49
|
+
return !!statusCode && statusCode >= 400;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/core/classifier.ts"],"names":[],"mappings":";;AAMA,kDAqBC;AAED,wCASC;AAED,0BAEC;AAxCD;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,UAAmB,EAAE,KAAe;IACtE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACxC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACrF,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IAC5D,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,UAAU,CAAC;IAC1C,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,UAAU,CAAC;IAC7D,IAAI,UAAU,IAAI,GAAG;QAAE,OAAO,SAAS,CAAC;IAExC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,cAAc,CAAC,MAAmB;IAChD,MAAM,MAAM,GAAgC;QAC1C,OAAO,EAAE,qBAAqB;QAC9B,QAAQ,EAAE,sBAAsB;QAChC,OAAO,EAAE,8BAA8B;QACvC,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,sBAAsB;KAChC,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,OAAO,CAAC,UAAmB;IACzC,OAAO,CAAC,CAAC,UAAU,IAAI,UAAU,IAAI,GAAG,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DeviceInfo } from '../types';
|
|
2
|
+
export declare function generateId(): string;
|
|
3
|
+
export declare function getSessionId(): string;
|
|
4
|
+
export declare function resetSession(): string;
|
|
5
|
+
export declare function getDeviceInfo(appVersion: string): Promise<DeviceInfo>;
|
|
6
|
+
export declare function sanitizeHeaders(headers: Record<string, string>, sensitiveHeaders?: string[]): Record<string, string>;
|
|
7
|
+
export declare function sanitizeBody(body: unknown, logBodies: boolean): unknown;
|
|
8
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsB3E;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,gBAAgB,GAAE,MAAM,EAAO,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAGvE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateId = generateId;
|
|
4
|
+
exports.getSessionId = getSessionId;
|
|
5
|
+
exports.resetSession = resetSession;
|
|
6
|
+
exports.getDeviceInfo = getDeviceInfo;
|
|
7
|
+
exports.sanitizeHeaders = sanitizeHeaders;
|
|
8
|
+
exports.sanitizeBody = sanitizeBody;
|
|
9
|
+
let currentSessionId = null;
|
|
10
|
+
function generateId() {
|
|
11
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
12
|
+
}
|
|
13
|
+
function getSessionId() {
|
|
14
|
+
if (!currentSessionId) {
|
|
15
|
+
currentSessionId = generateId();
|
|
16
|
+
}
|
|
17
|
+
return currentSessionId;
|
|
18
|
+
}
|
|
19
|
+
function resetSession() {
|
|
20
|
+
currentSessionId = generateId();
|
|
21
|
+
return currentSessionId;
|
|
22
|
+
}
|
|
23
|
+
async function getDeviceInfo(appVersion) {
|
|
24
|
+
var _a, _b, _c, _d;
|
|
25
|
+
try {
|
|
26
|
+
const { Platform } = require('react-native');
|
|
27
|
+
const DeviceInfo = require('react-native-device-info');
|
|
28
|
+
return {
|
|
29
|
+
platform: Platform.OS,
|
|
30
|
+
osVersion: (_b = (_a = Platform.Version) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'unknown',
|
|
31
|
+
appVersion,
|
|
32
|
+
deviceModel: await DeviceInfo.getModel().catch(() => 'unknown'),
|
|
33
|
+
buildType: __DEV__ ? 'debug' : 'release',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (_e) {
|
|
37
|
+
const { Platform } = require('react-native');
|
|
38
|
+
return {
|
|
39
|
+
platform: Platform.OS,
|
|
40
|
+
osVersion: (_d = (_c = Platform.Version) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : 'unknown',
|
|
41
|
+
appVersion,
|
|
42
|
+
deviceModel: 'unknown',
|
|
43
|
+
buildType: __DEV__ ? 'debug' : 'release',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function sanitizeHeaders(headers, sensitiveHeaders = []) {
|
|
48
|
+
const defaultSensitive = ['authorization', 'x-api-key', 'cookie', 'set-cookie'];
|
|
49
|
+
const allSensitive = [...defaultSensitive, ...sensitiveHeaders.map(h => h.toLowerCase())];
|
|
50
|
+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [
|
|
51
|
+
key,
|
|
52
|
+
allSensitive.includes(key.toLowerCase()) ? '[REDACTED]' : value,
|
|
53
|
+
]));
|
|
54
|
+
}
|
|
55
|
+
function sanitizeBody(body, logBodies) {
|
|
56
|
+
if (!logBodies)
|
|
57
|
+
return '[body omitted - enable logNetworkBodies]';
|
|
58
|
+
return body;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":";;AAIA,gCAEC;AAED,oCAKC;AAED,oCAGC;AAED,sCAsBC;AAED,0CAaC;AAED,oCAGC;AA5DD,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C,SAAgB,UAAU;IACxB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,SAAgB,YAAY;IAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAgB,YAAY;IAC1B,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAChC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAEM,KAAK,UAAU,aAAa,CAAC,UAAkB;;IACpD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAEvD,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,EAAuB;YAC1C,SAAS,EAAE,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,QAAQ,EAAE,mCAAI,SAAS;YACpD,UAAU;YACV,WAAW,EAAE,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;YAC/D,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SACzC,CAAC;IACJ,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;QAC7C,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,EAAuB;YAC1C,SAAS,EAAE,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,QAAQ,EAAE,mCAAI,SAAS;YACpD,UAAU;YACV,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SACzC,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,eAAe,CAC7B,OAA+B,EAC/B,mBAA6B,EAAE;IAE/B,MAAM,gBAAgB,GAAG,CAAC,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IAChF,MAAM,YAAY,GAAG,CAAC,GAAG,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAE1F,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QAC5C,GAAG;QACH,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK;KAChE,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAgB,YAAY,CAAC,IAAa,EAAE,SAAkB;IAC5D,IAAI,CAAC,SAAS;QAAE,OAAO,0CAA0C,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { QALinkConfig } from './types';
|
|
2
|
+
declare class QALinkSDK {
|
|
3
|
+
private transport;
|
|
4
|
+
private config;
|
|
5
|
+
private cleanups;
|
|
6
|
+
private initialized;
|
|
7
|
+
private currentScreen;
|
|
8
|
+
/**
|
|
9
|
+
* Inicializa el SDK. Llamar al inicio de la app, antes de cualquier request.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* QALink.init({
|
|
13
|
+
* serverUrl: 'ws://192.168.1.100:3000',
|
|
14
|
+
* appVersion: '1.2.3',
|
|
15
|
+
* captureRuntimeErrors: true,
|
|
16
|
+
* console: {
|
|
17
|
+
* captureLogs: true,
|
|
18
|
+
* captureWarnings: true,
|
|
19
|
+
* captureErrors: true,
|
|
20
|
+
* ignorePatterns: ['[react-query]'],
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
init(config: QALinkConfig): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Registra interceptor para una instancia específica de Axios.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* import axios from 'axios';
|
|
30
|
+
* QALink.interceptAxios(axios);
|
|
31
|
+
*
|
|
32
|
+
* // También con instancias custom:
|
|
33
|
+
* const api = axios.create({ baseURL: 'https://api.miapp.com' });
|
|
34
|
+
* QALink.interceptAxios(api);
|
|
35
|
+
*/
|
|
36
|
+
interceptAxios(axiosInstance: any): void;
|
|
37
|
+
/**
|
|
38
|
+
* Registra una acción del usuario (breadcrumb).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* QALink.addBreadcrumb('TAP → Confirmar Compra', { total: 150 });
|
|
42
|
+
*/
|
|
43
|
+
addBreadcrumb(action: string, data?: Record<string, unknown>): void;
|
|
44
|
+
/**
|
|
45
|
+
* Registra la pantalla actual. Llamar en cada cambio de navegación.
|
|
46
|
+
* Genera automáticamente un breadcrumb de navegación.
|
|
47
|
+
*/
|
|
48
|
+
setScreen(screenName: string): void;
|
|
49
|
+
/** Estado actual de la conexión WebSocket */
|
|
50
|
+
getStatus(): string;
|
|
51
|
+
/** Limpia todos los interceptores y desconecta. Útil en tests. */
|
|
52
|
+
destroy(): void;
|
|
53
|
+
private log;
|
|
54
|
+
}
|
|
55
|
+
export declare const QALink: QALinkSDK;
|
|
56
|
+
export * from './types';
|
|
57
|
+
export { getSourceLabel } from './core/classifier';
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAmD,MAAM,SAAS,CAAC;AASxF,cAAM,SAAS;IACb,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAa;IAElC;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA0E/C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,aAAa,EAAE,GAAG,GAAG,IAAI;IASxC;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAiBnE;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMnC,6CAA6C;IAC7C,SAAS,IAAI,MAAM;IAInB,kEAAkE;IAClE,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,GAAG;CAKZ;AAED,eAAO,MAAM,MAAM,WAAkB,CAAC;AAEtC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|