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
|
@@ -3,10 +3,10 @@ import { useTranslation } from '../i18n';
|
|
|
3
3
|
import { Pressable, StyleSheet, Text, View, } from 'react-native';
|
|
4
4
|
import { AppColors } from '../styles/AppColors';
|
|
5
5
|
import { AppFonts } from '../styles/AppFonts';
|
|
6
|
-
import { formatTime, getJsonContent, getJsonPreviewText, parseStackLine, } from '../helpers';
|
|
6
|
+
import { formatTime, getJsonContent, getJsonPreviewText, parseStackLine, openInVSCode, } from '../helpers';
|
|
7
7
|
import HighlightText from './HighlightText';
|
|
8
8
|
import CopyButton from './CopyButton';
|
|
9
|
-
import { ChevronIcon } from './NetworkIcons';
|
|
9
|
+
import { ChevronIcon, ExternalLinkIcon } from './NetworkIcons';
|
|
10
10
|
import { useInspector } from './Inspector/InspectorContext';
|
|
11
11
|
const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle, numberOfLines) => {
|
|
12
12
|
if (!message)
|
|
@@ -226,18 +226,34 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
226
226
|
<Text style={styles.footerText}>{formatTime(item.timestamp)}</Text>
|
|
227
227
|
{parsedCaller && (<>
|
|
228
228
|
<View style={styles.footerDot}/>
|
|
229
|
-
<
|
|
229
|
+
<Pressable onPress={e => {
|
|
230
|
+
e.stopPropagation?.();
|
|
231
|
+
openInVSCode(parsedCaller.rawFilePath ||
|
|
232
|
+
parsedCaller.fullPath ||
|
|
233
|
+
parsedCaller.fileName, parsedCaller.lineNumber, parsedCaller.columnNumber);
|
|
234
|
+
}} hitSlop={8} style={{
|
|
235
|
+
flexDirection: 'row',
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
gap: 4,
|
|
238
|
+
maxWidth: 185,
|
|
239
|
+
backgroundColor: `${AppColors.sky600}10`,
|
|
240
|
+
borderColor: `${AppColors.sky600}2B`,
|
|
241
|
+
borderWidth: 1,
|
|
242
|
+
borderRadius: 5,
|
|
243
|
+
paddingHorizontal: 5,
|
|
244
|
+
paddingVertical: 1.5,
|
|
245
|
+
}}>
|
|
230
246
|
{parsedCaller.fileExt && parsedCaller.fileExt !== 'other' && (<View style={{
|
|
231
247
|
backgroundColor: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
|
|
232
|
-
? `${AppColors.brandPurple}
|
|
233
|
-
: `${AppColors.teal600}
|
|
248
|
+
? `${AppColors.brandPurple}22`
|
|
249
|
+
: `${AppColors.teal600}22`,
|
|
234
250
|
borderRadius: 3,
|
|
235
251
|
paddingHorizontal: 3.5,
|
|
236
|
-
paddingVertical:
|
|
252
|
+
paddingVertical: 0.5,
|
|
237
253
|
}}>
|
|
238
254
|
<Text style={{
|
|
239
255
|
fontFamily: AppFonts.interBold,
|
|
240
|
-
fontSize: 8
|
|
256
|
+
fontSize: 8,
|
|
241
257
|
color: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
|
|
242
258
|
? AppColors.brandPurple
|
|
243
259
|
: AppColors.teal600,
|
|
@@ -245,11 +261,19 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
|
|
|
245
261
|
{parsedCaller.fileExt.toUpperCase()}
|
|
246
262
|
</Text>
|
|
247
263
|
</View>)}
|
|
248
|
-
<Text style={[
|
|
264
|
+
<Text style={[
|
|
265
|
+
styles.footerText,
|
|
266
|
+
{
|
|
267
|
+
fontFamily: AppFonts.interBold,
|
|
268
|
+
color: AppColors.sky600,
|
|
269
|
+
fontSize: 9.5,
|
|
270
|
+
},
|
|
271
|
+
]} numberOfLines={1} ellipsizeMode="middle">
|
|
249
272
|
{parsedCaller.fileName}
|
|
250
273
|
{parsedCaller.lineNumber ? `:${parsedCaller.lineNumber}` : ''}
|
|
251
274
|
</Text>
|
|
252
|
-
|
|
275
|
+
<ExternalLinkIcon color={AppColors.sky600} size={8.5}/>
|
|
276
|
+
</Pressable>
|
|
253
277
|
</>)}
|
|
254
278
|
</View>
|
|
255
279
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { Share } from 'react-native';
|
|
2
3
|
// Components
|
|
3
4
|
import TouchableScale from './TouchableScale';
|
|
4
5
|
// Helpers
|
|
@@ -16,6 +17,19 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
|
|
|
16
17
|
setCopied(true);
|
|
17
18
|
setTimeout(() => setCopied(false), 1200);
|
|
18
19
|
}, [value, label]);
|
|
20
|
+
const handleLongPress = useCallback(() => {
|
|
21
|
+
try {
|
|
22
|
+
const resolvedValue = typeof value === 'function' ? value() : value;
|
|
23
|
+
const textToShare = typeof resolvedValue === 'string'
|
|
24
|
+
? resolvedValue
|
|
25
|
+
: JSON.stringify(resolvedValue, null, 2);
|
|
26
|
+
Share.share({
|
|
27
|
+
message: textToShare,
|
|
28
|
+
title: label,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}, [value, label]);
|
|
19
33
|
const containerStyle = [
|
|
20
34
|
styles.iconSquareBtn,
|
|
21
35
|
copied && styles.iconSquareBtnSuccess,
|
|
@@ -25,7 +39,7 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
|
|
|
25
39
|
: iconType === 'fetch'
|
|
26
40
|
? FetchIcon
|
|
27
41
|
: CopyIcon;
|
|
28
|
-
return (<TouchableScale onPress={handlePress} hitSlop={12} style={containerStyle}>
|
|
42
|
+
return (<TouchableScale onPress={handlePress} onLongPress={handleLongPress} hitSlop={12} style={containerStyle}>
|
|
29
43
|
{copied ? (<CheckIcon color={AppColors.greenColor} size={14}/>) : (<IconComponent color={AppColors.grayTextWeak} size={14}/>)}
|
|
30
44
|
</TouchableScale>);
|
|
31
45
|
});
|
|
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
|
|
|
2
2
|
import { Animated, Pressable, View, Text } from 'react-native';
|
|
3
3
|
// Stylesheet
|
|
4
4
|
import { AppColors } from '../styles/AppColors';
|
|
5
|
+
import { AppFonts } from '../styles/AppFonts';
|
|
5
6
|
// Helpers
|
|
6
7
|
import { formatDateTime } from '../helpers';
|
|
7
8
|
// Assets
|
|
@@ -35,10 +36,61 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
|
|
|
35
36
|
<View style={[styles.domainIconWrap, { backgroundColor: `${color}18` }]}>
|
|
36
37
|
<ScreenIcon color={color} size={14}/>
|
|
37
38
|
</View>
|
|
38
|
-
<View style={{ flex: 1, justifyContent: 'center' }}>
|
|
39
|
-
<
|
|
40
|
-
{
|
|
41
|
-
|
|
39
|
+
<View style={{ flex: 1, justifyContent: 'center', minWidth: 0 }}>
|
|
40
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
41
|
+
<Text style={[styles.domainTitleText, { flexShrink: 1 }]} numberOfLines={1}>
|
|
42
|
+
{pageName}
|
|
43
|
+
</Text>
|
|
44
|
+
{total > 0 && (<View style={{
|
|
45
|
+
flexDirection: 'row',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
gap: 4,
|
|
48
|
+
backgroundColor: stats.failed > 0
|
|
49
|
+
? '#FEE2E2'
|
|
50
|
+
: stats.loading > 0
|
|
51
|
+
? '#FEF3C7'
|
|
52
|
+
: '#D1FAE5',
|
|
53
|
+
paddingHorizontal: 5,
|
|
54
|
+
paddingVertical: 1.5,
|
|
55
|
+
borderRadius: 4,
|
|
56
|
+
}}>
|
|
57
|
+
<View style={{
|
|
58
|
+
width: 24,
|
|
59
|
+
height: 3.5,
|
|
60
|
+
borderRadius: 2,
|
|
61
|
+
backgroundColor: '#E5E7EB',
|
|
62
|
+
flexDirection: 'row',
|
|
63
|
+
overflow: 'hidden',
|
|
64
|
+
}}>
|
|
65
|
+
{pctSuccess > 0 && (<View style={{
|
|
66
|
+
width: `${pctSuccess}%`,
|
|
67
|
+
height: '100%',
|
|
68
|
+
backgroundColor: '#10B981',
|
|
69
|
+
}}/>)}
|
|
70
|
+
{pctFailed > 0 && (<View style={{
|
|
71
|
+
width: `${pctFailed}%`,
|
|
72
|
+
height: '100%',
|
|
73
|
+
backgroundColor: '#EF4444',
|
|
74
|
+
}}/>)}
|
|
75
|
+
{pctLoading > 0 && (<View style={{
|
|
76
|
+
width: `${pctLoading}%`,
|
|
77
|
+
height: '100%',
|
|
78
|
+
backgroundColor: '#F59E0B',
|
|
79
|
+
}}/>)}
|
|
80
|
+
</View>
|
|
81
|
+
<Text style={{
|
|
82
|
+
fontFamily: AppFonts.interBold,
|
|
83
|
+
fontSize: 8.5,
|
|
84
|
+
color: stats.failed > 0
|
|
85
|
+
? '#991B1B'
|
|
86
|
+
: stats.loading > 0
|
|
87
|
+
? '#92400E'
|
|
88
|
+
: '#065F46',
|
|
89
|
+
}}>
|
|
90
|
+
{pctSuccess}%
|
|
91
|
+
</Text>
|
|
92
|
+
</View>)}
|
|
93
|
+
</View>
|
|
42
94
|
<Text style={styles.domainSummaryText} numberOfLines={1}>
|
|
43
95
|
{formatDateTime(timestamp)}
|
|
44
96
|
</Text>
|
|
@@ -94,30 +146,6 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
|
|
|
94
146
|
})}
|
|
95
147
|
</View>
|
|
96
148
|
</View>
|
|
97
|
-
|
|
98
|
-
{total > 0 && (<View style={styles.domainProgressTrack}>
|
|
99
|
-
{pctSuccess > 0 && (<View style={[
|
|
100
|
-
styles.domainProgressSegment,
|
|
101
|
-
{
|
|
102
|
-
width: `${pctSuccess}%`,
|
|
103
|
-
backgroundColor: AppColors.greenColor,
|
|
104
|
-
},
|
|
105
|
-
]}/>)}
|
|
106
|
-
{pctFailed > 0 && (<View style={[
|
|
107
|
-
styles.domainProgressSegment,
|
|
108
|
-
{
|
|
109
|
-
width: `${pctFailed}%`,
|
|
110
|
-
backgroundColor: AppColors.errorColor,
|
|
111
|
-
},
|
|
112
|
-
]}/>)}
|
|
113
|
-
{pctLoading > 0 && (<View style={[
|
|
114
|
-
styles.domainProgressSegment,
|
|
115
|
-
{
|
|
116
|
-
width: `${pctLoading}%`,
|
|
117
|
-
backgroundColor: AppColors.darkOrange,
|
|
118
|
-
},
|
|
119
|
-
]}/>)}
|
|
120
|
-
</View>)}
|
|
121
149
|
</Pressable>);
|
|
122
150
|
};
|
|
123
151
|
export default DomainHeader;
|
|
@@ -3,6 +3,12 @@ import { Text } from 'react-native';
|
|
|
3
3
|
// Helpers
|
|
4
4
|
import { escapeRegex, handleOpenExternalLink } from '../helpers';
|
|
5
5
|
const HighlightText = React.memo(function HighlightText({ text, search, style, highlightStyle, detectLinks, numberOfLines, ...rest }) {
|
|
6
|
+
// Optimization: For huge strings (> 20KB) with no active search, bypass all splitting/regex overhead
|
|
7
|
+
if (!text || (text.length > 20000 && !search && !detectLinks)) {
|
|
8
|
+
return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
|
|
9
|
+
{text}
|
|
10
|
+
</Text>);
|
|
11
|
+
}
|
|
6
12
|
// Regex to detect absolute URLs
|
|
7
13
|
const URL_REGEX = /(https?:\/\/[^\s]+)/g;
|
|
8
14
|
const renderNormalOrHighlighted = (subText, keyPrefix) => {
|
|
@@ -17,7 +23,7 @@ const HighlightText = React.memo(function HighlightText({ text, search, style, h
|
|
|
17
23
|
</Text>) : (<Text key={i}>{part}</Text>))}
|
|
18
24
|
</Text>);
|
|
19
25
|
};
|
|
20
|
-
if (detectLinks && text) {
|
|
26
|
+
if (detectLinks && text && text.length < 20000) {
|
|
21
27
|
const parts = text.split(URL_REGEX);
|
|
22
28
|
return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
|
|
23
29
|
{parts.map((part, i) => {
|
|
@@ -9,7 +9,7 @@ import styles from '../../styles';
|
|
|
9
9
|
import { AppColors } from '../../styles/AppColors';
|
|
10
10
|
import { AppFonts } from '../../styles/AppFonts';
|
|
11
11
|
import { useTranslation } from '../../i18n';
|
|
12
|
-
import Svg, { Path, Line, Circle, G, Defs, LinearGradient as SvgLinearGradient, Stop, } from 'react-native-svg';
|
|
12
|
+
import Svg, { Path, Rect, Line, Circle, G, Defs, LinearGradient as SvgLinearGradient, Stop, } from 'react-native-svg';
|
|
13
13
|
import TouchableScale from '../TouchableScale';
|
|
14
14
|
import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
|
|
15
15
|
import { getEventCategory, } from '../../helpers';
|
|
@@ -299,41 +299,55 @@ const AnalyticsHeader = React.memo(() => {
|
|
|
299
299
|
</View>
|
|
300
300
|
</View>
|
|
301
301
|
|
|
302
|
-
{/* ──
|
|
302
|
+
{/* ── Crashlytics-Style Multi-Bar Histogram with Trendline Overlay ── */}
|
|
303
303
|
<View style={{ height: chartHeight, position: 'relative' }}>
|
|
304
304
|
<Svg width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`}>
|
|
305
305
|
<Defs>
|
|
306
|
-
<SvgLinearGradient id="
|
|
307
|
-
<Stop offset="0%" stopColor=
|
|
308
|
-
<Stop offset="
|
|
309
|
-
|
|
306
|
+
<SvgLinearGradient id="crashlyticsBarGrad" x1="0" y1="0" x2="0" y2="1">
|
|
307
|
+
<Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.9"/>
|
|
308
|
+
<Stop offset="100%" stopColor="#6366F1" stopOpacity="0.6"/>
|
|
309
|
+
</SvgLinearGradient>
|
|
310
|
+
<SvgLinearGradient id="crashlyticsAreaGrad" x1="0" y1="0" x2="0" y2="1">
|
|
311
|
+
<Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.25"/>
|
|
312
|
+
<Stop offset="80%" stopColor="#8B5CF6" stopOpacity="0.04"/>
|
|
313
|
+
<Stop offset="100%" stopColor="#8B5CF6" stopOpacity="0.0"/>
|
|
310
314
|
</SvgLinearGradient>
|
|
311
315
|
</Defs>
|
|
312
316
|
|
|
313
|
-
{/*
|
|
314
|
-
<Line x1="
|
|
315
|
-
<Line x1="
|
|
316
|
-
|
|
317
|
-
{/* Glowing Area Fill */}
|
|
318
|
-
<Path d={histogramData.areaPath} fill="url(#telemetryAreaGrad)"/>
|
|
317
|
+
{/* Subtle Grid Guidelines */}
|
|
318
|
+
<Line x1="6" y1={chartHeight - 6} x2={svgWidth - 6} y2={chartHeight - 6} stroke="#E2E8F0" strokeWidth="1"/>
|
|
319
|
+
<Line x1="6" y1={chartHeight / 2} x2={svgWidth - 6} y2={chartHeight / 2} stroke="#F1F5F9" strokeWidth="1" strokeDasharray="4,4"/>
|
|
319
320
|
|
|
320
|
-
{/*
|
|
321
|
-
<Path d={histogramData.
|
|
321
|
+
{/* Glowing Area Underlay */}
|
|
322
|
+
<Path d={histogramData.areaPath} fill="url(#crashlyticsAreaGrad)"/>
|
|
322
323
|
|
|
323
|
-
{/*
|
|
324
|
+
{/* Vertical Crashlytics Histogram Bars */}
|
|
324
325
|
{histogramData.buckets.map((b, idx) => {
|
|
325
326
|
const isSelected = selectedBucketIdx === idx;
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
const barWidth = 10;
|
|
328
|
+
const barX = b.x - barWidth / 2;
|
|
329
|
+
const barHeight = Math.max(3, chartHeight - 8 - b.y);
|
|
330
|
+
const barY = chartHeight - 8 - barHeight;
|
|
331
|
+
return (<G key={`bar-${b.index}`}>
|
|
332
|
+
{/* Background slot */}
|
|
333
|
+
<Rect x={barX} y={6} width={barWidth} height={chartHeight - 14} rx={3} fill={isSelected ? '#EDE9FE' : '#F8FAFC'}/>
|
|
328
334
|
|
|
329
|
-
{
|
|
335
|
+
{/* Active Event Bar */}
|
|
336
|
+
{b.total > 0 && (<Rect x={barX} y={barY} width={barWidth} height={barHeight} rx={3} fill="url(#crashlyticsBarGrad)"/>)}
|
|
330
337
|
|
|
331
|
-
{
|
|
332
|
-
|
|
333
|
-
<Circle cx={b.x} cy={b.y} r="3" fill={AppColors.brandPurple}/>
|
|
334
|
-
</G>)}
|
|
338
|
+
{/* Selected Cursor Line */}
|
|
339
|
+
{isSelected && (<Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke="#8B5CF6" strokeWidth="1.5" strokeDasharray="2,2"/>)}
|
|
335
340
|
</G>);
|
|
336
341
|
})}
|
|
342
|
+
|
|
343
|
+
{/* Crashlytics Apex Trendline */}
|
|
344
|
+
<Path d={histogramData.linePath} fill="none" stroke="#8B5CF6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
345
|
+
|
|
346
|
+
{/* Pulsing Dot on Current Active Time Bucket */}
|
|
347
|
+
{histogramData.buckets.length > 0 && (<G>
|
|
348
|
+
<Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="5.5" fill="#8B5CF6" opacity="0.25"/>
|
|
349
|
+
<Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="3" fill="#7C3AED"/>
|
|
350
|
+
</G>)}
|
|
337
351
|
</Svg>
|
|
338
352
|
|
|
339
353
|
{/* Invisible Touch Columns for Interactive Bucket Selection */}
|
|
@@ -10,7 +10,7 @@ import { AppFonts } from '../../styles/AppFonts';
|
|
|
10
10
|
import { METHOD_COLORS } from '../../constants';
|
|
11
11
|
import { LIB_VERSION } from '../../constants';
|
|
12
12
|
import { getStatusColor, getAppName, getBundleIdentifier, formatTime, getSize } from '../../helpers';
|
|
13
|
-
import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, AppleIcon, AndroidIcon, PackageBoxIcon, } from '../NetworkIcons';
|
|
13
|
+
import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, PackageBoxIcon, } from '../NetworkIcons';
|
|
14
14
|
const InspectorHeader = React.memo(() => {
|
|
15
15
|
const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, activeTab, environment, selectedCrash, setSelectedCrash, } = useInspector();
|
|
16
16
|
const envConfig = useMemo(() => {
|
|
@@ -108,9 +108,9 @@ const InspectorHeader = React.memo(() => {
|
|
|
108
108
|
flex: 1,
|
|
109
109
|
}}>
|
|
110
110
|
<AppHeaderLogo size={46} customIcon={appIcon}/>
|
|
111
|
-
<View style={{ gap: 2, flex: 1 }}>
|
|
112
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: '
|
|
113
|
-
<Text style={[styles.headerTitle]} numberOfLines={1}>
|
|
111
|
+
<View style={{ gap: 2, flex: 1, minWidth: 0 }}>
|
|
112
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'nowrap', minWidth: 0 }}>
|
|
113
|
+
<Text style={[styles.headerTitle, { flexShrink: 1 }]} numberOfLines={1}>
|
|
114
114
|
{getAppName()}
|
|
115
115
|
</Text>
|
|
116
116
|
<View style={[
|
|
@@ -334,6 +334,20 @@ const InspectorHeader = React.memo(() => {
|
|
|
334
334
|
: '—'}
|
|
335
335
|
</Text>
|
|
336
336
|
</View>
|
|
337
|
+
{selected.response != null && (<View style={{
|
|
338
|
+
flexDirection: 'row',
|
|
339
|
+
alignItems: 'center',
|
|
340
|
+
gap: 4,
|
|
341
|
+
paddingHorizontal: 8,
|
|
342
|
+
paddingVertical: 3,
|
|
343
|
+
borderRadius: 20,
|
|
344
|
+
backgroundColor: `${AppColors.white}29`,
|
|
345
|
+
}}>
|
|
346
|
+
<SizeIcon color={AppColors.white} size={11}/>
|
|
347
|
+
<Text style={styles.headerSubTitle}>
|
|
348
|
+
{getSize(selected.response)}
|
|
349
|
+
</Text>
|
|
350
|
+
</View>)}
|
|
337
351
|
</View>
|
|
338
352
|
</View>) : activeTab === 'analytics' && selectedEvent != null ? (<View style={styles.headerDetailCenter}>
|
|
339
353
|
<View style={styles.headerDetailRow}>
|
|
@@ -6,11 +6,11 @@ import HighlightText from '../HighlightText';
|
|
|
6
6
|
import JsonViewer from '../JsonViewer';
|
|
7
7
|
import CopyButton from '../CopyButton';
|
|
8
8
|
import SegmentedTabs from '../SegmentedTabs';
|
|
9
|
-
import { ClearIcon, SearchIcon, PrettyIcon, RawIcon, TableIcon, LayersIcon, TerminalIcon, RequestIcon, InfoCircleIcon, DocIcon, } from '../NetworkIcons';
|
|
9
|
+
import { ClearIcon, SearchIcon, PrettyIcon, RawIcon, TableIcon, LayersIcon, TerminalIcon, RequestIcon, InfoCircleIcon, DocIcon, ExternalLinkIcon, } from '../NetworkIcons';
|
|
10
10
|
import { AppColors } from '../../styles/AppColors';
|
|
11
11
|
import { AppFonts } from '../../styles/AppFonts';
|
|
12
12
|
import styles from '../../styles';
|
|
13
|
-
import { getJsonContent, getSize, formatDateTime, parseStackLine, } from '../../helpers';
|
|
13
|
+
import { getJsonContent, getSize, formatDateTime, parseStackLine, openInVSCode, } from '../../helpers';
|
|
14
14
|
const getRelativeTime = (timestamp) => {
|
|
15
15
|
const now = Date.now();
|
|
16
16
|
const diff = now - timestamp;
|
|
@@ -269,16 +269,18 @@ const LogDetail = React.memo(() => {
|
|
|
269
269
|
: originFrame?.fileName || t('console.consoleLog')}
|
|
270
270
|
</Text>
|
|
271
271
|
|
|
272
|
-
{originFrame?.fileName && (<
|
|
272
|
+
{originFrame?.fileName && (<Pressable onPress={() => openInVSCode(originFrame.rawFilePath ||
|
|
273
|
+
originFrame.fullPath ||
|
|
274
|
+
originFrame.fileName, originFrame.lineNumber, originFrame.columnNumber)} hitSlop={8} style={[
|
|
273
275
|
styles.metaChip,
|
|
274
276
|
{
|
|
275
|
-
backgroundColor: `${AppColors.brandPurple}
|
|
276
|
-
borderColor: `${AppColors.brandPurple}
|
|
277
|
+
backgroundColor: `${AppColors.brandPurple}15`,
|
|
278
|
+
borderColor: `${AppColors.brandPurple}40`,
|
|
277
279
|
paddingHorizontal: 7,
|
|
278
280
|
paddingVertical: 2,
|
|
279
281
|
},
|
|
280
282
|
]}>
|
|
281
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap:
|
|
283
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flexShrink: 1 }}>
|
|
282
284
|
<DocIcon color={AppColors.brandPurple} size={10}/>
|
|
283
285
|
<Text style={[
|
|
284
286
|
styles.metaChipText,
|
|
@@ -288,8 +290,9 @@ const LogDetail = React.memo(() => {
|
|
|
288
290
|
{originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
|
|
289
291
|
{originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
|
|
290
292
|
</Text>
|
|
293
|
+
<ExternalLinkIcon color={AppColors.brandPurple} size={9}/>
|
|
291
294
|
</View>
|
|
292
|
-
</
|
|
295
|
+
</Pressable>)}
|
|
293
296
|
</View>
|
|
294
297
|
|
|
295
298
|
{/* Enhanced metadata metrics row */}
|
|
@@ -18,6 +18,7 @@ import PerformanceTab from './PerformanceTab';
|
|
|
18
18
|
import CrashTab from './CrashTab';
|
|
19
19
|
import CrashDetail from './CrashDetail';
|
|
20
20
|
import SettingsPanel from './SettingsPanel';
|
|
21
|
+
import Toast from '../Toast';
|
|
21
22
|
import styles from '../../styles';
|
|
22
23
|
import { AppColors } from '../../styles/AppColors';
|
|
23
24
|
import NavigationTracker from './NavigationTracker';
|
|
@@ -115,6 +116,9 @@ const MainScreen = () => {
|
|
|
115
116
|
]}>
|
|
116
117
|
<SettingsPanel />
|
|
117
118
|
</View>)}
|
|
119
|
+
|
|
120
|
+
{/* Bottom floating toast notification */}
|
|
121
|
+
<Toast />
|
|
118
122
|
</View>
|
|
119
123
|
</View>
|
|
120
124
|
</ErrorBoundary>)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from '../../i18n';
|
|
3
|
-
import { Image, Linking, Pressable, ScrollView, Text, TextInput, View, } from 'react-native';
|
|
3
|
+
import { Alert, Image, Linking, Pressable, ScrollView, Text, TextInput, View, } from 'react-native';
|
|
4
4
|
import { animateNextLayout, useInspector } from './InspectorContext';
|
|
5
5
|
import TouchableScale from '../TouchableScale';
|
|
6
6
|
import SegmentedTabs from '../SegmentedTabs';
|
|
@@ -20,6 +20,26 @@ import { FailIcon, StatusIcon, HeadersIcon, RequestIcon, ResponseIcon, DownloadI
|
|
|
20
20
|
const NetworkDetail = React.memo(() => {
|
|
21
21
|
const { t } = useTranslation();
|
|
22
22
|
const { selected, detailDisplayUrl, apiDetailActiveTab, setApiDetailActiveTab, detailSearch, setDetailSearch, reqExpanded, setReqExpanded, resExpanded, setResExpanded, showReqDiff, setShowReqDiff, showResDiff, setShowResDiff, prevRequestData, prevResponseData, logRouteMapRef, } = useInspector();
|
|
23
|
+
const handleOpenUrl = () => {
|
|
24
|
+
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${detailDisplayUrl}`, [
|
|
25
|
+
{ text: t('common.cancel') || 'Cancel', style: 'cancel' },
|
|
26
|
+
{
|
|
27
|
+
text: t('common.open') || 'Open',
|
|
28
|
+
onPress: () => {
|
|
29
|
+
Linking.canOpenURL(detailDisplayUrl)
|
|
30
|
+
.then(supported => {
|
|
31
|
+
if (supported) {
|
|
32
|
+
Linking.openURL(detailDisplayUrl);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
Linking.openURL(detailDisplayUrl).catch(() => { });
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.catch(() => { });
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
};
|
|
23
43
|
if (!selected)
|
|
24
44
|
return null;
|
|
25
45
|
return (<View style={{ flex: 1 }}>
|
|
@@ -227,7 +247,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
227
247
|
</View>
|
|
228
248
|
|
|
229
249
|
<View style={styles.detailInfoRight}>
|
|
230
|
-
<TouchableScale style={[styles.iconSquareBtn, { backgroundColor: `${AppColors.sky600}15` }]} onPress={
|
|
250
|
+
<TouchableScale style={[styles.iconSquareBtn, { backgroundColor: `${AppColors.sky600}15` }]} onPress={handleOpenUrl} hitSlop={12}>
|
|
231
251
|
<ExternalLinkIcon color={AppColors.sky600} size={14}/>
|
|
232
252
|
</TouchableScale>
|
|
233
253
|
<CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
|
|
@@ -245,7 +265,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
245
265
|
padding: 10,
|
|
246
266
|
marginTop: 8,
|
|
247
267
|
gap: 6,
|
|
248
|
-
}} onPress={
|
|
268
|
+
}} onPress={handleOpenUrl}>
|
|
249
269
|
<View style={{
|
|
250
270
|
flexDirection: 'row',
|
|
251
271
|
alignItems: 'center',
|
|
@@ -428,7 +448,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
428
448
|
if (!reqExpanded && !showReqDiff)
|
|
429
449
|
setReqExpanded(true);
|
|
430
450
|
}}/>
|
|
431
|
-
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer data={selected.request} search={detailSearch}
|
|
451
|
+
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer data={selected.request} search={detailSearch}/>) : (<View style={styles.codeBlock}>
|
|
432
452
|
<Text style={[
|
|
433
453
|
styles.codeText,
|
|
434
454
|
{ color: AppColors.grayTextWeak },
|
|
@@ -447,26 +467,6 @@ const NetworkDetail = React.memo(() => {
|
|
|
447
467
|
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
448
468
|
</Pressable>)}
|
|
449
469
|
</View>
|
|
450
|
-
{selected.response != null && (<View style={{
|
|
451
|
-
flexDirection: 'row',
|
|
452
|
-
alignItems: 'center',
|
|
453
|
-
gap: 4,
|
|
454
|
-
paddingHorizontal: 10,
|
|
455
|
-
height: 34,
|
|
456
|
-
borderRadius: 8,
|
|
457
|
-
borderWidth: 1,
|
|
458
|
-
borderColor: `${AppColors.purple}30`,
|
|
459
|
-
backgroundColor: `${AppColors.purple}10`,
|
|
460
|
-
}}>
|
|
461
|
-
<SizeIcon color={AppColors.purple} size={12}/>
|
|
462
|
-
<Text style={{
|
|
463
|
-
fontFamily: AppFonts.interBold,
|
|
464
|
-
fontSize: 10.5,
|
|
465
|
-
color: AppColors.purple,
|
|
466
|
-
}}>
|
|
467
|
-
{getSize(selected.response)}
|
|
468
|
-
</Text>
|
|
469
|
-
</View>)}
|
|
470
470
|
</View>
|
|
471
471
|
|
|
472
472
|
<View style={styles.sectionContainer}>
|
|
@@ -475,7 +475,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
475
475
|
if (!resExpanded && !showResDiff)
|
|
476
476
|
setResExpanded(true);
|
|
477
477
|
}}/>
|
|
478
|
-
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer data={selected.response} search={detailSearch}
|
|
478
|
+
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer data={selected.response} search={detailSearch} wrap/>) : (<View style={styles.codeBlock}>
|
|
479
479
|
<Text style={[
|
|
480
480
|
styles.codeText,
|
|
481
481
|
{ color: AppColors.grayTextWeak },
|
|
@@ -57,7 +57,7 @@ const JsonTableRow = React.memo(({ itemKey, val, search, }) => {
|
|
|
57
57
|
// ── JsonViewer Component ─────────────────────────────────────────────────────
|
|
58
58
|
const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wrap, fullHeight = false, maxHeight, mode: externalMode, onModeChange, hideTabs = false, }) => {
|
|
59
59
|
const { t } = useTranslation();
|
|
60
|
-
const [internalMode, setInternalMode] = useState('
|
|
60
|
+
const [internalMode, setInternalMode] = useState('raw');
|
|
61
61
|
const mode = externalMode ?? internalMode;
|
|
62
62
|
const setMode = (newMode) => {
|
|
63
63
|
if (onModeChange) {
|
|
@@ -78,6 +78,10 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
78
78
|
return String(data);
|
|
79
79
|
}
|
|
80
80
|
}, [data]);
|
|
81
|
+
const [showFullRaw, setShowFullRaw] = useState(false);
|
|
82
|
+
const RAW_LIMIT = 50000;
|
|
83
|
+
const isTruncated = rawText.length > RAW_LIMIT && !showFullRaw;
|
|
84
|
+
const displayRawText = isTruncated ? rawText.slice(0, RAW_LIMIT) : rawText;
|
|
81
85
|
useEffect(() => {
|
|
82
86
|
if (externalMode) {
|
|
83
87
|
setInternalMode(externalMode);
|
|
@@ -148,7 +152,7 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
148
152
|
<View style={contentWrapperStyle}>
|
|
149
153
|
{mode === 'pretty' && (isEmpty ? (<View style={localStyles.emptyTable}>
|
|
150
154
|
<Text style={localStyles.emptyTableText}>
|
|
151
|
-
{t('network.
|
|
155
|
+
{t('network.jsonViewer.emptyPayload')}
|
|
152
156
|
</Text>
|
|
153
157
|
</View>) : (wrap ? (fullHeight ? (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ flexGrow: 1 }}>
|
|
154
158
|
<View style={[styles.codeBlock, { width: '100%' }]}>{tree}</View>
|
|
@@ -163,12 +167,30 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
|
|
|
163
167
|
</ScrollView>))))}
|
|
164
168
|
|
|
165
169
|
{mode === 'raw' && (<ScrollView style={scrollStyle} contentContainerStyle={localStyles.rawContainer} showsVerticalScrollIndicator={true} nestedScrollEnabled={true}>
|
|
166
|
-
<HighlightText text={
|
|
170
|
+
<HighlightText text={displayRawText} search={search} detectLinks={rawText.length < 20000} style={localStyles.rawPlainText} selectable={true} highlightStyle={{
|
|
167
171
|
backgroundColor: AppColors.yellowHighlight,
|
|
168
172
|
color: AppColors.primaryBlack,
|
|
169
173
|
borderRadius: 3,
|
|
170
174
|
paddingHorizontal: 2,
|
|
171
175
|
}}/>
|
|
176
|
+
{isTruncated && (<TouchableOpacity onPress={() => setShowFullRaw(true)} style={{
|
|
177
|
+
marginTop: 10,
|
|
178
|
+
backgroundColor: `${AppColors.brandPurple}15`,
|
|
179
|
+
paddingVertical: 8,
|
|
180
|
+
paddingHorizontal: 12,
|
|
181
|
+
borderRadius: 6,
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
borderWidth: 1,
|
|
184
|
+
borderColor: `${AppColors.brandPurple}30`,
|
|
185
|
+
}}>
|
|
186
|
+
<Text style={{
|
|
187
|
+
fontFamily: AppFonts.interBold,
|
|
188
|
+
fontSize: 11,
|
|
189
|
+
color: AppColors.brandPurple,
|
|
190
|
+
}}>
|
|
191
|
+
Showing first 50 KB ({Math.round(rawText.length / 1024)} KB total) · Tap to load full
|
|
192
|
+
</Text>
|
|
193
|
+
</TouchableOpacity>)}
|
|
172
194
|
</ScrollView>)}
|
|
173
195
|
|
|
174
196
|
{mode === 'table' && (fullHeight ? (<ScrollView style={scrollStyle}>
|