react-native-inapp-inspector 2.2.2 → 2.2.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.
Files changed (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. package/src/types/interfaces.ts +515 -0
package/src/index.tsx ADDED
@@ -0,0 +1,2336 @@
1
+ import React, {useEffect, useMemo, useRef, useState, useCallback} from 'react';
2
+ import {
3
+ Alert,
4
+ Animated,
5
+ PanResponder,
6
+ Platform,
7
+ UIManager,
8
+ LogBox,
9
+ InteractionManager,
10
+ } from 'react-native';
11
+ import {NavigationContext} from '@react-navigation/native';
12
+
13
+ // i18n
14
+ import {I18nextProvider, i18n} from './i18n';
15
+
16
+ // Components
17
+ import ErrorBoundary from './components/ErrorBoundary';
18
+ import MainScreen from './components/Inspector/MainScreen';
19
+ import {
20
+ InspectorContext,
21
+ animateNextLayout,
22
+ InspectorContextValue,
23
+ } from './components/Inspector/InspectorContext';
24
+
25
+ // Helpers
26
+ import {
27
+ formatDisplayUrl,
28
+ getNavigationInfo,
29
+ deduplicateLogs,
30
+ getDomainColor,
31
+ getEventCategory,
32
+ matchNetworkLogQuery,
33
+ sendSessionTelemetryPing,
34
+ trackTelemetryEvent,
35
+ trackInspectorOpen,
36
+ trackTabSwitch,
37
+ trackLogExport,
38
+ GA4_MEASUREMENT_ID,
39
+ GA4_API_SECRET,
40
+ setupMemoryWarningHandler,
41
+ pruneAllLogs,
42
+ subscribeMemoryWarning,
43
+ } from './helpers';
44
+ // #5 — settings persistence
45
+ import {
46
+ loadSettings,
47
+ saveSettings,
48
+ setCustomStorage,
49
+ clearPersistedSettings,
50
+ calculateRamBasedLimits,
51
+ } from './helpers/settingsStore';
52
+ import {
53
+ getNativeSystemMetrics,
54
+ pushNativeLogRecord,
55
+ fetchNativeCachedPage,
56
+ } from './native/NativeInspector';
57
+
58
+ // Network
59
+ import {
60
+ setupNetworkLogger,
61
+ clearNetworkLogs,
62
+ subscribeNetworkLogs,
63
+ setNetworkModuleEnabled,
64
+ setMaxNetworkLogsLimit,
65
+ } from './customHooks/networkLogger';
66
+
67
+ // Console
68
+ import {
69
+ setupConsoleLogger,
70
+ clearConsoleLogs,
71
+ subscribeConsoleLogs,
72
+ getConsoleLogs,
73
+ setConsoleModuleEnabled,
74
+ setMaxConsoleLogsLimit,
75
+ } from './customHooks/consoleLogger';
76
+ import {IGNORED_LOG_PREFIXES} from './customHooks/logFilters';
77
+
78
+ // Crash Protection
79
+ import {
80
+ setupGlobalCrashHandler,
81
+ subscribeCrashEvents,
82
+ emitCrashEvent,
83
+ getCrashRecords,
84
+ clearCrashRecords,
85
+ simulateTestCrash,
86
+ exportCrashReport,
87
+ parseCrashStackTrace,
88
+ setMaxCrashLogsLimit,
89
+ setCrashModuleEnabled,
90
+ } from './customHooks/crashHandler';
91
+
92
+ import {
93
+ subscribeAnalyticsEvents,
94
+ clearAnalyticsEvents,
95
+ autoSetupAnalyticsLogger,
96
+ isAnalyticsConnected,
97
+ setAnalyticsModuleEnabled,
98
+ setMaxAnalyticsLogsLimit,
99
+ } from './customHooks/analyticsLogger';
100
+
101
+ import {
102
+ getReduxState,
103
+ subscribeReduxState,
104
+ setReduxAutoRefresh,
105
+ getLastActionForReducer,
106
+ clearActionHistory,
107
+ isReduxConnected,
108
+ setReduxModuleEnabled,
109
+ } from './customHooks/reduxLogger';
110
+
111
+ import {setPerformanceModuleEnabled} from './customHooks/performanceTracker';
112
+ import {setBundleModuleEnabled} from './customHooks/bundleAnalyzer';
113
+ import {
114
+ showNativeFloatingButton,
115
+ hideNativeFloatingButton,
116
+ setNativeFloatingButtonBadge,
117
+ subscribeNativeFloatingButtonPress,
118
+ subscribeNativeDeviceShake,
119
+ startNativeFpsMonitoring,
120
+ stopNativeFpsMonitoring,
121
+ getNativeFpsMetrics,
122
+ getNativeStorageItem,
123
+ setNativeStorageItem,
124
+ isNativeModuleAvailable,
125
+ } from './native/NativeInspector';
126
+
127
+ // Constants
128
+ import {
129
+ StatusFilter,
130
+ NetworkLog,
131
+ RouteInfo,
132
+ LocalFilter,
133
+ SortOrder,
134
+ GroupedListItem,
135
+ ActiveTab,
136
+ ConsoleLog,
137
+ Method,
138
+ AnalyticsEvent,
139
+ AnalyticsFilters,
140
+ NetworkInspectorProps,
141
+ CrashRecord,
142
+ ParsedStackFrame,
143
+ CrashBreadcrumb,
144
+ SearchScope,
145
+ } from './types';
146
+ import {LIB_VERSION} from './constants';
147
+
148
+ // Stylesheet
149
+ import {toggleGlobalTheme} from './styles';
150
+
151
+ const NetworkInspector = ({
152
+ enabled = true,
153
+ isEnabled = true,
154
+ storage,
155
+ navigationRef,
156
+ appIcon,
157
+ environment,
158
+ initialVisible,
159
+ visible: controlledVisible,
160
+ }: NetworkInspectorProps): React.JSX.Element => {
161
+ // Set custom storage synchronously during render phase
162
+ setCustomStorage(storage || null);
163
+
164
+ const [isDark, setIsDark] = useState(false);
165
+ const [reduxState, setReduxState] = useState<any>(null);
166
+ // Per-reducer last action is kept in component state so the
167
+ // Redux tab re-renders live on every dispatch, independent of the state tree ref.
168
+ const [reduxLastActionMap, setReduxLastActionMap] = useState<
169
+ Record<string, any>
170
+ >({});
171
+ // Inspector panel height as a percentage of the screen (configurable in Settings).
172
+ const [modalHeightPercent, setModalHeightPercent] = useState<number>(90);
173
+ const [modalAnimationType, setModalAnimationType] = useState<'slide' | 'fade' | 'none'>('slide');
174
+
175
+ const [logs, setLogs] = useState<NetworkLog[]>([]);
176
+ const [visible, setVisible] = useState<boolean>(
177
+ initialVisible ?? controlledVisible ?? false,
178
+ );
179
+
180
+ useEffect(() => {
181
+ if (controlledVisible !== undefined) {
182
+ setVisible(controlledVisible);
183
+ }
184
+ }, [controlledVisible]);
185
+
186
+ const [isReady, setIsReady] = useState(false);
187
+ const [selected, setSelected] = useState<NetworkLog | null>(null);
188
+ const [selectedLogs, setSelectedLogs] = useState<Set<number>>(new Set());
189
+ const [search, setSearch] = useState('');
190
+ const [searchScope, setSearchScope] = useState<SearchScope>('all');
191
+ const [isRegexSearch, setIsRegexSearch] = useState<boolean>(false);
192
+ const [isCaseSensitive, setIsCaseSensitive] = useState<boolean>(false);
193
+ const [quickFilter, setQuickFilter] = useState<string>('all');
194
+ const [detailSearch, setDetailSearch] = useState('');
195
+ const [reduxSearch, setReduxSearch] = useState('');
196
+ const [selectedReduxSlice, setSelectedReduxSlice] = useState<string | null>(null);
197
+ const [selectedReduxAction, setSelectedReduxAction] = useState<any | null>(null);
198
+ const [reduxActiveSubTab, setReduxActiveSubTab] = useState<'state' | 'timeline'>('state');
199
+
200
+ const [apiDetailActiveTab, setApiDetailActiveTab] = useState<
201
+ 'metadata' | 'headers' | 'request' | 'response'
202
+ >('response');
203
+
204
+ useEffect(() => {
205
+ LogBox.ignoreAllLogs(enabled);
206
+ return () => {
207
+ LogBox.ignoreAllLogs(false);
208
+ };
209
+ }, [enabled]);
210
+
211
+ useEffect(() => {
212
+ if (selected) {
213
+ setApiDetailActiveTab('response');
214
+ setDetailSearch('');
215
+ }
216
+ }, [selected]);
217
+ const [statusFilters, setStatusFilters] = useState<Set<StatusFilter>>(
218
+ new Set(),
219
+ );
220
+ const [methodFilters, setMethodFilters] = useState<Set<Method>>(new Set());
221
+ const [sectionFilters, setSectionFilters] = useState<
222
+ Record<string, Set<LocalFilter>>
223
+ >({});
224
+ const [collapsedSections, setCollapsedSections] = useState<Set<string>>(
225
+ new Set(),
226
+ );
227
+ const [showHeaderInfo, setShowHeaderInfo] = useState(true);
228
+ const [sortOrder, setSortOrder] = useState<SortOrder>('newest');
229
+ // #7 — sort order for the Logs (console) tab
230
+ const [logSortOrder, setLogSortOrder] = useState<SortOrder>('newest');
231
+ const [reqExpanded, setReqExpanded] = useState<boolean | undefined>(true);
232
+ const [resExpanded, setResExpanded] = useState<boolean | undefined>(true);
233
+ const [showReqDiff, setShowReqDiff] = useState<boolean>(false);
234
+ const [showResDiff, setShowResDiff] = useState<boolean>(false);
235
+
236
+ // ─── Analytics state ───────────────────────────────────────────────────────
237
+ const [activeTab, setActiveTab] = useState<ActiveTab>('apis');
238
+ const [analyticsEvents, setAnalyticsEvents] = useState<AnalyticsEvent[]>([]);
239
+
240
+ // ─── Logs state ────────────────────────────────────────────────────────────
241
+ const [consoleLogs, setConsoleLogs] = useState<ConsoleLog[]>([]);
242
+ const [selectedLog, setSelectedLog] = useState<ConsoleLog | null>(null);
243
+ // ─── Pause states ─────────────────────────────────────────────────────────
244
+ const [isNetworkPaused, setIsNetworkPaused] = useState<boolean>(false);
245
+ const isNetworkPausedRef = useRef(isNetworkPaused);
246
+ isNetworkPausedRef.current = isNetworkPaused;
247
+ const latestNetworkLogsRef = useRef<NetworkLog[]>([]);
248
+
249
+ const [isConsolePaused, setIsConsolePaused] = useState<boolean>(false);
250
+ const isConsolePausedRef = useRef(isConsolePaused);
251
+ isConsolePausedRef.current = isConsolePaused;
252
+ const latestConsoleLogsRef = useRef<ConsoleLog[]>([]);
253
+
254
+ const [isAnalyticsPaused, setIsAnalyticsPaused] = useState<boolean>(false);
255
+ const isAnalyticsPausedRef = useRef(isAnalyticsPaused);
256
+ isAnalyticsPausedRef.current = isAnalyticsPaused;
257
+ const latestAnalyticsEventsRef = useRef<AnalyticsEvent[]>([]);
258
+
259
+ const [lastReadLogsCount, setLastReadLogsCount] = useState(0);
260
+ const [lastReadApisCount, setLastReadApisCount] = useState(0);
261
+ const [lastReadCrashesCount, setLastReadCrashesCount] = useState(0);
262
+
263
+ // ─── Crash state ───────────────────────────────────────────────────────────
264
+ const [crashRecords, setCrashRecords] = useState<CrashRecord[]>(() =>
265
+ getCrashRecords(),
266
+ );
267
+ const [selectedCrash, setSelectedCrash] = useState<CrashRecord | null>(null);
268
+ const [maxCrashLogs, setMaxCrashLogs] = useState<number>(50);
269
+
270
+ useEffect(() => {
271
+ setMaxCrashLogsLimit(maxCrashLogs);
272
+ }, [maxCrashLogs]);
273
+
274
+ useEffect(() => {
275
+ if (visible) {
276
+ if (activeTab === 'apis') {
277
+ setLastReadApisCount(logs.length);
278
+ }
279
+ if (activeTab === 'logs') {
280
+ setLastReadLogsCount(consoleLogs.length);
281
+ }
282
+ if (activeTab === 'crash') {
283
+ setLastReadCrashesCount(crashRecords.length);
284
+ }
285
+ }
286
+ }, [visible]);
287
+
288
+ useEffect(() => {
289
+ if (activeTab === 'apis') {
290
+ setLastReadApisCount(logs.length);
291
+ }
292
+ }, [activeTab, logs.length]);
293
+
294
+ useEffect(() => {
295
+ if (activeTab === 'logs') {
296
+ setLastReadLogsCount(consoleLogs.length);
297
+ }
298
+ }, [activeTab, consoleLogs.length]);
299
+
300
+ useEffect(() => {
301
+ if (activeTab === 'crash') {
302
+ setLastReadCrashesCount(crashRecords.length);
303
+ }
304
+ }, [activeTab, crashRecords.length]);
305
+
306
+ const [maxConsoleLogs, setMaxConsoleLogs] = useState<number>(100);
307
+ const [showConsoleLevels, setShowConsoleLevels] = useState<{
308
+ info: boolean;
309
+ warn: boolean;
310
+ error: boolean;
311
+ }>({
312
+ info: true,
313
+ warn: false,
314
+ error: true,
315
+ });
316
+ const visibleConsoleLogs = useMemo(() => {
317
+ const filtered = consoleLogs.filter(log => {
318
+ const type = log.type;
319
+ if (type === 'info' && !showConsoleLevels?.info) return false;
320
+ if (type === 'warn' && !showConsoleLevels?.warn) return false;
321
+ if (type === 'error' && !showConsoleLevels?.error) return false;
322
+
323
+ const message = log.message || '';
324
+ const allPrefixes = [
325
+ ...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.info) || []),
326
+ ...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.warn) || []),
327
+ ...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.error) || []),
328
+ ].filter(p => typeof p === 'string' && p.trim().length > 0);
329
+ const isIgnored = allPrefixes.some(
330
+ prefix =>
331
+ message
332
+ .toLowerCase()
333
+ .trim()
334
+ .startsWith(prefix.toLowerCase().trim()) ||
335
+ message.toLowerCase().trim().includes(prefix.toLowerCase().trim()),
336
+ );
337
+ return !isIgnored;
338
+ });
339
+ return filtered.slice(0, maxConsoleLogs);
340
+ }, [consoleLogs, showConsoleLevels, maxConsoleLogs]);
341
+ const [logSearch, setLogSearch] = useState('');
342
+ const [logFilters, setLogFilters] = useState<
343
+ Set<'all' | 'info' | 'warn' | 'error' | 'user-log' | 'analytics'>
344
+ >(new Set(['user-log']));
345
+
346
+ // ─── Settings state ──────────────────────────────────────────────────────────
347
+ const [settingsPage, setSettingsPage] = useState<
348
+ | 'main'
349
+ | 'apis'
350
+ | 'logs'
351
+ | 'analytics'
352
+ | 'redux'
353
+ | null
354
+ >(null);
355
+ const [settingsActiveSubTab, setSettingsActiveSubTab] = useState<'module' | 'ui'>('module');
356
+ const [tabVisibility, setTabVisibility] = useState<
357
+ Record<ActiveTab, boolean>
358
+ >({
359
+ apis: true,
360
+ logs: true,
361
+ analytics: false,
362
+ redux: false,
363
+ bundle: false,
364
+ performance: false,
365
+ crash: false,
366
+ });
367
+
368
+ const [maxNetworkLogs, setMaxNetworkLogs] = useState<number>(100);
369
+ const [maxAnalyticsEventsLimit, setMaxAnalyticsEventsLimit] = useState<number>(75);
370
+ const [isAutoRamLimitEnabled, setIsAutoRamLimitEnabled] = useState<boolean>(true);
371
+ const [deviceFreeRamMb, setDeviceFreeRamMb] = useState<number>(1800);
372
+
373
+ const [reduxAutoRefresh, setReduxAutoRefreshState] = useState<boolean>(true);
374
+ const [reduxExpandDepth, setReduxExpandDepth] = useState<number>(1);
375
+
376
+ // #6 — tab the inspector opens on. Shown with a DEFAULT badge in Settings.
377
+ const [defaultTab, setDefaultTab] = useState<ActiveTab>('apis');
378
+ const [showDuplicateLogs, setShowDuplicateLogs] = useState<boolean>(false);
379
+ const [showUpdateToast, setShowUpdateToast] = useState<boolean>(true);
380
+
381
+ // Synchronize runtime background listeners with active settings
382
+ useEffect(() => {
383
+ setNetworkModuleEnabled(!!tabVisibility.apis);
384
+ setConsoleModuleEnabled(!!tabVisibility.logs);
385
+ setAnalyticsModuleEnabled(!!tabVisibility.analytics);
386
+ setReduxModuleEnabled(!!tabVisibility.redux);
387
+ setPerformanceModuleEnabled(!!tabVisibility.performance);
388
+ setCrashModuleEnabled(!!tabVisibility.crash);
389
+ setBundleModuleEnabled(!!tabVisibility.bundle);
390
+ }, [tabVisibility]);
391
+
392
+ // Query native hardware RAM and auto-tune limits if auto-RAM is active
393
+ useEffect(() => {
394
+ getNativeSystemMetrics()
395
+ .then(metrics => {
396
+ if (metrics) {
397
+ const freeRam = metrics.totalPhysicalRamMb - metrics.residentRamMb;
398
+ const freeMb = Math.max(200, Math.round(freeRam > 0 ? freeRam : 1500));
399
+ setDeviceFreeRamMb(freeMb);
400
+ if (isAutoRamLimitEnabled) {
401
+ const profile = calculateRamBasedLimits(freeMb);
402
+ setMaxNetworkLogs(profile.maxNetworkLogs);
403
+ setMaxConsoleLogs(profile.maxConsoleLogs);
404
+ setMaxAnalyticsEventsLimit(profile.maxAnalyticsEvents);
405
+ setMaxCrashLogs(profile.maxCrashRecords);
406
+ }
407
+ }
408
+ })
409
+ .catch(() => {});
410
+ }, [isAutoRamLimitEnabled]);
411
+
412
+ const resetToDefaults = async () => {
413
+ await clearPersistedSettings();
414
+ setIsDark(false);
415
+ toggleGlobalTheme(false);
416
+ setModalHeightPercent(90);
417
+ setModalAnimationType('slide');
418
+ setTabVisibility({
419
+ apis: true,
420
+ logs: true,
421
+ analytics: false,
422
+ redux: false,
423
+ bundle: false,
424
+ performance: false,
425
+ crash: false,
426
+ });
427
+ setDefaultTab('apis');
428
+ setIsAutoRamLimitEnabled(true);
429
+ const profile = calculateRamBasedLimits(deviceFreeRamMb);
430
+ setMaxCrashLogs(profile.maxCrashRecords);
431
+ setMaxNetworkLogs(profile.maxNetworkLogs);
432
+ setMaxConsoleLogs(profile.maxConsoleLogs);
433
+ setMaxAnalyticsEventsLimit(profile.maxAnalyticsEvents);
434
+ setShowConsoleLevels({
435
+ info: true,
436
+ warn: true,
437
+ error: true,
438
+ });
439
+ setReduxAutoRefreshState(true);
440
+ setReduxExpandDepth(1);
441
+ setShowDuplicateLogs(false);
442
+ setShowUpdateToast(true);
443
+ Alert.alert('Settings Reset', 'All settings have been reset to default values.');
444
+ };
445
+
446
+ // #5 — hydrate persisted settings once, then auto-save on any change.
447
+ const settingsHydratedRef = useRef(false);
448
+ useEffect(() => {
449
+ let cancelled = false;
450
+ loadSettings().then(saved => {
451
+ if (cancelled) return;
452
+ if (saved.isDark != null) {
453
+ setIsDark(saved.isDark);
454
+ toggleGlobalTheme(saved.isDark);
455
+ }
456
+ if (saved.modalHeightPercent != null)
457
+ setModalHeightPercent(saved.modalHeightPercent);
458
+ if (saved.modalAnimationType)
459
+ setModalAnimationType(saved.modalAnimationType as 'slide' | 'fade' | 'none');
460
+ if (saved.tabVisibility)
461
+ setTabVisibility(prev => ({
462
+ ...prev,
463
+ ...(saved.tabVisibility as Record<ActiveTab, boolean>),
464
+ apis: true, // APIs is always required
465
+ }));
466
+ if (saved.defaultTab) setDefaultTab(saved.defaultTab as ActiveTab);
467
+ if (saved.isAutoRamLimitEnabled != null)
468
+ setIsAutoRamLimitEnabled(saved.isAutoRamLimitEnabled);
469
+ if (saved.maxNetworkLogs != null) setMaxNetworkLogs(saved.maxNetworkLogs);
470
+ if (saved.maxConsoleLogs != null) setMaxConsoleLogs(saved.maxConsoleLogs);
471
+ if (saved.maxAnalyticsEventsLimit != null)
472
+ setMaxAnalyticsEventsLimit(saved.maxAnalyticsEventsLimit);
473
+ if (saved.maxCrashLogs != null) setMaxCrashLogs(saved.maxCrashLogs);
474
+ if (saved.showConsoleLevels)
475
+ setShowConsoleLevels(saved.showConsoleLevels);
476
+ if (saved.reduxAutoRefresh != null)
477
+ setReduxAutoRefreshState(saved.reduxAutoRefresh);
478
+ if (saved.reduxExpandDepth != null)
479
+ setReduxExpandDepth(saved.reduxExpandDepth);
480
+ if (saved.showDuplicateLogs != null)
481
+ setShowDuplicateLogs(saved.showDuplicateLogs);
482
+ if (saved.showUpdateToast != null)
483
+ setShowUpdateToast(saved.showUpdateToast);
484
+ if (saved.defaultTab) {
485
+ const dt = saved.defaultTab as ActiveTab;
486
+ const vis = {
487
+ ...{
488
+ apis: true,
489
+ logs: true,
490
+ analytics: false,
491
+ redux: false,
492
+ bundle: false,
493
+ performance: false,
494
+ crash: false,
495
+ },
496
+ ...(saved.tabVisibility || {}),
497
+ apis: true,
498
+ } as Record<ActiveTab, boolean>;
499
+ setActiveTab(vis[dt] ? dt : 'apis');
500
+ }
501
+ settingsHydratedRef.current = true;
502
+ });
503
+ return () => {
504
+ cancelled = true;
505
+ };
506
+ }, []);
507
+
508
+ useEffect(() => {
509
+ if (!settingsHydratedRef.current) return;
510
+ saveSettings({
511
+ isDark,
512
+ modalHeightPercent,
513
+ modalAnimationType,
514
+ tabVisibility,
515
+ defaultTab,
516
+ maxNetworkLogs,
517
+ maxConsoleLogs,
518
+ maxAnalyticsEventsLimit,
519
+ maxCrashLogs,
520
+ isAutoRamLimitEnabled,
521
+ showConsoleLevels,
522
+ reduxAutoRefresh,
523
+ reduxExpandDepth,
524
+ showDuplicateLogs,
525
+ showUpdateToast,
526
+ });
527
+ }, [
528
+ isDark,
529
+ modalHeightPercent,
530
+ modalAnimationType,
531
+ tabVisibility,
532
+ defaultTab,
533
+ maxNetworkLogs,
534
+ maxConsoleLogs,
535
+ maxAnalyticsEventsLimit,
536
+ maxCrashLogs,
537
+ isAutoRamLimitEnabled,
538
+ showConsoleLevels,
539
+ reduxAutoRefresh,
540
+ reduxExpandDepth,
541
+ showDuplicateLogs,
542
+ ]);
543
+
544
+ // #1 — check NPM for a newer published version; surfaces an animated dot
545
+ // in the header next to the npm chip when an update is available.
546
+ const [latestNpmVersion, setLatestNpmVersion] = useState<string | null>(null);
547
+ const updateAvailable = useMemo(() => {
548
+ if (!latestNpmVersion) return false;
549
+ const parse = (v: string) =>
550
+ v
551
+ .replace(/^v/, '')
552
+ .split('.')
553
+ .map(n => parseInt(n, 10) || 0);
554
+ const cur = parse(LIB_VERSION);
555
+ const latest = parse(latestNpmVersion);
556
+ for (let i = 0; i < 3; i++) {
557
+ if ((latest[i] || 0) > (cur[i] || 0)) return true;
558
+ if ((latest[i] || 0) < (cur[i] || 0)) return false;
559
+ }
560
+ return false;
561
+ }, [latestNpmVersion]);
562
+
563
+ useEffect(() => {
564
+ let cancelled = false;
565
+ fetch('https://registry.npmjs.org/react-native-inapp-inspector/latest')
566
+ .then(res => (res.ok ? res.json() : null))
567
+ .then(data => {
568
+ if (!cancelled && data && typeof data.version === 'string') {
569
+ setLatestNpmVersion(data.version);
570
+ }
571
+ })
572
+ .catch(() => {
573
+ // Offline / blocked — silently skip the update check.
574
+ });
575
+ return () => {
576
+ cancelled = true;
577
+ };
578
+ }, []);
579
+
580
+ useEffect(() => {
581
+ setReduxAutoRefresh(reduxAutoRefresh);
582
+ }, [reduxAutoRefresh]);
583
+
584
+ // Auto-unselect Redux or Analytics tab if module is not connected / available
585
+ useEffect(() => {
586
+ if (activeTab === 'redux' && !isReduxConnected()) {
587
+ setActiveTab('apis');
588
+ }
589
+ if (activeTab === 'analytics' && !isAnalyticsConnected()) {
590
+ setActiveTab('apis');
591
+ }
592
+ }, [activeTab]);
593
+
594
+ const toggleTabVisibility = (key: ActiveTab) => {
595
+ if (key === 'apis') return;
596
+ if (key === 'redux' && !isReduxConnected()) return;
597
+ if (key === 'analytics' && !isAnalyticsConnected()) return;
598
+ setTabVisibility(prev => {
599
+ const nextVal = !prev[key];
600
+ const newVisibility = {...prev, [key]: nextVal};
601
+ if (!nextVal && activeTab === key) {
602
+ animateNextLayout();
603
+ setActiveTab('apis');
604
+ }
605
+ // #6 — a hidden module can't be the default landing tab.
606
+ if (!nextVal && defaultTab === key) {
607
+ setDefaultTab('apis');
608
+ }
609
+ return newVisibility;
610
+ });
611
+ };
612
+
613
+ const loadNativePage = useCallback(async (tabKey: ActiveTab, query: string = '') => {
614
+ if (tabKey === 'apis' || tabKey === 'logs' || tabKey === 'analytics' || tabKey === 'crash') {
615
+ const pageData = await fetchNativeCachedPage(tabKey, 0, 100, query);
616
+ if (pageData && pageData.items && pageData.items.length > 0) {
617
+ if (tabKey === 'apis') setLogs(pageData.items);
618
+ else if (tabKey === 'logs') setConsoleLogs(pageData.items);
619
+ else if (tabKey === 'analytics') setAnalyticsEvents(pageData.items);
620
+ else if (tabKey === 'crash') setCrashRecords(pageData.items);
621
+ }
622
+ }
623
+ }, []);
624
+
625
+ const switchActiveTab = useCallback((key: ActiveTab) => {
626
+ if (key === 'redux' && !isReduxConnected()) return;
627
+ if (key === 'analytics' && !isAnalyticsConnected()) return;
628
+
629
+ setSelected(null);
630
+ setSelectedEvent(null);
631
+ setSelectedLog(null);
632
+ setSelectedReduxSlice(null);
633
+ setSelectedReduxAction(null);
634
+ setSelectedCrash(null);
635
+
636
+ if (typeof React.startTransition === 'function') {
637
+ React.startTransition(() => {
638
+ setActiveTab(key);
639
+ loadNativePage(key);
640
+ });
641
+ } else {
642
+ setActiveTab(key);
643
+ loadNativePage(key);
644
+ }
645
+ }, [loadNativePage]);
646
+
647
+ const [selectedEvent, setSelectedEvent] = useState<AnalyticsEvent | null>(
648
+ null,
649
+ );
650
+ const [analyticsSearch, setAnalyticsSearch] = useState('');
651
+ const [analyticsFilters, setAnalyticsFilters] = useState<AnalyticsFilters>({
652
+ categories: new Set(['all']),
653
+ screens: new Set(),
654
+ sources: new Set(['all']),
655
+ userTypes: new Set(['all']),
656
+ timeWindow: 'all',
657
+ payloadComplexity: 'all',
658
+ hasRevenue: false,
659
+ hasItems: false,
660
+ hasUserProps: false,
661
+ hasParams: false,
662
+ onlyDuplicates: false,
663
+ onlyConversions: false,
664
+ sortBy: 'time_desc',
665
+ });
666
+
667
+ const isAnalyticsFilterApplied = useMemo(() => {
668
+ if (analyticsFilters.categories.size > 0 && !analyticsFilters.categories.has('all')) return true;
669
+ if (analyticsFilters.screens.size > 0) return true;
670
+ if (analyticsFilters.sources.size > 0 && !analyticsFilters.sources.has('all')) return true;
671
+ if (analyticsFilters.userTypes.size > 0 && !analyticsFilters.userTypes.has('all')) return true;
672
+ if (analyticsFilters.timeWindow !== 'all') return true;
673
+ if (analyticsFilters.payloadComplexity !== 'all') return true;
674
+ if (analyticsFilters.hasRevenue) return true;
675
+ if (analyticsFilters.hasItems) return true;
676
+ if (analyticsFilters.hasUserProps) return true;
677
+ if (analyticsFilters.hasParams) return true;
678
+ if (analyticsFilters.onlyDuplicates) return true;
679
+ if (analyticsFilters.onlyConversions) return true;
680
+ if (analyticsFilters.sortBy !== 'time_desc') return true;
681
+ return false;
682
+ }, [analyticsFilters]);
683
+
684
+ const resetAnalyticsFilters = useCallback(() => {
685
+ setAnalyticsFilters({
686
+ categories: new Set(['all']),
687
+ screens: new Set(),
688
+ sources: new Set(['all']),
689
+ userTypes: new Set(['all']),
690
+ timeWindow: 'all',
691
+ payloadComplexity: 'all',
692
+ hasRevenue: false,
693
+ hasItems: false,
694
+ hasUserProps: false,
695
+ hasParams: false,
696
+ onlyDuplicates: false,
697
+ onlyConversions: false,
698
+ sortBy: 'time_desc',
699
+ });
700
+ }, []);
701
+
702
+ const [isAnalyticsLayoutReady, setIsAnalyticsLayoutReady] = useState(false);
703
+ const [analyticsHeaderExpanded, setAnalyticsHeaderExpanded] = useState(false);
704
+
705
+ const [newEventIds, setNewEventIds] = useState<Set<number>>(new Set());
706
+ const prevEventIdsRef = useRef<Set<number>>(new Set());
707
+
708
+ const [navState, setNavState] = useState<any>(null);
709
+ const navigationContext = React.useContext(NavigationContext);
710
+ const hasNavigationContext = navigationContext !== undefined;
711
+
712
+ const currentRouteRef = useRef<RouteInfo>({
713
+ path: 'Navigators',
714
+ params: null,
715
+ });
716
+ useEffect(() => {
717
+ if (navState) {
718
+ currentRouteRef.current = getNavigationInfo(navState);
719
+ }
720
+ }, [navState]);
721
+
722
+ useEffect(() => {
723
+ if (!navigationRef) return;
724
+
725
+ const updateState = () => {
726
+ try {
727
+ if (typeof navigationRef.isReady === 'function' && navigationRef.isReady()) {
728
+ const state = navigationRef.getRootState();
729
+ if (state) {
730
+ setNavState(state);
731
+ }
732
+ }
733
+ } catch (err) {
734
+ // Safe check
735
+ }
736
+ };
737
+
738
+ // Initialize state
739
+ updateState();
740
+
741
+ // Listen to changes
742
+ const unsubscribe = typeof navigationRef.addListener === 'function'
743
+ ? navigationRef.addListener('state', () => {
744
+ updateState();
745
+ })
746
+ : null;
747
+
748
+ return () => {
749
+ if (unsubscribe) {
750
+ unsubscribe();
751
+ }
752
+ };
753
+ }, [navigationRef]);
754
+
755
+ const logRouteMapRef = useRef<Map<number, RouteInfo>>(new Map());
756
+ const prevLogIdsRef = useRef<Set<number>>(new Set());
757
+ const [newLogIds, setNewLogIds] = useState<Set<number>>(new Set());
758
+
759
+ const pulseAnim = useRef(new Animated.Value(1)).current;
760
+ const activePulseAnim = useRef(new Animated.Value(0.4)).current;
761
+ const unreadPulseAnim = useRef(new Animated.Value(1)).current;
762
+ // #4 — diagonal light streak sweeping across the floating launcher
763
+ const fabShineAnim = useRef(new Animated.Value(0)).current;
764
+ // #11 — header "clear all" icon spin/scale animation
765
+ const clearAnim = useRef(new Animated.Value(0)).current;
766
+
767
+ // #4 — draggable floating launcher (drag anywhere on screen)
768
+ const fabPan = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
769
+ const fabPanRef = useRef({x: 0, y: 0});
770
+ useEffect(() => {
771
+ const idX = fabPan.x.addListener(v => (fabPanRef.current.x = v.value));
772
+ const idY = fabPan.y.addListener(v => (fabPanRef.current.y = v.value));
773
+ return () => {
774
+ fabPan.x.removeListener(idX);
775
+ fabPan.y.removeListener(idY);
776
+ };
777
+ }, [fabPan]);
778
+ const fabDraggedRef = useRef(false);
779
+ const fabPanResponder = useRef(
780
+ PanResponder.create({
781
+ // Let taps fall through to the launcher; only hijack once the
782
+ // finger actually moves, so onPress still fires on a tap.
783
+ onStartShouldSetPanResponder: () => false,
784
+ onMoveShouldSetPanResponder: (_e, g) =>
785
+ Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
786
+ onPanResponderGrant: () => {
787
+ fabDraggedRef.current = true;
788
+ fabPan.setOffset({
789
+ x: fabPanRef.current.x,
790
+ y: fabPanRef.current.y,
791
+ });
792
+ fabPan.setValue({x: 0, y: 0});
793
+ },
794
+ onPanResponderMove: Animated.event([null, {dx: fabPan.x, dy: fabPan.y}], {
795
+ useNativeDriver: false,
796
+ }),
797
+ onPanResponderRelease: () => {
798
+ fabPan.flattenOffset();
799
+ // small delay so the trailing tap (if any) is ignored
800
+ setTimeout(() => {
801
+ fabDraggedRef.current = false;
802
+ }, 50);
803
+ },
804
+ onPanResponderTerminate: () => {
805
+ fabPan.flattenOffset();
806
+ fabDraggedRef.current = false;
807
+ },
808
+ }),
809
+ ).current;
810
+
811
+ // #2 — scroll-to-top button for the main APIs list, always visible at the
812
+ // bottom right of the list.
813
+
814
+ const runClearAllWithAnimation = useCallback(() => {
815
+ Animated.sequence([
816
+ Animated.timing(clearAnim, {
817
+ toValue: 1,
818
+ duration: 320,
819
+ useNativeDriver: true,
820
+ }),
821
+ Animated.timing(clearAnim, {
822
+ toValue: 0,
823
+ duration: 0,
824
+ useNativeDriver: true,
825
+ }),
826
+ ]).start();
827
+ handleClearAll();
828
+ }, [clearAnim]);
829
+
830
+ useEffect(() => {
831
+ if (Platform.OS === 'android') {
832
+ UIManager.setLayoutAnimationEnabledExperimental?.(true);
833
+ }
834
+ }, []);
835
+
836
+ useEffect(() => {
837
+ const loop = Animated.loop(
838
+ Animated.sequence([
839
+ Animated.timing(pulseAnim, {
840
+ toValue: 1.2,
841
+ duration: 800,
842
+ useNativeDriver: true,
843
+ }),
844
+ Animated.timing(pulseAnim, {
845
+ toValue: 1,
846
+ duration: 800,
847
+ useNativeDriver: true,
848
+ }),
849
+ ]),
850
+ );
851
+ loop.start();
852
+ return () => loop.stop();
853
+ }, [pulseAnim]);
854
+
855
+ // #4 — sweep the shine streak across the launcher, pause, repeat.
856
+ useEffect(() => {
857
+ const loop = Animated.loop(
858
+ Animated.sequence([
859
+ Animated.timing(fabShineAnim, {
860
+ toValue: 1,
861
+ duration: 1100,
862
+ useNativeDriver: true,
863
+ }),
864
+ Animated.delay(1600),
865
+ Animated.timing(fabShineAnim, {
866
+ toValue: 0,
867
+ duration: 0,
868
+ useNativeDriver: true,
869
+ }),
870
+ ]),
871
+ );
872
+ loop.start();
873
+ return () => loop.stop();
874
+ }, [fabShineAnim]);
875
+
876
+ useEffect(() => {
877
+ const loop = Animated.loop(
878
+ Animated.sequence([
879
+ Animated.timing(activePulseAnim, {
880
+ toValue: 1,
881
+ duration: 1200,
882
+ useNativeDriver: true,
883
+ }),
884
+ Animated.timing(activePulseAnim, {
885
+ toValue: 0.4,
886
+ duration: 1200,
887
+ useNativeDriver: true,
888
+ }),
889
+ ]),
890
+ );
891
+ loop.start();
892
+ return () => loop.stop();
893
+ }, [activePulseAnim]);
894
+
895
+ useEffect(() => {
896
+ const loop = Animated.loop(
897
+ Animated.sequence([
898
+ Animated.timing(unreadPulseAnim, {
899
+ toValue: 1.3,
900
+ duration: 800,
901
+ useNativeDriver: true,
902
+ }),
903
+ Animated.timing(unreadPulseAnim, {
904
+ toValue: 0.8,
905
+ duration: 800,
906
+ useNativeDriver: true,
907
+ }),
908
+ ]),
909
+ );
910
+ loop.start();
911
+ return () => loop.stop();
912
+ }, [unreadPulseAnim]);
913
+
914
+ const isNativeModule = useMemo(() => isNativeModuleAvailable(), []);
915
+ const useNativeFab =
916
+ (Platform.OS === 'ios' || Platform.OS === 'android') && isNativeModule;
917
+
918
+ // Send anonymous initialization heartbeat to GA4 Measurement Protocol
919
+ useEffect(() => {
920
+ sendSessionTelemetryPing({
921
+ environment,
922
+ hasNavigation: Boolean(navigationRef),
923
+ hasAppIcon: Boolean(appIcon),
924
+ });
925
+ }, [environment, navigationRef, appIcon]);
926
+
927
+ // 100% Native Main-Thread Floating Button Lifecycle
928
+ useEffect(() => {
929
+ if (!useNativeFab || !isEnabled || !enabled) {
930
+ if (useNativeFab) {
931
+ hideNativeFloatingButton();
932
+ }
933
+ return;
934
+ }
935
+
936
+ if (visible) {
937
+ hideNativeFloatingButton();
938
+ } else {
939
+ showNativeFloatingButton();
940
+ setNativeFloatingButtonBadge(
941
+ logs.length > 0 || analyticsEvents.length > 0,
942
+ );
943
+ }
944
+ }, [
945
+ useNativeFab,
946
+ isEnabled,
947
+ enabled,
948
+ visible,
949
+ logs.length,
950
+ analyticsEvents.length,
951
+ ]);
952
+
953
+ // Subscribe to native UI-thread floating button tap events
954
+ useEffect(() => {
955
+ if (!useNativeFab) return;
956
+ const unsubscribe = subscribeNativeFloatingButtonPress(() => {
957
+ setVisible(true);
958
+ });
959
+ return () => {
960
+ unsubscribe();
961
+ };
962
+ }, [useNativeFab]);
963
+
964
+ // Subscribe to physical hardware shake events (or Cmd+Ctrl+Z on iOS simulator)
965
+ useEffect(() => {
966
+ if (!useNativeFab) return;
967
+ const unsubscribe = subscribeNativeDeviceShake(() => {
968
+ setVisible(prev => !prev);
969
+ });
970
+ return () => {
971
+ unsubscribe();
972
+ };
973
+ }, [useNativeFab]);
974
+
975
+ const isVisibleRefObj = useRef(visible);
976
+
977
+ // #6 — every time the inspector is opened, land on chosen default tab & sync latest logs
978
+ useEffect(() => {
979
+ isVisibleRefObj.current = visible;
980
+ if (visible) {
981
+ const target =
982
+ defaultTab === 'apis' || tabVisibility?.[defaultTab]
983
+ ? defaultTab
984
+ : 'apis';
985
+ setActiveTab(target);
986
+
987
+ // Track inspector opened event in GA4
988
+ trackInspectorOpen({activeTab: target});
989
+
990
+ // Instant synchronization of data collected while modal was closed
991
+ if (latestNetworkLogsRef.current.length > 0) {
992
+ const deduped = deduplicateLogs(latestNetworkLogsRef.current);
993
+ const incoming = new Set(deduped.map(l => l.id));
994
+ prevLogIdsRef.current = incoming;
995
+ setLogs(deduped);
996
+ }
997
+ if (latestConsoleLogsRef.current.length > 0) {
998
+ setConsoleLogs(latestConsoleLogsRef.current);
999
+ }
1000
+ if (latestAnalyticsEventsRef.current.length > 0) {
1001
+ setAnalyticsEvents(latestAnalyticsEventsRef.current);
1002
+ }
1003
+ const freshState = getReduxState();
1004
+ if (freshState) {
1005
+ setReduxState(typeof freshState === 'object' ? {...freshState} : freshState);
1006
+ setReduxLastActionMap({...getLastActionForReducer()});
1007
+ }
1008
+ const freshCrashes = getCrashRecords();
1009
+ if (freshCrashes.length > 0) {
1010
+ setCrashRecords(freshCrashes);
1011
+ }
1012
+ }
1013
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1014
+ }, [visible]);
1015
+
1016
+ useEffect(() => {
1017
+ if (visible) {
1018
+ const task = InteractionManager.runAfterInteractions(() => {
1019
+ setIsReady(true);
1020
+ });
1021
+ const fallbackTimer = setTimeout(() => {
1022
+ setIsReady(true);
1023
+ }, 350);
1024
+ return () => {
1025
+ task.cancel();
1026
+ clearTimeout(fallbackTimer);
1027
+ };
1028
+ } else {
1029
+ setIsReady(false);
1030
+ setSelectedLogs(new Set());
1031
+ }
1032
+ }, [visible]);
1033
+
1034
+ useEffect(() => {
1035
+ setupNetworkLogger();
1036
+ clearNetworkLogs();
1037
+ setupConsoleLogger();
1038
+ autoSetupAnalyticsLogger();
1039
+ setupGlobalCrashHandler();
1040
+ const cleanupMemoryWarning = setupMemoryWarningHandler();
1041
+
1042
+ const isVisibleRef = isVisibleRefObj;
1043
+
1044
+ const unsubscribeCrash = subscribeCrashEvents(crashInfo => {
1045
+ if (crashInfo.message === '__CLEARED__') {
1046
+ setCrashRecords([]);
1047
+ setSelectedCrash(null);
1048
+ return;
1049
+ }
1050
+ const updated = getCrashRecords();
1051
+ if (updated.length > 0) {
1052
+ pushNativeLogRecord('crash', JSON.stringify(updated[0]));
1053
+ }
1054
+ if (isVisibleRef.current) {
1055
+ setCrashRecords(updated);
1056
+ }
1057
+ });
1058
+
1059
+ // Sync crashes recorded before this subscription ran (e.g. a render crash
1060
+ // during the very first commit, which fires before effects are mounted).
1061
+ const pendingCrashes = getCrashRecords();
1062
+ if (pendingCrashes.length > 0) {
1063
+ pendingCrashes.forEach(c => pushNativeLogRecord('crash', JSON.stringify(c)));
1064
+ setCrashRecords(pendingCrashes);
1065
+ }
1066
+
1067
+ let timeoutId: ReturnType<typeof setTimeout>;
1068
+
1069
+ let isFirstNetworkCall = true;
1070
+ const unsubscribe = subscribeNetworkLogs((raw: NetworkLog[]) => {
1071
+ latestNetworkLogsRef.current = raw;
1072
+ if (raw.length > 0) {
1073
+ pushNativeLogRecord('apis', JSON.stringify(raw[0]));
1074
+ }
1075
+ if (isNetworkPausedRef.current) return;
1076
+ if (!isVisibleRef.current) return; // ZERO-RENDER INACTIVE MODE
1077
+
1078
+ clearTimeout(timeoutId);
1079
+ const updateNetworkState = () => {
1080
+ const deduped = deduplicateLogs(raw);
1081
+ const incoming = new Set(deduped.map(l => l.id));
1082
+ const freshIds = new Set<number>();
1083
+ incoming.forEach(id => {
1084
+ if (!prevLogIdsRef.current.has(id)) freshIds.add(id);
1085
+ });
1086
+ prevLogIdsRef.current = incoming;
1087
+ if (freshIds.size > 0) {
1088
+ freshIds.forEach(id => {
1089
+ if (!logRouteMapRef.current.has(id)) {
1090
+ logRouteMapRef.current.set(id, currentRouteRef.current);
1091
+ }
1092
+ });
1093
+ setNewLogIds(freshIds);
1094
+ setTimeout(() => setNewLogIds(new Set()), 1200);
1095
+ }
1096
+ setLogs(deduped);
1097
+ };
1098
+
1099
+ if (isFirstNetworkCall) {
1100
+ isFirstNetworkCall = false;
1101
+ const deduped = deduplicateLogs(raw);
1102
+ const incoming = new Set(deduped.map(l => l.id));
1103
+ prevLogIdsRef.current = incoming;
1104
+ setLogs(deduped);
1105
+ } else {
1106
+ timeoutId = setTimeout(updateNetworkState, 250);
1107
+ }
1108
+ });
1109
+
1110
+ // ─── Analytics subscription ──────────────────────────────────────────────
1111
+ let analyticsTimeoutId: ReturnType<typeof setTimeout>;
1112
+ let isFirstAnalyticsCall = true;
1113
+
1114
+ const unsubscribeAnalytics = subscribeAnalyticsEvents(
1115
+ (raw: AnalyticsEvent[]) => {
1116
+ latestAnalyticsEventsRef.current = raw;
1117
+ if (raw.length > 0) {
1118
+ pushNativeLogRecord('analytics', JSON.stringify(raw[0]));
1119
+ }
1120
+ if (isAnalyticsPausedRef.current) return;
1121
+ if (!isVisibleRef.current) return; // ZERO-RENDER INACTIVE MODE
1122
+
1123
+ clearTimeout(analyticsTimeoutId);
1124
+ const updateAnalyticsState = () => {
1125
+ const incoming = new Set(raw.map(e => e.id));
1126
+ const freshIds = new Set<number>();
1127
+ incoming.forEach(id => {
1128
+ if (!prevEventIdsRef.current.has(id)) freshIds.add(id);
1129
+ });
1130
+ prevEventIdsRef.current = incoming;
1131
+ if (freshIds.size > 0) {
1132
+ freshIds.forEach(id => {
1133
+ if (!logRouteMapRef.current.has(id + 1000000)) {
1134
+ logRouteMapRef.current.set(
1135
+ id + 1000000,
1136
+ currentRouteRef.current,
1137
+ );
1138
+ }
1139
+ });
1140
+ setNewEventIds(freshIds);
1141
+ setTimeout(() => setNewEventIds(new Set()), 1200);
1142
+ }
1143
+ setAnalyticsEvents(raw);
1144
+ };
1145
+
1146
+ if (isFirstAnalyticsCall) {
1147
+ isFirstAnalyticsCall = false;
1148
+ const incoming = new Set(raw.map(e => e.id));
1149
+ prevEventIdsRef.current = incoming;
1150
+ setAnalyticsEvents(raw);
1151
+ } else {
1152
+ analyticsTimeoutId = setTimeout(updateAnalyticsState, 200);
1153
+ }
1154
+ },
1155
+ );
1156
+
1157
+ // ─── Console subscription ────────────────────────────────────────────────
1158
+ let consoleTimeoutId: ReturnType<typeof setTimeout>;
1159
+ let isFirstConsoleCall = true;
1160
+
1161
+ const unsubscribeConsole = subscribeConsoleLogs((raw: ConsoleLog[]) => {
1162
+ latestConsoleLogsRef.current = raw;
1163
+ if (raw.length > 0) {
1164
+ pushNativeLogRecord('logs', JSON.stringify(raw[0]));
1165
+ }
1166
+ if (isConsolePausedRef.current) return;
1167
+ if (!isVisibleRef.current) return; // ZERO-RENDER INACTIVE MODE
1168
+
1169
+ clearTimeout(consoleTimeoutId);
1170
+ if (isFirstConsoleCall) {
1171
+ isFirstConsoleCall = false;
1172
+ setConsoleLogs(raw);
1173
+ } else {
1174
+ consoleTimeoutId = setTimeout(() => {
1175
+ setConsoleLogs(raw);
1176
+ }, 200);
1177
+ }
1178
+ });
1179
+
1180
+ const initialReduxState = getReduxState();
1181
+ if (initialReduxState) {
1182
+ setReduxState(typeof initialReduxState === 'object' ? {...initialReduxState} : initialReduxState);
1183
+ setReduxLastActionMap({...getLastActionForReducer()});
1184
+ }
1185
+ const unsubscribeRedux = subscribeReduxState(() => {
1186
+ if (!isVisibleRef.current) return; // ZERO-RENDER INACTIVE MODE
1187
+ const freshState = getReduxState();
1188
+ setReduxState(freshState && typeof freshState === 'object' ? {...freshState} : freshState);
1189
+ setReduxLastActionMap({...getLastActionForReducer()});
1190
+ });
1191
+
1192
+ return () => {
1193
+ unsubscribe();
1194
+ clearTimeout(timeoutId);
1195
+ unsubscribeAnalytics();
1196
+ clearTimeout(analyticsTimeoutId);
1197
+ unsubscribeConsole();
1198
+ clearTimeout(consoleTimeoutId);
1199
+ unsubscribeRedux();
1200
+ unsubscribeCrash();
1201
+ cleanupMemoryWarning();
1202
+ };
1203
+ }, []);
1204
+
1205
+ useEffect(() => {
1206
+ setMaxNetworkLogsLimit(maxNetworkLogs);
1207
+ }, [maxNetworkLogs]);
1208
+
1209
+ useEffect(() => {
1210
+ setMaxCrashLogsLimit(maxCrashLogs);
1211
+ }, [maxCrashLogs]);
1212
+
1213
+ useEffect(() => {
1214
+ if (!isNetworkPaused && latestNetworkLogsRef.current.length > 0) {
1215
+ const deduped = deduplicateLogs(latestNetworkLogsRef.current);
1216
+ setLogs(deduped);
1217
+ }
1218
+ }, [isNetworkPaused]);
1219
+
1220
+ useEffect(() => {
1221
+ if (!isAnalyticsPaused && latestAnalyticsEventsRef.current.length > 0) {
1222
+ setAnalyticsEvents(latestAnalyticsEventsRef.current);
1223
+ }
1224
+ }, [isAnalyticsPaused]);
1225
+
1226
+ useEffect(() => {
1227
+ if (!isConsolePaused && latestConsoleLogsRef.current.length > 0) {
1228
+ setConsoleLogs(latestConsoleLogsRef.current);
1229
+ }
1230
+ }, [isConsolePaused]);
1231
+
1232
+ useEffect(() => {
1233
+ setReqExpanded(true);
1234
+ setResExpanded(true);
1235
+ setShowReqDiff(false);
1236
+ setShowResDiff(false);
1237
+ setDetailSearch('');
1238
+ }, [selected]);
1239
+
1240
+ const filteredLogs = useMemo(() => {
1241
+ let result = logs.filter(log => {
1242
+ // 1. Quick Filter Check (All, Errors, Success, Slow, GET, POST, GraphQL)
1243
+ if (quickFilter && quickFilter !== 'all') {
1244
+ if (quickFilter === 'errors') {
1245
+ const isErr =
1246
+ log.status === 0 ||
1247
+ log.status == null ||
1248
+ (typeof log.status === 'number' && log.status >= 400);
1249
+ if (!isErr) return false;
1250
+ } else if (quickFilter === 'success') {
1251
+ const isSuccess =
1252
+ typeof log.status === 'number' &&
1253
+ log.status >= 200 &&
1254
+ log.status < 400;
1255
+ if (!isSuccess) return false;
1256
+ } else if (quickFilter === 'slow') {
1257
+ if ((log.duration || 0) < 500) return false;
1258
+ } else if (quickFilter === 'graphql') {
1259
+ const isGql =
1260
+ (log.url || '').toLowerCase().includes('graphql') ||
1261
+ (log.client || '').toLowerCase().includes('graphql') ||
1262
+ (log.client || '').toLowerCase().includes('apollo');
1263
+ if (!isGql) return false;
1264
+ } else {
1265
+ if (log.method?.toUpperCase() !== quickFilter.toUpperCase()) {
1266
+ return false;
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // 2. Status Filter Check (from filters accordion dropdown)
1272
+ if (statusFilters.size > 0) {
1273
+ const matched = [...statusFilters].some(f => {
1274
+ if (f === 'ALL') return true;
1275
+ if (f === 'Failed') return log.status === 0 || log.status == null;
1276
+ const codeNum = parseInt(f, 10);
1277
+ if (!isNaN(codeNum)) {
1278
+ return Number(log.status) === codeNum;
1279
+ }
1280
+ return String(log.status)[0] === f[0];
1281
+ });
1282
+ if (!matched) return false;
1283
+ }
1284
+
1285
+ // 3. Method Filter Check (from filters accordion dropdown)
1286
+ if (methodFilters.size > 0) {
1287
+ const matchedMethod = [...methodFilters].some(m => {
1288
+ if (m === 'ALL') return true;
1289
+ return log.method?.toUpperCase() === m;
1290
+ });
1291
+ if (!matchedMethod) return false;
1292
+ }
1293
+
1294
+ // 4. Advanced Search Query Engine Check with Scope, Regex and Case-Sensitivity
1295
+ if (search && search.trim().length > 0) {
1296
+ const routePath = logRouteMapRef.current.get(log.id)?.path || '';
1297
+ const isMatch = matchNetworkLogQuery(log, search, routePath, {
1298
+ scope: searchScope,
1299
+ isRegex: isRegexSearch,
1300
+ isCaseSensitive: isCaseSensitive,
1301
+ });
1302
+ if (!isMatch) return false;
1303
+ }
1304
+
1305
+ return true;
1306
+ });
1307
+
1308
+ if (sortOrder === 'oldest') {
1309
+ result = [...result].reverse();
1310
+ }
1311
+
1312
+ // #9 — collapse consecutive identical requests
1313
+ if (!showDuplicateLogs) {
1314
+ const collapsed: NetworkLog[] = [];
1315
+ for (const log of result) {
1316
+ const last = collapsed[collapsed.length - 1];
1317
+ if (
1318
+ last &&
1319
+ last.method === log.method &&
1320
+ last.url === log.url &&
1321
+ last.status === log.status
1322
+ ) {
1323
+ collapsed[collapsed.length - 1] = {
1324
+ ...last,
1325
+ duplicateCount: (last.duplicateCount || 1) + 1,
1326
+ };
1327
+ } else {
1328
+ collapsed.push({...log, duplicateCount: 1});
1329
+ }
1330
+ }
1331
+ result = collapsed;
1332
+ }
1333
+
1334
+ return result.slice(0, maxNetworkLogs);
1335
+ }, [
1336
+ logs,
1337
+ search,
1338
+ searchScope,
1339
+ isRegexSearch,
1340
+ isCaseSensitive,
1341
+ quickFilter,
1342
+ statusFilters,
1343
+ methodFilters,
1344
+ sortOrder,
1345
+ maxNetworkLogs,
1346
+ showDuplicateLogs,
1347
+ ]);
1348
+
1349
+ const availableMethods = useMemo(() => {
1350
+ const methods = new Set<Method>();
1351
+ logs.forEach(log => {
1352
+ if (log.method) methods.add(log.method.toUpperCase() as Method);
1353
+ });
1354
+ return ['ALL' as Method, ...Array.from(methods)];
1355
+ }, [logs]);
1356
+
1357
+ const toggleSectionFilter = useCallback(
1358
+ (pageName: string, filter: LocalFilter) => {
1359
+ setSectionFilters(prev => {
1360
+ const current =
1361
+ prev[pageName] || new Set(['success', 'failed', 'loading']);
1362
+ const next = new Set(current);
1363
+
1364
+ if (next.has(filter)) {
1365
+ next.delete(filter);
1366
+ } else {
1367
+ next.add(filter);
1368
+ }
1369
+
1370
+ return {...prev, [pageName]: next};
1371
+ });
1372
+ },
1373
+ [],
1374
+ );
1375
+
1376
+ const toggleSectionCollapse = useCallback((pageName: string) => {
1377
+ setCollapsedSections(prev => {
1378
+ const next = new Set(prev);
1379
+ if (next.has(pageName)) next.delete(pageName);
1380
+ else next.add(pageName);
1381
+ return next;
1382
+ });
1383
+ }, []);
1384
+
1385
+ const groupedData = useMemo(() => {
1386
+ const result: GroupedListItem[] = [];
1387
+ const groups: {pageName: string; color: string; logs: NetworkLog[]}[] = [];
1388
+
1389
+ for (let i = 0; i < filteredLogs.length; i++) {
1390
+ const log = filteredLogs[i];
1391
+ const routeInfo = logRouteMapRef.current.get(log.id);
1392
+ const pageName = routeInfo?.path || 'Navigators';
1393
+
1394
+ if (
1395
+ groups.length === 0 ||
1396
+ groups[groups.length - 1].pageName !== pageName
1397
+ ) {
1398
+ groups.push({pageName, color: getDomainColor(pageName), logs: []});
1399
+ }
1400
+ groups[groups.length - 1].logs.push(log);
1401
+ }
1402
+
1403
+ groups.forEach((g, idx) => {
1404
+ let success = 0;
1405
+ let failed = 0;
1406
+ let loading = 0;
1407
+
1408
+ g.logs.forEach(l => {
1409
+ if (l.status == null) loading++;
1410
+ else if (l.status === 0 || l.status >= 400) failed++;
1411
+ else success++;
1412
+ });
1413
+
1414
+ const activeFilters =
1415
+ sectionFilters[g.pageName] || new Set(['success', 'failed', 'loading']);
1416
+
1417
+ const isCollapsed = collapsedSections.has(g.pageName);
1418
+ const timestamp = g.logs[0]?.startTime || 0;
1419
+
1420
+ result.push({
1421
+ type: 'header',
1422
+ id: `hdr-${g.logs[0]?.id || 'empty'}-${g.pageName}`,
1423
+ pageName: g.pageName,
1424
+ color: g.color,
1425
+ stats: {success, failed, loading},
1426
+ timestamp,
1427
+ activeFilters,
1428
+ isCollapsed,
1429
+ isFirst: idx === 0,
1430
+ });
1431
+
1432
+ if (!isCollapsed) {
1433
+ const displayLogs = g.logs.filter(l => {
1434
+ if (l.status == null) return activeFilters.has('loading');
1435
+ if (l.status === 0 || l.status >= 400)
1436
+ return activeFilters.has('failed');
1437
+ return activeFilters.has('success');
1438
+ });
1439
+
1440
+ displayLogs.forEach((log, index) => {
1441
+ result.push({
1442
+ type: 'log',
1443
+ id: log.id,
1444
+ log,
1445
+ isLast: index === displayLogs.length - 1,
1446
+ color: g.color,
1447
+ });
1448
+ });
1449
+ }
1450
+ });
1451
+
1452
+ return result;
1453
+ }, [filteredLogs, logs, sectionFilters, collapsedSections]);
1454
+
1455
+ const {minStart, totalRange} = useMemo(() => {
1456
+ if (filteredLogs.length === 0) return {minStart: 0, totalRange: 0};
1457
+ const min = Math.min(...filteredLogs.map(l => l.startTime));
1458
+ const max = Math.max(
1459
+ ...filteredLogs.map(l => l.startTime + (l.duration || 10)),
1460
+ );
1461
+ return {minStart: min, totalRange: max - min};
1462
+ }, [filteredLogs]);
1463
+
1464
+ const prevSameRequest = useMemo(() => {
1465
+ if (!selected) return null;
1466
+ const index = logs.findIndex(l => l.id === selected.id);
1467
+ if (index === -1) return null;
1468
+ for (let i = index + 1; i < logs.length; i++) {
1469
+ const l = logs[i];
1470
+ if (l.url === selected.url && l.method === selected.method) {
1471
+ return l;
1472
+ }
1473
+ }
1474
+ return null;
1475
+ }, [selected, logs]);
1476
+
1477
+ const prevRequestData = prevSameRequest ? prevSameRequest.request : null;
1478
+ const prevResponseData = prevSameRequest ? prevSameRequest.response : null;
1479
+
1480
+ const filteredAnalyticsEvents = useMemo(() => {
1481
+ let events = analyticsEvents;
1482
+
1483
+ // Search query filter
1484
+ if (analyticsSearch && analyticsSearch.trim().length > 0) {
1485
+ const queryTokens = analyticsSearch.trim().toLowerCase().split(/\s+/).filter(Boolean);
1486
+ events = events.filter(e => {
1487
+ const searchTarget = [
1488
+ e.name || '',
1489
+ JSON.stringify(e.params || ''),
1490
+ JSON.stringify(e.userProperties || ''),
1491
+ e.screenName || '',
1492
+ e.pageTitle || '',
1493
+ e.userId || '',
1494
+ e.source || '',
1495
+ ].join(' ').toLowerCase();
1496
+
1497
+ return queryTokens.every(token => searchTarget.includes(token));
1498
+ });
1499
+ }
1500
+
1501
+ // Category filter
1502
+ if (analyticsFilters.categories.size > 0 && !analyticsFilters.categories.has('all')) {
1503
+ events = events.filter(e => {
1504
+ const cat = getEventCategory(e.name);
1505
+ return analyticsFilters.categories.has(cat);
1506
+ });
1507
+ }
1508
+
1509
+ // Time window filter
1510
+ if (analyticsFilters.timeWindow !== 'all') {
1511
+ const now = Date.now();
1512
+ const cutoff =
1513
+ analyticsFilters.timeWindow === '1m'
1514
+ ? now - 60 * 1000
1515
+ : analyticsFilters.timeWindow === '5m'
1516
+ ? now - 5 * 60 * 1000
1517
+ : analyticsFilters.timeWindow === '15m'
1518
+ ? now - 15 * 60 * 1000
1519
+ : now - 60 * 60 * 1000;
1520
+ events = events.filter(e => e.timestamp >= cutoff);
1521
+ }
1522
+
1523
+ // Source filter
1524
+ if (analyticsFilters.sources.size > 0 && !analyticsFilters.sources.has('all')) {
1525
+ events = events.filter(e => analyticsFilters.sources.has(e.source || 'manual'));
1526
+ }
1527
+
1528
+ // User identification filter
1529
+ if (analyticsFilters.userTypes.size > 0 && !analyticsFilters.userTypes.has('all')) {
1530
+ events = events.filter(e => {
1531
+ const isIdentified = Boolean(e.userId && e.userId.trim() !== '');
1532
+ if (analyticsFilters.userTypes.has('identified') && isIdentified) return true;
1533
+ if (analyticsFilters.userTypes.has('anonymous') && !isIdentified) return true;
1534
+ return false;
1535
+ });
1536
+ }
1537
+
1538
+ // Payload complexity filter
1539
+ if (analyticsFilters.payloadComplexity !== 'all') {
1540
+ if (analyticsFilters.payloadComplexity === 'none') {
1541
+ events = events.filter(
1542
+ e => !e.params || Object.keys(e.params).length === 0,
1543
+ );
1544
+ } else if (analyticsFilters.payloadComplexity === 'simple') {
1545
+ events = events.filter(
1546
+ e => e.params && Object.keys(e.params).length <= 3,
1547
+ );
1548
+ } else if (analyticsFilters.payloadComplexity === 'heavy') {
1549
+ events = events.filter(
1550
+ e => e.params && Object.keys(e.params).length > 3,
1551
+ );
1552
+ }
1553
+ }
1554
+
1555
+ // Conversion / Goal events filter
1556
+ if (analyticsFilters.onlyConversions) {
1557
+ const CONVERSION_PATTERNS = [
1558
+ 'purchase',
1559
+ 'item_purchase',
1560
+ 'ecommerce_purchase',
1561
+ 'sign_up',
1562
+ 'login',
1563
+ 'lead',
1564
+ 'generate_lead',
1565
+ 'tutorial_complete',
1566
+ 'add_payment_info',
1567
+ 'begin_checkout',
1568
+ 'spend_virtual_currency',
1569
+ ];
1570
+ events = events.filter(e =>
1571
+ CONVERSION_PATTERNS.some(pat => e.name.toLowerCase().includes(pat)),
1572
+ );
1573
+ }
1574
+
1575
+ // Screen filter
1576
+ if (analyticsFilters.screens.size > 0) {
1577
+ events = events.filter(e => {
1578
+ const scr =
1579
+ e.screenName ||
1580
+ e.params?.firebase_screen ||
1581
+ e.params?.screen_name ||
1582
+ e.params?.firebase_screen_class ||
1583
+ e.screenClass ||
1584
+ '';
1585
+ return analyticsFilters.screens.has(scr);
1586
+ });
1587
+ }
1588
+
1589
+ // Has Revenue filter
1590
+ if (analyticsFilters.hasRevenue) {
1591
+ events = events.filter(
1592
+ e =>
1593
+ (e.params?.value != null && !isNaN(Number(e.params.value))) ||
1594
+ (e.params?.price != null && !isNaN(Number(e.params.price))),
1595
+ );
1596
+ }
1597
+
1598
+ // Has Items filter
1599
+ if (analyticsFilters.hasItems) {
1600
+ events = events.filter(
1601
+ e => Array.isArray(e.params?.items) && e.params.items.length > 0,
1602
+ );
1603
+ }
1604
+
1605
+ // Has User Props filter
1606
+ if (analyticsFilters.hasUserProps) {
1607
+ events = events.filter(
1608
+ e => e.userProperties && Object.keys(e.userProperties).length > 0,
1609
+ );
1610
+ }
1611
+
1612
+ // Has Params filter
1613
+ if (analyticsFilters.hasParams) {
1614
+ events = events.filter(
1615
+ e => e.params && Object.keys(e.params).length > 0,
1616
+ );
1617
+ }
1618
+
1619
+ // Deduplication
1620
+ const deduplicatedEvents: (AnalyticsEvent & {count?: number})[] = [];
1621
+ for (const e of events) {
1622
+ if (deduplicatedEvents.length === 0) {
1623
+ deduplicatedEvents.push({...e, count: 1});
1624
+ continue;
1625
+ }
1626
+ const last = deduplicatedEvents[deduplicatedEvents.length - 1];
1627
+ if (
1628
+ last.name === e.name &&
1629
+ JSON.stringify(last.params) === JSON.stringify(e.params) &&
1630
+ JSON.stringify(last.userProperties) === JSON.stringify(e.userProperties)
1631
+ ) {
1632
+ last.count = (last.count || 1) + 1;
1633
+ // Point to the newest timestamp and id
1634
+ last.timestamp = e.timestamp;
1635
+ last.id = e.id;
1636
+ } else {
1637
+ deduplicatedEvents.push({...e, count: 1});
1638
+ }
1639
+ }
1640
+
1641
+ let result = deduplicatedEvents;
1642
+
1643
+ // Only duplicates filter
1644
+ if (analyticsFilters.onlyDuplicates) {
1645
+ result = result.filter(e => (e.count || 1) > 1);
1646
+ }
1647
+
1648
+ // Sorting
1649
+ if (analyticsFilters.sortBy === 'time_asc') {
1650
+ result = [...result].sort((a, b) => a.timestamp - b.timestamp);
1651
+ } else if (analyticsFilters.sortBy === 'revenue_desc') {
1652
+ result = [...result].sort((a, b) => {
1653
+ const valA = Number(a.params?.value ?? a.params?.price ?? 0);
1654
+ const valB = Number(b.params?.value ?? b.params?.price ?? 0);
1655
+ return valB - valA;
1656
+ });
1657
+ } else if (analyticsFilters.sortBy === 'count_desc') {
1658
+ result = [...result].sort((a, b) => (b.count || 1) - (a.count || 1));
1659
+ }
1660
+
1661
+ return result.slice(0, maxAnalyticsEventsLimit);
1662
+ }, [analyticsEvents, analyticsSearch, analyticsFilters, maxAnalyticsEventsLimit]);
1663
+
1664
+ const filteredConsoleLogs = useMemo(() => {
1665
+ let result = visibleConsoleLogs;
1666
+
1667
+ // Filters check
1668
+ if (logFilters.size > 0 && !logFilters.has('all')) {
1669
+ result = result.filter(log => {
1670
+ if (logFilters.has(log.type)) return true;
1671
+ if (logFilters.has('user-log') && log.sourceMethod === 'log')
1672
+ return true;
1673
+ if (
1674
+ logFilters.has('analytics') &&
1675
+ log.message?.toLowerCase().includes('[analytics error]')
1676
+ )
1677
+ return true;
1678
+ return false;
1679
+ });
1680
+ }
1681
+
1682
+ // Comprehensive Search bar check
1683
+ if (logSearch && logSearch.trim().length > 0) {
1684
+ const queryTokens = logSearch.trim().toLowerCase().split(/\s+/).filter(Boolean);
1685
+ result = result.filter(log => {
1686
+ const searchTarget = [
1687
+ log.message || '',
1688
+ log.caller || '',
1689
+ log.type || '',
1690
+ log.sourceMethod || '',
1691
+ ].join(' ').toLowerCase();
1692
+
1693
+ return queryTokens.every(token => searchTarget.includes(token));
1694
+ });
1695
+ }
1696
+
1697
+ // #7 — apply sort order (newest/oldest first)
1698
+ result = [...result].sort((a, b) =>
1699
+ logSortOrder === 'newest'
1700
+ ? b.timestamp - a.timestamp
1701
+ : a.timestamp - b.timestamp,
1702
+ );
1703
+
1704
+ // #9 — collapse consecutive identical messages into one row with a ×N
1705
+ // counter unless duplicates are explicitly enabled in Settings.
1706
+ if (!showDuplicateLogs) {
1707
+ const collapsed: ConsoleLog[] = [];
1708
+ for (const log of result) {
1709
+ const last = collapsed[collapsed.length - 1];
1710
+ if (
1711
+ last &&
1712
+ last.type === log.type &&
1713
+ last.sourceMethod === log.sourceMethod &&
1714
+ last.message === log.message
1715
+ ) {
1716
+ collapsed[collapsed.length - 1] = {
1717
+ ...last,
1718
+ duplicateCount: (last.duplicateCount || 1) + 1,
1719
+ };
1720
+ } else {
1721
+ collapsed.push({...log, duplicateCount: 1});
1722
+ }
1723
+ }
1724
+ result = collapsed;
1725
+ }
1726
+
1727
+ return result;
1728
+ }, [
1729
+ visibleConsoleLogs,
1730
+ logFilters,
1731
+ logSearch,
1732
+ logSortOrder,
1733
+ showDuplicateLogs,
1734
+ ]);
1735
+
1736
+ const logCounts = useMemo(() => {
1737
+ let searchedLogs = visibleConsoleLogs;
1738
+ if (logSearch && logSearch.trim().length > 0) {
1739
+ const queryTokens = logSearch.trim().toLowerCase().split(/\s+/).filter(Boolean);
1740
+ searchedLogs = visibleConsoleLogs.filter(log => {
1741
+ const searchTarget = [
1742
+ log.message || '',
1743
+ log.caller || '',
1744
+ log.type || '',
1745
+ log.sourceMethod || '',
1746
+ ].join(' ').toLowerCase();
1747
+
1748
+ return queryTokens.every(token => searchTarget.includes(token));
1749
+ });
1750
+ }
1751
+
1752
+ const total = visibleConsoleLogs.length;
1753
+
1754
+ return {
1755
+ all: `${searchedLogs.length}/${total}`,
1756
+ info: `${searchedLogs.filter(l => l.type === 'info').length}/${total}`,
1757
+ warn: `${searchedLogs.filter(l => l.type === 'warn').length}/${total}`,
1758
+ error: `${searchedLogs.filter(l => l.type === 'error').length}/${total}`,
1759
+ 'user-log': `${
1760
+ searchedLogs.filter(l => l.sourceMethod === 'log').length
1761
+ }/${total}`,
1762
+ analytics: `${
1763
+ searchedLogs.filter(l =>
1764
+ l.message.toLowerCase().includes('[analytics error]'),
1765
+ ).length
1766
+ }/${total}`,
1767
+ };
1768
+ }, [visibleConsoleLogs, logSearch]);
1769
+
1770
+ function closeModal() {
1771
+ animateNextLayout();
1772
+ setVisible(false);
1773
+ setSelected(null);
1774
+ setSelectedEvent(null);
1775
+ setSelectedLog(null);
1776
+ setSelectedReduxSlice(null);
1777
+ setSelectedReduxAction(null);
1778
+ setSelectedCrash(null);
1779
+ }
1780
+
1781
+ function handleClearAll() {
1782
+ clearNetworkLogs();
1783
+ setLogs([]);
1784
+ setSelectedLogs(new Set());
1785
+ setSectionFilters({});
1786
+ setCollapsedSections(new Set());
1787
+ setStatusFilters(new Set());
1788
+ setMethodFilters(new Set());
1789
+ prevLogIdsRef.current = new Set();
1790
+ logRouteMapRef.current = new Map();
1791
+ // Also clear analytics
1792
+ clearAnalyticsEvents();
1793
+ setAnalyticsEvents([]);
1794
+ prevEventIdsRef.current = new Set();
1795
+ // Also clear console logs
1796
+ clearConsoleLogs();
1797
+ setConsoleLogs([]);
1798
+ }
1799
+
1800
+ function handleDelete() {
1801
+ if (activeTab === 'logs') {
1802
+ Alert.alert(
1803
+ 'Clear Logs',
1804
+ 'Are you sure you want to clear all console logs?',
1805
+ [
1806
+ {text: 'Cancel', style: 'cancel'},
1807
+ {
1808
+ text: 'Clear All',
1809
+ onPress: () => {
1810
+ clearConsoleLogs();
1811
+ setConsoleLogs([]);
1812
+ },
1813
+ style: 'destructive',
1814
+ },
1815
+ ],
1816
+ );
1817
+ return;
1818
+ }
1819
+ if (activeTab === 'analytics') {
1820
+ Alert.alert(
1821
+ 'Clear Analytics',
1822
+ 'Are you sure you want to clear all analytics events?',
1823
+ [
1824
+ {text: 'Cancel', style: 'cancel'},
1825
+ {
1826
+ text: 'Clear All',
1827
+ onPress: () => {
1828
+ clearAnalyticsEvents();
1829
+ setAnalyticsEvents([]);
1830
+ setSelectedEvent(null);
1831
+ prevEventIdsRef.current = new Set();
1832
+ },
1833
+ style: 'destructive',
1834
+ },
1835
+ ],
1836
+ );
1837
+ return;
1838
+ }
1839
+ if (activeTab === 'redux') {
1840
+ Alert.alert(
1841
+ 'Clear Redux Timeline',
1842
+ 'Are you sure you want to clear the dispatched action history?',
1843
+ [
1844
+ {text: 'Cancel', style: 'cancel'},
1845
+ {
1846
+ text: 'Clear All',
1847
+ onPress: () => {
1848
+ clearActionHistory();
1849
+ setReduxLastActionMap({});
1850
+ },
1851
+ style: 'destructive',
1852
+ },
1853
+ ],
1854
+ );
1855
+ return;
1856
+ }
1857
+ if (activeTab === 'crash') {
1858
+ Alert.alert(
1859
+ 'Clear Crash History',
1860
+ 'Are you sure you want to clear all intercepted crash records?',
1861
+ [
1862
+ {text: 'Cancel', style: 'cancel'},
1863
+ {
1864
+ text: 'Clear All',
1865
+ onPress: () => {
1866
+ clearCrashRecords();
1867
+ setCrashRecords([]);
1868
+ setSelectedCrash(null);
1869
+ },
1870
+ style: 'destructive',
1871
+ },
1872
+ ],
1873
+ );
1874
+ return;
1875
+ }
1876
+ // Default: APIs tab. Only clears NETWORK logs — never touches the other tabs.
1877
+ if (selectedLogs.size > 0) {
1878
+ setLogs(prev => prev.filter(l => !selectedLogs.has(l.id)));
1879
+ setSelectedLogs(new Set());
1880
+ } else {
1881
+ Alert.alert(
1882
+ 'Clear Logs',
1883
+ 'Are you sure you want to clear all network logs?',
1884
+ [
1885
+ {text: 'Cancel', style: 'cancel'},
1886
+ {
1887
+ text: 'Clear All',
1888
+ onPress: clearNetworkOnly,
1889
+ style: 'destructive',
1890
+ },
1891
+ ],
1892
+ );
1893
+ }
1894
+ }
1895
+
1896
+ // Clears ONLY network logs + their derived selection/filter state.
1897
+ function clearNetworkOnly() {
1898
+ clearNetworkLogs();
1899
+ setLogs([]);
1900
+ setSelectedLogs(new Set());
1901
+ setSectionFilters({});
1902
+ setCollapsedSections(new Set());
1903
+ setStatusFilters(new Set());
1904
+ setMethodFilters(new Set());
1905
+ prevLogIdsRef.current = new Set();
1906
+ logRouteMapRef.current = new Map();
1907
+ }
1908
+
1909
+ const detailTitle = useMemo(() => {
1910
+ if (!selected) return '';
1911
+ try {
1912
+ const path = new URL(selected.url).pathname;
1913
+ const parts = path.split('/').filter(Boolean);
1914
+ return parts.length > 0 ? `/${parts.slice(-2).join('/')}` : '/';
1915
+ } catch {
1916
+ const parts = selected.url.split('/').filter(Boolean);
1917
+ return parts.length > 0 ? `/${parts.slice(-2).join('/')}` : selected.url;
1918
+ }
1919
+ }, [selected]);
1920
+
1921
+ const detailDisplayUrl = useMemo(() => {
1922
+ return selected ? formatDisplayUrl(selected.url) : '';
1923
+ }, [selected]);
1924
+
1925
+ const toggleSelect = useCallback((id: number) => {
1926
+ setSelectedLogs(prev => {
1927
+ const next = new Set(prev);
1928
+ if (next.has(id)) next.delete(id);
1929
+ else next.add(id);
1930
+ return next;
1931
+ });
1932
+ }, []);
1933
+
1934
+ const contextValue: InspectorContextValue = {
1935
+ // ─── Modal / launcher ───────────────────────────────────────────────
1936
+ visible,
1937
+ setVisible,
1938
+ closeModal,
1939
+ isReady,
1940
+ isEnabled,
1941
+ appIcon,
1942
+ environment,
1943
+ modalHeightPercent,
1944
+ setModalHeightPercent,
1945
+ modalAnimationType,
1946
+ setModalAnimationType,
1947
+ hasNavigationContext,
1948
+ setNavState,
1949
+
1950
+ // ─── Tabs ───────────────────────────────────────────────────────────
1951
+ activeTab,
1952
+ switchActiveTab,
1953
+ tabVisibility,
1954
+ toggleTabVisibility,
1955
+ lastReadApisCount,
1956
+ lastReadLogsCount,
1957
+
1958
+ // ─── Selection / header state ───────────────────────────────────────
1959
+ selected,
1960
+ setSelected,
1961
+ selectedEvent,
1962
+ setSelectedEvent,
1963
+ selectedLog,
1964
+ setSelectedLog,
1965
+ showHeaderInfo,
1966
+ setShowHeaderInfo,
1967
+ settingsPage,
1968
+ setSettingsPage,
1969
+ updateAvailable,
1970
+ latestNpmVersion,
1971
+ clearAnim,
1972
+ activePulseAnim,
1973
+ unreadPulseAnim,
1974
+ runClearAllWithAnimation,
1975
+
1976
+ // ─── FAB / launcher ─────────────────────────────────────────────────
1977
+ useNativeFab,
1978
+ fabPan,
1979
+ fabPanResponder,
1980
+ fabDraggedRef,
1981
+ pulseAnim,
1982
+ fabShineAnim,
1983
+
1984
+ // ─── Network (APIs) ─────────────────────────────────────────────────
1985
+ logs,
1986
+ filteredLogs,
1987
+ groupedData,
1988
+ search,
1989
+ setSearch,
1990
+ searchScope,
1991
+ setSearchScope,
1992
+ isRegexSearch,
1993
+ setIsRegexSearch,
1994
+ isCaseSensitive,
1995
+ setIsCaseSensitive,
1996
+ quickFilter,
1997
+ setQuickFilter,
1998
+ statusFilters,
1999
+ setStatusFilters,
2000
+ methodFilters,
2001
+ setMethodFilters,
2002
+ availableMethods,
2003
+ sortOrder,
2004
+ setSortOrder,
2005
+ selectedLogs,
2006
+ toggleSelect,
2007
+ minStart,
2008
+ totalRange,
2009
+ newLogIds,
2010
+ toggleSectionFilter,
2011
+ toggleSectionCollapse,
2012
+ handleDelete,
2013
+ isNetworkPaused,
2014
+ setIsNetworkPaused,
2015
+
2016
+ // ─── Network detail ─────────────────────────────────────────────────
2017
+ detailTitle,
2018
+ detailDisplayUrl,
2019
+ apiDetailActiveTab,
2020
+ setApiDetailActiveTab,
2021
+ detailSearch,
2022
+ setDetailSearch,
2023
+ reqExpanded,
2024
+ setReqExpanded,
2025
+ resExpanded,
2026
+ setResExpanded,
2027
+ showReqDiff,
2028
+ setShowReqDiff,
2029
+ showResDiff,
2030
+ setShowResDiff,
2031
+ prevRequestData,
2032
+ prevResponseData,
2033
+ logRouteMapRef,
2034
+
2035
+ // ─── Console (Logs) ─────────────────────────────────────────────────
2036
+ consoleLogs,
2037
+ visibleConsoleLogs,
2038
+ filteredConsoleLogs,
2039
+ logSearch,
2040
+ setLogSearch,
2041
+ logFilters,
2042
+ setLogFilters,
2043
+ logCounts,
2044
+ logSortOrder,
2045
+ setLogSortOrder,
2046
+ isConsolePaused,
2047
+ setIsConsolePaused,
2048
+
2049
+ // ─── Analytics ──────────────────────────────────────────────────────
2050
+ analyticsEvents,
2051
+ filteredAnalyticsEvents,
2052
+ analyticsSearch,
2053
+ setAnalyticsSearch,
2054
+ analyticsFilters,
2055
+ setAnalyticsFilters,
2056
+ isAnalyticsFilterApplied,
2057
+ resetAnalyticsFilters,
2058
+ newEventIds,
2059
+ isAnalyticsLayoutReady,
2060
+ setIsAnalyticsLayoutReady,
2061
+ analyticsHeaderExpanded,
2062
+ setAnalyticsHeaderExpanded,
2063
+ isAnalyticsPaused,
2064
+ setIsAnalyticsPaused,
2065
+
2066
+ // ─── Redux ──────────────────────────────────────────────────────────
2067
+ reduxState,
2068
+ setReduxState,
2069
+ reduxLastActionMap,
2070
+ reduxSearch,
2071
+ setReduxSearch,
2072
+ selectedReduxSlice,
2073
+ setSelectedReduxSlice,
2074
+ selectedReduxAction,
2075
+ setSelectedReduxAction,
2076
+ reduxActiveSubTab,
2077
+ setReduxActiveSubTab,
2078
+
2079
+ // ─── Crash ───────────────────────────────────────────────────────────
2080
+ crashRecords,
2081
+ setCrashRecords,
2082
+ selectedCrash,
2083
+ setSelectedCrash,
2084
+ lastReadCrashesCount,
2085
+ maxCrashLogs,
2086
+ setMaxCrashLogs,
2087
+ clearAllCrashes: () => {
2088
+ clearCrashRecords();
2089
+ setCrashRecords([]);
2090
+ setSelectedCrash(null);
2091
+ },
2092
+
2093
+ // ─── Settings ───────────────────────────────────────────────────────
2094
+ settingsActiveSubTab,
2095
+ setSettingsActiveSubTab,
2096
+ defaultTab,
2097
+ setDefaultTab,
2098
+ isDark,
2099
+ setIsDark,
2100
+ showDuplicateLogs,
2101
+ setShowDuplicateLogs,
2102
+ showUpdateToast,
2103
+ setShowUpdateToast,
2104
+ showConsoleLevels,
2105
+ setShowConsoleLevels,
2106
+ resetToDefaults,
2107
+ storage,
2108
+ maxNetworkLogs,
2109
+ setMaxNetworkLogs,
2110
+ maxConsoleLogs,
2111
+ setMaxConsoleLogs,
2112
+ maxAnalyticsEventsLimit,
2113
+ setMaxAnalyticsEventsLimit,
2114
+ isAutoRamLimitEnabled,
2115
+ setIsAutoRamLimitEnabled,
2116
+ deviceFreeRamMb,
2117
+ reduxAutoRefresh,
2118
+ setReduxAutoRefreshState,
2119
+ reduxExpandDepth,
2120
+ setReduxExpandDepth,
2121
+ };
2122
+
2123
+ return (
2124
+ <InspectorContext.Provider value={contextValue}>
2125
+ <MainScreen />
2126
+ </InspectorContext.Provider>
2127
+ );
2128
+ };
2129
+
2130
+ const NetworkInspectorWrapper = (props: NetworkInspectorProps) => {
2131
+ // If running in production release build and not explicitly force-enabled (e.g. for QA builds),
2132
+ // return null immediately for 0-overhead production stubbing.
2133
+ if (typeof __DEV__ !== 'undefined' && !__DEV__ && !props?.forceEnable) {
2134
+ return null;
2135
+ }
2136
+
2137
+ return (
2138
+ <I18nextProvider i18n={i18n}>
2139
+ <ErrorBoundary fallbackType="inline">
2140
+ <NetworkInspector {...props} />
2141
+ </ErrorBoundary>
2142
+ </I18nextProvider>
2143
+ );
2144
+ };
2145
+
2146
+ export default NetworkInspectorWrapper;
2147
+
2148
+ // Re-export public APIs
2149
+ export {
2150
+ setupNetworkLogger,
2151
+ clearNetworkLogs,
2152
+ subscribeNetworkLogs,
2153
+ addAxiosInterceptors,
2154
+ } from './customHooks/networkLogger';
2155
+
2156
+ export {
2157
+ setupConsoleLogger,
2158
+ clearConsoleLogs,
2159
+ subscribeConsoleLogs,
2160
+ } from './customHooks/consoleLogger';
2161
+
2162
+ export {
2163
+ setupAnalyticsLogger,
2164
+ logAnalyticsEvent,
2165
+ subscribeAnalyticsEvents,
2166
+ clearAnalyticsEvents,
2167
+ getCurrentUserProperties,
2168
+ getCurrentUserId,
2169
+ getDefaultEventParameters,
2170
+ getCollectionEnabled,
2171
+ } from './customHooks/analyticsLogger';
2172
+
2173
+ export {
2174
+ setupGlobalCrashHandler,
2175
+ subscribeCrashEvents,
2176
+ emitCrashEvent,
2177
+ getCrashRecords,
2178
+ clearCrashRecords,
2179
+ simulateTestCrash,
2180
+ exportCrashReport,
2181
+ parseCrashStackTrace,
2182
+ recordCustomCrash,
2183
+ addCrashBreadcrumb,
2184
+ recordNavigationBreadcrumb,
2185
+ recordNetworkBreadcrumb,
2186
+ recordReduxBreadcrumb,
2187
+ recordUserActionBreadcrumb,
2188
+ computeCrashFingerprint,
2189
+ type CrashEventPayload,
2190
+ } from './customHooks/crashHandler';
2191
+
2192
+ export {default as CrashTab} from './components/Inspector/CrashTab';
2193
+ export {default as ErrorBoundary} from './components/ErrorBoundary';
2194
+
2195
+ export {
2196
+ connectReduxStore,
2197
+ inspectorReduxMiddleware,
2198
+ getReduxState,
2199
+ subscribeReduxState,
2200
+ getActionHistory,
2201
+ clearActionHistory,
2202
+ getLastActionForReducer,
2203
+ } from './customHooks/reduxLogger';
2204
+
2205
+ export {
2206
+ getEventCategory,
2207
+ registerGAPlugin,
2208
+ type GAPlugin,
2209
+ } from './helpers/gaAnalyticsRegistry';
2210
+
2211
+ export {
2212
+ usePerformanceTracker,
2213
+ useComponentProfiler,
2214
+ useNavigationProfiler,
2215
+ trackComponentRender,
2216
+ trackNavigationTransition,
2217
+ trackHeavyTask,
2218
+ measureAsync,
2219
+ getHermesMemoryStats,
2220
+ registerComponentProfile,
2221
+ subscribeRenderProfiles,
2222
+ getRenderProfiles,
2223
+ logPerformanceEvent,
2224
+ clearPerformanceEvents,
2225
+ subscribePerformanceEvents,
2226
+ getPerformanceEvents,
2227
+ getInitialRenderProfiles,
2228
+ getInitialPerformanceEvents,
2229
+ generateFixSnippet,
2230
+ } from './customHooks/performanceTracker';
2231
+
2232
+ export {
2233
+ InspectLog,
2234
+ InspectTrackTime,
2235
+ InspectCatch,
2236
+ type InspectLogOptions,
2237
+ } from './decorators';
2238
+
2239
+ export {
2240
+ getNativeDeviceMetrics,
2241
+ enableNativeCrashProtection,
2242
+ subscribeNativeCrashes,
2243
+ showNativeFloatingButton,
2244
+ hideNativeFloatingButton,
2245
+ setNativeFloatingButtonBadge,
2246
+ subscribeNativeFloatingButtonPress,
2247
+ subscribeNativeDeviceShake,
2248
+ startNativeFpsMonitoring,
2249
+ stopNativeFpsMonitoring,
2250
+ getNativeFpsMetrics,
2251
+ getNativeStorageItem,
2252
+ setNativeStorageItem,
2253
+ isNativeModuleAvailable,
2254
+ type NativeDeviceMetrics,
2255
+ type NativeCrashEvent,
2256
+ type FloatingButtonOptions,
2257
+ type NativeFpsMetrics,
2258
+ } from './native/NativeInspector';
2259
+
2260
+ export {
2261
+ sendSessionTelemetryPing,
2262
+ trackTelemetryEvent,
2263
+ trackInspectorOpen,
2264
+ trackTabSwitch,
2265
+ trackLogExport,
2266
+ GA4_MEASUREMENT_ID,
2267
+ GA4_API_SECRET,
2268
+ setupMemoryWarningHandler,
2269
+ pruneAllLogs,
2270
+ subscribeMemoryWarning,
2271
+ type MemoryPruneSummary,
2272
+ } from './helpers';
2273
+
2274
+ export {
2275
+ setMaxNetworkLogsLimit,
2276
+ getMaxNetworkLogsLimit,
2277
+ pruneNetworkLogs,
2278
+ } from './customHooks/networkLogger';
2279
+
2280
+ export {
2281
+ setMaxConsoleLogsLimit,
2282
+ getMaxConsoleLogsLimit,
2283
+ pruneConsoleLogs,
2284
+ } from './customHooks/consoleLogger';
2285
+
2286
+ export {
2287
+ setMaxReduxHistoryLimit,
2288
+ getMaxReduxHistoryLimit,
2289
+ pruneReduxHistory,
2290
+ } from './customHooks/reduxLogger';
2291
+
2292
+ export {
2293
+ setMaxAnalyticsLogsLimit,
2294
+ getMaxAnalyticsLogsLimit,
2295
+ pruneAnalyticsLogs,
2296
+ } from './customHooks/analyticsLogger';
2297
+
2298
+ export {
2299
+ getMaxCrashLogsLimit,
2300
+ pruneCrashRecords,
2301
+ } from './customHooks/crashHandler';
2302
+
2303
+ export {
2304
+ setMaxPerformanceEventsLimit,
2305
+ getMaxPerformanceEventsLimit,
2306
+ prunePerformanceEvents,
2307
+ } from './customHooks/performanceTracker';
2308
+
2309
+ export {
2310
+ BrandSquareIcon,
2311
+ BrandCircleIcon,
2312
+ } from './components/NetworkIcons';
2313
+
2314
+ export {
2315
+ ActiveTab,
2316
+ Method,
2317
+ StatusFilter,
2318
+ SortOrder,
2319
+ LocalFilter,
2320
+ ModalAnimationType,
2321
+ SettingsPage,
2322
+ SettingsSubTab,
2323
+ LogFilter,
2324
+ ConsoleLogType,
2325
+ AnalyticsEventSource,
2326
+ GAEventCategory,
2327
+ StackFrameType,
2328
+ DiffResultType,
2329
+ BundleSubTab,
2330
+ PerformanceSubTab,
2331
+ CrashType,
2332
+ CrashExportFormat,
2333
+ CrashDetailSubTab,
2334
+ CrashFilterType,
2335
+ BreadcrumbType,
2336
+ } from './types';