react-native-inapp-inspector 1.1.28 → 1.1.31
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/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -80
- package/dist/commonjs/components/AnalyticsDetail.js +668 -108
- package/dist/commonjs/components/AppHeaderLogo.js +15 -15
- package/dist/commonjs/components/ConsoleLogCard.js +112 -66
- package/dist/commonjs/components/CopyButton.js +15 -1
- package/dist/commonjs/components/DomainHeader.js +64 -32
- package/dist/commonjs/components/HighlightText.js +7 -1
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +332 -221
- package/dist/commonjs/components/Inspector/ConsoleTab.js +162 -56
- package/dist/commonjs/components/Inspector/InspectorHeader.js +70 -119
- package/dist/commonjs/components/Inspector/LogDetail.js +53 -17
- package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
- package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
- package/dist/commonjs/components/Inspector/NetworkTab.js +4 -1
- package/dist/commonjs/components/Inspector/ReduxDetail.js +237 -42
- package/dist/commonjs/components/Inspector/ReduxTab.js +456 -35
- package/dist/commonjs/components/JsonViewer.js +26 -4
- package/dist/commonjs/components/LogCard.js +103 -11
- package/dist/commonjs/components/NetworkIcons.js +19 -10
- package/dist/commonjs/components/Toast.d.ts +3 -0
- package/dist/commonjs/components/Toast.js +152 -0
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- package/dist/commonjs/components/TouchableScale.js +3 -3
- package/dist/commonjs/components/TreeNode.js +12 -2
- package/dist/commonjs/constants/index.js +6 -6
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/reduxLogger.js +128 -5
- package/dist/commonjs/helpers/index.d.ts +4 -0
- package/dist/commonjs/helpers/index.js +107 -92
- package/dist/commonjs/helpers/toast.d.ts +4 -0
- package/dist/commonjs/helpers/toast.js +20 -0
- package/dist/commonjs/i18n/locales/en.json +49 -3
- package/dist/commonjs/styles/AppColors.d.ts +6 -0
- package/dist/commonjs/styles/AppColors.js +6 -0
- package/dist/commonjs/styles/index.d.ts +82 -15
- package/dist/commonjs/styles/index.js +92 -26
- package/dist/commonjs/types/interfaces.d.ts +7 -0
- package/dist/esm/components/AnalyticsDetail.js +670 -110
- package/dist/esm/components/AppHeaderLogo.js +15 -15
- package/dist/esm/components/ConsoleLogCard.js +114 -68
- package/dist/esm/components/CopyButton.js +15 -1
- package/dist/esm/components/DomainHeader.js +64 -32
- package/dist/esm/components/HighlightText.js +7 -1
- package/dist/esm/components/Inspector/AnalyticsTab.js +334 -223
- package/dist/esm/components/Inspector/ConsoleTab.js +163 -57
- package/dist/esm/components/Inspector/InspectorHeader.js +71 -120
- package/dist/esm/components/Inspector/LogDetail.js +55 -19
- package/dist/esm/components/Inspector/MainScreen.js +4 -0
- package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
- package/dist/esm/components/Inspector/NetworkTab.js +4 -1
- package/dist/esm/components/Inspector/ReduxDetail.js +239 -44
- package/dist/esm/components/Inspector/ReduxTab.js +458 -37
- package/dist/esm/components/JsonViewer.js +26 -4
- package/dist/esm/components/LogCard.js +105 -13
- package/dist/esm/components/NetworkIcons.js +19 -10
- package/dist/esm/components/Toast.d.ts +3 -0
- package/dist/esm/components/Toast.js +117 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- package/dist/esm/components/TouchableScale.js +3 -3
- package/dist/esm/components/TreeNode.js +12 -2
- package/dist/esm/constants/index.js +6 -6
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/reduxLogger.js +128 -5
- package/dist/esm/helpers/index.d.ts +4 -0
- package/dist/esm/helpers/index.js +102 -92
- package/dist/esm/helpers/toast.d.ts +4 -0
- package/dist/esm/helpers/toast.js +15 -0
- package/dist/esm/i18n/locales/en.json +49 -3
- package/dist/esm/styles/AppColors.d.ts +6 -0
- package/dist/esm/styles/AppColors.js +6 -0
- package/dist/esm/styles/index.d.ts +82 -15
- package/dist/esm/styles/index.js +92 -26
- package/dist/esm/types/interfaces.d.ts +7 -0
- package/ios/NetworkInspectorModule.m +0 -10
- package/package.json +13 -10
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { Animated, View, Pressable, Text, StyleSheet } from 'react-native';
|
|
2
|
+
import { Alert, Animated, Linking, View, Pressable, Text, StyleSheet, } from 'react-native';
|
|
3
3
|
import Svg, { Path } from 'react-native-svg';
|
|
4
4
|
import { AppColors } from '../styles/AppColors';
|
|
5
5
|
import { METHOD_COLORS } from '../constants';
|
|
6
|
-
import { getStatusColor, getDurationColor,
|
|
6
|
+
import { getStatusColor, getDurationColor, formatDateTime, getSize, } from '../helpers';
|
|
7
7
|
import { CalendarIcon, ClockIcon, SizeIcon } from './NetworkIcons';
|
|
8
8
|
import { AppFonts } from '../styles/AppFonts';
|
|
9
9
|
import styles from '../styles';
|
|
@@ -13,6 +13,27 @@ import { useTranslation } from '../i18n';
|
|
|
13
13
|
const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, timelineTotalRange, isNew, isSelected, onToggleSelect, searchStr, }) {
|
|
14
14
|
const { t } = useTranslation();
|
|
15
15
|
const methodColor = METHOD_COLORS[item.method] ?? METHOD_COLORS.ALL;
|
|
16
|
+
const handleOpenUrl = (e) => {
|
|
17
|
+
e?.stopPropagation?.();
|
|
18
|
+
Alert.alert(t('common.openInBrowser') || 'Open in Browser', `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${item.url}`, [
|
|
19
|
+
{ text: t('common.cancel') || 'Cancel', style: 'cancel' },
|
|
20
|
+
{
|
|
21
|
+
text: t('common.open') || 'Open',
|
|
22
|
+
onPress: () => {
|
|
23
|
+
Linking.canOpenURL(item.url)
|
|
24
|
+
.then(supported => {
|
|
25
|
+
if (supported) {
|
|
26
|
+
Linking.openURL(item.url);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
Linking.openURL(item.url).catch(() => { });
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.catch(() => { });
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
};
|
|
16
37
|
const isFailed = item.status === 0 || (item.status != null && item.status >= 400);
|
|
17
38
|
const isLoading = item.status == null;
|
|
18
39
|
const statusColor = getStatusColor(item.status);
|
|
@@ -38,10 +59,6 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
38
59
|
}).start();
|
|
39
60
|
}
|
|
40
61
|
}, [isNew]);
|
|
41
|
-
const path = getPath(item.url);
|
|
42
|
-
const baseUrl = getBaseUrl(item.url) || item.url;
|
|
43
|
-
const slug = path && path !== '/' ? path : item.url;
|
|
44
|
-
const showSlug = slug !== baseUrl && slug !== item.url;
|
|
45
62
|
const triggeredAt = formatDateTime(item.startTime);
|
|
46
63
|
const isJson = item.url.split('?')[0].toLowerCase().endsWith('.json');
|
|
47
64
|
return (<TouchableScale onPress={onPress} style={[
|
|
@@ -52,7 +69,7 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
52
69
|
},
|
|
53
70
|
]}>
|
|
54
71
|
<View style={styles.cardBody}>
|
|
55
|
-
{/* Row 1: Header (Checkbox, Serial, Method Badge,
|
|
72
|
+
{/* Row 1: Header (Checkbox, Serial, Method Badge, Client Tag, Status Pill) */}
|
|
56
73
|
<View style={styles.cardHeaderRow}>
|
|
57
74
|
<View style={styles.cardHeaderLeft}>
|
|
58
75
|
<Pressable onPress={() => onToggleSelect(item.id)} hitSlop={12} style={[
|
|
@@ -112,7 +129,82 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
112
129
|
</Text>
|
|
113
130
|
</View>)}
|
|
114
131
|
|
|
115
|
-
|
|
132
|
+
{/* Protocol / HTTPS Badge */}
|
|
133
|
+
<View style={[
|
|
134
|
+
styles.chip,
|
|
135
|
+
{
|
|
136
|
+
backgroundColor: item.url.toLowerCase().startsWith('https')
|
|
137
|
+
? '#ECFDF5'
|
|
138
|
+
: '#FFFBEB',
|
|
139
|
+
borderColor: item.url.toLowerCase().startsWith('https')
|
|
140
|
+
? '#A7F3D0'
|
|
141
|
+
: '#FDE68A',
|
|
142
|
+
paddingHorizontal: 4,
|
|
143
|
+
paddingVertical: 1,
|
|
144
|
+
borderRadius: 4,
|
|
145
|
+
},
|
|
146
|
+
]}>
|
|
147
|
+
<Text style={[
|
|
148
|
+
styles.chipText,
|
|
149
|
+
{
|
|
150
|
+
fontFamily: AppFonts.interBold,
|
|
151
|
+
fontSize: 8,
|
|
152
|
+
color: item.url.toLowerCase().startsWith('https')
|
|
153
|
+
? '#059669'
|
|
154
|
+
: '#D97706',
|
|
155
|
+
},
|
|
156
|
+
]}>
|
|
157
|
+
{item.url.toLowerCase().startsWith('https') ? 'HTTPS' : 'HTTP'}
|
|
158
|
+
</Text>
|
|
159
|
+
</View>
|
|
160
|
+
|
|
161
|
+
{/* GraphQL Indicator */}
|
|
162
|
+
{(item.url.toLowerCase().includes('graphql') ||
|
|
163
|
+
item.client === 'apollo' ||
|
|
164
|
+
item.client === 'graphql') && (<View style={[
|
|
165
|
+
styles.chip,
|
|
166
|
+
{
|
|
167
|
+
backgroundColor: '#FDF2F8',
|
|
168
|
+
borderColor: '#FBCFE8',
|
|
169
|
+
paddingHorizontal: 4,
|
|
170
|
+
paddingVertical: 1,
|
|
171
|
+
borderRadius: 4,
|
|
172
|
+
},
|
|
173
|
+
]}>
|
|
174
|
+
<Text style={[
|
|
175
|
+
styles.chipText,
|
|
176
|
+
{
|
|
177
|
+
fontFamily: AppFonts.interBold,
|
|
178
|
+
fontSize: 8,
|
|
179
|
+
color: '#DB2777',
|
|
180
|
+
},
|
|
181
|
+
]}>
|
|
182
|
+
GQL
|
|
183
|
+
</Text>
|
|
184
|
+
</View>)}
|
|
185
|
+
|
|
186
|
+
{/* In-Line Duration / Latency Pill */}
|
|
187
|
+
{item.duration != null && !isFailed && (<View style={[
|
|
188
|
+
styles.chip,
|
|
189
|
+
{
|
|
190
|
+
backgroundColor: `${durationColor}12`,
|
|
191
|
+
borderColor: `${durationColor}2E`,
|
|
192
|
+
paddingHorizontal: 4.5,
|
|
193
|
+
paddingVertical: 1,
|
|
194
|
+
borderRadius: 4,
|
|
195
|
+
},
|
|
196
|
+
]}>
|
|
197
|
+
<Text style={[
|
|
198
|
+
styles.chipText,
|
|
199
|
+
{
|
|
200
|
+
fontFamily: AppFonts.interBold,
|
|
201
|
+
fontSize: 8.5,
|
|
202
|
+
color: durationColor,
|
|
203
|
+
},
|
|
204
|
+
]}>
|
|
205
|
+
{item.duration}ms
|
|
206
|
+
</Text>
|
|
207
|
+
</View>)}
|
|
116
208
|
</View>
|
|
117
209
|
|
|
118
210
|
<View style={[
|
|
@@ -149,12 +241,12 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
149
241
|
</View>
|
|
150
242
|
</View>
|
|
151
243
|
|
|
152
|
-
{/* Row 2: Full URL Capsule Container */}
|
|
244
|
+
{/* Row 2: Full URL Capsule Container (Clickable with link prompt) */}
|
|
153
245
|
<View style={styles.cardSlugBox}>
|
|
154
|
-
<
|
|
155
|
-
<Text style={styles.slugTag}>URL
|
|
156
|
-
<HighlightText text={item.url} search={searchStr} style={styles.slugText} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
|
|
157
|
-
</
|
|
246
|
+
<Pressable onPress={handleOpenUrl} style={styles.slugLeft} hitSlop={6}>
|
|
247
|
+
<Text style={styles.slugTag}>URL ↗</Text>
|
|
248
|
+
<HighlightText text={item.url} search={searchStr} style={[styles.slugText, { color: AppColors.skyBlue, textDecorationLine: 'underline' }]} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
|
|
249
|
+
</Pressable>
|
|
158
250
|
|
|
159
251
|
<View style={styles.slugRight}>
|
|
160
252
|
{isJson && (<View style={[
|
|
@@ -362,15 +362,21 @@ export const ForwardChevronIcon = ({ color = AppColors.grayTextWeak, size = 14,
|
|
|
362
362
|
export const BundleIcon = PackageIcon;
|
|
363
363
|
export const LiveStateIcon = ({ color = AppColors.purple, size = 14, }) => {
|
|
364
364
|
return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
365
|
-
<
|
|
366
|
-
<Circle cx="12" cy="12" r="
|
|
367
|
-
<Circle cx="12" cy="12" r="
|
|
365
|
+
<Circle cx="12" cy="12" r="9" stroke={color} strokeWidth="1.8" strokeDasharray="3 3" opacity={0.6}/>
|
|
366
|
+
<Circle cx="12" cy="12" r="4.5" stroke={color} strokeWidth="1.8"/>
|
|
367
|
+
<Circle cx="12" cy="12" r="2" fill={color}/>
|
|
368
|
+
<Path d="M12 2v2.5M12 19.5V22M2 12h2.5M19.5 12H22" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
368
369
|
</Svg>);
|
|
369
370
|
};
|
|
370
371
|
export const TimelineIcon = ({ color = AppColors.purple, size = 14, }) => {
|
|
371
372
|
return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
372
|
-
<
|
|
373
|
-
<
|
|
373
|
+
<Path d="M6 3v18" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
374
|
+
<Circle cx="6" cy="6" r="2.5" stroke={color} strokeWidth="1.8" fill={`${color}25`}/>
|
|
375
|
+
<Path d="M6 12h5a3 3 0 0 0 3-3V6" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
376
|
+
<Circle cx="14" cy="6" r="2.5" stroke={color} strokeWidth="1.8" fill={color}/>
|
|
377
|
+
<Circle cx="6" cy="18" r="2.5" stroke={color} strokeWidth="1.8" fill={color}/>
|
|
378
|
+
<Path d="M6 18h8" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
379
|
+
<Circle cx="17" cy="18" r="2.5" stroke={color} strokeWidth="1.8" fill={`${color}25`}/>
|
|
374
380
|
</Svg>);
|
|
375
381
|
};
|
|
376
382
|
export const RefreshCcwIcon = ({ color = AppColors.grayTextWeak, size = 14, }) => {
|
|
@@ -380,15 +386,18 @@ export const RefreshCcwIcon = ({ color = AppColors.grayTextWeak, size = 14, }) =
|
|
|
380
386
|
};
|
|
381
387
|
export const StorageIcon = ({ color = AppColors.purple, size = 14, }) => {
|
|
382
388
|
return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
383
|
-
<Ellipse cx="12" cy="
|
|
384
|
-
<Path d="
|
|
385
|
-
<Path d="M4
|
|
389
|
+
<Ellipse cx="12" cy="5" rx="8" ry="2.5" stroke={color} strokeWidth="1.8"/>
|
|
390
|
+
<Path d="M4 5v4.5c0 1.38 3.58 2.5 8 2.5s8-1.12 8-2.5V5" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
391
|
+
<Path d="M4 9.5v4.5c0 1.38 3.58 2.5 8 2.5s8-1.12 8-2.5v-4.5" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
392
|
+
<Path d="M4 14v4.5c0 1.38 3.58 2.5 8 2.5s8-1.12 8-2.5V14" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
386
393
|
</Svg>);
|
|
387
394
|
};
|
|
388
395
|
export const MetadataIcon = ({ color = AppColors.purple, size = 14, }) => {
|
|
389
396
|
return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
390
|
-
<
|
|
391
|
-
<Path d="
|
|
397
|
+
<Rect x="3" y="3" width="18" height="18" rx="4" stroke={color} strokeWidth="1.8"/>
|
|
398
|
+
<Path d="M7 8h10M7 12h6M7 16h7" stroke={color} strokeWidth="1.8" strokeLinecap="round"/>
|
|
399
|
+
<Circle cx="16.5" cy="12" r="1.5" fill={color}/>
|
|
400
|
+
<Circle cx="16.5" cy="16" r="1.5" fill={color}/>
|
|
392
401
|
</Svg>);
|
|
393
402
|
};
|
|
394
403
|
export const ReduxIcon = ({ color = AppColors.purple, size = 14, }) => {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Animated, StyleSheet, Text, View, Platform } from 'react-native';
|
|
3
|
+
import { subscribeToast } from '../helpers/toast';
|
|
4
|
+
import { CheckIcon } from './NetworkIcons';
|
|
5
|
+
import { AppColors } from '../styles/AppColors';
|
|
6
|
+
import { AppFonts } from '../styles/AppFonts';
|
|
7
|
+
const Toast = React.memo(() => {
|
|
8
|
+
const [toastMessage, setToastMessage] = useState(null);
|
|
9
|
+
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
10
|
+
const translateYAnim = useRef(new Animated.Value(20)).current;
|
|
11
|
+
const hideTimerRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const unsubscribe = subscribeToast((message) => {
|
|
14
|
+
if (hideTimerRef.current) {
|
|
15
|
+
clearTimeout(hideTimerRef.current);
|
|
16
|
+
}
|
|
17
|
+
setToastMessage(message);
|
|
18
|
+
Animated.parallel([
|
|
19
|
+
Animated.timing(opacityAnim, {
|
|
20
|
+
toValue: 1,
|
|
21
|
+
duration: 180,
|
|
22
|
+
useNativeDriver: true,
|
|
23
|
+
}),
|
|
24
|
+
Animated.spring(translateYAnim, {
|
|
25
|
+
toValue: 0,
|
|
26
|
+
tension: 80,
|
|
27
|
+
friction: 8,
|
|
28
|
+
useNativeDriver: true,
|
|
29
|
+
}),
|
|
30
|
+
]).start();
|
|
31
|
+
hideTimerRef.current = setTimeout(() => {
|
|
32
|
+
Animated.parallel([
|
|
33
|
+
Animated.timing(opacityAnim, {
|
|
34
|
+
toValue: 0,
|
|
35
|
+
duration: 200,
|
|
36
|
+
useNativeDriver: true,
|
|
37
|
+
}),
|
|
38
|
+
Animated.timing(translateYAnim, {
|
|
39
|
+
toValue: 20,
|
|
40
|
+
duration: 200,
|
|
41
|
+
useNativeDriver: true,
|
|
42
|
+
}),
|
|
43
|
+
]).start(() => {
|
|
44
|
+
setToastMessage(null);
|
|
45
|
+
});
|
|
46
|
+
}, 2000);
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
unsubscribe();
|
|
50
|
+
if (hideTimerRef.current) {
|
|
51
|
+
clearTimeout(hideTimerRef.current);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}, [opacityAnim, translateYAnim]);
|
|
55
|
+
if (!toastMessage) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return (<Animated.View pointerEvents="none" style={[
|
|
59
|
+
styles.toastContainer,
|
|
60
|
+
{
|
|
61
|
+
opacity: opacityAnim,
|
|
62
|
+
transform: [{ translateY: translateYAnim }],
|
|
63
|
+
},
|
|
64
|
+
]}>
|
|
65
|
+
<View style={styles.toastCard}>
|
|
66
|
+
<View style={styles.iconCircle}>
|
|
67
|
+
<CheckIcon color={AppColors.white} size={11}/>
|
|
68
|
+
</View>
|
|
69
|
+
<Text style={styles.toastText} numberOfLines={2}>
|
|
70
|
+
{toastMessage}
|
|
71
|
+
</Text>
|
|
72
|
+
</View>
|
|
73
|
+
</Animated.View>);
|
|
74
|
+
});
|
|
75
|
+
const styles = StyleSheet.create({
|
|
76
|
+
toastContainer: {
|
|
77
|
+
position: 'absolute',
|
|
78
|
+
bottom: Platform.OS === 'ios' ? 44 : 28,
|
|
79
|
+
left: 20,
|
|
80
|
+
right: 20,
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
justifyContent: 'center',
|
|
83
|
+
zIndex: 999999,
|
|
84
|
+
},
|
|
85
|
+
toastCard: {
|
|
86
|
+
flexDirection: 'row',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
backgroundColor: '#0F172AEE', // dark slate glass
|
|
89
|
+
paddingVertical: 10,
|
|
90
|
+
paddingHorizontal: 16,
|
|
91
|
+
borderRadius: 24,
|
|
92
|
+
shadowColor: '#000000',
|
|
93
|
+
shadowOffset: { width: 0, height: 6 },
|
|
94
|
+
shadowOpacity: 0.28,
|
|
95
|
+
shadowRadius: 10,
|
|
96
|
+
elevation: 8,
|
|
97
|
+
borderWidth: 1,
|
|
98
|
+
borderColor: '#33415588',
|
|
99
|
+
maxWidth: '90%',
|
|
100
|
+
gap: 9,
|
|
101
|
+
},
|
|
102
|
+
iconCircle: {
|
|
103
|
+
width: 18,
|
|
104
|
+
height: 18,
|
|
105
|
+
borderRadius: 9,
|
|
106
|
+
backgroundColor: AppColors.greenColor,
|
|
107
|
+
alignItems: 'center',
|
|
108
|
+
justifyContent: 'center',
|
|
109
|
+
},
|
|
110
|
+
toastText: {
|
|
111
|
+
color: AppColors.white,
|
|
112
|
+
fontFamily: AppFonts.interBold,
|
|
113
|
+
fontSize: 12.5,
|
|
114
|
+
letterSpacing: -0.1,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
export default Toast;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
2
|
import { Animated, Pressable, StyleSheet, Platform } from 'react-native';
|
|
3
|
-
const TouchableScale = React.memo(function TouchableScale({ onPress, style, children, hitSlop, disabled, }) {
|
|
3
|
+
const TouchableScale = React.memo(function TouchableScale({ onPress, onLongPress, style, children, hitSlop, disabled, }) {
|
|
4
4
|
if (Platform.OS === 'android') {
|
|
5
|
-
return (<Pressable disabled={disabled} onPress={onPress} hitSlop={hitSlop} style={({ pressed }) => [
|
|
5
|
+
return (<Pressable disabled={disabled} onPress={onPress} onLongPress={onLongPress} hitSlop={hitSlop} style={({ pressed }) => [
|
|
6
6
|
style,
|
|
7
7
|
{ opacity: pressed ? 0.75 : 1 },
|
|
8
8
|
]}>
|
|
@@ -38,7 +38,7 @@ const TouchableScale = React.memo(function TouchableScale({ onPress, style, chil
|
|
|
38
38
|
flexShrink: flattenedStyle.flexShrink,
|
|
39
39
|
gap: flattenedStyle.gap,
|
|
40
40
|
};
|
|
41
|
-
return (<Pressable disabled={disabled} style={style} onPressIn={() => animatePress(true)} onPressOut={() => animatePress(false)} onPress={onPress} hitSlop={hitSlop}>
|
|
41
|
+
return (<Pressable disabled={disabled} style={style} onPressIn={() => animatePress(true)} onPressOut={() => animatePress(false)} onPress={onPress} onLongPress={onLongPress} hitSlop={hitSlop}>
|
|
42
42
|
<Animated.View style={[{ opacity, transform: [{ scale }] }, layoutStyle]}>
|
|
43
43
|
{children}
|
|
44
44
|
</Animated.View>
|
|
@@ -7,9 +7,19 @@ import { ChevronIcon, DocIcon } from './NetworkIcons';
|
|
|
7
7
|
// Stylesheet
|
|
8
8
|
import { AppColors } from '../styles/AppColors';
|
|
9
9
|
import styles from '../styles';
|
|
10
|
-
const TreeNode = React.memo(function TreeNode({ data, name, level = 0, search, forceOpen, defaultExpandDepth, }) {
|
|
11
|
-
const [localOpen, setLocalOpen] = useState(
|
|
10
|
+
const TreeNode = React.memo(function TreeNode({ data, name, level = 0, search, forceOpen, defaultExpandDepth = 1, }) {
|
|
11
|
+
const [localOpen, setLocalOpen] = useState(() => {
|
|
12
|
+
if (forceOpen !== undefined) {
|
|
13
|
+
return forceOpen;
|
|
14
|
+
}
|
|
15
|
+
return defaultExpandDepth !== undefined ? level < defaultExpandDepth : level < 1;
|
|
16
|
+
});
|
|
12
17
|
const open = localOpen;
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (forceOpen !== undefined) {
|
|
20
|
+
setLocalOpen(forceOpen);
|
|
21
|
+
}
|
|
22
|
+
}, [forceOpen]);
|
|
13
23
|
const isObject = typeof data === 'object' && data !== null;
|
|
14
24
|
const isArray = Array.isArray(data);
|
|
15
25
|
// Declare all hooks at the top unconditionally (prevents "Rendered fewer hooks than expected" error)
|
|
@@ -9,12 +9,12 @@ export const STATUS_FILTERS = [
|
|
|
9
9
|
'Failed',
|
|
10
10
|
];
|
|
11
11
|
export const METHOD_COLORS = {
|
|
12
|
-
ALL:
|
|
13
|
-
GET:
|
|
14
|
-
POST:
|
|
15
|
-
PUT:
|
|
16
|
-
PATCH:
|
|
17
|
-
DELETE:
|
|
12
|
+
ALL: '#64748B', // Slate
|
|
13
|
+
GET: '#059669', // Emerald
|
|
14
|
+
POST: '#2563EB', // Royal Blue
|
|
15
|
+
PUT: '#D97706', // Amber Gold
|
|
16
|
+
PATCH: '#7C3AED', // Rich Violet
|
|
17
|
+
DELETE: '#DC2626', // Crimson Red
|
|
18
18
|
};
|
|
19
19
|
export const DOMAIN_COLORS = AppColors.domainColors;
|
|
20
20
|
export const DURATION_FAST_MS = 200;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "1.1.
|
|
1
|
+
export declare const LIB_VERSION = "1.1.31";
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// that bypass the wrapped dispatch (thunk/saga internals capture the raw
|
|
12
12
|
// dispatch reference at store-creation time) still update the state tree
|
|
13
13
|
// and per-reducer "last action" consistently.
|
|
14
|
+
import { parseStackLine } from '../helpers';
|
|
14
15
|
let currentReduxState = null;
|
|
15
16
|
const listeners = new Set();
|
|
16
17
|
let globalReduxAutoRefresh = true;
|
|
@@ -63,7 +64,106 @@ function actionTypeOf(action) {
|
|
|
63
64
|
}
|
|
64
65
|
return 'UNKNOWN_ACTION';
|
|
65
66
|
}
|
|
66
|
-
function
|
|
67
|
+
function detectActionOrigin(stack, actionType, actionObj) {
|
|
68
|
+
const stackLower = (stack || '').toLowerCase();
|
|
69
|
+
const typeLower = (actionType || '').toLowerCase();
|
|
70
|
+
// Explicit hint on action if developer tagged it
|
|
71
|
+
if (actionObj && actionObj.__origin) {
|
|
72
|
+
const raw = String(actionObj.__origin).toLowerCase();
|
|
73
|
+
if (raw.includes('saga'))
|
|
74
|
+
return { originType: 'saga' };
|
|
75
|
+
if (raw.includes('thunk'))
|
|
76
|
+
return { originType: 'thunk' };
|
|
77
|
+
if (raw.includes('ui'))
|
|
78
|
+
return { originType: 'ui' };
|
|
79
|
+
}
|
|
80
|
+
// 1. Saga Detection (Stack + Action naming conventions)
|
|
81
|
+
const isSaga = stackLower.includes('redux-saga') ||
|
|
82
|
+
stackLower.includes('runputeffect') ||
|
|
83
|
+
stackLower.includes('proc.js') ||
|
|
84
|
+
stackLower.includes('effects.js') ||
|
|
85
|
+
stackLower.includes('sagamiddleware') ||
|
|
86
|
+
stackLower.includes('saga.ts') ||
|
|
87
|
+
stackLower.includes('saga.js') ||
|
|
88
|
+
stackLower.includes('sagas') ||
|
|
89
|
+
typeLower.includes('saga') ||
|
|
90
|
+
typeLower.startsWith('saga/') ||
|
|
91
|
+
typeLower.startsWith('@saga/');
|
|
92
|
+
// 2. Thunk / RTK Query Detection
|
|
93
|
+
const isThunk = stackLower.includes('createasyncthunk') ||
|
|
94
|
+
stackLower.includes('redux-thunk') ||
|
|
95
|
+
stackLower.includes('rtk-query') ||
|
|
96
|
+
stackLower.includes('thunkmiddleware') ||
|
|
97
|
+
stackLower.includes('actioncreator') ||
|
|
98
|
+
typeLower.includes('thunk') ||
|
|
99
|
+
typeLower.endsWith('/pending') ||
|
|
100
|
+
typeLower.endsWith('/fulfilled') ||
|
|
101
|
+
typeLower.endsWith('/rejected');
|
|
102
|
+
// 3. Parse Stack Frames to find best application caller
|
|
103
|
+
const lines = (stack || '').split('\n').map(l => l.trim()).filter(Boolean);
|
|
104
|
+
let bestFrame = null;
|
|
105
|
+
for (let i = 0; i < lines.length; i++) {
|
|
106
|
+
const line = lines[i];
|
|
107
|
+
// Ignore logger / middleware lines inside inspector
|
|
108
|
+
if (line.includes('reduxLogger') ||
|
|
109
|
+
line.includes('inspectorReduxMiddleware') ||
|
|
110
|
+
line.includes('recordAction') ||
|
|
111
|
+
line.includes('redux.js') ||
|
|
112
|
+
line.includes('redux.cjs')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseStackLine(line, false);
|
|
116
|
+
if (parsed.frameType === 'app' || (!parsed.isRuntimeNoise && parsed.fileName !== 'Unknown')) {
|
|
117
|
+
bestFrame = parsed;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!bestFrame && lines.length > 0) {
|
|
122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
123
|
+
if (!lines[i].includes('reduxLogger') && !lines[i].includes('inspectorReduxMiddleware')) {
|
|
124
|
+
bestFrame = parseStackLine(lines[i], false);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let originType = 'direct';
|
|
130
|
+
if (isSaga || (bestFrame && bestFrame.fileName?.toLowerCase().includes('saga'))) {
|
|
131
|
+
originType = 'saga';
|
|
132
|
+
}
|
|
133
|
+
else if (isThunk || (bestFrame && bestFrame.fileName?.toLowerCase().includes('thunk'))) {
|
|
134
|
+
originType = 'thunk';
|
|
135
|
+
}
|
|
136
|
+
else if (bestFrame &&
|
|
137
|
+
(bestFrame.fileName?.endsWith('.tsx') ||
|
|
138
|
+
bestFrame.functionName?.includes('onPress') ||
|
|
139
|
+
bestFrame.functionName?.includes('touchable') ||
|
|
140
|
+
bestFrame.functionName?.includes('Effect'))) {
|
|
141
|
+
originType = 'ui';
|
|
142
|
+
}
|
|
143
|
+
else if (stackLower.includes('listener') || stackLower.includes('subscribe')) {
|
|
144
|
+
originType = 'listener';
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
originType,
|
|
148
|
+
caller: bestFrame?.copyableLocation || bestFrame?.raw || undefined,
|
|
149
|
+
callerFile: bestFrame?.fullPath || bestFrame?.fileName || undefined,
|
|
150
|
+
callerLine: bestFrame?.lineNumber ? Number(bestFrame.lineNumber) : undefined,
|
|
151
|
+
callerCol: bestFrame?.columnNumber ? Number(bestFrame.columnNumber) : undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function extractSliceName(actionType, affectedSlices) {
|
|
155
|
+
if (affectedSlices && affectedSlices.length > 0) {
|
|
156
|
+
return affectedSlices[0];
|
|
157
|
+
}
|
|
158
|
+
// If Redux Toolkit action format: e.g. "users/fetchById" -> "users"
|
|
159
|
+
const match = actionType.match(/^([a-zA-Z0-9_\-]+)\//);
|
|
160
|
+
if (match && match[1] && !match[1].startsWith('@@')) {
|
|
161
|
+
return match[1];
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function recordAction(action, prevState, nextState, rawStack) {
|
|
166
|
+
const stack = rawStack || new Error().stack || '';
|
|
67
167
|
const type = actionTypeOf(action);
|
|
68
168
|
const payload = action && typeof action === 'object' && action.payload !== undefined
|
|
69
169
|
? action.payload
|
|
@@ -71,13 +171,26 @@ function recordAction(action, prevState, nextState) {
|
|
|
71
171
|
const now = Date.now();
|
|
72
172
|
const timestamp = new Date(now).toLocaleTimeString();
|
|
73
173
|
const affectedSlices = [];
|
|
174
|
+
const origin = detectActionOrigin(stack, type, action);
|
|
175
|
+
const sliceName = extractSliceName(type, affectedSlices);
|
|
74
176
|
if (prevState &&
|
|
75
177
|
nextState &&
|
|
76
178
|
typeof prevState === 'object' &&
|
|
77
179
|
typeof nextState === 'object') {
|
|
78
180
|
Object.keys(nextState).forEach(key => {
|
|
79
181
|
if (prevState[key] !== nextState[key]) {
|
|
80
|
-
lastActionForReducer[key] = {
|
|
182
|
+
lastActionForReducer[key] = {
|
|
183
|
+
type,
|
|
184
|
+
payload,
|
|
185
|
+
timestamp,
|
|
186
|
+
updatedAt: now,
|
|
187
|
+
originType: origin.originType,
|
|
188
|
+
callerFile: origin.callerFile,
|
|
189
|
+
callerLine: origin.callerLine,
|
|
190
|
+
callerCol: origin.callerCol,
|
|
191
|
+
caller: origin.caller,
|
|
192
|
+
stack,
|
|
193
|
+
};
|
|
81
194
|
affectedSlices.push(key);
|
|
82
195
|
}
|
|
83
196
|
});
|
|
@@ -91,6 +204,13 @@ function recordAction(action, prevState, nextState) {
|
|
|
91
204
|
affectedSlices,
|
|
92
205
|
prevState,
|
|
93
206
|
nextState,
|
|
207
|
+
stack,
|
|
208
|
+
caller: origin.caller,
|
|
209
|
+
callerFile: origin.callerFile,
|
|
210
|
+
callerLine: origin.callerLine,
|
|
211
|
+
callerCol: origin.callerCol,
|
|
212
|
+
originType: origin.originType,
|
|
213
|
+
sliceName,
|
|
94
214
|
});
|
|
95
215
|
if (actionHistory.length > MAX_HISTORY) {
|
|
96
216
|
actionHistory.length = MAX_HISTORY;
|
|
@@ -116,6 +236,7 @@ function recordAction(action, prevState, nextState) {
|
|
|
116
236
|
*/
|
|
117
237
|
export const inspectorReduxMiddleware = (storeApi) => (next) => (action) => {
|
|
118
238
|
middlewareAttached = true;
|
|
239
|
+
const capturedStack = new Error().stack || '';
|
|
119
240
|
// Thunks are functions — let them run; their inner plain-action dispatches
|
|
120
241
|
// pass back through this same middleware, so nothing is lost.
|
|
121
242
|
if (typeof action === 'function') {
|
|
@@ -126,7 +247,7 @@ export const inspectorReduxMiddleware = (storeApi) => (next) => (action) => {
|
|
|
126
247
|
const nextState = storeApi.getState();
|
|
127
248
|
if (currentReduxState == null)
|
|
128
249
|
currentReduxState = nextState;
|
|
129
|
-
recordAction(action, prevState, nextState);
|
|
250
|
+
recordAction(action, prevState, nextState, capturedStack);
|
|
130
251
|
return result;
|
|
131
252
|
};
|
|
132
253
|
export const connectReduxStore = (store) => {
|
|
@@ -148,6 +269,7 @@ export const connectReduxStore = (store) => {
|
|
|
148
269
|
const originalDispatch = store.dispatch.bind(store);
|
|
149
270
|
let inWrappedDispatch = false;
|
|
150
271
|
store.dispatch = (action) => {
|
|
272
|
+
const capturedStack = new Error().stack || '';
|
|
151
273
|
if (middlewareAttached || typeof action === 'function') {
|
|
152
274
|
// Middleware handles recording, or it's a thunk whose inner dispatches
|
|
153
275
|
// will be picked up individually.
|
|
@@ -168,7 +290,7 @@ export const connectReduxStore = (store) => {
|
|
|
168
290
|
finally {
|
|
169
291
|
inWrappedDispatch = false;
|
|
170
292
|
}
|
|
171
|
-
recordAction(action, prevState, store.getState());
|
|
293
|
+
recordAction(action, prevState, store.getState(), capturedStack);
|
|
172
294
|
return result;
|
|
173
295
|
};
|
|
174
296
|
setReduxState(store.getState());
|
|
@@ -192,6 +314,7 @@ export const connectReduxStore = (store) => {
|
|
|
192
314
|
}
|
|
193
315
|
// Change arrived outside the wrapped dispatch — record it so the
|
|
194
316
|
// timeline stays consistent, even without the original action type.
|
|
195
|
-
|
|
317
|
+
const subStack = new Error().stack || '';
|
|
318
|
+
recordAction({ type: '@@inspector/EXTERNAL_STATE_CHANGE' }, prevState, nextState, subStack);
|
|
196
319
|
});
|
|
197
320
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NetworkLog, RouteInfo, DiffResult, JsonContent, StackFrameType } from '../types';
|
|
2
2
|
export declare const getDomainColor: (domain: string) => string;
|
|
3
3
|
export declare const formatDateTime: (timestamp: number) => string;
|
|
4
|
+
export declare const formatTimestamp: (timestamp: number) => string;
|
|
4
5
|
export declare const getStatusColor: (status: number | null) => string;
|
|
5
6
|
export declare const getDurationColor: (duration: number | null) => string;
|
|
6
7
|
export declare const getSize: (data: unknown) => string;
|
|
@@ -44,6 +45,7 @@ export interface ParsedStackFrame {
|
|
|
44
45
|
frameType: StackFrameType | 'app' | 'dependency' | 'runtime' | 'native';
|
|
45
46
|
isUserCode: boolean;
|
|
46
47
|
isRuntimeNoise: boolean;
|
|
48
|
+
rawFilePath?: string;
|
|
47
49
|
lineNumber?: string;
|
|
48
50
|
columnNumber?: string;
|
|
49
51
|
isOrigin?: boolean;
|
|
@@ -51,6 +53,8 @@ export interface ParsedStackFrame {
|
|
|
51
53
|
}
|
|
52
54
|
/** Parses a stack trace line to extract function name, file name, extension (.tsx/.jsx/.ts), line, and column numbers */
|
|
53
55
|
export declare const parseStackLine: (rawLine: string, isOrigin?: boolean) => ParsedStackFrame;
|
|
56
|
+
/** Opens a file and line number directly in VS Code / system editor */
|
|
57
|
+
export declare const openInVSCode: (filePath: string, lineNumber?: string | number, columnNumber?: string | number) => void;
|
|
54
58
|
export declare const ANALYTICS_EVENT_PALETTE: string[];
|
|
55
59
|
export declare const getEventColor: (name: string) => string;
|
|
56
60
|
export { getEventCategory, registerGAPlugin, type GAEventCategory, type GAPlugin, } from './gaAnalyticsRegistry';
|