react-native-inapp-inspector 1.1.24 → 1.1.26

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.
Files changed (114) hide show
  1. package/android/build.gradle +36 -0
  2. package/android/src/main/AndroidManifest.xml +3 -0
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +114 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +28 -0
  5. package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
  6. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  7. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +9 -145
  9. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  10. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  11. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  12. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  13. package/dist/commonjs/components/Inspector/BundleTab.js +826 -406
  14. package/dist/commonjs/components/Inspector/CrashDetail.d.ts +3 -0
  15. package/dist/commonjs/components/Inspector/CrashDetail.js +796 -0
  16. package/dist/commonjs/components/Inspector/CrashFilterModal.d.ts +20 -0
  17. package/dist/commonjs/components/Inspector/CrashFilterModal.js +597 -0
  18. package/dist/commonjs/components/Inspector/CrashTab.d.ts +3 -0
  19. package/dist/commonjs/components/Inspector/CrashTab.js +727 -0
  20. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  21. package/dist/commonjs/components/Inspector/InspectorHeader.js +65 -28
  22. package/dist/commonjs/components/Inspector/LogDetail.js +227 -114
  23. package/dist/commonjs/components/Inspector/MainScreen.js +13 -4
  24. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  25. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  26. package/dist/commonjs/components/Inspector/SettingsPanel.js +90 -1
  27. package/dist/commonjs/components/Inspector/TabBar.js +15 -3
  28. package/dist/commonjs/components/NetworkIcons.d.ts +25 -0
  29. package/dist/commonjs/components/NetworkIcons.js +188 -1
  30. package/dist/commonjs/components/SegmentedTabs.js +33 -23
  31. package/dist/commonjs/components/TreeNode.js +13 -12
  32. package/dist/commonjs/constants/version.d.ts +1 -1
  33. package/dist/commonjs/constants/version.js +1 -1
  34. package/dist/commonjs/customHooks/analyticsLogger.js +70 -44
  35. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +5 -1
  36. package/dist/commonjs/customHooks/bundleAnalyzer.js +497 -126
  37. package/dist/commonjs/customHooks/consoleLogger.d.ts +1 -0
  38. package/dist/commonjs/customHooks/consoleLogger.js +153 -51
  39. package/dist/commonjs/customHooks/crashHandler.d.ts +64 -0
  40. package/dist/commonjs/customHooks/crashHandler.js +681 -0
  41. package/dist/commonjs/customHooks/networkLogger.js +74 -49
  42. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  43. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  44. package/dist/commonjs/decorators/index.d.ts +49 -0
  45. package/dist/commonjs/decorators/index.js +142 -0
  46. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  47. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  48. package/dist/commonjs/helpers/index.d.ts +7 -4
  49. package/dist/commonjs/helpers/index.js +102 -64
  50. package/dist/commonjs/i18n/locales/en.json +182 -4
  51. package/dist/commonjs/index.d.ts +6 -0
  52. package/dist/commonjs/index.js +310 -8
  53. package/dist/commonjs/styles/index.d.ts +4 -1
  54. package/dist/commonjs/styles/index.js +22 -19
  55. package/dist/commonjs/types/enums.d.ts +156 -9
  56. package/dist/commonjs/types/enums.js +139 -2
  57. package/dist/commonjs/types/interfaces.d.ts +70 -1
  58. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  59. package/dist/esm/components/ConsoleLogCard.js +49 -25
  60. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  61. package/dist/esm/components/ErrorBoundary.js +10 -113
  62. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  63. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  64. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  65. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  66. package/dist/esm/components/Inspector/BundleTab.js +827 -407
  67. package/dist/esm/components/Inspector/CrashDetail.d.ts +3 -0
  68. package/dist/esm/components/Inspector/CrashDetail.js +758 -0
  69. package/dist/esm/components/Inspector/CrashFilterModal.d.ts +20 -0
  70. package/dist/esm/components/Inspector/CrashFilterModal.js +557 -0
  71. package/dist/esm/components/Inspector/CrashTab.d.ts +3 -0
  72. package/dist/esm/components/Inspector/CrashTab.js +689 -0
  73. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  74. package/dist/esm/components/Inspector/InspectorHeader.js +66 -29
  75. package/dist/esm/components/Inspector/LogDetail.js +227 -114
  76. package/dist/esm/components/Inspector/MainScreen.js +14 -5
  77. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  78. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  79. package/dist/esm/components/Inspector/SettingsPanel.js +91 -2
  80. package/dist/esm/components/Inspector/TabBar.js +16 -4
  81. package/dist/esm/components/NetworkIcons.d.ts +25 -0
  82. package/dist/esm/components/NetworkIcons.js +161 -0
  83. package/dist/esm/components/SegmentedTabs.js +33 -23
  84. package/dist/esm/components/TreeNode.js +13 -12
  85. package/dist/esm/constants/version.d.ts +1 -1
  86. package/dist/esm/constants/version.js +1 -1
  87. package/dist/esm/customHooks/analyticsLogger.js +70 -44
  88. package/dist/esm/customHooks/bundleAnalyzer.d.ts +5 -1
  89. package/dist/esm/customHooks/bundleAnalyzer.js +493 -125
  90. package/dist/esm/customHooks/consoleLogger.d.ts +1 -0
  91. package/dist/esm/customHooks/consoleLogger.js +151 -50
  92. package/dist/esm/customHooks/crashHandler.d.ts +64 -0
  93. package/dist/esm/customHooks/crashHandler.js +659 -0
  94. package/dist/esm/customHooks/networkLogger.js +74 -49
  95. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  96. package/dist/esm/customHooks/performanceTracker.js +38 -1
  97. package/dist/esm/decorators/index.d.ts +49 -0
  98. package/dist/esm/decorators/index.js +137 -0
  99. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  100. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  101. package/dist/esm/helpers/index.d.ts +7 -4
  102. package/dist/esm/helpers/index.js +100 -63
  103. package/dist/esm/i18n/locales/en.json +182 -4
  104. package/dist/esm/index.d.ts +6 -0
  105. package/dist/esm/index.js +262 -8
  106. package/dist/esm/styles/index.d.ts +4 -1
  107. package/dist/esm/styles/index.js +22 -19
  108. package/dist/esm/types/enums.d.ts +156 -9
  109. package/dist/esm/types/enums.js +138 -3
  110. package/dist/esm/types/interfaces.d.ts +70 -1
  111. package/ios/NetworkInspectorModule.h +6 -0
  112. package/ios/NetworkInspectorModule.m +125 -0
  113. package/package.json +5 -2
  114. package/react-native-inapp-inspector.podspec +18 -0
