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.
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupConsoleInterceptor = setupConsoleInterceptor;
3
+ exports.setupConsoleInterceptor = void 0;
4
4
  const session_1 = require("../core/session");
5
- // Guardamos referencia a los métodos originales ANTES de sobrescribir
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
- * Patrones que identifican logs internos de React Native
15
- * para clasificarlos correctamente en el dashboard.
16
- */
17
- const RN_WARNING_PATTERNS = [
18
- 'Warning:',
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
- return 'rn_error';
66
- if (RN_ERROR_PATTERNS.some(p => message.includes(p)))
67
- return 'rn_error';
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
- var _a;
76
- if (arg === null)
77
- return 'null';
78
- if (arg === undefined)
79
- return 'undefined';
80
- if (typeof arg === 'string')
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
- var _a;
95
- if (!config)
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
- var _a;
108
- const consoleConfig = (_a = config.console) !== null && _a !== void 0 ? _a : {};
109
- const { captureLogs = true, captureWarnings = true, captureErrors = true, } = consoleConfig;
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
- var _a;
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
- if (!shouldCapture(message, consoleConfig))
118
- return;
119
- // Evitar loop infinito si QALink mismo hace console.log en debug mode
120
- if (message.startsWith('[QALink]'))
121
- return;
122
- const category = classifyConsoleMessage(message);
123
- const event = {
124
- id: (0, session_1.generateId)(),
125
- type: eventType,
126
- level,
127
- message,
128
- args: serializedArgs,
129
- category,
130
- timestamp: Date.now(),
131
- screen: getCurrentScreen(),
132
- sessionId: (0, session_1.getSessionId)(),
133
- };
134
- transport.send(event);
135
- (_a = config.onEvent) === null || _a === void 0 ? void 0 : _a.call(config, event);
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
- // Sobrescribir métodos de consola
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
- // Cleanup: restaurar consola original
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
- //# sourceMappingURL=console.js.map
115
+ exports.setupConsoleInterceptor = setupConsoleInterceptor;
@@ -1,141 +1,83 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupRuntimeErrorHandler = 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
- return 'red_screen';
23
- if (message.includes('NativeModule') ||
24
- message.includes('Native module') ||
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
- // ── 1. Handler global de errores fatales (pantalla roja) ──────────────────
44
- // React Native expone ErrorUtils para capturar errores antes de que
45
- // la app crashee y muestre el red screen.
16
+
17
+ // ── 1. Handler global de errores fatales ──────────────────────────────────
46
18
  try {
47
- const originalGlobalHandler = ErrorUtils.getGlobalHandler();
48
- ErrorUtils.setGlobalHandler((error, isFatal) => {
49
- var _a, _b, _c;
50
- const fatal = isFatal !== null && isFatal !== void 0 ? isFatal : false;
51
- const category = classifyRuntimeError((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '', fatal);
52
- const event = {
53
- id: (0, session_1.generateId)(),
54
- type: 'runtime_error',
55
- level: fatal ? 'fatal' : 'error',
56
- message: (_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : String(error),
57
- stack: error === null || error === void 0 ? void 0 : error.stack,
58
- isFatal: fatal,
59
- category,
60
- timestamp: Date.now(),
61
- screen: getCurrentScreen(),
62
- sessionId: (0, session_1.getSessionId)(),
63
- };
64
- transport.send(event);
65
- (_c = config.onEvent) === null || _c === void 0 ? void 0 : _c.call(config, event);
66
- // CRÍTICO: siempre llamar al handler original
67
- // Si no lo hacemos, RN no muestra la pantalla roja y el error se pierde
68
- originalGlobalHandler(error, isFatal);
69
- });
70
- cleanups.push(() => {
71
- ErrorUtils.setGlobalHandler(originalGlobalHandler);
72
- });
73
- }
74
- catch (e) {
75
- // ErrorUtils puede no estar disponible en algunos entornos (ej: Jest)
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 LogBox = require('react-native/Libraries/LogBox/LogBox');
111
- if (LogBox && LogBox.__warn) {
112
- const originalWarn = LogBox.__warn.bind(LogBox);
113
- LogBox.__warn = (message, stack) => {
114
- var _a;
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: 'warning',
57
+ level: 'error',
119
58
  message,
120
- stack,
59
+ stack: error?.stack,
121
60
  isFatal: false,
122
- category: 'yellow_box',
61
+ category: 'unknown',
123
62
  timestamp: Date.now(),
124
63
  screen: getCurrentScreen(),
125
64
  sessionId: (0, session_1.getSessionId)(),
126
65
  };
127
- transport.send(event);
128
- (_a = config.onEvent) === null || _a === void 0 ? void 0 : _a.call(config, event);
129
- originalWarn(message, stack);
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
- LogBox.__warn = originalWarn;
73
+ try { global.removeEventListener('unhandledrejection', unhandledRejectionHandler); } catch {}
133
74
  });
134
75
  }
135
- }
136
- catch (_c) {
137
- // LogBox puede no estar disponible en versiones viejas de RN
138
- }
139
- return () => cleanups.forEach(fn => fn());
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
- //# sourceMappingURL=runtime.js.map
83
+ exports.setupRuntimeErrorHandler = setupRuntimeErrorHandler;
package/package.json CHANGED
@@ -1,29 +1,16 @@
1
1
  {
2
2
  "name": "react-native-qalink",
3
- "version": "0.1.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
- "dist",
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"