react-native-qalink 0.1.1 → 0.1.3
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/dist/interceptors/console.js +67 -111
- package/dist/interceptors/runtime.js +60 -118
- package/package.json +4 -17
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupConsoleInterceptor =
|
|
3
|
+
exports.setupConsoleInterceptor = void 0;
|
|
4
4
|
const session_1 = require("../core/session");
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
const originalConsole = {
|
|
7
7
|
log: console.log.bind(console),
|
|
8
8
|
warn: console.warn.bind(console),
|
|
@@ -10,132 +10,88 @@ const originalConsole = {
|
|
|
10
10
|
info: console.info.bind(console),
|
|
11
11
|
debug: console.debug.bind(console),
|
|
12
12
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
'VirtualizedList',
|
|
20
|
-
'Each child in a list',
|
|
21
|
-
'componentWillMount',
|
|
22
|
-
'componentWillReceiveProps',
|
|
23
|
-
'componentWillUpdate',
|
|
24
|
-
'findDOMNode',
|
|
25
|
-
'ReactDOM.render',
|
|
26
|
-
'key prop',
|
|
27
|
-
'unique "key"',
|
|
28
|
-
'Maximum update depth',
|
|
29
|
-
];
|
|
30
|
-
const RN_ERROR_PATTERNS = [
|
|
31
|
-
'Invariant Violation',
|
|
32
|
-
'Text strings must be rendered',
|
|
33
|
-
'Cannot update a component',
|
|
34
|
-
'Element type is invalid',
|
|
35
|
-
'Objects are not valid as a React child',
|
|
36
|
-
'TypeError',
|
|
37
|
-
'ReferenceError',
|
|
38
|
-
'Cannot read property',
|
|
39
|
-
'Cannot read properties',
|
|
40
|
-
'is not a function',
|
|
41
|
-
'undefined is not an object',
|
|
42
|
-
'null is not an object',
|
|
43
|
-
];
|
|
44
|
-
const METRO_ERROR_PATTERNS = [
|
|
45
|
-
'Unable to resolve module',
|
|
46
|
-
'Module not found',
|
|
47
|
-
'SyntaxError',
|
|
48
|
-
'TransformError',
|
|
49
|
-
'bundling failed',
|
|
50
|
-
'Metro Bundler',
|
|
51
|
-
'Error: Metro',
|
|
52
|
-
];
|
|
53
|
-
const THIRD_PARTY_PATTERNS = [
|
|
54
|
-
'[react-navigation]',
|
|
55
|
-
'[react-query]',
|
|
56
|
-
'[redux]',
|
|
57
|
-
'[mobx]',
|
|
58
|
-
'[zustand]',
|
|
59
|
-
'Reanimated',
|
|
60
|
-
'Gesture Handler',
|
|
61
|
-
'SafeAreaProvider',
|
|
62
|
-
];
|
|
13
|
+
|
|
14
|
+
const RN_WARNING_PATTERNS = ['Warning:', 'VirtualizedList', 'Each child in a list', 'componentWillMount', 'componentWillReceiveProps', 'componentWillUpdate', 'findDOMNode', 'ReactDOM.render', 'key prop', 'unique "key"', 'Maximum update depth'];
|
|
15
|
+
const RN_ERROR_PATTERNS = ['Invariant Violation', 'Text strings must be rendered', 'Cannot update a component', 'Element type is invalid', 'Objects are not valid as a React child', 'TypeError', 'ReferenceError', 'Cannot read property', 'Cannot read properties', 'is not a function', 'undefined is not an object', 'null is not an object'];
|
|
16
|
+
const METRO_ERROR_PATTERNS = ['Unable to resolve module', 'Module not found', 'SyntaxError', 'TransformError', 'bundling failed', 'Metro Bundler', 'Error: Metro'];
|
|
17
|
+
const THIRD_PARTY_PATTERNS = ['[react-navigation]', '[react-query]', '[redux]', '[mobx]', '[zustand]', 'Reanimated', 'Gesture Handler', 'SafeAreaProvider'];
|
|
18
|
+
|
|
63
19
|
function classifyConsoleMessage(message) {
|
|
64
|
-
if (METRO_ERROR_PATTERNS.some(p => message.includes(p)))
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
if (RN_WARNING_PATTERNS.some(p => message.includes(p)))
|
|
69
|
-
return 'rn_warning';
|
|
70
|
-
if (THIRD_PARTY_PATTERNS.some(p => message.includes(p)))
|
|
71
|
-
return 'third_party';
|
|
20
|
+
if (METRO_ERROR_PATTERNS.some(p => message.includes(p))) return 'rn_error';
|
|
21
|
+
if (RN_ERROR_PATTERNS.some(p => message.includes(p))) return 'rn_error';
|
|
22
|
+
if (RN_WARNING_PATTERNS.some(p => message.includes(p))) return 'rn_warning';
|
|
23
|
+
if (THIRD_PARTY_PATTERNS.some(p => message.includes(p))) return 'third_party';
|
|
72
24
|
return 'user_log';
|
|
73
25
|
}
|
|
26
|
+
|
|
74
27
|
function serializeArg(arg) {
|
|
75
|
-
|
|
76
|
-
if (arg ===
|
|
77
|
-
|
|
78
|
-
if (arg ===
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return arg;
|
|
82
|
-
if (typeof arg === 'number' || typeof arg === 'boolean')
|
|
83
|
-
return String(arg);
|
|
84
|
-
if (arg instanceof Error)
|
|
85
|
-
return `${arg.name}: ${arg.message}\n${(_a = arg.stack) !== null && _a !== void 0 ? _a : ''}`;
|
|
86
|
-
try {
|
|
87
|
-
return JSON.stringify(arg, null, 2);
|
|
88
|
-
}
|
|
89
|
-
catch (_b) {
|
|
90
|
-
return String(arg);
|
|
91
|
-
}
|
|
28
|
+
if (arg === null) return 'null';
|
|
29
|
+
if (arg === undefined) return 'undefined';
|
|
30
|
+
if (typeof arg === 'string') return arg;
|
|
31
|
+
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
|
|
32
|
+
if (arg instanceof Error) return `${arg.name}: ${arg.message}\n${arg.stack ?? ''}`;
|
|
33
|
+
try { return JSON.stringify(arg, null, 2); } catch { return String(arg); }
|
|
92
34
|
}
|
|
35
|
+
|
|
93
36
|
function shouldCapture(message, config) {
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
return true;
|
|
97
|
-
// Ignorar si matchea algún patrón de exclusión
|
|
98
|
-
if ((_a = config.ignorePatterns) === null || _a === void 0 ? void 0 : _a.some(p => message.includes(p)))
|
|
99
|
-
return false;
|
|
100
|
-
// Si hay patrones de inclusión, solo capturar si matchea alguno
|
|
37
|
+
if (!config) return true;
|
|
38
|
+
if (config.ignorePatterns?.some(p => message.includes(p))) return false;
|
|
101
39
|
if (config.includePatterns && config.includePatterns.length > 0) {
|
|
102
40
|
return config.includePatterns.some(p => message.includes(p));
|
|
103
41
|
}
|
|
104
42
|
return true;
|
|
105
43
|
}
|
|
44
|
+
|
|
45
|
+
// FLAG para prevenir loops de recursión
|
|
46
|
+
let _isSending = false;
|
|
47
|
+
|
|
106
48
|
function setupConsoleInterceptor(transport, config, getCurrentScreen) {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
49
|
+
const consoleConfig = config.console ?? {};
|
|
50
|
+
const { captureLogs = true, captureWarnings = true, captureErrors = true } = consoleConfig;
|
|
51
|
+
|
|
110
52
|
function intercept(level, eventType, originalFn) {
|
|
111
53
|
return (...args) => {
|
|
112
|
-
|
|
113
|
-
// Siempre llamar al original primero — Metro sigue mostrando todo
|
|
54
|
+
// SIEMPRE llamar el original primero
|
|
114
55
|
originalFn(...args);
|
|
56
|
+
|
|
57
|
+
// Evitar loop recursivo — si ya estamos enviando, no volver a entrar
|
|
58
|
+
if (_isSending) return;
|
|
59
|
+
|
|
115
60
|
const serializedArgs = args.map(serializeArg);
|
|
116
61
|
const message = serializedArgs.join(' ');
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
62
|
+
|
|
63
|
+
// Ignorar logs internos de QALink
|
|
64
|
+
if (message.startsWith('[QALink]')) return;
|
|
65
|
+
|
|
66
|
+
// Ignorar errores de call stack para evitar loop infinito
|
|
67
|
+
if (message.includes('Maximum call stack') || message.includes('call stack size exceeded')) return;
|
|
68
|
+
|
|
69
|
+
if (!shouldCapture(message, consoleConfig)) return;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
_isSending = true;
|
|
73
|
+
const category = classifyConsoleMessage(message);
|
|
74
|
+
const event = {
|
|
75
|
+
id: (0, session_1.generateId)(),
|
|
76
|
+
type: eventType,
|
|
77
|
+
level,
|
|
78
|
+
message,
|
|
79
|
+
args: serializedArgs,
|
|
80
|
+
category,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
screen: getCurrentScreen(),
|
|
83
|
+
sessionId: (0, session_1.getSessionId)(),
|
|
84
|
+
};
|
|
85
|
+
transport.send(event);
|
|
86
|
+
config.onEvent?.(event);
|
|
87
|
+
} catch {
|
|
88
|
+
// Nunca romper la app por un error del interceptor
|
|
89
|
+
} finally {
|
|
90
|
+
_isSending = false;
|
|
91
|
+
}
|
|
136
92
|
};
|
|
137
93
|
}
|
|
138
|
-
|
|
94
|
+
|
|
139
95
|
if (captureLogs) {
|
|
140
96
|
console.log = intercept('log', 'console_log', originalConsole.log);
|
|
141
97
|
console.info = intercept('info', 'console_log', originalConsole.info);
|
|
@@ -147,7 +103,7 @@ function setupConsoleInterceptor(transport, config, getCurrentScreen) {
|
|
|
147
103
|
if (captureErrors) {
|
|
148
104
|
console.error = intercept('error', 'console_error', originalConsole.error);
|
|
149
105
|
}
|
|
150
|
-
|
|
106
|
+
|
|
151
107
|
return () => {
|
|
152
108
|
console.log = originalConsole.log;
|
|
153
109
|
console.warn = originalConsole.warn;
|
|
@@ -156,4 +112,4 @@ function setupConsoleInterceptor(transport, config, getCurrentScreen) {
|
|
|
156
112
|
console.debug = originalConsole.debug;
|
|
157
113
|
};
|
|
158
114
|
}
|
|
159
|
-
|
|
115
|
+
exports.setupConsoleInterceptor = setupConsoleInterceptor;
|
|
@@ -1,141 +1,83 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupRuntimeErrorHandler =
|
|
3
|
+
exports.setupRuntimeErrorHandler = void 0;
|
|
4
4
|
const session_1 = require("../core/session");
|
|
5
|
-
|
|
6
|
-
* Captura los errores del runtime de React Native:
|
|
7
|
-
*
|
|
8
|
-
* 1. ErrorUtils.setGlobalHandler → captura la PANTALLA ROJA (errores fatales)
|
|
9
|
-
* Son los errores que crashean la app completamente. React Native los
|
|
10
|
-
* intercepta antes de mostrar el red screen y nosotros nos enganchamos ahí.
|
|
11
|
-
*
|
|
12
|
-
* 2. LogBox.ignoreLogs / console.error → captura las CAJAS AMARILLAS (warnings)
|
|
13
|
-
* Son los warnings que RN muestra en desarrollo. Se generan a través de
|
|
14
|
-
* console.error internamente, así que el interceptor de consola los captura,
|
|
15
|
-
* pero aquí los marcamos también con el handler de RN para mayor precisión.
|
|
16
|
-
*
|
|
17
|
-
* 3. NativeModules errors → errores del bridge JS<->Native
|
|
18
|
-
* Ocurren cuando un módulo nativo falla o no está disponible.
|
|
19
|
-
*/
|
|
5
|
+
|
|
20
6
|
function classifyRuntimeError(message, isFatal) {
|
|
21
|
-
if (isFatal)
|
|
22
|
-
|
|
23
|
-
if (message.includes('
|
|
24
|
-
|
|
25
|
-
message.includes('TurboModule') ||
|
|
26
|
-
message.includes('requireNativeComponent')) {
|
|
27
|
-
return 'native_module';
|
|
28
|
-
}
|
|
29
|
-
if (message.includes('bridge') ||
|
|
30
|
-
message.includes('JSContext') ||
|
|
31
|
-
message.includes('RCTBridge') ||
|
|
32
|
-
message.includes('JavaScriptCore')) {
|
|
33
|
-
return 'bridge';
|
|
34
|
-
}
|
|
35
|
-
if (message.startsWith('Warning:') || message.includes('deprecated')) {
|
|
36
|
-
return 'yellow_box';
|
|
37
|
-
}
|
|
7
|
+
if (isFatal) return 'red_screen';
|
|
8
|
+
if (message.includes('NativeModule') || message.includes('Native module') || message.includes('TurboModule') || message.includes('requireNativeComponent')) return 'native_module';
|
|
9
|
+
if (message.includes('bridge') || message.includes('JSContext') || message.includes('RCTBridge') || message.includes('JavaScriptCore')) return 'bridge';
|
|
10
|
+
if (message.startsWith('Warning:') || message.includes('deprecated')) return 'yellow_box';
|
|
38
11
|
return 'unknown';
|
|
39
12
|
}
|
|
13
|
+
|
|
40
14
|
function setupRuntimeErrorHandler(transport, config, getCurrentScreen) {
|
|
41
|
-
var _a, _b;
|
|
42
15
|
const cleanups = [];
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
// la app crashee y muestre el red screen.
|
|
16
|
+
|
|
17
|
+
// ── 1. Handler global de errores fatales ──────────────────────────────────
|
|
46
18
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
catch
|
|
75
|
-
|
|
76
|
-
}
|
|
19
|
+
if (typeof ErrorUtils !== 'undefined' && ErrorUtils.getGlobalHandler) {
|
|
20
|
+
const originalGlobalHandler = ErrorUtils.getGlobalHandler();
|
|
21
|
+
ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
22
|
+
try {
|
|
23
|
+
const fatal = isFatal ?? false;
|
|
24
|
+
const category = classifyRuntimeError(error?.message ?? '', fatal);
|
|
25
|
+
const event = {
|
|
26
|
+
id: (0, session_1.generateId)(),
|
|
27
|
+
type: 'runtime_error',
|
|
28
|
+
level: fatal ? 'fatal' : 'error',
|
|
29
|
+
message: error?.message ?? String(error),
|
|
30
|
+
stack: error?.stack,
|
|
31
|
+
isFatal: fatal,
|
|
32
|
+
category,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
screen: getCurrentScreen(),
|
|
35
|
+
sessionId: (0, session_1.getSessionId)(),
|
|
36
|
+
};
|
|
37
|
+
transport.send(event);
|
|
38
|
+
config.onEvent?.(event);
|
|
39
|
+
} catch {}
|
|
40
|
+
originalGlobalHandler(error, isFatal);
|
|
41
|
+
});
|
|
42
|
+
cleanups.push(() => {
|
|
43
|
+
try { ErrorUtils.setGlobalHandler(originalGlobalHandler); } catch {}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
|
|
77
48
|
// ── 2. Promesas rechazadas no manejadas ───────────────────────────────────
|
|
78
|
-
// Cuando un Promise.reject() no tiene .catch(), RN lo captura aquí.
|
|
79
|
-
// Ejemplo típico: llamada a API sin try/catch
|
|
80
|
-
const unhandledRejectionHandler = (event) => {
|
|
81
|
-
var _a, _b, _c;
|
|
82
|
-
const error = event === null || event === void 0 ? void 0 : event.reason;
|
|
83
|
-
const message = (_b = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : String(error)) !== null && _b !== void 0 ? _b : 'Unhandled Promise Rejection';
|
|
84
|
-
const runtimeEvent = {
|
|
85
|
-
id: (0, session_1.generateId)(),
|
|
86
|
-
type: 'runtime_error',
|
|
87
|
-
level: 'error',
|
|
88
|
-
message,
|
|
89
|
-
stack: error === null || error === void 0 ? void 0 : error.stack,
|
|
90
|
-
isFatal: false,
|
|
91
|
-
category: 'unknown',
|
|
92
|
-
timestamp: Date.now(),
|
|
93
|
-
screen: getCurrentScreen(),
|
|
94
|
-
sessionId: (0, session_1.getSessionId)(),
|
|
95
|
-
};
|
|
96
|
-
transport.send(runtimeEvent);
|
|
97
|
-
(_c = config.onEvent) === null || _c === void 0 ? void 0 : _c.call(config, runtimeEvent);
|
|
98
|
-
};
|
|
99
|
-
if (typeof global !== 'undefined') {
|
|
100
|
-
(_b = (_a = global).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'unhandledrejection', unhandledRejectionHandler);
|
|
101
|
-
cleanups.push(() => {
|
|
102
|
-
var _a, _b;
|
|
103
|
-
(_b = (_a = global).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'unhandledrejection', unhandledRejectionHandler);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
// ── 3. Warnings de LogBox (cajas amarillas) ───────────────────────────────
|
|
107
|
-
// En RN >= 0.64 los warnings se manejan via LogBox.
|
|
108
|
-
// Nos enganchamos al método interno que registra los warnings.
|
|
109
49
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const event = {
|
|
50
|
+
const unhandledRejectionHandler = (event) => {
|
|
51
|
+
try {
|
|
52
|
+
const error = event?.reason;
|
|
53
|
+
const message = error?.message ?? String(error) ?? 'Unhandled Promise Rejection';
|
|
54
|
+
const runtimeEvent = {
|
|
116
55
|
id: (0, session_1.generateId)(),
|
|
117
56
|
type: 'runtime_error',
|
|
118
|
-
level: '
|
|
57
|
+
level: 'error',
|
|
119
58
|
message,
|
|
120
|
-
stack,
|
|
59
|
+
stack: error?.stack,
|
|
121
60
|
isFatal: false,
|
|
122
|
-
category: '
|
|
61
|
+
category: 'unknown',
|
|
123
62
|
timestamp: Date.now(),
|
|
124
63
|
screen: getCurrentScreen(),
|
|
125
64
|
sessionId: (0, session_1.getSessionId)(),
|
|
126
65
|
};
|
|
127
|
-
transport.send(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
66
|
+
transport.send(runtimeEvent);
|
|
67
|
+
config.onEvent?.(runtimeEvent);
|
|
68
|
+
} catch {}
|
|
69
|
+
};
|
|
70
|
+
if (typeof global !== 'undefined' && global.addEventListener) {
|
|
71
|
+
global.addEventListener('unhandledrejection', unhandledRejectionHandler);
|
|
131
72
|
cleanups.push(() => {
|
|
132
|
-
|
|
73
|
+
try { global.removeEventListener('unhandledrejection', unhandledRejectionHandler); } catch {}
|
|
133
74
|
});
|
|
134
75
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
76
|
+
} catch {}
|
|
77
|
+
|
|
78
|
+
// NOTE: LogBox.__warn removido — causa crash en bridge nativo C++
|
|
79
|
+
// Los warnings ya son capturados por el interceptor de console.warn
|
|
80
|
+
|
|
81
|
+
return () => cleanups.forEach(fn => { try { fn(); } catch {} });
|
|
140
82
|
}
|
|
141
|
-
|
|
83
|
+
exports.setupRuntimeErrorHandler = setupRuntimeErrorHandler;
|
package/package.json
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-qalink",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Real-time error capture SDK for React Native — helps QA teams identify if bugs are frontend or backend",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
|
|
9
|
-
"README.md"
|
|
10
|
-
],
|
|
11
|
-
"keywords": [
|
|
12
|
-
"react-native",
|
|
13
|
-
"debugging",
|
|
14
|
-
"qa",
|
|
15
|
-
"error-tracking",
|
|
16
|
-
"network-interceptor"
|
|
17
|
-
],
|
|
7
|
+
"files": ["dist", "README.md"],
|
|
8
|
+
"keywords": ["react-native", "debugging", "qa", "error-tracking", "network-interceptor"],
|
|
18
9
|
"peerDependencies": {
|
|
19
10
|
"react-native": ">=0.70.0",
|
|
20
11
|
"axios": ">=1.0.0"
|
|
21
12
|
},
|
|
22
|
-
"peerDependenciesMeta": {
|
|
23
|
-
"axios": {
|
|
24
|
-
"optional": true
|
|
25
|
-
}
|
|
26
|
-
},
|
|
13
|
+
"peerDependenciesMeta": { "axios": { "optional": true } },
|
|
27
14
|
"devDependencies": {
|
|
28
15
|
"typescript": "^5.0.0",
|
|
29
16
|
"@types/react-native": "^0.72.0"
|