@@ -0,0 +1,659 @@
1
+ import { LogBox, NativeModules, TurboModuleRegistry, NativeEventEmitter, Platform, AppState, Dimensions, } from 'react-native';
2
+ import { CrashExportFormat, CrashType } from '../types/enums';
3
+ import { addLogFromCrash } from './consoleLogger';
4
+ import { t } from '../i18n';
5
+ export { CrashExportFormat };
6
+ let crashListeners = [];
7
+ let isCrashHandlerInitialized = false;
8
+ let lastHandledErrorTimestamp = 0;
9
+ let crashRecordsStore = [];
10
+ let breadcrumbsStore = [];
11
+ const MAX_BREADCRUMBS = 50;
12
+ let maxStoredCrashes = 100;
13
+ const appStartTime = Date.now();
14
+ // ─── BREADCRUMB MANAGERS ───────────────────────────────────────────────────────
15
+ export const setMaxCrashLogsLimit = (max) => {
16
+ maxStoredCrashes = Math.max(10, max);
17
+ if (crashRecordsStore.length > maxStoredCrashes) {
18
+ crashRecordsStore = crashRecordsStore.slice(0, maxStoredCrashes);
19
+ }
20
+ };
21
+ export const addCrashBreadcrumb = (type, message, data) => {
22
+ try {
23
+ const entry = {
24
+ type,
25
+ message,
26
+ timestamp: Date.now(),
27
+ data,
28
+ };
29
+ breadcrumbsStore.unshift(entry);
30
+ if (breadcrumbsStore.length > MAX_BREADCRUMBS) {
31
+ breadcrumbsStore = breadcrumbsStore.slice(0, MAX_BREADCRUMBS);
32
+ }
33
+ }
34
+ catch { }
35
+ };
36
+ export const recordNavigationBreadcrumb = (fromRoute, toRoute) => {
37
+ addCrashBreadcrumb('navigation', t('crash.breadcrumbNavigation', { from: fromRoute || '/', to: toRoute || '/' }), {
38
+ from: fromRoute,
39
+ to: toRoute,
40
+ });
41
+ };
42
+ export const recordNetworkBreadcrumb = (url, method, status, duration) => {
43
+ const statusStr = status != null ? ` [${status}]` : '';
44
+ const durStr = duration != null ? ` (${Math.round(duration)}ms)` : '';
45
+ addCrashBreadcrumb('network', `${method.toUpperCase()} ${url}${statusStr}${durStr}`, {
46
+ url,
47
+ method,
48
+ status,
49
+ duration,
50
+ });
51
+ };
52
+ export const recordReduxBreadcrumb = (actionType, payload) => {
53
+ addCrashBreadcrumb('redux', t('crash.breadcrumbAction', { actionType }), {
54
+ type: actionType,
55
+ payloadSummary: typeof payload === 'object' && payload !== null
56
+ ? Object.keys(payload)
57
+ : typeof payload,
58
+ });
59
+ };
60
+ export const recordUserActionBreadcrumb = (action, metadata) => {
61
+ addCrashBreadcrumb('user', action, metadata);
62
+ };
63
+ export const clearCrashBreadcrumbs = () => {
64
+ breadcrumbsStore = [];
65
+ };
66
+ export const getCrashBreadcrumbs = () => {
67
+ return [...breadcrumbsStore];
68
+ };
69
+ // ─── STACK TRACE PARSING & INTELLIGENT SOURCE DETECTION ───────────────────────
70
+ /**
71
+ * Intelligent stack trace parser with multi-engine support:
72
+ * - Hermes bytecode & source maps
73
+ * - Android Java / Kotlin native frames
74
+ * - iOS Objective-C / Swift symbols
75
+ * - JavaScript standard V8 & SpiderMonkey
76
+ */
77
+ export const parseCrashStackTrace = (stack) => {
78
+ if (!stack || typeof stack !== 'string')
79
+ return [];
80
+ const lines = stack.split('\n');
81
+ const frames = [];
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed)
85
+ continue;
86
+ // Filter out internal wrapper noise
87
+ if (trimmed.includes('crashHandler.ts') ||
88
+ trimmed.includes('handleInterceptedCrash') ||
89
+ trimmed.includes('addLogFromCrash') ||
90
+ trimmed.includes('setupGlobalCrashHandler')) {
91
+ continue;
92
+ }
93
+ // Pattern 1: iOS/Hermes format: method@file:line:column or method@url:line:col
94
+ const iosMatch = trimmed.match(/^([^@]+)@(.*):(\d+):(\d+)$/);
95
+ if (iosMatch) {
96
+ const [, method, file, lineNum, colNum] = iosMatch;
97
+ const cleanFile = file.split('?')[0].split('/').pop() || file;
98
+ const isAppCode = !file.includes('node_modules') &&
99
+ !file.includes('react-native') &&
100
+ !file.includes('react-dom') &&
101
+ !file.includes('hermes') &&
102
+ !file.includes('internal');
103
+ frames.push({
104
+ method: method.trim(),
105
+ file: cleanFile,
106
+ lineNumber: parseInt(lineNum, 10),
107
+ column: parseInt(colNum, 10),
108
+ raw: trimmed,
109
+ isAppCode,
110
+ });
111
+ continue;
112
+ }
113
+ // Pattern 2: V8/Android JS format: at method (file:line:column)
114
+ const androidMatch = trimmed.match(/^at\s+(.+)\s+\((.+):(\d+):(\d+)\)$/);
115
+ if (androidMatch) {
116
+ const [, method, file, lineNum, colNum] = androidMatch;
117
+ const cleanFile = file.split('?')[0].split('/').pop() || file;
118
+ const isAppCode = !file.includes('node_modules') &&
119
+ !file.includes('react-native') &&
120
+ !file.includes('react-dom') &&
121
+ !file.includes('internal');
122
+ frames.push({
123
+ method: method.trim(),
124
+ file: cleanFile,
125
+ lineNumber: parseInt(lineNum, 10),
126
+ column: parseInt(colNum, 10),
127
+ raw: trimmed,
128
+ isAppCode,
129
+ });
130
+ continue;
131
+ }
132
+ // Pattern 3: Simple at file:line:column
133
+ const simpleMatch = trimmed.match(/^at\s+(.+):(\d+):(\d+)$/);
134
+ if (simpleMatch) {
135
+ const [, file, lineNum, colNum] = simpleMatch;
136
+ const cleanFile = file.split('?')[0].split('/').pop() || file;
137
+ const isAppCode = !file.includes('node_modules') && !file.includes('react-native');
138
+ frames.push({
139
+ method: '<anonymous>',
140
+ file: cleanFile,
141
+ lineNumber: parseInt(lineNum, 10),
142
+ column: parseInt(colNum, 10),
143
+ raw: trimmed,
144
+ isAppCode,
145
+ });
146
+ continue;
147
+ }
148
+ // Pattern 4: Java / Android Native Frame: at com.pkg.Class.method(Class.java:123)
149
+ const javaMatch = trimmed.match(/^at\s+([a-zA-Z0-9_$.]+)\(([a-zA-Z0-9_$]+\.java):(\d+)\)$/);
150
+ if (javaMatch) {
151
+ const [, methodPath, fileName, lineNum] = javaMatch;
152
+ const isAppCode = !methodPath.startsWith('com.facebook.react') &&
153
+ !methodPath.startsWith('android.');
154
+ frames.push({
155
+ method: methodPath,
156
+ file: fileName,
157
+ lineNumber: parseInt(lineNum, 10),
158
+ column: 0,
159
+ raw: trimmed,
160
+ isAppCode,
161
+ });
162
+ continue;
163
+ }
164
+ // Pattern 5: iOS Mach-O Symbol: 0 AppName 0x000000010... -[ViewController method] + 48
165
+ const machOMatch = trimmed.match(/^\d+\s+([^\s]+)\s+(0x[0-9a-fA-F]+)\s+(.+)$/);
166
+ if (machOMatch) {
167
+ const [, binaryName, address, symbol] = machOMatch;
168
+ const isAppCode = !binaryName.startsWith('lib') &&
169
+ !binaryName.startsWith('Core') &&
170
+ !binaryName.startsWith('React');
171
+ frames.push({
172
+ method: symbol,
173
+ file: binaryName,
174
+ lineNumber: 0,
175
+ column: 0,
176
+ raw: trimmed,
177
+ isAppCode,
178
+ });
179
+ continue;
180
+ }
181
+ // Pattern 6: Fallback for generic frame lines
182
+ frames.push({
183
+ method: trimmed.startsWith('at ') ? trimmed.slice(3) : trimmed,
184
+ file: 'runtime',
185
+ lineNumber: 0,
186
+ column: 0,
187
+ raw: trimmed,
188
+ isAppCode: false,
189
+ });
190
+ }
191
+ return frames;
192
+ };
193
+ // ─── DIAGNOSTICS & SYSTEM METRICS ─────────────────────────────────────────────
194
+ const getMemoryDiagnostics = () => {
195
+ try {
196
+ const performance = globalThis.performance;
197
+ if (performance && performance.memory) {
198
+ return {
199
+ usedJSHeapSize: Math.round(performance.memory.usedJSHeapSize / (1024 * 1024)),
200
+ totalJSHeapSize: Math.round(performance.memory.totalJSHeapSize / (1024 * 1024)),
201
+ };
202
+ }
203
+ }
204
+ catch { }
205
+ return undefined;
206
+ };
207
+ const getScreenDimensions = () => {
208
+ try {
209
+ const window = Dimensions.get('window');
210
+ return `${Math.round(window.width)}x${Math.round(window.height)} (${window.scale}x)`;
211
+ }
212
+ catch {
213
+ return t('crash.unknown');
214
+ }
215
+ };
216
+ /**
217
+ * Computes a unique fingerprint signature to group recurring crash occurrences.
218
+ */
219
+ export const computeCrashFingerprint = (crash) => {
220
+ const type = crash.type || 'js';
221
+ const name = crash.name || 'Error';
222
+ const topFrame = crash.parsedStack && crash.parsedStack.length > 0
223
+ ? crash.parsedStack[0]
224
+ : null;
225
+ const location = topFrame
226
+ ? `${topFrame.file}:${topFrame.lineNumber}`
227
+ : 'unknown';
228
+ return `${type}_${name}_${location}`;
229
+ };
230
+ // ─── CRASH RECORD MANAGEMENT & INTERCEPTION ───────────────────────────────────
231
+ export const getCrashRecords = () => {
232
+ return [...crashRecordsStore];
233
+ };
234
+ export const clearCrashRecords = () => {
235
+ crashRecordsStore = [];
236
+ emitCrashEvent({
237
+ error: null,
238
+ message: '__CLEARED__',
239
+ timestamp: Date.now(),
240
+ });
241
+ };
242
+ export const subscribeCrashEvents = (listener) => {
243
+ crashListeners.push(listener);
244
+ return () => {
245
+ crashListeners = crashListeners.filter(l => l !== listener);
246
+ };
247
+ };
248
+ export const emitCrashEvent = (payload) => {
249
+ if (payload.message !== '__CLEARED__') {
250
+ const now = Date.now();
251
+ // Throttle duplicate rapid crash emissions within 250ms
252
+ if (now - lastHandledErrorTimestamp < 250) {
253
+ return;
254
+ }
255
+ lastHandledErrorTimestamp = now;
256
+ }
257
+ crashListeners.forEach(listener => {
258
+ try {
259
+ listener(payload);
260
+ }
261
+ catch { }
262
+ });
263
+ };
264
+ /**
265
+ * Core exception interceptor. Ingests all forms of errors, parses call stacks,
266
+ * collects device telemetry, records breadcrumbs, and updates the store.
267
+ */
268
+ export const handleInterceptedCrash = (errorOrTitle, rawStack, isFatal = false, customType, componentStack) => {
269
+ try {
270
+ const errorObj = errorOrTitle instanceof Error
271
+ ? errorOrTitle
272
+ : typeof errorOrTitle === 'object' && errorOrTitle !== null
273
+ ? errorOrTitle
274
+ : new Error(String(errorOrTitle || t('crash.runtimeException')));
275
+ const rawMsg = errorObj.message ||
276
+ (typeof errorOrTitle === 'string' ? errorOrTitle : t('crash.runtimeException'));
277
+ let stackString = '';
278
+ if (typeof rawStack === 'string') {
279
+ stackString = rawStack;
280
+ }
281
+ else if (Array.isArray(rawStack)) {
282
+ stackString = rawStack
283
+ .map(f => `at ${f.methodName || '<anonymous>'} (${f.file || 'unknown'}:${f.lineNumber || 0}:${f.column || 0})`)
284
+ .join('\n');
285
+ }
286
+ else {
287
+ stackString = errorObj.stack || new Error().stack || '';
288
+ }
289
+ // Determine Crash Type
290
+ let inferredType = customType || CrashType.Js;
291
+ const lowMsg = rawMsg.toLowerCase();
292
+ if (!customType) {
293
+ if (lowMsg.includes('native') ||
294
+ lowMsg.includes('sigsegv') ||
295
+ lowMsg.includes('nsrange') ||
296
+ lowMsg.includes('nullpointerexception') ||
297
+ lowMsg.includes('fatal signal')) {
298
+ inferredType = CrashType.Native;
299
+ }
300
+ else if (lowMsg.includes('promise') ||
301
+ lowMsg.includes('unhandled rejection') ||
302
+ lowMsg.includes('unhandledrejection')) {
303
+ inferredType = CrashType.Promise;
304
+ }
305
+ else if (lowMsg.includes('render') ||
306
+ lowMsg.includes('errorboundary') ||
307
+ componentStack) {
308
+ inferredType = CrashType.Render;
309
+ }
310
+ else {
311
+ inferredType = CrashType.Js;
312
+ }
313
+ }
314
+ const now = new Date();
315
+ const dateStr = now.toLocaleDateString();
316
+ const timeStr = now.toLocaleTimeString();
317
+ const parsedStack = parseCrashStackTrace(stackString);
318
+ const log = addLogFromCrash(errorObj, `[${isFatal ? t('crash.logFatalCrash') : t('crash.logUnhandledError')}] ${rawMsg}`, stackString, isFatal);
319
+ const isHermes = typeof globalThis.HermesInternal !== 'undefined';
320
+ const isFabric = typeof globalThis.nativeFabricUIManager !== 'undefined';
321
+ const crashRecord = {
322
+ id: `crash_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
323
+ error: errorObj,
324
+ isFatal,
325
+ type: inferredType,
326
+ message: rawMsg,
327
+ name: errorObj.name || (isFatal ? t('crash.errorNameFatal') : t('crash.errorNameUnhandled')),
328
+ stack: stackString,
329
+ parsedStack,
330
+ componentStack: componentStack || undefined,
331
+ timestamp: Date.now(),
332
+ dateStr,
333
+ timeStr,
334
+ deviceInfo: {
335
+ platform: Platform.OS,
336
+ osVersion: String(Platform.Version),
337
+ rnVersion: Platform.constants?.reactNativeVersion
338
+ ? `${Platform.constants.reactNativeVersion.major}.${Platform.constants.reactNativeVersion.minor}.${Platform.constants.reactNativeVersion.patch}`
339
+ : t('crash.unknown'),
340
+ isHermes,
341
+ isFabric,
342
+ appState: AppState.currentState || 'active',
343
+ },
344
+ memoryInfo: getMemoryDiagnostics(),
345
+ breadcrumbs: [...breadcrumbsStore],
346
+ logId: log?.id,
347
+ };
348
+ crashRecordsStore.unshift(crashRecord);
349
+ if (crashRecordsStore.length > maxStoredCrashes) {
350
+ crashRecordsStore = crashRecordsStore.slice(0, maxStoredCrashes);
351
+ }
352
+ emitCrashEvent({
353
+ error: errorObj,
354
+ isFatal,
355
+ message: `[${isFatal ? t('crash.logFatalCrash') : t('crash.logUnhandledError')}] ${rawMsg}`,
356
+ stack: stackString,
357
+ timestamp: Date.now(),
358
+ logId: log?.id,
359
+ crashRecord,
360
+ });
361
+ return crashRecord;
362
+ }
363
+ catch (err) {
364
+ const fallbackRecord = {
365
+ id: `crash_${Date.now()}`,
366
+ isFatal: true,
367
+ type: CrashType.Js,
368
+ message: String(errorOrTitle || t('crash.unknownException')),
369
+ timestamp: Date.now(),
370
+ dateStr: new Date().toLocaleDateString(),
371
+ timeStr: new Date().toLocaleTimeString(),
372
+ };
373
+ return fallbackRecord;
374
+ }
375
+ };
376
+ /**
377
+ * Manually report a caught error with custom metadata.
378
+ */
379
+ export const recordCustomCrash = (error, options) => {
380
+ return handleInterceptedCrash(error, typeof error === 'object' ? error.stack : undefined, options?.isFatal ?? false, options?.type || CrashType.Custom, options?.componentStack);
381
+ };
382
+ /**
383
+ * Simulates a crash for developer testing without crashing the host app.
384
+ * Uses real runtime Error objects — no mocked file paths or fake stacks.
385
+ */
386
+ export const simulateTestCrash = (type = CrashType.Js, customMessage, customStack) => {
387
+ const err = new Error(customMessage || _defaultSimMessage(type));
388
+ return handleInterceptedCrash(type === CrashType.Native ? err.message : err, customStack || err.stack, type === CrashType.Native, type, type === CrashType.Render ? _buildComponentStack() : undefined);
389
+ };
390
+ /** Generates a generic error message per type — no fake references. */
391
+ const _defaultSimMessage = (type) => {
392
+ switch (type) {
393
+ case 'native':
394
+ return t('crash.simNativeMessage');
395
+ case 'promise':
396
+ return t('crash.simPromiseMessage');
397
+ case 'render':
398
+ return t('crash.simRenderMessage');
399
+ default:
400
+ return t('crash.simJsMessage');
401
+ }
402
+ };
403
+ /** Builds a component stack from the real React tree if possible. */
404
+ const _buildComponentStack = () => {
405
+ try {
406
+ const err = new Error();
407
+ if (err.stack) {
408
+ // Return the live stack as a pseudo component-stack
409
+ return err.stack
410
+ .split('\n')
411
+ .slice(1, 6)
412
+ .map(line => ` ${line.trim()}`)
413
+ .join('\n');
414
+ }
415
+ }
416
+ catch { }
417
+ return undefined;
418
+ };
419
+ // ─── EXPORT & REPORTING UTILITIES ─────────────────────────────────────────────
420
+ /**
421
+ * Formats a crash into Text, Markdown, or JSON.
422
+ */
423
+ export const exportCrashReport = (crash, format = 'text') => {
424
+ if (format === 'json') {
425
+ return JSON.stringify(crash, null, 2);
426
+ }
427
+ const uptimeSec = Math.round((crash.timestamp - appStartTime) / 1000);
428
+ if (format === 'markdown') {
429
+ const lines = [];
430
+ lines.push(`## ${t('crash.mdReportTitle', { name: crash.name || 'Error' })}`);
431
+ lines.push(`> **${crash.message}**`);
432
+ lines.push('');
433
+ lines.push(`- **${t('crash.reportType')}** \`${crash.type.toUpperCase()}\``);
434
+ lines.push(`- **${t('crash.reportFatal')}** \`${crash.isFatal
435
+ ? t('crash.mdSeverityFatal')
436
+ : t('crash.mdSeverityHandled')}\``);
437
+ lines.push(`- **${t('crash.reportTimestamp')}** ${crash.dateStr} ${crash.timeStr}`);
438
+ lines.push(`- **${t('crash.reportUptime')}** ${uptimeSec}s`);
439
+ lines.push(`- **${t('crash.reportPlatform')}** ${crash.deviceInfo?.platform?.toUpperCase()} (v${crash.deviceInfo?.osVersion})`);
440
+ lines.push(`- **${t('crash.reportReactNative')}** v${crash.deviceInfo?.rnVersion || 'N/A'}`);
441
+ lines.push(`- **${t('crash.jsEngine')}** ${crash.deviceInfo?.isHermes ? t('crash.hermesEngine') : t('crash.jsc')}`);
442
+ lines.push(`- **${t('crash.reportArchitecture')}** ${crash.deviceInfo?.isFabric
443
+ ? t('crash.reportFabricNew')
444
+ : t('crash.reportPaperLegacy')}`);
445
+ if (crash.memoryInfo) {
446
+ lines.push(`- **${t('crash.reportJsMemory')}** ${crash.memoryInfo.usedJSHeapSize} MB / ${crash.memoryInfo.totalJSHeapSize} MB`);
447
+ }
448
+ lines.push('');
449
+ lines.push(`### ${t('crash.mdStackTrace')}`);
450
+ lines.push('```');
451
+ lines.push(crash.stack || t('crash.reportNoStackTrace'));
452
+ lines.push('```');
453
+ if (crash.componentStack) {
454
+ lines.push('');
455
+ lines.push(`### ${t('crash.mdComponentHierarchy')}`);
456
+ lines.push('```');
457
+ lines.push(crash.componentStack);
458
+ lines.push('```');
459
+ }
460
+ if (crash.breadcrumbs && crash.breadcrumbs.length > 0) {
461
+ lines.push('');
462
+ lines.push(`### ${t('crash.mdRecentBreadcrumbs')}`);
463
+ crash.breadcrumbs.forEach(b => {
464
+ const time = new Date(b.timestamp).toLocaleTimeString();
465
+ lines.push(`- \`[${time}]\` **[${b.type.toUpperCase()}]** ${b.message}`);
466
+ });
467
+ }
468
+ return lines.join('\n');
469
+ }
470
+ // Standard Text format
471
+ const lines = [];
472
+ lines.push('====================================================');
473
+ lines.push(` ${t('crash.reportTitle')} `);
474
+ lines.push('====================================================');
475
+ lines.push(`${t('crash.reportErrorName')} ${crash.name || 'Error'}`);
476
+ lines.push(`${t('crash.reportMessage')} ${crash.message}`);
477
+ lines.push(`${t('crash.reportType')} ${crash.type.toUpperCase()}`);
478
+ lines.push(`${t('crash.reportFatal')} ${crash.isFatal ? t('crash.reportFatalYes') : t('crash.reportFatalNo')}`);
479
+ lines.push(`${t('crash.reportTimestamp')} ${crash.dateStr} ${crash.timeStr} (${crash.timestamp})`);
480
+ lines.push(`${t('crash.reportUptime')} ${t('crash.reportUptimeValue', {
481
+ seconds: uptimeSec,
482
+ })}`);
483
+ lines.push(`${t('crash.reportPlatform')} ${crash.deviceInfo?.platform?.toUpperCase()} (v${crash.deviceInfo?.osVersion})`);
484
+ lines.push(`${t('crash.reportReactNative')} ${crash.deviceInfo?.rnVersion || 'N/A'}`);
485
+ lines.push(`${t('crash.reportHermes')} ${crash.deviceInfo?.isHermes
486
+ ? t('crash.reportEnabled')
487
+ : t('crash.reportDisabled')}`);
488
+ lines.push(`${t('crash.reportArchitecture')} ${crash.deviceInfo?.isFabric
489
+ ? t('crash.reportFabricNew')
490
+ : t('crash.reportPaperLegacy')}`);
491
+ lines.push(`${t('crash.reportScreenSize')} ${getScreenDimensions()}`);
492
+ lines.push(`${t('crash.reportAppState')} ${crash.deviceInfo?.appState || 'active'}`);
493
+ if (crash.memoryInfo) {
494
+ lines.push(`${t('crash.reportJsMemory')} ${crash.memoryInfo.usedJSHeapSize} MB / ${crash.memoryInfo.totalJSHeapSize} MB`);
495
+ }
496
+ lines.push('----------------------------------------------------');
497
+ lines.push(`${t('crash.reportStackTrace')}`);
498
+ lines.push(crash.stack || t('crash.reportNoStackTrace'));
499
+ if (crash.componentStack) {
500
+ lines.push('----------------------------------------------------');
501
+ lines.push(`${t('crash.reportComponentHierarchy')}`);
502
+ lines.push(crash.componentStack);
503
+ }
504
+ if (crash.breadcrumbs && crash.breadcrumbs.length > 0) {
505
+ lines.push('----------------------------------------------------');
506
+ lines.push(`${t('crash.reportRecentBreadcrumbs')}`);
507
+ crash.breadcrumbs.forEach(b => {
508
+ const time = new Date(b.timestamp).toLocaleTimeString();
509
+ lines.push(` [${time}] [${b.type.toUpperCase()}] ${b.message}`);
510
+ });
511
+ }
512
+ lines.push('====================================================');
513
+ return lines.join('\n');
514
+ };
515
+ // ─── GLOBAL HANDLER INITIALIZATION ────────────────────────────────────────────
516
+ /**
517
+ * Natively intercepts and prevents crashes, native RedBox dialogues,
518
+ * unhandled JS errors, and unhandled promise rejections.
519
+ */
520
+ export const setupGlobalCrashHandler = () => {
521
+ if (isCrashHandlerInitialized)
522
+ return;
523
+ isCrashHandlerInitialized = true;
524
+ // 1. Completely silence React Native LogBox & RedBox overlays
525
+ try {
526
+ if (LogBox && typeof LogBox.ignoreAllLogs === 'function') {
527
+ LogBox.ignoreAllLogs(true);
528
+ }
529
+ }
530
+ catch { }
531
+ try {
532
+ // @ts-ignore
533
+ const LogBoxData = require('react-native/Libraries/LogBox/Data/LogBoxData');
534
+ if (LogBoxData && typeof LogBoxData.setDisabled === 'function') {
535
+ LogBoxData.setDisabled(true);
536
+ }
537
+ }
538
+ catch { }
539
+ // 2. Intercept Native Module: NetworkInspectorModule (Android & iOS)
540
+ try {
541
+ const networkInspectorModule = NativeModules?.NetworkInspectorModule;
542
+ if (networkInspectorModule) {
543
+ if (typeof networkInspectorModule.enableNativeCrashProtection === 'function') {
544
+ networkInspectorModule.enableNativeCrashProtection();
545
+ }
546
+ const nativeEmitter = new NativeEventEmitter(networkInspectorModule);
547
+ nativeEmitter.addListener('onNativeCrash', (event) => {
548
+ const platform = event?.platform || Platform.OS;
549
+ const msg = `[${platform.toUpperCase()} ${t('crash.nativeCrashTitle')}] ${event?.message || t('crash.nativeUncaughtException')}`;
550
+ const stack = event?.stack || '';
551
+ handleInterceptedCrash(msg, stack, true, 'native');
552
+ });
553
+ }
554
+ }
555
+ catch { }
556
+ // 3. Intercept Native ExceptionsManager & RedBox modules (Bridge & TurboModules)
557
+ try {
558
+ const tmReg = TurboModuleRegistry || globalThis.__turboModuleProxy;
559
+ const nativeExceptionsManager = NativeModules?.ExceptionsManager ||
560
+ (tmReg?.get ? tmReg.get('ExceptionsManager') : null);
561
+ if (nativeExceptionsManager) {
562
+ nativeExceptionsManager.reportFatalException = (title, stack, exceptionId) => {
563
+ handleInterceptedCrash(title, stack, true, 'native');
564
+ try {
565
+ if (typeof nativeExceptionsManager.dismissRedbox === 'function') {
566
+ nativeExceptionsManager.dismissRedbox();
567
+ }
568
+ }
569
+ catch { }
570
+ };
571
+ nativeExceptionsManager.reportSoftException = (title, stack, exceptionId) => {
572
+ handleInterceptedCrash(title, stack, false, 'js');
573
+ try {
574
+ if (typeof nativeExceptionsManager.dismissRedbox === 'function') {
575
+ nativeExceptionsManager.dismissRedbox();
576
+ }
577
+ }
578
+ catch { }
579
+ };
580
+ nativeExceptionsManager.reportException = (data) => {
581
+ const msg = data?.message || data?.title || t('crash.nativeException');
582
+ const stack = data?.stack || data?.rawStack;
583
+ handleInterceptedCrash(msg, stack, data?.isFatal ?? true, 'js');
584
+ try {
585
+ if (typeof nativeExceptionsManager.dismissRedbox === 'function') {
586
+ nativeExceptionsManager.dismissRedbox();
587
+ }
588
+ }
589
+ catch { }
590
+ };
591
+ nativeExceptionsManager.updateExceptionMessage = () => { };
592
+ try {
593
+ if (typeof nativeExceptionsManager.dismissRedbox === 'function') {
594
+ nativeExceptionsManager.dismissRedbox();
595
+ }
596
+ }
597
+ catch { }
598
+ }
599
+ const nativeRedBox = NativeModules?.RedBox ||
600
+ NativeModules?.RCTRedBox ||
601
+ (tmReg?.get ? tmReg.get('RedBox') || tmReg.get('RCTRedBox') : null);
602
+ if (nativeRedBox) {
603
+ if (typeof nativeRedBox.showErrorMessage === 'function') {
604
+ nativeRedBox.showErrorMessage = () => { };
605
+ }
606
+ if (typeof nativeRedBox.showUserError === 'function') {
607
+ nativeRedBox.showUserError = () => { };
608
+ }
609
+ if (typeof nativeRedBox.dismiss === 'function') {
610
+ nativeRedBox.dismiss();
611
+ }
612
+ }
613
+ }
614
+ catch { }
615
+ // 4. Hook React Native ErrorUtils
616
+ try {
617
+ const globalObj = globalThis;
618
+ const errorUtils = globalObj.ErrorUtils;
619
+ if (errorUtils) {
620
+ if (typeof errorUtils.setGlobalHandler === 'function') {
621
+ errorUtils.setGlobalHandler((error, isFatal) => {
622
+ handleInterceptedCrash(error, error?.stack, isFatal ?? false, 'js');
623
+ // Suppress calling default crashing handler to prevent termination & RedBox
624
+ });
625
+ }
626
+ if (typeof errorUtils.reportFatalError === 'function') {
627
+ errorUtils.reportFatalError = (error) => {
628
+ handleInterceptedCrash(error, error?.stack, true, 'js');
629
+ };
630
+ }
631
+ if (typeof errorUtils.reportError === 'function') {
632
+ errorUtils.reportError = (error) => {
633
+ handleInterceptedCrash(error, error?.stack, false, 'js');
634
+ };
635
+ }
636
+ }
637
+ }
638
+ catch { }
639
+ // 5. Hook global Promise unhandled rejections
640
+ try {
641
+ const globalObj = globalThis;
642
+ const handleUnhandledPromise = (eventOrError) => {
643
+ try {
644
+ const reason = eventOrError?.reason || eventOrError?.detail?.reason || eventOrError;
645
+ const message = reason?.message || String(reason || t('crash.unhandledPromiseRejection'));
646
+ const stack = reason?.stack || (eventOrError?.stack ?? new Error().stack);
647
+ handleInterceptedCrash(reason, stack, false, 'promise');
648
+ }
649
+ catch { }
650
+ };
651
+ if (typeof globalObj.addEventListener === 'function') {
652
+ globalObj.addEventListener('unhandledrejection', handleUnhandledPromise);
653
+ }
654
+ if (typeof globalObj.onunhandledrejection !== 'undefined') {
655
+ globalObj.onunhandledrejection = handleUnhandledPromise;
656
+ }
657
+ }
658
+ catch { }
659
+ };