react-native-inapp-inspector 2.2.2 → 2.2.4

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