react-native-inapp-inspector 1.0.3 → 1.0.5
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/README.md +3 -3
- package/dist/commonjs/components/AnalyticsEventCard.js +10 -10
- package/dist/commonjs/components/CodeSnippet.js +233 -10
- package/dist/commonjs/components/ConsoleLogCard.js +55 -9
- package/dist/commonjs/components/CopyButton.js +2 -1
- package/dist/commonjs/components/ErrorBoundary.d.ts +20 -0
- package/dist/commonjs/components/ErrorBoundary.js +332 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +5 -0
- package/dist/commonjs/components/NetworkIcons.js +45 -1
- package/dist/commonjs/customHooks/reduxLogger.d.ts +4 -0
- package/dist/commonjs/customHooks/reduxLogger.js +30 -0
- package/dist/commonjs/customHooks/webViewLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/webViewLogger.js +281 -246
- package/dist/commonjs/helpers/index.js +2 -1
- package/dist/commonjs/index.d.ts +5 -3
- package/dist/commonjs/index.js +685 -911
- package/dist/commonjs/styles/AppColors.d.ts +29 -1
- package/dist/commonjs/styles/AppColors.js +38 -2
- package/dist/commonjs/styles/index.d.ts +438 -229
- package/dist/commonjs/styles/index.js +448 -209
- package/dist/commonjs/types/index.d.ts +2 -2
- package/dist/esm/components/AnalyticsEventCard.js +10 -10
- package/dist/esm/components/CodeSnippet.js +232 -12
- package/dist/esm/components/ConsoleLogCard.js +55 -9
- package/dist/esm/components/CopyButton.js +2 -1
- package/dist/esm/components/ErrorBoundary.d.ts +20 -0
- package/dist/esm/components/ErrorBoundary.js +295 -0
- package/dist/esm/components/NetworkIcons.d.ts +5 -0
- package/dist/esm/components/NetworkIcons.js +39 -0
- package/dist/esm/customHooks/reduxLogger.d.ts +4 -0
- package/dist/esm/customHooks/reduxLogger.js +23 -0
- package/dist/esm/customHooks/webViewLogger.d.ts +2 -0
- package/dist/esm/customHooks/webViewLogger.js +281 -246
- package/dist/esm/helpers/index.js +2 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.js +683 -914
- package/dist/esm/styles/AppColors.d.ts +29 -1
- package/dist/esm/styles/AppColors.js +35 -1
- package/dist/esm/styles/index.d.ts +438 -229
- package/dist/esm/styles/index.js +412 -209
- package/dist/esm/types/index.d.ts +2 -2
- package/example/App.tsx +351 -127
- package/example/ios/Podfile.lock +26 -0
- package/example/metro.config.js +1 -1
- package/example/package-lock.json +20 -4
- package/example/package.json +4 -3
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
|
2
|
-
import { Alert, Animated, StyleSheet, FlatList,
|
|
2
|
+
import { Alert, Animated, StyleSheet, FlatList, Modal, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, } from 'react-native';
|
|
3
3
|
import Svg, { Circle, Path } from 'react-native-svg';
|
|
4
4
|
import LinearGradient from 'react-native-linear-gradient';
|
|
5
5
|
import { useNavigationState, NavigationContext } from '@react-navigation/native';
|
|
@@ -8,8 +8,6 @@ import TouchableScale from './components/TouchableScale';
|
|
|
8
8
|
import useAccordion from './customHooks/useAccordion';
|
|
9
9
|
import MetaAccordion from './components/MetaAccordion';
|
|
10
10
|
import CopyButton from './components/CopyButton';
|
|
11
|
-
import MiniBarChart from './components/MiniBarChart';
|
|
12
|
-
import MiniLineChart from './components/MiniLineChart';
|
|
13
11
|
import SectionHeader from './components/SectionHeader';
|
|
14
12
|
import EmptyState from './components/EmptyState';
|
|
15
13
|
import JsonViewer from './components/JsonViewer';
|
|
@@ -22,13 +20,14 @@ import { ConsoleLogCard } from './components/ConsoleLogCard';
|
|
|
22
20
|
import HighlightText from './components/HighlightText';
|
|
23
21
|
import CodeSnippet from './components/CodeSnippet';
|
|
24
22
|
// Helpers
|
|
25
|
-
import { formatDateTime, getStatusColor,
|
|
23
|
+
import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
|
|
26
24
|
// Assets
|
|
27
|
-
import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon,
|
|
25
|
+
import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, ChevronIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, DebugIcon, } from './components/NetworkIcons';
|
|
26
|
+
import ErrorBoundary from './components/ErrorBoundary';
|
|
28
27
|
// Stylesheet
|
|
29
28
|
import { AppColors } from './styles/AppColors';
|
|
30
29
|
import { AppFonts } from './styles/AppFonts';
|
|
31
|
-
import styles from './styles';
|
|
30
|
+
import styles, { toggleGlobalTheme } from './styles';
|
|
32
31
|
// Network
|
|
33
32
|
import { setupNetworkLogger, clearNetworkLogs, subscribeNetworkLogs, } from './customHooks/networkLogger';
|
|
34
33
|
// Console
|
|
@@ -39,6 +38,7 @@ import AnalyticsEventCard, { getEventColor, } from './components/AnalyticsEventC
|
|
|
39
38
|
import AnalyticsDetail from './components/AnalyticsDetail';
|
|
40
39
|
// WebView
|
|
41
40
|
import { getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
|
|
41
|
+
import { getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
|
|
42
42
|
import { METHOD_COLORS, STATUS_FILTERS } from './constants';
|
|
43
43
|
const NavigationTracker = ({ onStateChange }) => {
|
|
44
44
|
const navState = useNavigationState(state => state);
|
|
@@ -48,6 +48,9 @@ const NavigationTracker = ({ onStateChange }) => {
|
|
|
48
48
|
return null;
|
|
49
49
|
};
|
|
50
50
|
const NetworkInspector = () => {
|
|
51
|
+
const [isDark, setIsDark] = useState(false);
|
|
52
|
+
const [reduxState, setReduxState] = useState(null);
|
|
53
|
+
const [expandedReducers, setExpandedReducers] = useState({});
|
|
51
54
|
const [logs, setLogs] = useState([]);
|
|
52
55
|
const [visible, setVisible] = useState(false);
|
|
53
56
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -55,6 +58,14 @@ const NetworkInspector = () => {
|
|
|
55
58
|
const [selectedLogs, setSelectedLogs] = useState(new Set());
|
|
56
59
|
const [search, setSearch] = useState('');
|
|
57
60
|
const [detailSearch, setDetailSearch] = useState('');
|
|
61
|
+
const [reduxSearch, setReduxSearch] = useState('');
|
|
62
|
+
const [apiDetailActiveTab, setApiDetailActiveTab] = useState('metadata');
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (selected) {
|
|
65
|
+
setApiDetailActiveTab('metadata');
|
|
66
|
+
setDetailSearch('');
|
|
67
|
+
}
|
|
68
|
+
}, [selected]);
|
|
58
69
|
const [statusFilters, setStatusFilters] = useState(new Set());
|
|
59
70
|
const [methodFilters, setMethodFilters] = useState(new Set());
|
|
60
71
|
const [sectionFilters, setSectionFilters] = useState({});
|
|
@@ -96,20 +107,23 @@ const NetworkInspector = () => {
|
|
|
96
107
|
const [webViewNavHistory, setWebViewNavHistory] = useState([]);
|
|
97
108
|
const [webViewSubTab, setWebViewSubTab] = useState('html');
|
|
98
109
|
const [webViewSearch, setWebViewSearch] = useState('');
|
|
99
|
-
const [htmlSearch, setHtmlSearch] = useState('');
|
|
100
|
-
const [cssSearch, setCssSearch] = useState('');
|
|
101
|
-
const [jsSearch, setJsSearch] = useState('');
|
|
102
110
|
const [webViewHtml, setWebViewHtml] = useState('');
|
|
103
111
|
const [webViewCss, setWebViewCss] = useState('');
|
|
104
112
|
const [webViewJs, setWebViewJs] = useState('');
|
|
105
113
|
const [webViewHtmlUrl, setWebViewHtmlUrl] = useState('');
|
|
106
114
|
const [htmlSubTab, setHtmlSubTab] = useState('html');
|
|
115
|
+
const [isHtmlTabReady, setIsHtmlTabReady] = useState(true);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
setIsHtmlTabReady(false);
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
setIsHtmlTabReady(true);
|
|
120
|
+
}, 120);
|
|
121
|
+
return () => clearTimeout(timer);
|
|
122
|
+
}, [htmlSubTab, webViewSubTab, activeTab]);
|
|
107
123
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
|
108
124
|
const [analyticsSearch, setAnalyticsSearch] = useState('');
|
|
109
125
|
const [hideScreenView, setHideScreenView] = useState(true);
|
|
110
126
|
const [analyticsSubTab, setAnalyticsSubTab] = useState('ga_events');
|
|
111
|
-
const [groupByScreen, setGroupByScreen] = useState(false);
|
|
112
|
-
const [expandedScreens, setExpandedScreens] = useState(new Set());
|
|
113
127
|
const [topEventsExpanded, setTopEventsExpanded] = useState(true);
|
|
114
128
|
const [newEventIds, setNewEventIds] = useState(new Set());
|
|
115
129
|
const prevEventIdsRef = useRef(new Set());
|
|
@@ -249,6 +263,10 @@ const NetworkInspector = () => {
|
|
|
249
263
|
setWebViewHtmlUrl(getWebViewHtmlUrl());
|
|
250
264
|
}, 200);
|
|
251
265
|
});
|
|
266
|
+
setReduxState(getReduxState());
|
|
267
|
+
const unsubscribeRedux = subscribeReduxState(() => {
|
|
268
|
+
setReduxState(getReduxState());
|
|
269
|
+
});
|
|
252
270
|
return () => {
|
|
253
271
|
unsubscribe();
|
|
254
272
|
clearTimeout(timeoutId);
|
|
@@ -258,6 +276,7 @@ const NetworkInspector = () => {
|
|
|
258
276
|
clearTimeout(consoleTimeoutId);
|
|
259
277
|
unsubscribeWebView();
|
|
260
278
|
clearTimeout(webViewTimeoutId);
|
|
279
|
+
unsubscribeRedux();
|
|
261
280
|
};
|
|
262
281
|
}, []);
|
|
263
282
|
useEffect(() => {
|
|
@@ -542,39 +561,6 @@ const NetworkInspector = () => {
|
|
|
542
561
|
analytics: `${searchedLogs.filter(l => l.message.toLowerCase().includes('[analytics error]')).length}/${total}`,
|
|
543
562
|
};
|
|
544
563
|
}, [visibleConsoleLogs, logSearch]);
|
|
545
|
-
const groupedAnalyticsEvents = useMemo(() => {
|
|
546
|
-
if (!groupByScreen)
|
|
547
|
-
return [];
|
|
548
|
-
const map = new Map();
|
|
549
|
-
for (const e of filteredAnalyticsEvents) {
|
|
550
|
-
const routeInfo = logRouteMapRef.current.get(e.id + 1000000);
|
|
551
|
-
let screenName = e.screenName ||
|
|
552
|
-
e.screenClass ||
|
|
553
|
-
e.pageTitle ||
|
|
554
|
-
e.pageLocation ||
|
|
555
|
-
e.params?.firebase_screen ||
|
|
556
|
-
e.params?.screen_name ||
|
|
557
|
-
e.params?.firebase_screen_class ||
|
|
558
|
-
e.params?.screen_class;
|
|
559
|
-
if (!screenName) {
|
|
560
|
-
if (routeInfo && routeInfo.path !== 'Navigators') {
|
|
561
|
-
const parts = routeInfo.path.split(' ➔ ');
|
|
562
|
-
screenName = parts[parts.length - 1];
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
screenName = 'Unknown Component';
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (!map.has(screenName))
|
|
569
|
-
map.set(screenName, []);
|
|
570
|
-
map.get(screenName).push(e);
|
|
571
|
-
}
|
|
572
|
-
const sections = [];
|
|
573
|
-
for (const [title, data] of map.entries()) {
|
|
574
|
-
sections.push({ title, data });
|
|
575
|
-
}
|
|
576
|
-
return sections;
|
|
577
|
-
}, [groupByScreen, filteredAnalyticsEvents]);
|
|
578
564
|
const topEventsArray = useMemo(() => {
|
|
579
565
|
const freq = {};
|
|
580
566
|
filteredAnalyticsEvents.forEach(e => {
|
|
@@ -584,51 +570,6 @@ const NetworkInspector = () => {
|
|
|
584
570
|
});
|
|
585
571
|
return Object.entries(freq).sort((a, b) => b[1] - a[1]);
|
|
586
572
|
}, [filteredAnalyticsEvents]);
|
|
587
|
-
const groupedNetworkLogs = useMemo(() => {
|
|
588
|
-
if (!groupByScreen)
|
|
589
|
-
return [];
|
|
590
|
-
const map = new Map();
|
|
591
|
-
for (const l of filteredLogs) {
|
|
592
|
-
const routeInfo = logRouteMapRef.current.get(l.id);
|
|
593
|
-
let screenName = 'Unknown Origin';
|
|
594
|
-
if (routeInfo && routeInfo.path !== 'Navigators') {
|
|
595
|
-
const parts = routeInfo.path.split(' ➔ ');
|
|
596
|
-
screenName = parts[parts.length - 1];
|
|
597
|
-
}
|
|
598
|
-
if (!map.has(screenName))
|
|
599
|
-
map.set(screenName, []);
|
|
600
|
-
map.get(screenName).push(l);
|
|
601
|
-
}
|
|
602
|
-
const sections = [];
|
|
603
|
-
for (const [title, data] of map.entries()) {
|
|
604
|
-
sections.push({ title, data });
|
|
605
|
-
}
|
|
606
|
-
return sections;
|
|
607
|
-
}, [groupByScreen, filteredLogs]);
|
|
608
|
-
const toggleScreenAccordion = useCallback((title) => {
|
|
609
|
-
setExpandedScreens(prev => {
|
|
610
|
-
const next = new Set(prev);
|
|
611
|
-
if (next.has(title))
|
|
612
|
-
next.delete(title);
|
|
613
|
-
else
|
|
614
|
-
next.add(title);
|
|
615
|
-
return next;
|
|
616
|
-
});
|
|
617
|
-
}, []);
|
|
618
|
-
const renderScreenSectionHeader = useCallback(({ section: { title, data } }) => {
|
|
619
|
-
const isExpanded = expandedScreens.has(title);
|
|
620
|
-
return (<Pressable onPress={() => toggleScreenAccordion(title)} style={styles.screenSectionHeader}>
|
|
621
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
622
|
-
<View style={{
|
|
623
|
-
transform: [{ rotate: isExpanded ? '180deg' : '0deg' }],
|
|
624
|
-
}}>
|
|
625
|
-
<ExpandCollapseIcon color={AppColors.primaryBlack} size={14} isExpanded={false}/>
|
|
626
|
-
</View>
|
|
627
|
-
<Text style={styles.screenSectionTitle}>{title}</Text>
|
|
628
|
-
</View>
|
|
629
|
-
<Text style={styles.screenSectionCount}>{data.length} logs</Text>
|
|
630
|
-
</Pressable>);
|
|
631
|
-
}, [expandedScreens, toggleScreenAccordion]);
|
|
632
573
|
function closeModal() {
|
|
633
574
|
setVisible(false);
|
|
634
575
|
setSelected(null);
|
|
@@ -775,12 +716,317 @@ const NetworkInspector = () => {
|
|
|
775
716
|
toggleSectionFilter,
|
|
776
717
|
toggleSectionCollapse,
|
|
777
718
|
]);
|
|
719
|
+
const renderInsightsDashboard = () => {
|
|
720
|
+
const apiTotal = logs.length;
|
|
721
|
+
const apiErrors = logs.filter(l => (l.status != null && l.status >= 400) || l.status === 0 || l.status == null).length;
|
|
722
|
+
const apiSuccess = apiTotal - apiErrors;
|
|
723
|
+
const apiSuccessRate = apiTotal > 0 ? Math.round((apiSuccess / apiTotal) * 100) : 100;
|
|
724
|
+
const durations = logs.filter(l => l.duration != null).map(l => l.duration);
|
|
725
|
+
const avgTime = durations.length > 0
|
|
726
|
+
? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length)
|
|
727
|
+
: null;
|
|
728
|
+
const logTotal = visibleConsoleLogs.length;
|
|
729
|
+
const logErrors = visibleConsoleLogs.filter(l => l.type === 'error').length;
|
|
730
|
+
const logWarns = visibleConsoleLogs.filter(l => l.type === 'warn').length;
|
|
731
|
+
const logInfos = visibleConsoleLogs.filter(l => l.type === 'info').length;
|
|
732
|
+
const analyticsTotal = analyticsEvents.length;
|
|
733
|
+
const uniqueEvents = new Set(analyticsEvents.map(e => e.name)).size;
|
|
734
|
+
const screenViews = analyticsEvents.filter(e => e.name === 'screen_view' || e.name === 'page_view' || e.name === 'firebase_screen_class').length;
|
|
735
|
+
const webviewTotal = webViewNavHistory.length;
|
|
736
|
+
return (<View style={styles.dashboardContainer}>
|
|
737
|
+
{/* Module 1: APIs */}
|
|
738
|
+
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('apis')}>
|
|
739
|
+
<View style={styles.dashboardModuleHeader}>
|
|
740
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
741
|
+
<SignalIcon color={AppColors.purple} size={18}/>
|
|
742
|
+
<Text style={styles.dashboardModuleTitle}>APIs & Network</Text>
|
|
743
|
+
</View>
|
|
744
|
+
<Text style={styles.dashboardModuleGoText}>View Details →</Text>
|
|
745
|
+
</View>
|
|
746
|
+
<View style={styles.dashboardModuleGrid}>
|
|
747
|
+
<View style={styles.dashboardGridItem}>
|
|
748
|
+
<Text style={styles.dashboardGridVal}>{apiTotal}</Text>
|
|
749
|
+
<Text style={styles.dashboardGridLbl}>Requests</Text>
|
|
750
|
+
</View>
|
|
751
|
+
<View style={styles.dashboardGridItem}>
|
|
752
|
+
<Text style={[styles.dashboardGridVal, apiSuccessRate < 90 && { color: AppColors.warningIconGold }]}>
|
|
753
|
+
{apiSuccessRate}%
|
|
754
|
+
</Text>
|
|
755
|
+
<Text style={styles.dashboardGridLbl}>Success Rate</Text>
|
|
756
|
+
</View>
|
|
757
|
+
<View style={styles.dashboardGridItem}>
|
|
758
|
+
<Text style={[styles.dashboardGridVal, apiErrors > 0 && { color: AppColors.errorColor }]}>
|
|
759
|
+
{apiErrors}
|
|
760
|
+
</Text>
|
|
761
|
+
<Text style={styles.dashboardGridLbl}>Errors</Text>
|
|
762
|
+
</View>
|
|
763
|
+
<View style={styles.dashboardGridItem}>
|
|
764
|
+
<Text style={styles.dashboardGridVal}>
|
|
765
|
+
{avgTime != null ? `${avgTime}ms` : '—'}
|
|
766
|
+
</Text>
|
|
767
|
+
<Text style={styles.dashboardGridLbl}>Avg Latency</Text>
|
|
768
|
+
</View>
|
|
769
|
+
</View>
|
|
770
|
+
</TouchableScale>
|
|
771
|
+
|
|
772
|
+
{/* Module 2: Logs */}
|
|
773
|
+
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('logs')}>
|
|
774
|
+
<View style={styles.dashboardModuleHeader}>
|
|
775
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
776
|
+
<TerminalIcon color="#0D9488" size={18}/>
|
|
777
|
+
<Text style={styles.dashboardModuleTitle}>Console Logs</Text>
|
|
778
|
+
</View>
|
|
779
|
+
<Text style={styles.dashboardModuleGoText}>View Details →</Text>
|
|
780
|
+
</View>
|
|
781
|
+
<View style={styles.dashboardModuleGrid}>
|
|
782
|
+
<View style={styles.dashboardGridItem}>
|
|
783
|
+
<Text style={styles.dashboardGridVal}>{logTotal}</Text>
|
|
784
|
+
<Text style={styles.dashboardGridLbl}>Total Logs</Text>
|
|
785
|
+
</View>
|
|
786
|
+
<View style={styles.dashboardGridItem}>
|
|
787
|
+
<Text style={[styles.dashboardGridVal, { color: '#0D9488' }]}>{logInfos}</Text>
|
|
788
|
+
<Text style={styles.dashboardGridLbl}>Info</Text>
|
|
789
|
+
</View>
|
|
790
|
+
<View style={styles.dashboardGridItem}>
|
|
791
|
+
<Text style={[styles.dashboardGridVal, logWarns > 0 && { color: AppColors.warningIconGold }]}>
|
|
792
|
+
{logWarns}
|
|
793
|
+
</Text>
|
|
794
|
+
<Text style={styles.dashboardGridLbl}>Warnings</Text>
|
|
795
|
+
</View>
|
|
796
|
+
<View style={styles.dashboardGridItem}>
|
|
797
|
+
<Text style={[styles.dashboardGridVal, logErrors > 0 && { color: AppColors.errorColor }]}>
|
|
798
|
+
{logErrors}
|
|
799
|
+
</Text>
|
|
800
|
+
<Text style={styles.dashboardGridLbl}>Errors</Text>
|
|
801
|
+
</View>
|
|
802
|
+
</View>
|
|
803
|
+
</TouchableScale>
|
|
804
|
+
|
|
805
|
+
{/* Module 3: Analytics */}
|
|
806
|
+
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('analytics')}>
|
|
807
|
+
<View style={styles.dashboardModuleHeader}>
|
|
808
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
809
|
+
<AnalyticsIcon color="#EA580C" size={18}/>
|
|
810
|
+
<Text style={styles.dashboardModuleTitle}>Analytics Events</Text>
|
|
811
|
+
</View>
|
|
812
|
+
<Text style={styles.dashboardModuleGoText}>View Details →</Text>
|
|
813
|
+
</View>
|
|
814
|
+
<View style={styles.dashboardModuleGrid}>
|
|
815
|
+
<View style={styles.dashboardGridItem}>
|
|
816
|
+
<Text style={styles.dashboardGridVal}>{analyticsTotal}</Text>
|
|
817
|
+
<Text style={styles.dashboardGridLbl}>Total Events</Text>
|
|
818
|
+
</View>
|
|
819
|
+
<View style={styles.dashboardGridItem}>
|
|
820
|
+
<Text style={[styles.dashboardGridVal, { color: '#EA580C' }]}>{uniqueEvents}</Text>
|
|
821
|
+
<Text style={styles.dashboardGridLbl}>Unique Names</Text>
|
|
822
|
+
</View>
|
|
823
|
+
<View style={styles.dashboardGridItem}>
|
|
824
|
+
<Text style={styles.dashboardGridVal}>{screenViews}</Text>
|
|
825
|
+
<Text style={styles.dashboardGridLbl}>Screen Views</Text>
|
|
826
|
+
</View>
|
|
827
|
+
<View style={styles.dashboardGridItem}>
|
|
828
|
+
<Text style={styles.dashboardGridVal}>
|
|
829
|
+
{analyticsTotal > 0 ? Math.round(analyticsTotal / Math.max(1, logs.length / 5)) : 0}
|
|
830
|
+
</Text>
|
|
831
|
+
<Text style={styles.dashboardGridLbl}>Events Ratio</Text>
|
|
832
|
+
</View>
|
|
833
|
+
</View>
|
|
834
|
+
</TouchableScale>
|
|
835
|
+
|
|
836
|
+
{/* Module 4: WebView */}
|
|
837
|
+
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('webview')}>
|
|
838
|
+
<View style={styles.dashboardModuleHeader}>
|
|
839
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
840
|
+
<GlobeIcon color="#2563EB" size={18}/>
|
|
841
|
+
<Text style={styles.dashboardModuleTitle}>WebView Captures</Text>
|
|
842
|
+
</View>
|
|
843
|
+
<Text style={styles.dashboardModuleGoText}>View Details →</Text>
|
|
844
|
+
</View>
|
|
845
|
+
<View style={styles.dashboardModuleGrid}>
|
|
846
|
+
<View style={styles.dashboardGridItem}>
|
|
847
|
+
<Text style={styles.dashboardGridVal}>{webviewTotal}</Text>
|
|
848
|
+
<Text style={styles.dashboardGridLbl}>History Size</Text>
|
|
849
|
+
</View>
|
|
850
|
+
<View style={styles.dashboardGridItem}>
|
|
851
|
+
<Text style={[styles.dashboardGridVal, { color: '#16A34A' }]}>Active</Text>
|
|
852
|
+
<Text style={styles.dashboardGridLbl}>Status</Text>
|
|
853
|
+
</View>
|
|
854
|
+
<View style={styles.dashboardGridItem}>
|
|
855
|
+
<Text numberOfLines={1} style={styles.dashboardGridVal}>
|
|
856
|
+
{webviewTotal > 0 ? `${webViewNavHistory[0]?.title?.substring(0, 10) ?? ''}...` : '—'}
|
|
857
|
+
</Text>
|
|
858
|
+
<Text style={styles.dashboardGridLbl}>Last URL</Text>
|
|
859
|
+
</View>
|
|
860
|
+
</View>
|
|
861
|
+
</TouchableScale>
|
|
862
|
+
|
|
863
|
+
{/* Module 5: Redux Store */}
|
|
864
|
+
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('redux')}>
|
|
865
|
+
<View style={styles.dashboardModuleHeader}>
|
|
866
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
867
|
+
<TerminalIcon color={AppColors.purple} size={18}/>
|
|
868
|
+
<Text style={styles.dashboardModuleTitle}>Redux Store State</Text>
|
|
869
|
+
</View>
|
|
870
|
+
<Text style={styles.dashboardModuleGoText}>View Details →</Text>
|
|
871
|
+
</View>
|
|
872
|
+
{reduxState ? (<View style={{ paddingHorizontal: 12, paddingBottom: 12, gap: 6 }}>
|
|
873
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 }}>
|
|
874
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
|
|
875
|
+
REDUCER NAME
|
|
876
|
+
</Text>
|
|
877
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
|
|
878
|
+
SIZE / FIELDS
|
|
879
|
+
</Text>
|
|
880
|
+
</View>
|
|
881
|
+
{Object.keys(reduxState).map(key => {
|
|
882
|
+
const val = reduxState[key];
|
|
883
|
+
const fieldsCount = typeof val === 'object' && val !== null ? Object.keys(val).length : 0;
|
|
884
|
+
const sizeStr = getSize(val);
|
|
885
|
+
return (<View key={key} style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 2 }}>
|
|
886
|
+
<Text style={{ fontFamily: AppFonts.interMedium, fontSize: 12, color: AppColors.grayTextStrong }}>
|
|
887
|
+
{key}
|
|
888
|
+
</Text>
|
|
889
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayTextWeak }}>
|
|
890
|
+
{sizeStr} ({fieldsCount} fields)
|
|
891
|
+
</Text>
|
|
892
|
+
</View>);
|
|
893
|
+
})}
|
|
894
|
+
</View>) : (<View style={{ padding: 12, alignItems: 'center' }}>
|
|
895
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 12, color: AppColors.grayTextWeak }}>
|
|
896
|
+
No connected Redux store.
|
|
897
|
+
</Text>
|
|
898
|
+
</View>)}
|
|
899
|
+
</TouchableScale>
|
|
900
|
+
</View>);
|
|
901
|
+
};
|
|
902
|
+
const renderReduxTab = () => {
|
|
903
|
+
if (!reduxState) {
|
|
904
|
+
return (<View style={styles.emptyContainer}>
|
|
905
|
+
<View style={styles.emptyIconWrap}>
|
|
906
|
+
<TerminalIcon color={AppColors.purple} size={32}/>
|
|
907
|
+
</View>
|
|
908
|
+
<Text style={styles.emptyTitle}>No Redux Store</Text>
|
|
909
|
+
<Text style={styles.emptySub}>
|
|
910
|
+
To inspect Redux store, call connectReduxStore(store) at app start.
|
|
911
|
+
</Text>
|
|
912
|
+
</View>);
|
|
913
|
+
}
|
|
914
|
+
const reducerKeys = Object.keys(reduxState);
|
|
915
|
+
if (reducerKeys.length === 0) {
|
|
916
|
+
return (<View style={styles.emptyContainer}>
|
|
917
|
+
<Text style={styles.emptyTitle}>Empty Store</Text>
|
|
918
|
+
<Text style={styles.emptySub}>Connected store state is empty.</Text>
|
|
919
|
+
</View>);
|
|
920
|
+
}
|
|
921
|
+
return (<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
|
|
922
|
+
<View style={{
|
|
923
|
+
flexDirection: 'row',
|
|
924
|
+
alignItems: 'center',
|
|
925
|
+
justifyContent: 'space-between',
|
|
926
|
+
paddingHorizontal: 16,
|
|
927
|
+
paddingVertical: 12,
|
|
928
|
+
borderBottomWidth: 1,
|
|
929
|
+
borderBottomColor: AppColors.dividerColor,
|
|
930
|
+
backgroundColor: AppColors.primaryLight,
|
|
931
|
+
}}>
|
|
932
|
+
<Text style={{
|
|
933
|
+
fontFamily: AppFonts.interBold,
|
|
934
|
+
color: AppColors.grayTextStrong,
|
|
935
|
+
fontSize: 12,
|
|
936
|
+
textTransform: 'uppercase',
|
|
937
|
+
letterSpacing: 0.6,
|
|
938
|
+
}}>
|
|
939
|
+
Redux Store ({reducerKeys.length} Reducers)
|
|
940
|
+
</Text>
|
|
941
|
+
<CopyButton value={() => reduxState} label="Overall Store"/>
|
|
942
|
+
</View>
|
|
943
|
+
|
|
944
|
+
<View style={{
|
|
945
|
+
flexDirection: 'row',
|
|
946
|
+
alignItems: 'center',
|
|
947
|
+
backgroundColor: AppColors.grayBackground,
|
|
948
|
+
borderRadius: 8,
|
|
949
|
+
marginHorizontal: 16,
|
|
950
|
+
marginTop: 12,
|
|
951
|
+
marginBottom: 8,
|
|
952
|
+
paddingHorizontal: 10,
|
|
953
|
+
borderWidth: 1,
|
|
954
|
+
borderColor: AppColors.dividerColor,
|
|
955
|
+
height: 36,
|
|
956
|
+
}}>
|
|
957
|
+
<TextInput placeholder="Search Redux keys or values..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
|
|
958
|
+
flex: 1,
|
|
959
|
+
fontFamily: AppFonts.interRegular,
|
|
960
|
+
fontSize: 12,
|
|
961
|
+
color: AppColors.grayTextStrong,
|
|
962
|
+
padding: 0,
|
|
963
|
+
}} autoCorrect={false} autoCapitalize="none"/>
|
|
964
|
+
{reduxSearch.length > 0 && (<Pressable onPress={() => setReduxSearch('')} hitSlop={10}>
|
|
965
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
966
|
+
</Pressable>)}
|
|
967
|
+
</View>
|
|
968
|
+
|
|
969
|
+
{reducerKeys.map(key => {
|
|
970
|
+
const isExpanded = expandedReducers[key];
|
|
971
|
+
const val = reduxState[key];
|
|
972
|
+
return (<View key={key} style={{
|
|
973
|
+
backgroundColor: AppColors.primaryLight,
|
|
974
|
+
borderBottomWidth: 1,
|
|
975
|
+
borderBottomColor: AppColors.dividerColor,
|
|
976
|
+
}}>
|
|
977
|
+
<Pressable onPress={() => {
|
|
978
|
+
setExpandedReducers(prev => ({
|
|
979
|
+
...prev,
|
|
980
|
+
[key]: !prev[key],
|
|
981
|
+
}));
|
|
982
|
+
}} style={{
|
|
983
|
+
flexDirection: 'row',
|
|
984
|
+
alignItems: 'center',
|
|
985
|
+
justifyContent: 'space-between',
|
|
986
|
+
paddingHorizontal: 16,
|
|
987
|
+
paddingVertical: 12,
|
|
988
|
+
}}>
|
|
989
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1 }}>
|
|
990
|
+
<Animated.View style={{ transform: [{ rotate: isExpanded ? '0deg' : '-90deg' }] }}>
|
|
991
|
+
<ChevronIcon color={AppColors.grayTextWeak} size={14}/>
|
|
992
|
+
</Animated.View>
|
|
993
|
+
<Text style={{
|
|
994
|
+
fontFamily: AppFonts.interBold,
|
|
995
|
+
fontSize: 13,
|
|
996
|
+
color: AppColors.purple,
|
|
997
|
+
}}>
|
|
998
|
+
{key}
|
|
999
|
+
</Text>
|
|
1000
|
+
<Text style={{
|
|
1001
|
+
fontFamily: AppFonts.interRegular,
|
|
1002
|
+
fontSize: 11,
|
|
1003
|
+
color: AppColors.grayTextWeak,
|
|
1004
|
+
}}>
|
|
1005
|
+
({typeof val === 'object' && val !== null ? `${Object.keys(val).length} fields` : typeof val})
|
|
1006
|
+
</Text>
|
|
1007
|
+
</View>
|
|
1008
|
+
<CopyButton value={() => val} label={`${key} Reducer`}/>
|
|
1009
|
+
</Pressable>
|
|
1010
|
+
|
|
1011
|
+
{isExpanded && (<View style={{
|
|
1012
|
+
backgroundColor: AppColors.grayBackground,
|
|
1013
|
+
paddingHorizontal: 12,
|
|
1014
|
+
paddingVertical: 8,
|
|
1015
|
+
borderTopWidth: 1,
|
|
1016
|
+
borderTopColor: AppColors.dividerColor,
|
|
1017
|
+
}}>
|
|
1018
|
+
<JsonViewer data={val} search={reduxSearch}/>
|
|
1019
|
+
</View>)}
|
|
1020
|
+
</View>);
|
|
1021
|
+
})}
|
|
1022
|
+
</ScrollView>);
|
|
1023
|
+
};
|
|
778
1024
|
return (<>
|
|
779
1025
|
{hasNavigationContext && (<NavigationTracker onStateChange={setNavState}/>)}
|
|
780
1026
|
<TouchableScale style={styles.fabWrapper} onPress={() => setVisible(true)} hitSlop={10}>
|
|
781
1027
|
<Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
|
|
782
1028
|
<LinearGradient colors={[AppColors.purple, '#8F6EFF']} style={styles.fab}>
|
|
783
|
-
<
|
|
1029
|
+
<DebugIcon color="#FFFFFF" size={28}/>
|
|
784
1030
|
</LinearGradient>
|
|
785
1031
|
{(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
|
|
786
1032
|
styles.fabBadge,
|
|
@@ -796,23 +1042,24 @@ const NetworkInspector = () => {
|
|
|
796
1042
|
</TouchableScale>
|
|
797
1043
|
|
|
798
1044
|
<Modal visible={visible} animationType="slide" transparent>
|
|
799
|
-
{visible && (<
|
|
800
|
-
<
|
|
1045
|
+
{visible && (<ErrorBoundary onClose={closeModal}>
|
|
1046
|
+
<View style={styles.modalBackdrop}>
|
|
1047
|
+
<Pressable style={styles.modalBackdropPressable} onPress={closeModal}/>
|
|
1048
|
+
<View style={styles.modalContentCard}>
|
|
1049
|
+
<StatusBar translucent backgroundColor="transparent" barStyle="light-content"/>
|
|
801
1050
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1051
|
+
<LinearGradient colors={[AppColors.purple, '#6B4EFF']} style={styles.headerGradient}>
|
|
1052
|
+
<View style={styles.header}>
|
|
1053
|
+
<View style={[
|
|
1054
|
+
styles.headerLeft,
|
|
805
1055
|
{
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1056
|
+
flexDirection: 'row',
|
|
1057
|
+
alignItems: 'center',
|
|
1058
|
+
gap: 16,
|
|
1059
|
+
flex: (selected == null && selectedEvent == null) ? 5 : 1,
|
|
809
1060
|
},
|
|
810
1061
|
]}>
|
|
811
|
-
|
|
812
|
-
styles.headerLeft,
|
|
813
|
-
{ flexDirection: 'row', alignItems: 'center', gap: 16 },
|
|
814
|
-
]}>
|
|
815
|
-
<TouchableScale onPress={() => {
|
|
1062
|
+
<TouchableScale onPress={() => {
|
|
816
1063
|
requestAnimationFrame(() => {
|
|
817
1064
|
setSelected(null);
|
|
818
1065
|
setSelectedEvent(null);
|
|
@@ -822,100 +1069,48 @@ const NetworkInspector = () => {
|
|
|
822
1069
|
selected == null &&
|
|
823
1070
|
selectedEvent == null && { display: 'none' },
|
|
824
1071
|
]}>
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
{selected == null && selectedEvent == null ? (<View style={styles.headerButtonGroup}>
|
|
829
|
-
{/* Network Dropdown */}
|
|
830
|
-
<Pressable onPress={() => {
|
|
831
|
-
setShowNetworkMenu(prev => !prev);
|
|
832
|
-
setShowUiMenu(false);
|
|
833
|
-
}} style={[
|
|
834
|
-
styles.headerGroupButton,
|
|
835
|
-
['apis', 'analytics', 'logs'].includes(activeTab) &&
|
|
836
|
-
styles.headerGroupButtonActive,
|
|
837
|
-
]}>
|
|
838
|
-
<Text style={[
|
|
839
|
-
styles.headerGroupButtonText,
|
|
840
|
-
['apis', 'analytics', 'logs'].includes(activeTab) && { color: '#FFFFFF' },
|
|
841
|
-
]}>
|
|
842
|
-
APIs
|
|
843
|
-
</Text>
|
|
844
|
-
<View style={{
|
|
845
|
-
transform: [
|
|
846
|
-
{ rotate: showNetworkMenu ? '180deg' : '0deg' },
|
|
847
|
-
],
|
|
848
|
-
}}>
|
|
849
|
-
<ChevronIcon color={['apis', 'analytics', 'logs'].includes(activeTab)
|
|
850
|
-
? '#FFFFFF'
|
|
851
|
-
: 'rgba(255, 255, 255, 0.6)'} size={10}/>
|
|
852
|
-
</View>
|
|
853
|
-
</Pressable>
|
|
1072
|
+
<WhiteBackNavigation />
|
|
1073
|
+
</TouchableScale>
|
|
854
1074
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
setShowUiMenu(prev => !prev);
|
|
858
|
-
setShowNetworkMenu(false);
|
|
859
|
-
}} style={[
|
|
860
|
-
styles.headerGroupButton,
|
|
861
|
-
activeTab === 'webview' &&
|
|
862
|
-
styles.headerGroupButtonActive,
|
|
863
|
-
]}>
|
|
864
|
-
<Text style={[
|
|
865
|
-
styles.headerGroupButtonText,
|
|
866
|
-
activeTab === 'webview' && { color: '#FFFFFF' },
|
|
867
|
-
]}>
|
|
868
|
-
UI
|
|
869
|
-
</Text>
|
|
870
|
-
<View style={{
|
|
871
|
-
transform: [
|
|
872
|
-
{ rotate: showUiMenu ? '180deg' : '0deg' },
|
|
873
|
-
],
|
|
874
|
-
}}>
|
|
875
|
-
<ChevronIcon color={activeTab === 'webview'
|
|
876
|
-
? '#FFFFFF'
|
|
877
|
-
: 'rgba(255, 255, 255, 0.6)'} size={10}/>
|
|
878
|
-
</View>
|
|
879
|
-
</Pressable>
|
|
880
|
-
</View>) : null}
|
|
881
|
-
</View>
|
|
1075
|
+
{selected == null && selectedEvent == null ? (<Text style={styles.headerTitle}>RN-InApp-Inspector</Text>) : null}
|
|
1076
|
+
</View>
|
|
882
1077
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1078
|
+
<View style={styles.headerCenter}>
|
|
1079
|
+
{selected != null ? (<View style={styles.headerDetailCenter}>
|
|
1080
|
+
<View style={styles.headerDetailRow}>
|
|
1081
|
+
<View style={[
|
|
887
1082
|
styles.headerMethodBadge,
|
|
888
1083
|
{
|
|
889
1084
|
backgroundColor: METHOD_COLORS[selected.method] ??
|
|
890
1085
|
AppColors.grayText,
|
|
891
1086
|
},
|
|
892
1087
|
]}>
|
|
893
|
-
|
|
894
|
-
|
|
1088
|
+
<Text style={styles.headerMethodText}>
|
|
1089
|
+
{selected.method}
|
|
1090
|
+
</Text>
|
|
1091
|
+
</View>
|
|
1092
|
+
<Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
|
|
1093
|
+
{detailTitle}
|
|
895
1094
|
</Text>
|
|
896
1095
|
</View>
|
|
897
|
-
<
|
|
898
|
-
{
|
|
899
|
-
</Text>
|
|
900
|
-
</View>
|
|
901
|
-
<View style={styles.headerDetailSubRow}>
|
|
902
|
-
<View style={[
|
|
1096
|
+
<View style={styles.headerDetailSubRow}>
|
|
1097
|
+
<View style={[
|
|
903
1098
|
styles.headerStatusDot,
|
|
904
1099
|
{ backgroundColor: getStatusColor(selected.status) },
|
|
905
1100
|
]}/>
|
|
906
|
-
|
|
907
|
-
|
|
1101
|
+
<Text style={styles.headerSubTitle}>
|
|
1102
|
+
{selected.status === 0
|
|
908
1103
|
? 'Failed'
|
|
909
1104
|
: selected.status ?? 'Pending'}{' '}
|
|
910
|
-
|
|
911
|
-
|
|
1105
|
+
•{' '}
|
|
1106
|
+
{selected.duration != null
|
|
912
1107
|
? `${selected.duration}ms`
|
|
913
1108
|
: '-'}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1109
|
+
</Text>
|
|
1110
|
+
</View>
|
|
1111
|
+
</View>) : selectedEvent != null ? (<View style={styles.headerDetailCenter}>
|
|
1112
|
+
<View style={styles.headerDetailRow}>
|
|
1113
|
+
<View style={[
|
|
919
1114
|
styles.headerMethodBadge,
|
|
920
1115
|
{
|
|
921
1116
|
backgroundColor: selectedEvent.source === 'firebase'
|
|
@@ -923,16 +1118,16 @@ const NetworkInspector = () => {
|
|
|
923
1118
|
: 'rgba(124,92,191,0.3)',
|
|
924
1119
|
},
|
|
925
1120
|
]}>
|
|
926
|
-
|
|
927
|
-
|
|
1121
|
+
<Text style={styles.headerMethodText}>
|
|
1122
|
+
{selectedEvent.source === 'firebase' ? 'FB' : 'MAN'}
|
|
1123
|
+
</Text>
|
|
1124
|
+
</View>
|
|
1125
|
+
<Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
|
|
1126
|
+
{selectedEvent.name}
|
|
928
1127
|
</Text>
|
|
929
1128
|
</View>
|
|
930
|
-
<
|
|
931
|
-
{
|
|
932
|
-
</Text>
|
|
933
|
-
</View>
|
|
934
|
-
<View style={styles.headerDetailSubRow}>
|
|
935
|
-
<View style={[
|
|
1129
|
+
<View style={styles.headerDetailSubRow}>
|
|
1130
|
+
<View style={[
|
|
936
1131
|
styles.headerStatusDot,
|
|
937
1132
|
{
|
|
938
1133
|
backgroundColor: selectedEvent.source === 'firebase'
|
|
@@ -940,133 +1135,76 @@ const NetworkInspector = () => {
|
|
|
940
1135
|
: AppColors.purple,
|
|
941
1136
|
},
|
|
942
1137
|
]}/>
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1138
|
+
<Text style={styles.headerSubTitle}>
|
|
1139
|
+
{Object.keys(selectedEvent.params).length} param
|
|
1140
|
+
{Object.keys(selectedEvent.params).length !== 1
|
|
946
1141
|
? 's'
|
|
947
1142
|
: ''}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
{activeTab === 'apis'
|
|
955
|
-
? `${logs.length} API${logs.length !== 1 ? 's' : ''}`
|
|
956
|
-
: activeTab === 'logs'
|
|
957
|
-
? `${visibleConsoleLogs.length} log${visibleConsoleLogs.length !== 1 ? 's' : ''}`
|
|
958
|
-
: activeTab === 'analytics'
|
|
959
|
-
? `${analyticsEvents.length} event${analyticsEvents.length !== 1 ? 's' : ''}`
|
|
960
|
-
: `${webViewLogs.length} log${webViewLogs.length !== 1 ? 's' : ''}`}
|
|
961
|
-
</Text>
|
|
962
|
-
</View>)}
|
|
963
|
-
</View>
|
|
964
|
-
|
|
965
|
-
<View style={styles.headerRight}>
|
|
966
|
-
<TouchableScale onPress={closeModal} hitSlop={15} style={styles.iconBtnMinimal}>
|
|
967
|
-
<CloseWhite />
|
|
968
|
-
</TouchableScale>
|
|
969
|
-
</View>
|
|
970
|
-
</View>
|
|
971
|
-
</LinearGradient>
|
|
1143
|
+
{' · '}
|
|
1144
|
+
{selectedEvent.source}
|
|
1145
|
+
</Text>
|
|
1146
|
+
</View>
|
|
1147
|
+
</View>) : null}
|
|
1148
|
+
</View>
|
|
972
1149
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
setShowNetworkMenu(false);
|
|
982
|
-
setShowUiMenu(false);
|
|
983
|
-
}}/>)}
|
|
1150
|
+
<View style={styles.headerRight}>
|
|
1151
|
+
<TouchableScale onPress={() => {
|
|
1152
|
+
const newTheme = !isDark;
|
|
1153
|
+
setIsDark(newTheme);
|
|
1154
|
+
toggleGlobalTheme(newTheme);
|
|
1155
|
+
}} hitSlop={15} style={[styles.closeButtonCircle, { marginRight: 8 }]}>
|
|
1156
|
+
{isDark ? (<SunIcon color="#FFFFFF" size={16}/>) : (<MoonIcon color="#FFFFFF" size={16}/>)}
|
|
1157
|
+
</TouchableScale>
|
|
984
1158
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
fontFamily: AppFonts.interMedium,
|
|
992
|
-
fontSize: 11.5,
|
|
993
|
-
color: AppColors.grayTextWeak,
|
|
994
|
-
}}>
|
|
995
|
-
{['apis', 'analytics', 'logs'].includes(activeTab)
|
|
996
|
-
? 'APIs'
|
|
997
|
-
: 'UI'}
|
|
998
|
-
{' ➔ '}
|
|
999
|
-
<Text style={{
|
|
1000
|
-
color: AppColors.purple,
|
|
1001
|
-
fontFamily: AppFonts.interBold,
|
|
1002
|
-
}}>
|
|
1003
|
-
{activeTab === 'apis'
|
|
1004
|
-
? 'APIs'
|
|
1005
|
-
: activeTab === 'analytics'
|
|
1006
|
-
? 'Analytics'
|
|
1007
|
-
: activeTab === 'logs'
|
|
1008
|
-
? 'Logs'
|
|
1009
|
-
: 'WebView'}
|
|
1010
|
-
</Text>
|
|
1011
|
-
</Text>
|
|
1012
|
-
</View>)}
|
|
1159
|
+
<TouchableScale onPress={closeModal} hitSlop={15} style={styles.closeButtonCircle}>
|
|
1160
|
+
<CloseWhite size={16}/>
|
|
1161
|
+
</TouchableScale>
|
|
1162
|
+
</View>
|
|
1163
|
+
</View>
|
|
1164
|
+
</LinearGradient>
|
|
1013
1165
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1166
|
+
{/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
|
|
1167
|
+
{selected == null && selectedEvent == null ? (<View style={styles.tabBarContainer}>
|
|
1168
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingRight: 16 }}>
|
|
1169
|
+
{[
|
|
1170
|
+
{ key: 'insights', label: 'Insights', count: 0, icon: 'insights' },
|
|
1171
|
+
{ key: 'apis', label: 'APIs', count: logs.length, icon: 'apis' },
|
|
1172
|
+
{ key: 'logs', label: 'Logs', count: consoleLogs.length, icon: 'logs' },
|
|
1173
|
+
{ key: 'analytics', label: 'Analytics', count: analyticsEvents.length, icon: 'analytics' },
|
|
1174
|
+
{ key: 'webview', label: 'WebView', count: webViewNavHistory.length, icon: 'webview' },
|
|
1175
|
+
{ key: 'redux', label: 'Redux', count: 0, icon: 'redux' },
|
|
1176
|
+
].map(tab => {
|
|
1177
|
+
const isActive = activeTab === tab.key;
|
|
1178
|
+
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
1179
|
+
const countLabel = tab.count > 9 ? '9+' : String(tab.count);
|
|
1180
|
+
return (<TouchableScale key={tab.key} onPress={() => {
|
|
1181
|
+
requestAnimationFrame(() => {
|
|
1182
|
+
setActiveTab(tab.key);
|
|
1183
|
+
});
|
|
1184
|
+
}} style={[
|
|
1185
|
+
styles.contentTabButton,
|
|
1186
|
+
isActive && styles.contentTabButtonActive,
|
|
1187
|
+
]}>
|
|
1188
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1189
|
+
{tab.icon === 'insights' && <InsightsIcon color={iconColor} size={14}/>}
|
|
1190
|
+
{tab.icon === 'apis' && <SignalIcon color={iconColor} size={14}/>}
|
|
1191
|
+
{tab.icon === 'logs' && <TerminalIcon color={iconColor} size={14}/>}
|
|
1192
|
+
{tab.icon === 'analytics' && <AnalyticsIcon color={iconColor} size={14}/>}
|
|
1193
|
+
{tab.icon === 'webview' && <GlobeIcon color={iconColor} size={14}/>}
|
|
1194
|
+
{tab.icon === 'redux' && <TerminalIcon color={iconColor} size={14}/>}
|
|
1195
|
+
<Text numberOfLines={1} ellipsizeMode="tail" style={[
|
|
1196
|
+
styles.contentTabButtonText,
|
|
1197
|
+
isActive && styles.contentTabButtonTextActive,
|
|
1198
|
+
]}>
|
|
1199
|
+
{tab.label} {tab.count > 0 ? `(${countLabel})` : ''}
|
|
1200
|
+
</Text>
|
|
1201
|
+
</View>
|
|
1202
|
+
</TouchableScale>);
|
|
1043
1203
|
})}
|
|
1044
|
-
|
|
1204
|
+
</ScrollView>
|
|
1205
|
+
</View>) : null}
|
|
1206
|
+
|
|
1045
1207
|
|
|
1046
|
-
{showUiMenu && selected == null && selectedEvent == null && (<View style={[styles.menuDropdown, { left: 120 }]}>
|
|
1047
|
-
{['webview'].map(tab => {
|
|
1048
|
-
const isActive = activeTab === tab;
|
|
1049
|
-
return (<Pressable key={tab} style={[
|
|
1050
|
-
styles.dropdownItem,
|
|
1051
|
-
isActive && { backgroundColor: `${AppColors.purple}12` },
|
|
1052
|
-
]} onPress={() => {
|
|
1053
|
-
setActiveTab(tab);
|
|
1054
|
-
setShowUiMenu(false);
|
|
1055
|
-
}} hitSlop={10}>
|
|
1056
|
-
<Text style={{
|
|
1057
|
-
fontFamily: AppFonts.interMedium,
|
|
1058
|
-
color: isActive
|
|
1059
|
-
? AppColors.purple
|
|
1060
|
-
: AppColors.primaryBlack,
|
|
1061
|
-
}}>
|
|
1062
|
-
WebView{' '}
|
|
1063
|
-
{webViewLogs.length > 0
|
|
1064
|
-
? `(${webViewLogs.length})`
|
|
1065
|
-
: ''}
|
|
1066
|
-
</Text>
|
|
1067
|
-
</Pressable>);
|
|
1068
|
-
})}
|
|
1069
|
-
</View>)}
|
|
1070
1208
|
|
|
1071
1209
|
{/* ─── Secondary Tab Bar for Analytics ──────────────────────── */}
|
|
1072
1210
|
{isReady && activeTab === 'analytics' && selectedEvent == null && (<View>
|
|
@@ -1080,14 +1218,6 @@ const NetworkInspector = () => {
|
|
|
1080
1218
|
</Pressable>)}
|
|
1081
1219
|
</View>
|
|
1082
1220
|
{analyticsSubTab === 'ga_events' && (<View style={styles.toolbarRight}>
|
|
1083
|
-
<TouchableScale style={[
|
|
1084
|
-
styles.toolbarBtn,
|
|
1085
|
-
groupByScreen && styles.toolbarBtnActive,
|
|
1086
|
-
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1087
|
-
<MapPinIcon color={groupByScreen
|
|
1088
|
-
? AppColors.purple
|
|
1089
|
-
: AppColors.grayTextStrong} size={18}/>
|
|
1090
|
-
</TouchableScale>
|
|
1091
1221
|
<TouchableScale style={[
|
|
1092
1222
|
styles.toolbarBtn,
|
|
1093
1223
|
!hideScreenView && styles.toolbarBtnActive,
|
|
@@ -1181,17 +1311,19 @@ const NetworkInspector = () => {
|
|
|
1181
1311
|
</View>
|
|
1182
1312
|
</View>)}
|
|
1183
1313
|
|
|
1184
|
-
{isReady ? (activeTab === '
|
|
1314
|
+
{isReady ? (activeTab === 'insights' ? (<ScrollView style={styles.insightsContainer} contentContainerStyle={styles.insightsContent} showsVerticalScrollIndicator={false}>
|
|
1315
|
+
{renderInsightsDashboard()}
|
|
1316
|
+
</ScrollView>) : activeTab === 'analytics' ? (selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : analyticsSubTab === 'top_events' ? (<FlatList data={topEventsArray} keyExtractor={item => item[0]} contentContainerStyle={[
|
|
1185
1317
|
styles.listContent,
|
|
1186
1318
|
{ paddingHorizontal: 16, paddingTop: 16 },
|
|
1187
1319
|
]} renderItem={({ item: [name, count] }) => {
|
|
1188
1320
|
const maxCount = topEventsArray[0]?.[1] || 1;
|
|
1189
1321
|
const color = getEventColor(name);
|
|
1190
1322
|
return (<View style={[
|
|
1191
|
-
|
|
1323
|
+
styles.analyticsTopEventsCard,
|
|
1192
1324
|
{ marginBottom: 12, paddingVertical: 16 },
|
|
1193
1325
|
]}>
|
|
1194
|
-
<View style={
|
|
1326
|
+
<View style={styles.analyticsTopEventRow}>
|
|
1195
1327
|
<View style={{
|
|
1196
1328
|
flexDirection: 'row',
|
|
1197
1329
|
alignItems: 'center',
|
|
@@ -1199,7 +1331,7 @@ const NetworkInspector = () => {
|
|
|
1199
1331
|
flex: 1,
|
|
1200
1332
|
}}>
|
|
1201
1333
|
<View style={[
|
|
1202
|
-
|
|
1334
|
+
styles.analyticsIconCircle,
|
|
1203
1335
|
{ backgroundColor: `${color}1A` },
|
|
1204
1336
|
]}>
|
|
1205
1337
|
<Svg width={14} height={14} viewBox="0 0 24 24" fill={color}>
|
|
@@ -1207,19 +1339,19 @@ const NetworkInspector = () => {
|
|
|
1207
1339
|
<Path d="M7 14l3-3 4 4 6-6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
|
1208
1340
|
</Svg>
|
|
1209
1341
|
</View>
|
|
1210
|
-
<Text style={
|
|
1342
|
+
<Text style={styles.analyticsTopEventName} numberOfLines={2}>
|
|
1211
1343
|
{name}
|
|
1212
1344
|
</Text>
|
|
1213
1345
|
</View>
|
|
1214
|
-
<View style={
|
|
1346
|
+
<View style={styles.analyticsTopEventBarWrap}>
|
|
1215
1347
|
<LinearGradient colors={[color, `${color}99`]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={[
|
|
1216
|
-
|
|
1348
|
+
styles.analyticsTopEventBar,
|
|
1217
1349
|
{
|
|
1218
1350
|
width: `${Math.max(6, (count / maxCount) * 100)}%`,
|
|
1219
1351
|
},
|
|
1220
1352
|
]}/>
|
|
1221
1353
|
</View>
|
|
1222
|
-
<Text style={
|
|
1354
|
+
<Text style={styles.analyticsTopEventCount}>
|
|
1223
1355
|
{count}
|
|
1224
1356
|
</Text>
|
|
1225
1357
|
</View>
|
|
@@ -1229,39 +1361,7 @@ const NetworkInspector = () => {
|
|
|
1229
1361
|
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1230
1362
|
</View>
|
|
1231
1363
|
<Text style={styles.emptyTitle}>No Top Events</Text>
|
|
1232
|
-
</View>}/>) :
|
|
1233
|
-
if (expandedScreens.has(section.title))
|
|
1234
|
-
return null;
|
|
1235
|
-
const events = section.data;
|
|
1236
|
-
const prev = events[index + 1];
|
|
1237
|
-
const next = events[index - 1];
|
|
1238
|
-
const msSincePrev = prev
|
|
1239
|
-
? item.timestamp - prev.timestamp
|
|
1240
|
-
: undefined;
|
|
1241
|
-
const thisMin = Math.floor(item.timestamp / 60000);
|
|
1242
|
-
const nextMin = next
|
|
1243
|
-
? Math.floor(next.timestamp / 60000)
|
|
1244
|
-
: -1;
|
|
1245
|
-
const showTimestamp = index === 0 || thisMin !== nextMin;
|
|
1246
|
-
return (<AnalyticsEventCard event={item} onPress={() => setSelectedEvent(item)} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === events.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={section.title}/>);
|
|
1247
|
-
}} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews ListHeaderComponent={null} ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
1248
|
-
<View style={styles.emptyIconWrap}>
|
|
1249
|
-
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1250
|
-
</View>
|
|
1251
|
-
<Text style={styles.emptyTitle}>
|
|
1252
|
-
{analyticsSearch.length > 0
|
|
1253
|
-
? 'No matching events'
|
|
1254
|
-
: 'No analytics events yet'}
|
|
1255
|
-
</Text>
|
|
1256
|
-
<Text style={styles.emptySub}>
|
|
1257
|
-
{analyticsSearch.length > 0
|
|
1258
|
-
? 'Try adjusting your search.'
|
|
1259
|
-
: 'Call setupAnalyticsLogger(analytics()) at app start.'}
|
|
1260
|
-
</Text>
|
|
1261
|
-
</View>} contentContainerStyle={[
|
|
1262
|
-
styles.listContent,
|
|
1263
|
-
filteredAnalyticsEvents.length === 0 && { flexGrow: 1 },
|
|
1264
|
-
]} keyboardShouldPersistTaps="handled"/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
|
|
1364
|
+
</View>}/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
|
|
1265
1365
|
const prev = filteredAnalyticsEvents[index + 1];
|
|
1266
1366
|
const next = filteredAnalyticsEvents[index - 1];
|
|
1267
1367
|
const msSincePrev = prev
|
|
@@ -1291,7 +1391,7 @@ const NetworkInspector = () => {
|
|
|
1291
1391
|
}
|
|
1292
1392
|
return screenName;
|
|
1293
1393
|
})()}/>);
|
|
1294
|
-
}} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews
|
|
1394
|
+
}} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
1295
1395
|
<View style={styles.emptyIconWrap}>
|
|
1296
1396
|
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1297
1397
|
</View>
|
|
@@ -1308,84 +1408,7 @@ const NetworkInspector = () => {
|
|
|
1308
1408
|
</View>} contentContainerStyle={[
|
|
1309
1409
|
styles.listContent,
|
|
1310
1410
|
filteredAnalyticsEvents.length === 0 && { flexGrow: 1 },
|
|
1311
|
-
]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (
|
|
1312
|
-
if (expandedScreens.has(section.title))
|
|
1313
|
-
return null;
|
|
1314
|
-
return renderItem({
|
|
1315
|
-
item: {
|
|
1316
|
-
type: 'log',
|
|
1317
|
-
log: item,
|
|
1318
|
-
color: AppColors.purple,
|
|
1319
|
-
},
|
|
1320
|
-
});
|
|
1321
|
-
}} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
|
|
1322
|
-
{logs.length > 0 && (<View style={styles.dashboardCard}>
|
|
1323
|
-
<View style={styles.dashboardStatsRow}>
|
|
1324
|
-
<View style={styles.statBox}>
|
|
1325
|
-
<Text style={styles.statValue}>
|
|
1326
|
-
{stats.filtered < stats.total
|
|
1327
|
-
? stats.filtered
|
|
1328
|
-
: stats.total}
|
|
1329
|
-
</Text>
|
|
1330
|
-
<Text style={styles.statLabel}>
|
|
1331
|
-
{stats.filtered < stats.total
|
|
1332
|
-
? `of ${stats.total} Req`
|
|
1333
|
-
: 'Requests'}
|
|
1334
|
-
</Text>
|
|
1335
|
-
<View style={styles.miniGraphWrap}>
|
|
1336
|
-
<MiniBarChart data={stats.reqTrend} color={AppColors.purple}/>
|
|
1337
|
-
</View>
|
|
1338
|
-
</View>
|
|
1339
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1340
|
-
<View style={styles.statBox}>
|
|
1341
|
-
<Text style={[
|
|
1342
|
-
styles.statValue,
|
|
1343
|
-
stats.errors > 0 && {
|
|
1344
|
-
color: AppColors.errorColor,
|
|
1345
|
-
},
|
|
1346
|
-
]}>
|
|
1347
|
-
{stats.errors}
|
|
1348
|
-
</Text>
|
|
1349
|
-
<Text style={styles.statLabel}>Errors</Text>
|
|
1350
|
-
<View style={styles.miniGraphWrap}>
|
|
1351
|
-
<MiniBarChart data={stats.errorTrend} color={AppColors.errorColor} maxVal={1}/>
|
|
1352
|
-
</View>
|
|
1353
|
-
</View>
|
|
1354
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1355
|
-
<View style={styles.statBox}>
|
|
1356
|
-
<Text style={[
|
|
1357
|
-
styles.statValue,
|
|
1358
|
-
stats.avgDuration != null
|
|
1359
|
-
? {
|
|
1360
|
-
color: getDurationColor(stats.avgDuration),
|
|
1361
|
-
}
|
|
1362
|
-
: null,
|
|
1363
|
-
]}>
|
|
1364
|
-
{stats.avgDuration != null
|
|
1365
|
-
? `${stats.avgDuration}ms`
|
|
1366
|
-
: '—'}
|
|
1367
|
-
</Text>
|
|
1368
|
-
<Text style={styles.statLabel}>Avg Time</Text>
|
|
1369
|
-
<View style={styles.miniGraphWrap}>
|
|
1370
|
-
<MiniLineChart data={stats.durationTrend} color={AppColors.darkOrange}/>
|
|
1371
|
-
</View>
|
|
1372
|
-
</View>
|
|
1373
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1374
|
-
<View style={styles.statBox}>
|
|
1375
|
-
<Text style={[
|
|
1376
|
-
styles.statValue,
|
|
1377
|
-
{ color: AppColors.skyBlue },
|
|
1378
|
-
]}>
|
|
1379
|
-
{stats.size}
|
|
1380
|
-
</Text>
|
|
1381
|
-
<Text style={styles.statLabel}>Payload</Text>
|
|
1382
|
-
<View style={styles.miniGraphWrap}>
|
|
1383
|
-
<MiniBarChart data={stats.sizeTrend} color={AppColors.skyBlue}/>
|
|
1384
|
-
</View>
|
|
1385
|
-
</View>
|
|
1386
|
-
</View>
|
|
1387
|
-
</View>)}
|
|
1388
|
-
|
|
1411
|
+
]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (<FlatList data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
|
|
1389
1412
|
<View style={styles.toolbarRow}>
|
|
1390
1413
|
<View style={styles.searchContainer}>
|
|
1391
1414
|
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
@@ -1405,14 +1428,6 @@ const NetworkInspector = () => {
|
|
|
1405
1428
|
</View>)}
|
|
1406
1429
|
</TouchableScale>
|
|
1407
1430
|
|
|
1408
|
-
<TouchableScale style={[
|
|
1409
|
-
styles.toolbarBtn,
|
|
1410
|
-
groupByScreen && styles.toolbarBtnActive,
|
|
1411
|
-
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1412
|
-
<MapPinIcon color={groupByScreen
|
|
1413
|
-
? AppColors.purple
|
|
1414
|
-
: AppColors.grayTextStrong} size={18}/>
|
|
1415
|
-
</TouchableScale>
|
|
1416
1431
|
|
|
1417
1432
|
<TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
|
|
1418
1433
|
<SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
|
|
@@ -1456,237 +1471,22 @@ const NetworkInspector = () => {
|
|
|
1456
1471
|
});
|
|
1457
1472
|
}
|
|
1458
1473
|
}} hitSlop={10}>
|
|
1459
|
-
{active ? (<
|
|
1460
|
-
AppColors.purpleShade50,
|
|
1461
|
-
'#EAE5FF',
|
|
1462
|
-
]} style={[
|
|
1474
|
+
{active ? (<View style={[
|
|
1463
1475
|
styles.statusFilterChip,
|
|
1464
1476
|
styles.statusFilterActive,
|
|
1477
|
+
{ overflow: 'hidden' },
|
|
1465
1478
|
]}>
|
|
1466
|
-
<
|
|
1467
|
-
styles.statusFilterText,
|
|
1468
|
-
{ color: AppColors.purple },
|
|
1469
|
-
]}>
|
|
1470
|
-
{filter}
|
|
1471
|
-
</Text>
|
|
1472
|
-
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1473
|
-
<Text style={styles.statusFilterText}>
|
|
1474
|
-
{filter}
|
|
1475
|
-
</Text>
|
|
1476
|
-
</View>)}
|
|
1477
|
-
</TouchableScale>);
|
|
1478
|
-
})}
|
|
1479
|
-
</ScrollView>
|
|
1480
|
-
|
|
1481
|
-
<Text style={[styles.filtersHeading, { marginTop: 16 }]}>
|
|
1482
|
-
METHOD
|
|
1483
|
-
</Text>
|
|
1484
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1485
|
-
{availableMethods.map(filter => {
|
|
1486
|
-
const isAll = filter === 'ALL';
|
|
1487
|
-
const active = isAll
|
|
1488
|
-
? methodFilters.size === 0
|
|
1489
|
-
: methodFilters.has(filter);
|
|
1490
|
-
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1491
|
-
if (isAll) {
|
|
1492
|
-
setMethodFilters(new Set());
|
|
1493
|
-
}
|
|
1494
|
-
else {
|
|
1495
|
-
setMethodFilters(prev => {
|
|
1496
|
-
const next = new Set(prev);
|
|
1497
|
-
next.has(filter)
|
|
1498
|
-
? next.delete(filter)
|
|
1499
|
-
: next.add(filter);
|
|
1500
|
-
return next;
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
}} hitSlop={10}>
|
|
1504
|
-
{active ? (<LinearGradient colors={[
|
|
1505
|
-
AppColors.purpleShade50,
|
|
1506
|
-
'#EAE5FF',
|
|
1507
|
-
]} style={[
|
|
1508
|
-
styles.statusFilterChip,
|
|
1509
|
-
styles.statusFilterActive,
|
|
1510
|
-
]}>
|
|
1511
|
-
<Text style={[
|
|
1512
|
-
styles.statusFilterText,
|
|
1513
|
-
{ color: AppColors.purple },
|
|
1514
|
-
]}>
|
|
1515
|
-
{filter}
|
|
1516
|
-
</Text>
|
|
1517
|
-
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1518
|
-
<Text style={styles.statusFilterText}>
|
|
1519
|
-
{filter}
|
|
1520
|
-
</Text>
|
|
1521
|
-
</View>)}
|
|
1522
|
-
</TouchableScale>);
|
|
1523
|
-
})}
|
|
1524
|
-
</ScrollView>
|
|
1525
|
-
</View>
|
|
1526
|
-
</Animated.View>
|
|
1527
|
-
|
|
1528
|
-
{(search ||
|
|
1529
|
-
statusFilters.size > 0 ||
|
|
1530
|
-
methodFilters.size > 0) && (<Text style={styles.resultCount}>
|
|
1531
|
-
{filteredLogs.length === logs.length
|
|
1532
|
-
? `${logs.length} requests`
|
|
1533
|
-
: `${filteredLogs.length} of ${logs.length} filtered requests`}
|
|
1534
|
-
</Text>)}
|
|
1535
|
-
</View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
|
|
1536
|
-
styles.listContent,
|
|
1537
|
-
filteredLogs.length === 0 && { flexGrow: 1 },
|
|
1538
|
-
]} keyboardShouldPersistTaps="handled"/>) : (<FlatList data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
|
|
1539
|
-
{logs.length > 0 && (<View style={styles.dashboardCard}>
|
|
1540
|
-
<View style={styles.dashboardStatsRow}>
|
|
1541
|
-
<View style={styles.statBox}>
|
|
1542
|
-
<Text style={styles.statValue}>
|
|
1543
|
-
{stats.filtered < stats.total
|
|
1544
|
-
? stats.filtered
|
|
1545
|
-
: stats.total}
|
|
1546
|
-
</Text>
|
|
1547
|
-
<Text style={styles.statLabel}>
|
|
1548
|
-
{stats.filtered < stats.total
|
|
1549
|
-
? `of ${stats.total} Req`
|
|
1550
|
-
: 'Requests'}
|
|
1551
|
-
</Text>
|
|
1552
|
-
<View style={styles.miniGraphWrap}>
|
|
1553
|
-
<MiniBarChart data={stats.reqTrend} color={AppColors.purple}/>
|
|
1554
|
-
</View>
|
|
1555
|
-
</View>
|
|
1556
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1557
|
-
<View style={styles.statBox}>
|
|
1558
|
-
<Text style={[
|
|
1559
|
-
styles.statValue,
|
|
1560
|
-
stats.errors > 0 && {
|
|
1561
|
-
color: AppColors.errorColor,
|
|
1562
|
-
},
|
|
1563
|
-
]}>
|
|
1564
|
-
{stats.errors}
|
|
1565
|
-
</Text>
|
|
1566
|
-
<Text style={styles.statLabel}>Errors</Text>
|
|
1567
|
-
<View style={styles.miniGraphWrap}>
|
|
1568
|
-
<MiniBarChart data={stats.errorTrend} color={AppColors.errorColor} maxVal={1}/>
|
|
1569
|
-
</View>
|
|
1570
|
-
</View>
|
|
1571
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1572
|
-
<View style={styles.statBox}>
|
|
1573
|
-
<Text style={[
|
|
1574
|
-
styles.statValue,
|
|
1575
|
-
stats.avgDuration != null
|
|
1576
|
-
? {
|
|
1577
|
-
color: getDurationColor(stats.avgDuration),
|
|
1578
|
-
}
|
|
1579
|
-
: null,
|
|
1580
|
-
]}>
|
|
1581
|
-
{stats.avgDuration != null
|
|
1582
|
-
? `${stats.avgDuration}ms`
|
|
1583
|
-
: '—'}
|
|
1584
|
-
</Text>
|
|
1585
|
-
<Text style={styles.statLabel}>Avg Time</Text>
|
|
1586
|
-
<View style={styles.miniGraphWrap}>
|
|
1587
|
-
<MiniLineChart data={stats.durationTrend} color={AppColors.darkOrange}/>
|
|
1588
|
-
</View>
|
|
1589
|
-
</View>
|
|
1590
|
-
<View style={styles.dashboardStatDivider}/>
|
|
1591
|
-
<View style={styles.statBox}>
|
|
1592
|
-
<Text style={[
|
|
1593
|
-
styles.statValue,
|
|
1594
|
-
{ color: AppColors.skyBlue },
|
|
1595
|
-
]}>
|
|
1596
|
-
{stats.size}
|
|
1597
|
-
</Text>
|
|
1598
|
-
<Text style={styles.statLabel}>Payload</Text>
|
|
1599
|
-
<View style={styles.miniGraphWrap}>
|
|
1600
|
-
<MiniBarChart data={stats.sizeTrend} color={AppColors.skyBlue}/>
|
|
1601
|
-
</View>
|
|
1602
|
-
</View>
|
|
1603
|
-
</View>
|
|
1604
|
-
</View>)}
|
|
1605
|
-
|
|
1606
|
-
<View style={styles.toolbarRow}>
|
|
1607
|
-
<View style={styles.searchContainer}>
|
|
1608
|
-
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
1609
|
-
<TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1610
|
-
{search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
1611
|
-
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1612
|
-
</Pressable>)}
|
|
1613
|
-
</View>
|
|
1614
|
-
|
|
1615
|
-
<View style={styles.toolbarRight}>
|
|
1616
|
-
<TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
|
|
1617
|
-
<TrashIcon color={AppColors.grayTextStrong} size={18}/>
|
|
1618
|
-
{selectedLogs.size > 0 && (<View style={styles.trashBadge}>
|
|
1619
|
-
<Text style={styles.trashBadgeText}>
|
|
1620
|
-
{selectedLogs.size}
|
|
1621
|
-
</Text>
|
|
1622
|
-
</View>)}
|
|
1623
|
-
</TouchableScale>
|
|
1624
|
-
|
|
1625
|
-
<TouchableScale style={[
|
|
1626
|
-
styles.toolbarBtn,
|
|
1627
|
-
groupByScreen && styles.toolbarBtnActive,
|
|
1628
|
-
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1629
|
-
<MapPinIcon color={groupByScreen
|
|
1630
|
-
? AppColors.purple
|
|
1631
|
-
: AppColors.grayTextStrong} size={18}/>
|
|
1632
|
-
</TouchableScale>
|
|
1633
|
-
|
|
1634
|
-
<TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
|
|
1635
|
-
<SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
|
|
1636
|
-
</TouchableScale>
|
|
1637
|
-
|
|
1638
|
-
<TouchableScale style={[
|
|
1639
|
-
styles.toolbarBtn,
|
|
1640
|
-
filtersAccordion.isOpen &&
|
|
1641
|
-
styles.toolbarBtnActive,
|
|
1642
|
-
]} onPress={filtersAccordion.toggleOpen} hitSlop={10}>
|
|
1643
|
-
<FilterIcon color={filtersAccordion.isOpen
|
|
1644
|
-
? AppColors.purple
|
|
1645
|
-
: AppColors.grayTextStrong} size={18}/>
|
|
1646
|
-
</TouchableScale>
|
|
1647
|
-
</View>
|
|
1648
|
-
</View>
|
|
1649
|
-
|
|
1650
|
-
<Animated.View style={[
|
|
1651
|
-
filtersAccordion.bodyStyle,
|
|
1652
|
-
{ overflow: 'hidden' },
|
|
1653
|
-
]}>
|
|
1654
|
-
<View style={styles.filtersContainer}>
|
|
1655
|
-
<Text style={styles.filtersHeading}>STATUS</Text>
|
|
1656
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1657
|
-
{STATUS_FILTERS.map(filter => {
|
|
1658
|
-
const isAll = filter === 'ALL';
|
|
1659
|
-
const active = isAll
|
|
1660
|
-
? statusFilters.size === 0
|
|
1661
|
-
: statusFilters.has(filter);
|
|
1662
|
-
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1663
|
-
if (isAll) {
|
|
1664
|
-
setStatusFilters(new Set());
|
|
1665
|
-
}
|
|
1666
|
-
else {
|
|
1667
|
-
setStatusFilters(prev => {
|
|
1668
|
-
const next = new Set(prev);
|
|
1669
|
-
next.has(filter)
|
|
1670
|
-
? next.delete(filter)
|
|
1671
|
-
: next.add(filter);
|
|
1672
|
-
return next;
|
|
1673
|
-
});
|
|
1674
|
-
}
|
|
1675
|
-
}} hitSlop={10}>
|
|
1676
|
-
{active ? (<LinearGradient colors={[
|
|
1479
|
+
<LinearGradient colors={[
|
|
1677
1480
|
AppColors.purpleShade50,
|
|
1678
1481
|
'#EAE5FF',
|
|
1679
|
-
]} style={
|
|
1680
|
-
styles.statusFilterChip,
|
|
1681
|
-
styles.statusFilterActive,
|
|
1682
|
-
]}>
|
|
1482
|
+
]} style={StyleSheet.absoluteFill} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}/>
|
|
1683
1483
|
<Text style={[
|
|
1684
1484
|
styles.statusFilterText,
|
|
1685
1485
|
{ color: AppColors.purple },
|
|
1686
1486
|
]}>
|
|
1687
1487
|
{filter}
|
|
1688
1488
|
</Text>
|
|
1689
|
-
</
|
|
1489
|
+
</View>) : (<View style={styles.statusFilterChip}>
|
|
1690
1490
|
<Text style={styles.statusFilterText}>
|
|
1691
1491
|
{filter}
|
|
1692
1492
|
</Text>
|
|
@@ -1718,20 +1518,22 @@ const NetworkInspector = () => {
|
|
|
1718
1518
|
});
|
|
1719
1519
|
}
|
|
1720
1520
|
}} hitSlop={10}>
|
|
1721
|
-
{active ? (<
|
|
1722
|
-
AppColors.purpleShade50,
|
|
1723
|
-
'#EAE5FF',
|
|
1724
|
-
]} style={[
|
|
1521
|
+
{active ? (<View style={[
|
|
1725
1522
|
styles.statusFilterChip,
|
|
1726
1523
|
styles.statusFilterActive,
|
|
1524
|
+
{ overflow: 'hidden' },
|
|
1727
1525
|
]}>
|
|
1526
|
+
<LinearGradient colors={[
|
|
1527
|
+
AppColors.purpleShade50,
|
|
1528
|
+
'#EAE5FF',
|
|
1529
|
+
]} style={StyleSheet.absoluteFill} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}/>
|
|
1728
1530
|
<Text style={[
|
|
1729
1531
|
styles.statusFilterText,
|
|
1730
1532
|
{ color: AppColors.purple },
|
|
1731
1533
|
]}>
|
|
1732
1534
|
{filter}
|
|
1733
1535
|
</Text>
|
|
1734
|
-
</
|
|
1536
|
+
</View>) : (<View style={styles.statusFilterChip}>
|
|
1735
1537
|
<Text style={styles.statusFilterText}>
|
|
1736
1538
|
{filter}
|
|
1737
1539
|
</Text>
|
|
@@ -1752,7 +1554,7 @@ const NetworkInspector = () => {
|
|
|
1752
1554
|
</View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
|
|
1753
1555
|
styles.listContent,
|
|
1754
1556
|
filteredLogs.length === 0 && { flexGrow: 1 },
|
|
1755
|
-
]} keyboardShouldPersistTaps="handled"/>)
|
|
1557
|
+
]} keyboardShouldPersistTaps="handled"/>) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
|
|
1756
1558
|
<View style={{
|
|
1757
1559
|
backgroundColor: '#FFFFFF',
|
|
1758
1560
|
borderBottomWidth: 1,
|
|
@@ -2199,78 +2001,23 @@ const NetworkInspector = () => {
|
|
|
2199
2001
|
})}
|
|
2200
2002
|
</View>
|
|
2201
2003
|
<View style={{ flex: 1, padding: 12 }}>
|
|
2202
|
-
{
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
alignItems: 'center',
|
|
2206
|
-
marginBottom: 8,
|
|
2207
|
-
gap: 8,
|
|
2208
|
-
}}>
|
|
2209
|
-
<View style={[
|
|
2210
|
-
styles.searchContainer,
|
|
2211
|
-
{ flex: 1 },
|
|
2212
|
-
]}>
|
|
2213
|
-
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2214
|
-
<TextInput placeholder="Search HTML..." placeholderTextColor={AppColors.grayTextWeak} value={htmlSearch} onChangeText={setHtmlSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2215
|
-
{htmlSearch.length > 0 && (<Pressable onPress={() => setHtmlSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2216
|
-
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2217
|
-
</Pressable>)}
|
|
2218
|
-
</View>
|
|
2219
|
-
<CopyButton value={webViewHtml} label="HTML Source"/>
|
|
2220
|
-
</View>
|
|
2221
|
-
<CodeSnippet code={webViewHtml} language="html" search={htmlSearch}/>
|
|
2222
|
-
</View>) : (<Text style={{
|
|
2004
|
+
{!isHtmlTabReady ? (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
|
|
2005
|
+
<ActivityIndicator size="large" color={AppColors.purple}/>
|
|
2006
|
+
</View>) : htmlSubTab === 'html' ? (webViewHtml ? (<CodeSnippet code={webViewHtml} language="html"/>) : (<Text style={{
|
|
2223
2007
|
fontFamily: 'monospace',
|
|
2224
2008
|
fontSize: 11,
|
|
2225
2009
|
color: '#94A3B8',
|
|
2226
2010
|
padding: 12,
|
|
2227
2011
|
}}>
|
|
2228
2012
|
No HTML content captured.
|
|
2229
|
-
</Text>)) : htmlSubTab === 'css' ? (webViewCss ? (<
|
|
2230
|
-
<View style={{
|
|
2231
|
-
flexDirection: 'row',
|
|
2232
|
-
alignItems: 'center',
|
|
2233
|
-
marginBottom: 8,
|
|
2234
|
-
gap: 8,
|
|
2235
|
-
}}>
|
|
2236
|
-
<View style={[
|
|
2237
|
-
styles.searchContainer,
|
|
2238
|
-
{ flex: 1 },
|
|
2239
|
-
]}>
|
|
2240
|
-
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2241
|
-
<TextInput placeholder="Search CSS..." placeholderTextColor={AppColors.grayTextWeak} value={cssSearch} onChangeText={setCssSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2242
|
-
{cssSearch.length > 0 && (<Pressable onPress={() => setCssSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2243
|
-
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2244
|
-
</Pressable>)}
|
|
2245
|
-
</View>
|
|
2246
|
-
<CopyButton value={webViewCss} label="CSS Source"/>
|
|
2247
|
-
</View>
|
|
2248
|
-
<CodeSnippet code={webViewCss} language="css" search={cssSearch}/>
|
|
2249
|
-
</View>) : (<Text style={{
|
|
2013
|
+
</Text>)) : htmlSubTab === 'css' ? (webViewCss ? (<CodeSnippet code={webViewCss} language="css"/>) : (<Text style={{
|
|
2250
2014
|
fontFamily: 'monospace',
|
|
2251
2015
|
fontSize: 11,
|
|
2252
2016
|
color: '#94A3B8',
|
|
2253
2017
|
padding: 12,
|
|
2254
2018
|
}}>
|
|
2255
2019
|
No CSS styles detected on this page.
|
|
2256
|
-
</Text>)) : webViewJs ? (<
|
|
2257
|
-
<View style={{
|
|
2258
|
-
flexDirection: 'row',
|
|
2259
|
-
alignItems: 'center',
|
|
2260
|
-
marginBottom: 8,
|
|
2261
|
-
gap: 8,
|
|
2262
|
-
}}>
|
|
2263
|
-
<View style={[styles.searchContainer, { flex: 1 }]}>
|
|
2264
|
-
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2265
|
-
<TextInput placeholder="Search Javascript..." placeholderTextColor={AppColors.grayTextWeak} value={jsSearch} onChangeText={setJsSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2266
|
-
{jsSearch.length > 0 && (<Pressable onPress={() => setJsSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2267
|
-
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2268
|
-
</Pressable>)}
|
|
2269
|
-
</View>
|
|
2270
|
-
<CopyButton value={webViewJs} label="JS Source"/>
|
|
2271
|
-
</View>
|
|
2272
|
-
<CodeSnippet code={webViewJs} language="javascript" search={jsSearch}/>
|
|
2273
|
-
</View>) : (<Text style={{
|
|
2020
|
+
</Text>)) : webViewJs ? (<CodeSnippet code={webViewJs} language="javascript"/>) : (<Text style={{
|
|
2274
2021
|
fontFamily: 'monospace',
|
|
2275
2022
|
fontSize: 11,
|
|
2276
2023
|
color: '#94A3B8',
|
|
@@ -2366,60 +2113,89 @@ const NetworkInspector = () => {
|
|
|
2366
2113
|
styles.listContent,
|
|
2367
2114
|
filteredNavHistory.length === 0 && { flexGrow: 1 },
|
|
2368
2115
|
]} keyboardShouldPersistTaps="handled"/>)}
|
|
2369
|
-
</View>) : (<
|
|
2370
|
-
{
|
|
2116
|
+
</View>) : activeTab === 'redux' ? (renderReduxTab()) : (<View style={{ flex: 1 }}>
|
|
2117
|
+
{/* Non-scrollable details header */}
|
|
2118
|
+
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
2119
|
+
{(() => {
|
|
2371
2120
|
const routeInfo = logRouteMapRef.current.get(selected.id);
|
|
2372
2121
|
const screenPath = routeInfo && routeInfo.path !== 'Navigators'
|
|
2373
2122
|
? routeInfo.path.split(' ➔ ')
|
|
2374
2123
|
: [];
|
|
2375
|
-
const parts = ['APIs',
|
|
2376
|
-
return (<View style={{
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2124
|
+
const parts = ['APIs', ...screenPath];
|
|
2125
|
+
return (<View style={{
|
|
2126
|
+
flexDirection: 'row',
|
|
2127
|
+
alignItems: 'center',
|
|
2128
|
+
backgroundColor: AppColors.primaryLight,
|
|
2129
|
+
borderRadius: 8,
|
|
2130
|
+
paddingVertical: 8,
|
|
2131
|
+
paddingHorizontal: 12,
|
|
2132
|
+
borderWidth: 1,
|
|
2133
|
+
borderColor: AppColors.dividerColor,
|
|
2134
|
+
marginBottom: 12,
|
|
2135
|
+
marginTop: 4,
|
|
2381
2136
|
}}>
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2137
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
|
|
2138
|
+
flexDirection: 'row',
|
|
2139
|
+
alignItems: 'center',
|
|
2140
|
+
gap: 6,
|
|
2141
|
+
}}>
|
|
2142
|
+
{parts.map((part, index) => {
|
|
2143
|
+
const isLast = index === parts.length - 1;
|
|
2144
|
+
return (<React.Fragment key={index}>
|
|
2145
|
+
{index > 0 && (<Text style={{ color: AppColors.grayTextWeak, fontSize: 11, marginHorizontal: 2 }}>
|
|
2146
|
+
/
|
|
2147
|
+
</Text>)}
|
|
2148
|
+
<View style={isLast
|
|
2149
|
+
? {
|
|
2150
|
+
backgroundColor: `${AppColors.purple}12`,
|
|
2151
|
+
paddingHorizontal: 8,
|
|
2152
|
+
paddingVertical: 3,
|
|
2153
|
+
borderRadius: 6,
|
|
2154
|
+
}
|
|
2155
|
+
: {
|
|
2156
|
+
paddingHorizontal: 4,
|
|
2157
|
+
paddingVertical: 2,
|
|
2158
|
+
}}>
|
|
2159
|
+
<Text style={{
|
|
2160
|
+
fontFamily: isLast ? AppFonts.interBold : AppFonts.interMedium,
|
|
2161
|
+
fontSize: 11.5,
|
|
2162
|
+
color: isLast ? AppColors.purple : AppColors.grayText,
|
|
2163
|
+
}}>
|
|
2164
|
+
{part}
|
|
2165
|
+
</Text>
|
|
2166
|
+
</View>
|
|
2167
|
+
</React.Fragment>);
|
|
2168
|
+
})}
|
|
2169
|
+
</ScrollView>
|
|
2170
|
+
</View>);
|
|
2395
2171
|
})()}
|
|
2396
2172
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2173
|
+
<View style={styles.detailInfoBar}>
|
|
2174
|
+
<View style={styles.detailInfoTop}>
|
|
2175
|
+
<View style={{
|
|
2400
2176
|
flexDirection: 'row',
|
|
2401
2177
|
alignItems: 'center',
|
|
2402
2178
|
gap: 10,
|
|
2403
2179
|
}}>
|
|
2404
|
-
|
|
2180
|
+
<View style={[
|
|
2405
2181
|
styles.methodBadge,
|
|
2406
2182
|
{
|
|
2407
2183
|
backgroundColor: `${METHOD_COLORS[selected.method] ??
|
|
2408
2184
|
METHOD_COLORS.ALL}15`,
|
|
2409
2185
|
},
|
|
2410
2186
|
]}>
|
|
2411
|
-
|
|
2187
|
+
<Text style={[
|
|
2412
2188
|
styles.methodBadgeText,
|
|
2413
2189
|
{
|
|
2414
2190
|
color: METHOD_COLORS[selected.method] ??
|
|
2415
2191
|
METHOD_COLORS.ALL,
|
|
2416
2192
|
},
|
|
2417
2193
|
]}>
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2194
|
+
{selected.method}
|
|
2195
|
+
</Text>
|
|
2196
|
+
</View>
|
|
2421
2197
|
|
|
2422
|
-
|
|
2198
|
+
{selected.status != null && (<View style={[
|
|
2423
2199
|
styles.chip,
|
|
2424
2200
|
{
|
|
2425
2201
|
backgroundColor: selected.status === 0
|
|
@@ -2430,10 +2206,10 @@ const NetworkInspector = () => {
|
|
|
2430
2206
|
: `${getStatusColor(selected.status)}40`,
|
|
2431
2207
|
},
|
|
2432
2208
|
]}>
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2209
|
+
{selected.status === 0 ? (<FailIcon size={8} color={AppColors.errorColor}/>) : (<Svg width={6} height={6} viewBox="0 0 10 10" fill="none">
|
|
2210
|
+
<Circle cx="5" cy="5" r="5" fill={getStatusColor(selected.status)}/>
|
|
2211
|
+
</Svg>)}
|
|
2212
|
+
<Text style={[
|
|
2437
2213
|
styles.chipText,
|
|
2438
2214
|
{
|
|
2439
2215
|
color: selected.status === 0
|
|
@@ -2441,183 +2217,176 @@ const NetworkInspector = () => {
|
|
|
2441
2217
|
: getStatusColor(selected.status),
|
|
2442
2218
|
},
|
|
2443
2219
|
]}>
|
|
2444
|
-
|
|
2220
|
+
{selected.status === 0
|
|
2445
2221
|
? 'Failed'
|
|
2446
2222
|
: String(selected.status)}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2223
|
+
</Text>
|
|
2224
|
+
</View>)}
|
|
2225
|
+
</View>
|
|
2226
|
+
<View style={styles.detailInfoRight}>
|
|
2227
|
+
<TouchableScale style={styles.iconSquareBtn} onPress={() => Linking.openURL(detailDisplayUrl)} hitSlop={12}>
|
|
2228
|
+
<GlobeIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2229
|
+
</TouchableScale>
|
|
2230
|
+
<CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
|
|
2231
|
+
<CopyButton value={getCurlCommand(selected)} label="cURL" iconType="terminal"/>
|
|
2232
|
+
<CopyButton value={detailDisplayUrl} label="URL"/>
|
|
2233
|
+
</View>
|
|
2457
2234
|
</View>
|
|
2235
|
+
|
|
2236
|
+
<Pressable style={styles.detailUrlContainer} onPress={() => Linking.openURL(detailDisplayUrl)}>
|
|
2237
|
+
<Text selectable={true} style={styles.detailUrl}>
|
|
2238
|
+
{detailDisplayUrl}
|
|
2239
|
+
</Text>
|
|
2240
|
+
</Pressable>
|
|
2458
2241
|
</View>
|
|
2242
|
+
</View>
|
|
2459
2243
|
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2244
|
+
{/* Sticky Segment Control */}
|
|
2245
|
+
<View style={{
|
|
2246
|
+
flexDirection: 'row',
|
|
2247
|
+
backgroundColor: AppColors.grayBackground,
|
|
2248
|
+
borderRadius: 10,
|
|
2249
|
+
padding: 3,
|
|
2250
|
+
marginHorizontal: 6,
|
|
2251
|
+
marginBottom: 10,
|
|
2252
|
+
marginTop: 6,
|
|
2253
|
+
borderWidth: 1,
|
|
2254
|
+
borderColor: AppColors.dividerColor,
|
|
2255
|
+
}}>
|
|
2256
|
+
{['metadata', 'headers', 'request', 'response'].map(tab => {
|
|
2257
|
+
const isActive = apiDetailActiveTab === tab;
|
|
2258
|
+
if (tab === 'request' && selected.request == null)
|
|
2259
|
+
return null;
|
|
2260
|
+
const getLabel = () => {
|
|
2261
|
+
if (tab === 'metadata')
|
|
2262
|
+
return 'Metadata';
|
|
2263
|
+
if (tab === 'headers')
|
|
2264
|
+
return 'Headers';
|
|
2265
|
+
if (tab === 'request')
|
|
2266
|
+
return 'Request';
|
|
2267
|
+
return 'Response';
|
|
2268
|
+
};
|
|
2269
|
+
return (<TouchableOpacity key={tab} onPress={() => setApiDetailActiveTab(tab)} style={{
|
|
2270
|
+
flex: 1,
|
|
2271
|
+
paddingVertical: 6,
|
|
2272
|
+
alignItems: 'center',
|
|
2273
|
+
borderRadius: 8,
|
|
2274
|
+
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
2275
|
+
}}>
|
|
2276
|
+
<Text style={{
|
|
2277
|
+
fontFamily: AppFonts.interBold,
|
|
2278
|
+
fontSize: 11,
|
|
2279
|
+
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
2280
|
+
}}>
|
|
2281
|
+
{getLabel()}
|
|
2282
|
+
</Text>
|
|
2283
|
+
</TouchableOpacity>);
|
|
2284
|
+
})}
|
|
2465
2285
|
</View>
|
|
2466
2286
|
|
|
2467
|
-
|
|
2287
|
+
{/* Scrollable Tab Content */}
|
|
2288
|
+
<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingHorizontal: 6, paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
2289
|
+
{apiDetailActiveTab === 'metadata' && (<>
|
|
2290
|
+
<MetaAccordion status={selected.status} statusColor={getStatusColor(selected.status)} duration={selected.duration} size={getSize(selected.response)} triggeredAt={formatDateTime(selected.startTime)}/>
|
|
2468
2291
|
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2292
|
+
{(() => {
|
|
2293
|
+
const routeInfo = logRouteMapRef.current.get(selected.id);
|
|
2294
|
+
if (!routeInfo || routeInfo.path === 'Navigators')
|
|
2295
|
+
return null;
|
|
2296
|
+
return <SourcePageCard routeInfo={routeInfo}/>;
|
|
2297
|
+
})()}
|
|
2475
2298
|
|
|
2476
|
-
|
|
2299
|
+
{(() => {
|
|
2300
|
+
const cType = selected.responseHeaders?.['content-type'] ||
|
|
2301
|
+
selected.responseHeaders?.['Content-Type'];
|
|
2302
|
+
if (cType?.includes('image/')) {
|
|
2303
|
+
return (<View style={styles.imagePreviewWrapper}>
|
|
2304
|
+
<Image source={{ uri: selected.url }} style={styles.imagePreview} resizeMode="contain"/>
|
|
2305
|
+
<TouchableScale style={styles.imageDownloadBtn} onPress={() => Linking.openURL(selected.url)} hitSlop={10}>
|
|
2306
|
+
<DownloadIcon color={AppColors.purple} size={18}/>
|
|
2307
|
+
</TouchableScale>
|
|
2308
|
+
</View>);
|
|
2309
|
+
}
|
|
2310
|
+
return null;
|
|
2311
|
+
})()}
|
|
2312
|
+
</>)}
|
|
2477
2313
|
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
</View>);
|
|
2488
|
-
}
|
|
2489
|
-
return null;
|
|
2490
|
-
})()}
|
|
2314
|
+
{apiDetailActiveTab === 'headers' && (<>
|
|
2315
|
+
<View style={styles.detailSearchRow}>
|
|
2316
|
+
<View style={styles.detailSearchBox}>
|
|
2317
|
+
<TextInput placeholder="Search headers..." placeholderTextColor={AppColors.grayTextWeak} value={detailSearch} onChangeText={setDetailSearch} style={styles.detailSearchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2318
|
+
{detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 8 }}>
|
|
2319
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2320
|
+
</Pressable>)}
|
|
2321
|
+
</View>
|
|
2322
|
+
</View>
|
|
2491
2323
|
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
{detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 8 }}>
|
|
2496
|
-
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2497
|
-
</Pressable>)}
|
|
2498
|
-
</View>
|
|
2499
|
-
<TouchableScale style={[styles.iconSquareBtn, { marginLeft: 8 }]} onPress={() => {
|
|
2500
|
-
const next = reqExpanded === true || resExpanded === true
|
|
2501
|
-
? false
|
|
2502
|
-
: true;
|
|
2503
|
-
setReqExpanded(next);
|
|
2504
|
-
setResExpanded(next);
|
|
2505
|
-
}} hitSlop={10}>
|
|
2506
|
-
<ExpandCollapseIcon isExpanded={reqExpanded === true || resExpanded === true} color={reqExpanded === true || resExpanded === true
|
|
2507
|
-
? AppColors.purple
|
|
2508
|
-
: AppColors.grayTextWeak} size={14}/>
|
|
2509
|
-
</TouchableScale>
|
|
2510
|
-
</View>
|
|
2324
|
+
<HeadersSection title="Request Headers" headers={selected.requestHeaders} search={detailSearch} resetKey={selected.id}/>
|
|
2325
|
+
<HeadersSection title="Response Headers" headers={selected.responseHeaders} search={detailSearch} resetKey={selected.id}/>
|
|
2326
|
+
</>)}
|
|
2511
2327
|
|
|
2512
|
-
|
|
2513
|
-
|
|
2328
|
+
{apiDetailActiveTab === 'request' && selected.request != null && (<>
|
|
2329
|
+
<View style={styles.detailSearchRow}>
|
|
2330
|
+
<View style={styles.detailSearchBox}>
|
|
2331
|
+
<TextInput placeholder="Search request..." placeholderTextColor={AppColors.grayTextWeak} value={detailSearch} onChangeText={setDetailSearch} style={styles.detailSearchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2332
|
+
{detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 8 }}>
|
|
2333
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2334
|
+
</Pressable>)}
|
|
2335
|
+
</View>
|
|
2336
|
+
</View>
|
|
2514
2337
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2338
|
+
<View style={styles.sectionContainer}>
|
|
2339
|
+
<SectionHeader title="Request" value={selected.request} expanded={reqExpanded} onToggleExpand={() => setReqExpanded(v => !v)} showDiff={prevRequestData != null} isDiffing={showReqDiff} onToggleDiff={() => {
|
|
2517
2340
|
setShowReqDiff(v => !v);
|
|
2518
2341
|
if (!reqExpanded && !showReqDiff)
|
|
2519
2342
|
setReqExpanded(true);
|
|
2520
2343
|
}}/>
|
|
2521
|
-
|
|
2522
|
-
|
|
2344
|
+
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : (<JsonViewer data={selected.request} search={detailSearch} forceOpen={reqExpanded}/>)}
|
|
2345
|
+
</View>
|
|
2346
|
+
</>)}
|
|
2523
2347
|
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2348
|
+
{apiDetailActiveTab === 'response' && (<>
|
|
2349
|
+
<View style={styles.detailSearchRow}>
|
|
2350
|
+
<View style={styles.detailSearchBox}>
|
|
2351
|
+
<TextInput placeholder="Search response..." placeholderTextColor={AppColors.grayTextWeak} value={detailSearch} onChangeText={setDetailSearch} style={styles.detailSearchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2352
|
+
{detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 8 }}>
|
|
2353
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2354
|
+
</Pressable>)}
|
|
2355
|
+
</View>
|
|
2356
|
+
</View>
|
|
2357
|
+
|
|
2358
|
+
<View style={styles.sectionContainer}>
|
|
2359
|
+
<SectionHeader title="Response" value={selected.response} expanded={resExpanded} onToggleExpand={() => setResExpanded(v => !v)} showDiff={prevResponseData != null} isDiffing={showResDiff} onToggleDiff={() => {
|
|
2360
|
+
setShowResDiff(v => !v);
|
|
2361
|
+
if (!resExpanded && !showResDiff)
|
|
2362
|
+
setResExpanded(true);
|
|
2363
|
+
}}/>
|
|
2364
|
+
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : (<JsonViewer data={selected.response} search={detailSearch} forceOpen={resExpanded}/>)}
|
|
2365
|
+
</View>
|
|
2366
|
+
</>)}
|
|
2367
|
+
</ScrollView>
|
|
2368
|
+
</View>)) : (<View style={styles.empty}>
|
|
2533
2369
|
<ActivityIndicator size="large" color={AppColors.purple}/>
|
|
2534
2370
|
<Text style={[styles.emptySub, { marginTop: 12 }]}>
|
|
2535
2371
|
Loading logs...
|
|
2536
2372
|
</Text>
|
|
2537
2373
|
</View>)}
|
|
2538
|
-
|
|
2374
|
+
</View>
|
|
2375
|
+
</View>
|
|
2376
|
+
</ErrorBoundary>)}
|
|
2539
2377
|
</Modal>
|
|
2540
2378
|
</>);
|
|
2541
2379
|
};
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
marginHorizontal: 16,
|
|
2549
|
-
marginBottom: 12,
|
|
2550
|
-
backgroundColor: AppColors.primaryLight,
|
|
2551
|
-
borderRadius: 12,
|
|
2552
|
-
borderWidth: 1,
|
|
2553
|
-
borderColor: AppColors.grayBorderSecondary,
|
|
2554
|
-
paddingHorizontal: 16,
|
|
2555
|
-
paddingVertical: 12,
|
|
2556
|
-
shadowColor: '#000',
|
|
2557
|
-
shadowOpacity: 0.04,
|
|
2558
|
-
shadowRadius: 4,
|
|
2559
|
-
shadowOffset: { width: 0, height: 1 },
|
|
2560
|
-
elevation: 1,
|
|
2561
|
-
},
|
|
2562
|
-
topEventsHeaderRow: {
|
|
2563
|
-
flexDirection: 'row',
|
|
2564
|
-
alignItems: 'center',
|
|
2565
|
-
justifyContent: 'space-between',
|
|
2566
|
-
marginBottom: 12,
|
|
2567
|
-
},
|
|
2568
|
-
topEventsTitle: {
|
|
2569
|
-
fontFamily: AppFonts.interBold,
|
|
2570
|
-
fontSize: 10,
|
|
2571
|
-
color: AppColors.grayTextWeak,
|
|
2572
|
-
letterSpacing: 0.8,
|
|
2573
|
-
textTransform: 'uppercase',
|
|
2574
|
-
},
|
|
2575
|
-
topEventsSubtitle: {
|
|
2576
|
-
fontFamily: AppFonts.interMedium,
|
|
2577
|
-
fontSize: 9,
|
|
2578
|
-
color: AppColors.grayTextWeak,
|
|
2579
|
-
letterSpacing: 0.4,
|
|
2580
|
-
},
|
|
2581
|
-
topEventRow: {
|
|
2582
|
-
flexDirection: 'row',
|
|
2583
|
-
alignItems: 'center',
|
|
2584
|
-
marginBottom: 8,
|
|
2585
|
-
gap: 8,
|
|
2586
|
-
},
|
|
2587
|
-
topEventName: {
|
|
2588
|
-
fontFamily: AppFonts.interMedium,
|
|
2589
|
-
fontSize: 12,
|
|
2590
|
-
color: AppColors.primaryBlack,
|
|
2591
|
-
flex: 1,
|
|
2592
|
-
},
|
|
2593
|
-
topEventBarWrap: {
|
|
2594
|
-
flex: 0.8,
|
|
2595
|
-
height: 3,
|
|
2596
|
-
backgroundColor: AppColors.grayBackground,
|
|
2597
|
-
borderRadius: 2,
|
|
2598
|
-
overflow: 'hidden',
|
|
2599
|
-
},
|
|
2600
|
-
topEventBar: {
|
|
2601
|
-
height: '100%',
|
|
2602
|
-
borderRadius: 2,
|
|
2603
|
-
},
|
|
2604
|
-
topEventCount: {
|
|
2605
|
-
fontFamily: AppFonts.interBold,
|
|
2606
|
-
fontSize: 11,
|
|
2607
|
-
color: AppColors.grayText,
|
|
2608
|
-
width: 24,
|
|
2609
|
-
textAlign: 'right',
|
|
2610
|
-
},
|
|
2611
|
-
iconCircle: {
|
|
2612
|
-
width: 24,
|
|
2613
|
-
height: 24,
|
|
2614
|
-
borderRadius: 12,
|
|
2615
|
-
alignItems: 'center',
|
|
2616
|
-
justifyContent: 'center',
|
|
2617
|
-
},
|
|
2618
|
-
});
|
|
2380
|
+
const NetworkInspectorWrapper = (props) => {
|
|
2381
|
+
return (<ErrorBoundary fallbackType="inline">
|
|
2382
|
+
<NetworkInspector {...props}/>
|
|
2383
|
+
</ErrorBoundary>);
|
|
2384
|
+
};
|
|
2385
|
+
export default NetworkInspectorWrapper;
|
|
2619
2386
|
// Re-export public APIs
|
|
2620
2387
|
export { setupNetworkLogger, clearNetworkLogs, subscribeNetworkLogs, } from './customHooks/networkLogger';
|
|
2621
2388
|
export { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './customHooks/consoleLogger';
|
|
2622
2389
|
export { setupAnalyticsLogger, logAnalyticsEvent, subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
|
|
2623
|
-
export { getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
|
|
2390
|
+
export { WebView, getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
|
|
2391
|
+
export { default as ErrorBoundary } from './components/ErrorBoundary';
|
|
2392
|
+
export { connectReduxStore, getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
|