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.
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/package.json +2 -1
- package/src/analytics.ts +35 -0
- package/src/bundle.ts +25 -0
- package/src/components/AnalyticsDetail.tsx +865 -0
- package/src/components/AnalyticsEventCard.tsx +441 -0
- package/src/components/AnalyticsGraph.tsx +583 -0
- package/src/components/AnimatedEntrance.tsx +64 -0
- package/src/components/AppHeaderLogo.tsx +109 -0
- package/src/components/BrandCircleIcon.tsx +144 -0
- package/src/components/BrandSquareIcon.tsx +144 -0
- package/src/components/CodeSnippet.tsx +725 -0
- package/src/components/ConsoleLogCard.tsx +628 -0
- package/src/components/CopyButton.tsx +87 -0
- package/src/components/DiffViewer.tsx +82 -0
- package/src/components/DomainHeader.tsx +237 -0
- package/src/components/EmptyState.tsx +73 -0
- package/src/components/EndOfListFooter.tsx +100 -0
- package/src/components/ErrorBoundary.tsx +688 -0
- package/src/components/HeadersSection.tsx +192 -0
- package/src/components/HighlightText.tsx +100 -0
- package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
- package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
- package/src/components/Inspector/BundleTab.tsx +4731 -0
- package/src/components/Inspector/ConsoleTab.tsx +702 -0
- package/src/components/Inspector/CrashDetail.tsx +941 -0
- package/src/components/Inspector/CrashFilterModal.tsx +721 -0
- package/src/components/Inspector/CrashTab.tsx +871 -0
- package/src/components/Inspector/FabLauncher.tsx +80 -0
- package/src/components/Inspector/InspectorContext.tsx +28 -0
- package/src/components/Inspector/InspectorHeader.tsx +973 -0
- package/src/components/Inspector/LogDetail.tsx +1427 -0
- package/src/components/Inspector/MainScreen.tsx +258 -0
- package/src/components/Inspector/NavigationTracker.tsx +13 -0
- package/src/components/Inspector/NetworkDetail.tsx +794 -0
- package/src/components/Inspector/NetworkTab.tsx +1095 -0
- package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
- package/src/components/Inspector/PerformanceTab.tsx +1894 -0
- package/src/components/Inspector/ReduxDetail.tsx +1651 -0
- package/src/components/Inspector/ReduxTab.tsx +954 -0
- package/src/components/Inspector/SettingsPanel.tsx +3181 -0
- package/src/components/Inspector/TabBar.tsx +187 -0
- package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
- package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
- package/src/components/JsonViewer.tsx +486 -0
- package/src/components/LogCard.tsx +491 -0
- package/src/components/LogSyntaxHighlighter.tsx +178 -0
- package/src/components/MetaAccordion.tsx +340 -0
- package/src/components/MiniBarChart.tsx +42 -0
- package/src/components/MiniLineChart.tsx +33 -0
- package/src/components/NetworkIcons.tsx +2118 -0
- package/src/components/SectionHeader.tsx +113 -0
- package/src/components/SegmentedTabs.tsx +83 -0
- package/src/components/Slider.tsx +300 -0
- package/src/components/SourcePageCard.tsx +148 -0
- package/src/components/Toast.tsx +131 -0
- package/src/components/TouchableScale.tsx +91 -0
- package/src/components/TreeNode.tsx +186 -0
- package/src/console.ts +24 -0
- package/src/constants/index.ts +38 -0
- package/src/constants/version.ts +3 -0
- package/src/crash.ts +32 -0
- package/src/customHooks/analyticsLogger.ts +336 -0
- package/src/customHooks/bundleAnalyzer.ts +1231 -0
- package/src/customHooks/consoleLogger.ts +497 -0
- package/src/customHooks/crashHandler.ts +944 -0
- package/src/customHooks/logFilters.ts +32 -0
- package/src/customHooks/networkLogger.ts +419 -0
- package/src/customHooks/performanceTracker.ts +1014 -0
- package/src/customHooks/reduxLogger.ts +406 -0
- package/src/customHooks/useAccordion.tsx +60 -0
- package/src/decorators/index.ts +184 -0
- package/src/helpers/gaAnalyticsRegistry.ts +204 -0
- package/src/helpers/index.ts +860 -0
- package/src/helpers/memoryManager.ts +138 -0
- package/src/helpers/searchQueryParser.ts +283 -0
- package/src/helpers/settingsStore.ts +171 -0
- package/src/helpers/telemetry.ts +505 -0
- package/src/helpers/toast.ts +17 -0
- package/src/i18n/index.ts +69 -0
- package/src/i18n/locales/en.json +1052 -0
- package/src/index.tsx +2336 -0
- package/src/native/NativeInspector.ts +397 -0
- package/src/native/NativeNetworkInspector.ts +24 -0
- package/src/network.ts +22 -0
- package/src/performance.ts +38 -0
- package/src/redux.ts +23 -0
- package/src/styles/AppColors.ts +408 -0
- package/src/styles/AppFonts.ts +8 -0
- package/src/styles/common.ts +209 -0
- package/src/styles/index.ts +1570 -0
- package/src/types/enums.ts +194 -0
- package/src/types/index.ts +34 -0
- 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';
|