react-native-inapp-inspector 1.1.28 → 1.1.29
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/AppHeaderLogo.js +12 -15
- package/dist/commonjs/components/ConsoleLogCard.js +31 -7
- package/dist/commonjs/components/CopyButton.js +15 -1
- package/dist/commonjs/components/DomainHeader.js +56 -28
- package/dist/commonjs/components/HighlightText.js +7 -1
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +35 -21
- package/dist/commonjs/components/Inspector/InspectorHeader.js +17 -3
- package/dist/commonjs/components/Inspector/LogDetail.js +8 -5
- package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
- package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
- package/dist/commonjs/components/JsonViewer.js +25 -3
- package/dist/commonjs/components/LogCard.js +103 -11
- 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 +1 -1
- 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/helpers/index.d.ts +4 -0
- package/dist/commonjs/helpers/index.js +91 -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 +4 -1
- package/dist/commonjs/styles/index.d.ts +60 -14
- package/dist/commonjs/styles/index.js +71 -26
- package/dist/esm/components/AppHeaderLogo.js +12 -15
- package/dist/esm/components/ConsoleLogCard.js +33 -9
- package/dist/esm/components/CopyButton.js +15 -1
- package/dist/esm/components/DomainHeader.js +56 -28
- package/dist/esm/components/HighlightText.js +7 -1
- package/dist/esm/components/Inspector/AnalyticsTab.js +36 -22
- package/dist/esm/components/Inspector/InspectorHeader.js +18 -4
- package/dist/esm/components/Inspector/LogDetail.js +10 -7
- package/dist/esm/components/Inspector/MainScreen.js +4 -0
- package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
- package/dist/esm/components/JsonViewer.js +25 -3
- package/dist/esm/components/LogCard.js +105 -13
- 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 +1 -1
- 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/helpers/index.d.ts +4 -0
- package/dist/esm/helpers/index.js +86 -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 +4 -1
- package/dist/esm/styles/index.d.ts +60 -14
- package/dist/esm/styles/index.js +71 -26
- package/ios/NetworkInspectorModule.m +0 -10
- package/package.json +4 -1
|
@@ -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={[
|
|
@@ -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>
|
|
@@ -8,7 +8,7 @@ import { ChevronIcon, DocIcon } from './NetworkIcons';
|
|
|
8
8
|
import { AppColors } from '../styles/AppColors';
|
|
9
9
|
import styles from '../styles';
|
|
10
10
|
const TreeNode = React.memo(function TreeNode({ data, name, level = 0, search, forceOpen, defaultExpandDepth, }) {
|
|
11
|
-
const [localOpen, setLocalOpen] = useState(forceOpen || level <
|
|
11
|
+
const [localOpen, setLocalOpen] = useState(forceOpen || (defaultExpandDepth !== undefined ? level < defaultExpandDepth : false));
|
|
12
12
|
const open = localOpen;
|
|
13
13
|
const isObject = typeof data === 'object' && data !== null;
|
|
14
14
|
const isArray = Array.isArray(data);
|
|
@@ -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.29";
|
|
@@ -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 */
|
|
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';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Platform, ToastAndroid, Alert, NativeModules, Linking, } from 'react-native';
|
|
2
|
+
import Clipboard from '@react-native-clipboard/clipboard';
|
|
3
|
+
import { showToast } from './toast';
|
|
2
4
|
// Stylesheet
|
|
3
5
|
import { AppColors } from '../styles/AppColors';
|
|
4
6
|
// Constants
|
|
@@ -23,6 +25,19 @@ export const formatDateTime = (timestamp) => {
|
|
|
23
25
|
const seconds = pad(date.getSeconds());
|
|
24
26
|
return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
|
|
25
27
|
};
|
|
28
|
+
export const formatTimestamp = (timestamp) => {
|
|
29
|
+
try {
|
|
30
|
+
const date = new Date(timestamp);
|
|
31
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
32
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
33
|
+
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
34
|
+
const ms = date.getMilliseconds().toString().padStart(3, '0');
|
|
35
|
+
return `${hours}:${minutes}:${seconds}.${ms}`;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return '—';
|
|
39
|
+
}
|
|
40
|
+
};
|
|
26
41
|
export const getStatusColor = (status) => {
|
|
27
42
|
if (!status || status === 0)
|
|
28
43
|
return AppColors.errorColor;
|
|
@@ -52,104 +67,43 @@ export const getSize = (data) => {
|
|
|
52
67
|
return '—';
|
|
53
68
|
}
|
|
54
69
|
};
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const inspectorMod = NativeModules?.NetworkInspectorModule ||
|
|
61
|
-
(tmReg?.get ? tmReg.get('NetworkInspectorModule') : null);
|
|
62
|
-
if (inspectorMod && typeof inspectorMod.copyToClipboard === 'function') {
|
|
63
|
-
return {
|
|
64
|
-
setString: (str) => inspectorMod.copyToClipboard(str),
|
|
65
|
-
setStringAsync: (str) => inspectorMod.copyToClipboard(str),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
70
|
+
export const copyToClipboard = (value, label) => {
|
|
71
|
+
const resolved = typeof value === 'function' ? value() : value;
|
|
72
|
+
let textToCopy = '';
|
|
73
|
+
if (typeof resolved === 'string') {
|
|
74
|
+
textToCopy = resolved;
|
|
68
75
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
tmReg.get('NativeClipboard') ||
|
|
76
|
-
tmReg.get('ExpoClipboard');
|
|
77
|
-
if (tm &&
|
|
78
|
-
(typeof tm.setString === 'function' ||
|
|
79
|
-
typeof tm.setStringAsync === 'function')) {
|
|
80
|
-
return tm;
|
|
81
|
-
}
|
|
76
|
+
else {
|
|
77
|
+
try {
|
|
78
|
+
textToCopy = JSON.stringify(resolved, null, 2);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
textToCopy = String(resolved);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
// 3. Check legacy NativeModules table (Bridge / Old Architecture)
|
|
84
|
+
// Use @react-native-clipboard/clipboard npm package
|
|
86
85
|
try {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
return NativeModules.RNCClipboard;
|
|
90
|
-
}
|
|
91
|
-
if (NativeModules?.ExpoClipboard &&
|
|
92
|
-
typeof NativeModules.ExpoClipboard.setStringAsync === 'function') {
|
|
93
|
-
return NativeModules.ExpoClipboard;
|
|
86
|
+
if (typeof Clipboard?.setString === 'function') {
|
|
87
|
+
Clipboard.setString(textToCopy);
|
|
94
88
|
}
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
return NativeModules.Clipboard;
|
|
89
|
+
else if (typeof Clipboard?.default?.setString === 'function') {
|
|
90
|
+
Clipboard.default.setString(textToCopy);
|
|
98
91
|
}
|
|
99
92
|
}
|
|
100
|
-
catch {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (Clipboard && typeof Clipboard.setString === 'function') {
|
|
104
|
-
return Clipboard;
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (__DEV__) {
|
|
95
|
+
console.warn('[NetworkInspector] Clipboard.setString failed:', err);
|
|
105
96
|
}
|
|
106
97
|
}
|
|
107
|
-
|
|
108
|
-
return null;
|
|
109
|
-
};
|
|
110
|
-
export const copyToClipboard = (value, label) => {
|
|
111
|
-
const resolved = typeof value === 'function' ? value() : value;
|
|
112
|
-
const text = typeof resolved === 'string' ? resolved : JSON.stringify(resolved, null, 2);
|
|
113
|
-
const textToCopy = text ?? '';
|
|
114
|
-
let copied = false;
|
|
98
|
+
// Trigger floating in-app bottom toast notification
|
|
115
99
|
try {
|
|
116
|
-
|
|
117
|
-
if (cb) {
|
|
118
|
-
if (typeof cb.setStringAsync === 'function') {
|
|
119
|
-
cb.setStringAsync(textToCopy);
|
|
120
|
-
copied = true;
|
|
121
|
-
}
|
|
122
|
-
else if (typeof cb.setString === 'function') {
|
|
123
|
-
cb.setString(textToCopy);
|
|
124
|
-
copied = true;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
100
|
+
showToast(label ? `${label} copied to clipboard` : 'Copied to clipboard');
|
|
127
101
|
}
|
|
128
102
|
catch { }
|
|
129
|
-
//
|
|
130
|
-
if (!copied) {
|
|
131
|
-
try {
|
|
132
|
-
const inspectorMod = NativeModules?.NetworkInspectorModule;
|
|
133
|
-
if (inspectorMod && typeof inspectorMod.copyToClipboard === 'function') {
|
|
134
|
-
inspectorMod.copyToClipboard(textToCopy);
|
|
135
|
-
copied = true;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch { }
|
|
139
|
-
}
|
|
140
|
-
if (!copied) {
|
|
141
|
-
try {
|
|
142
|
-
if (typeof navigator !== 'undefined' && navigator?.clipboard?.writeText) {
|
|
143
|
-
navigator.clipboard.writeText(textToCopy);
|
|
144
|
-
copied = true;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
catch { }
|
|
148
|
-
}
|
|
149
|
-
// Visual toast feedback on Android if ToastAndroid exists
|
|
103
|
+
// Native Android Toast fallback
|
|
150
104
|
try {
|
|
151
105
|
if (Platform.OS === 'android' && ToastAndroid?.show) {
|
|
152
|
-
ToastAndroid.show(`${label} copied
|
|
106
|
+
ToastAndroid.show(label ? `${label} copied to clipboard` : 'Copied to clipboard', ToastAndroid.SHORT);
|
|
153
107
|
}
|
|
154
108
|
}
|
|
155
109
|
catch { }
|
|
@@ -535,11 +489,16 @@ export const parseStackLine = (rawLine, isOrigin = false) => {
|
|
|
535
489
|
(functionName === 'next' && isNative);
|
|
536
490
|
const isDependency = cleanPath.includes('node_modules') ||
|
|
537
491
|
cleanPath.includes('react-native/Libraries') ||
|
|
538
|
-
(cleanPath.includes('react-native-inapp-inspector') &&
|
|
492
|
+
(cleanPath.includes('react-native-inapp-inspector') &&
|
|
493
|
+
!cleanPath.includes('/example/'));
|
|
539
494
|
const isUserCode = !isNative &&
|
|
540
495
|
!isInternalBytecode &&
|
|
541
496
|
!isDependency &&
|
|
542
|
-
(fileExt === 'tsx' ||
|
|
497
|
+
(fileExt === 'tsx' ||
|
|
498
|
+
fileExt === 'jsx' ||
|
|
499
|
+
fileExt === 'ts' ||
|
|
500
|
+
fileExt === 'js' ||
|
|
501
|
+
!fileName.includes('.bundle'));
|
|
543
502
|
const frameType = isUserCode
|
|
544
503
|
? 'app'
|
|
545
504
|
: isDependency
|
|
@@ -547,7 +506,10 @@ export const parseStackLine = (rawLine, isOrigin = false) => {
|
|
|
547
506
|
: isNative
|
|
548
507
|
? 'native'
|
|
549
508
|
: 'runtime';
|
|
550
|
-
const isRuntimeNoise = isInternalBytecode ||
|
|
509
|
+
const isRuntimeNoise = isInternalBytecode ||
|
|
510
|
+
isNative ||
|
|
511
|
+
functionName === 'asyncGeneratorStep' ||
|
|
512
|
+
functionName === '_next';
|
|
551
513
|
// Format clean relative project path
|
|
552
514
|
let relativePath = cleanPath;
|
|
553
515
|
if (relativePath.includes('/example/')) {
|
|
@@ -567,6 +529,7 @@ export const parseStackLine = (rawLine, isOrigin = false) => {
|
|
|
567
529
|
functionName,
|
|
568
530
|
fileName,
|
|
569
531
|
fullPath: relativePath,
|
|
532
|
+
rawFilePath: fullPath,
|
|
570
533
|
fileExt,
|
|
571
534
|
frameType,
|
|
572
535
|
isUserCode,
|
|
@@ -577,6 +540,30 @@ export const parseStackLine = (rawLine, isOrigin = false) => {
|
|
|
577
540
|
copyableLocation,
|
|
578
541
|
};
|
|
579
542
|
};
|
|
543
|
+
/** Opens a file and line number directly in VS Code */
|
|
544
|
+
export const openInVSCode = (filePath, lineNumber, columnNumber) => {
|
|
545
|
+
const line = lineNumber ? `:${lineNumber}` : '';
|
|
546
|
+
const col = columnNumber ? `:${columnNumber}` : '';
|
|
547
|
+
const cleanPath = filePath.replace(/^file:\/\//, '');
|
|
548
|
+
const vscodeUrl = `vscode://file/${cleanPath.replace(/^\/+/, '')}${line}${col}`;
|
|
549
|
+
Linking.canOpenURL(vscodeUrl)
|
|
550
|
+
.then(supported => {
|
|
551
|
+
if (supported) {
|
|
552
|
+
Linking.openURL(vscodeUrl);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
Linking.openURL(vscodeUrl).catch(() => {
|
|
556
|
+
const insidersUrl = `vscode-insiders://file/${cleanPath.replace(/^\/+/, '')}${line}${col}`;
|
|
557
|
+
Linking.openURL(insidersUrl).catch(() => {
|
|
558
|
+
Alert.alert('Open in VS Code', `Target location:\n${cleanPath}${line}${col}\n\nPlease ensure VS Code is installed.`, [{ text: 'OK' }]);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
.catch(() => {
|
|
564
|
+
Linking.openURL(vscodeUrl).catch(() => { });
|
|
565
|
+
});
|
|
566
|
+
};
|
|
580
567
|
// ─── Analytics Helpers ────────────────────────────────────────────────────────
|
|
581
568
|
export const ANALYTICS_EVENT_PALETTE = [
|
|
582
569
|
AppColors.googleBlue,
|
|
@@ -631,7 +618,11 @@ export const getCategoryColors = (category) => {
|
|
|
631
618
|
export const getRuntimeDiagnostics = () => {
|
|
632
619
|
const isHermes = typeof global.HermesInternal !== 'undefined';
|
|
633
620
|
const isV8 = typeof global._v8runtime !== 'undefined';
|
|
634
|
-
const engineType = isHermes
|
|
621
|
+
const engineType = isHermes
|
|
622
|
+
? 'hermes'
|
|
623
|
+
: isV8
|
|
624
|
+
? 'v8'
|
|
625
|
+
: 'jsc';
|
|
635
626
|
const isFabric = typeof global.nativeFabricUIManager !== 'undefined' ||
|
|
636
627
|
Boolean(global.__turboModuleProxy);
|
|
637
628
|
const archType = isFabric ? 'fabric' : 'paper';
|
|
@@ -641,11 +632,14 @@ export const getRuntimeDiagnostics = () => {
|
|
|
641
632
|
const hermesStats = global.HermesInternal?.getInstrumentedStats?.();
|
|
642
633
|
if (hermesStats?.js_heap_size) {
|
|
643
634
|
usedHeapMb = Number((hermesStats.js_heap_size / (1024 * 1024)).toFixed(1));
|
|
644
|
-
totalAllocMb = Number(((hermesStats.js_allocated_bytes || hermesStats.js_heap_size * 1.6) /
|
|
635
|
+
totalAllocMb = Number(((hermesStats.js_allocated_bytes || hermesStats.js_heap_size * 1.6) /
|
|
636
|
+
(1024 * 1024)).toFixed(1));
|
|
645
637
|
}
|
|
646
638
|
else if (global.performance?.memory?.usedJSHeapSize) {
|
|
647
|
-
usedHeapMb = Number((global.performance.memory.usedJSHeapSize /
|
|
648
|
-
|
|
639
|
+
usedHeapMb = Number((global.performance.memory.usedJSHeapSize /
|
|
640
|
+
(1024 * 1024)).toFixed(1));
|
|
641
|
+
totalAllocMb = Number((global.performance.memory.totalJSHeapSize /
|
|
642
|
+
(1024 * 1024)).toFixed(1));
|
|
649
643
|
}
|
|
650
644
|
}
|
|
651
645
|
catch { }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const listeners = new Set();
|
|
2
|
+
export const showToast = (message) => {
|
|
3
|
+
listeners.forEach(fn => {
|
|
4
|
+
try {
|
|
5
|
+
fn(message);
|
|
6
|
+
}
|
|
7
|
+
catch { }
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const subscribeToast = (listener) => {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
return () => {
|
|
13
|
+
listeners.delete(listener);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"expandAll": "Expand All",
|
|
16
16
|
"viewOnNpm": "View on NPM",
|
|
17
17
|
"later": "Later",
|
|
18
|
-
"source": "SOURCE"
|
|
18
|
+
"source": "SOURCE",
|
|
19
|
+
"open": "Open",
|
|
20
|
+
"openInBrowser": "Open in Browser",
|
|
21
|
+
"openInBrowserPrompt": "Are you sure you want to open this URL in your external browser?"
|
|
19
22
|
},
|
|
20
23
|
"header": {
|
|
21
24
|
"updateAvailableTitle": "Update Available",
|