react-native-inapp-inspector 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,794 @@
1
+ import React from 'react';
2
+ import {useTranslation} from '../../i18n';
3
+ import {
4
+ Alert,
5
+ Image,
6
+ Linking,
7
+ Pressable,
8
+ ScrollView,
9
+ Text,
10
+ TextInput,
11
+ View,
12
+ } from 'react-native';
13
+ import Svg, {Circle} from 'react-native-svg';
14
+ import {animateNextLayout, useInspector} from './InspectorContext';
15
+ import TouchableScale from '../TouchableScale';
16
+ import SegmentedTabs from '../SegmentedTabs';
17
+ import MetaAccordion from '../MetaAccordion';
18
+ import HeadersSection from '../HeadersSection';
19
+ import SourcePageCard from '../SourcePageCard';
20
+ import SectionHeader from '../SectionHeader';
21
+ import JsonViewer from '../JsonViewer';
22
+ import DiffViewer from '../DiffViewer';
23
+ import CopyButton from '../CopyButton';
24
+ import styles from '../../styles';
25
+ import {AppColors} from '../../styles/AppColors';
26
+ import {AppFonts} from '../../styles/AppFonts';
27
+ import {METHOD_COLORS} from '../../constants';
28
+ import {Method} from '../../types';
29
+ import {
30
+ getStatusColor,
31
+ getSize,
32
+ formatDateTime,
33
+ getFetchCommand,
34
+ getCurlCommand,
35
+ } from '../../helpers';
36
+ import {
37
+ FailIcon,
38
+ GlobeIcon,
39
+ StatusIcon,
40
+ HeadersIcon,
41
+ RequestIcon,
42
+ ResponseIcon,
43
+ DownloadIcon,
44
+ ClearIcon,
45
+ SizeIcon,
46
+ ExternalLinkIcon,
47
+ ClockIcon,
48
+ } from '../NetworkIcons';
49
+
50
+ const NetworkDetail = React.memo(() => {
51
+ const {t} = useTranslation();
52
+ const {
53
+ selected,
54
+ detailDisplayUrl,
55
+ apiDetailActiveTab,
56
+ setApiDetailActiveTab,
57
+ detailSearch,
58
+ setDetailSearch,
59
+ reqExpanded,
60
+ setReqExpanded,
61
+ resExpanded,
62
+ setResExpanded,
63
+ showReqDiff,
64
+ setShowReqDiff,
65
+ showResDiff,
66
+ setShowResDiff,
67
+ prevRequestData,
68
+ prevResponseData,
69
+ logRouteMapRef,
70
+ } = useInspector();
71
+
72
+ const handleOpenUrl = () => {
73
+ Alert.alert(
74
+ t('common.openInBrowser') || 'Open in Browser',
75
+ `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${detailDisplayUrl}`,
76
+ [
77
+ {text: t('common.cancel') || 'Cancel', style: 'cancel'},
78
+ {
79
+ text: t('common.open') || 'Open',
80
+ onPress: () => {
81
+ Linking.canOpenURL(detailDisplayUrl)
82
+ .then(supported => {
83
+ if (supported) {
84
+ Linking.openURL(detailDisplayUrl);
85
+ } else {
86
+ Linking.openURL(detailDisplayUrl).catch(() => {});
87
+ }
88
+ })
89
+ .catch(() => {});
90
+ },
91
+ },
92
+ ],
93
+ );
94
+ };
95
+
96
+ if (!selected) return null;
97
+
98
+ return (
99
+ <View style={{flex: 1}}>
100
+ {/* Non-scrollable details header */}
101
+ <View style={{paddingHorizontal: 6, paddingTop: 4}}>
102
+ <View style={styles.detailInfoBar}>
103
+ {(() => {
104
+ let schemeStr = '';
105
+ let hostStr = '';
106
+ let pathStr = detailDisplayUrl;
107
+ let queryStr = '';
108
+ try {
109
+ // Simple parsing fallback for React Native environments
110
+ const qIndex = detailDisplayUrl.indexOf('?');
111
+ let cleanUrlForParsing = detailDisplayUrl;
112
+ if (qIndex !== -1) {
113
+ pathStr = detailDisplayUrl.substring(0, qIndex);
114
+ queryStr = detailDisplayUrl.substring(qIndex);
115
+ cleanUrlForParsing = pathStr;
116
+ }
117
+ const schemeIndex =
118
+ cleanUrlForParsing.indexOf('://');
119
+ if (schemeIndex !== -1) {
120
+ schemeStr = cleanUrlForParsing.substring(
121
+ 0,
122
+ schemeIndex + 3,
123
+ );
124
+ const withoutScheme =
125
+ cleanUrlForParsing.substring(schemeIndex + 3);
126
+ const firstSlash = withoutScheme.indexOf('/');
127
+ if (firstSlash !== -1) {
128
+ hostStr = withoutScheme.substring(
129
+ 0,
130
+ firstSlash,
131
+ );
132
+ pathStr = withoutScheme.substring(firstSlash);
133
+ } else {
134
+ hostStr = withoutScheme;
135
+ pathStr = '/';
136
+ }
137
+ } else {
138
+ const firstSlash = cleanUrlForParsing.indexOf('/');
139
+ if (firstSlash !== -1) {
140
+ hostStr = cleanUrlForParsing.substring(
141
+ 0,
142
+ firstSlash,
143
+ );
144
+ pathStr = cleanUrlForParsing.substring(firstSlash);
145
+ } else if (cleanUrlForParsing) {
146
+ hostStr = cleanUrlForParsing;
147
+ pathStr = '/';
148
+ }
149
+ }
150
+ } catch (e) {}
151
+
152
+ return (
153
+ <>
154
+ <View style={styles.detailInfoTop}>
155
+ <View
156
+ style={{
157
+ flexDirection: 'row',
158
+ alignItems: 'center',
159
+ gap: 6,
160
+ flexWrap: 'wrap',
161
+ flex: 1,
162
+ }}>
163
+ {/* Method Chip */}
164
+ <View
165
+ style={[
166
+ styles.methodBadge,
167
+ {
168
+ backgroundColor:
169
+ METHOD_COLORS[
170
+ selected.method as Method
171
+ ] ?? METHOD_COLORS.ALL,
172
+ paddingHorizontal: 8,
173
+ paddingVertical: 4,
174
+ borderRadius: 7,
175
+ },
176
+ ]}>
177
+ <Text
178
+ style={[
179
+ styles.methodBadgeText,
180
+ {
181
+ color: AppColors.white,
182
+ fontSize: 10.5,
183
+ },
184
+ ]}>
185
+ {selected.method}
186
+ </Text>
187
+ </View>
188
+
189
+ {/* Client Chip (AXIOS / FETCH / XHR) */}
190
+ {selected.client && (
191
+ <View
192
+ style={[
193
+ styles.chip,
194
+ {
195
+ backgroundColor:
196
+ selected.client === 'axios'
197
+ ? `${AppColors.purple}14`
198
+ : selected.client === 'apollo' || selected.client === 'graphql'
199
+ ? `${AppColors.pink500}14`
200
+ : selected.client === 'xhr'
201
+ ? `${AppColors.amber500}14`
202
+ : `${AppColors.sky500}14`,
203
+ borderColor:
204
+ selected.client === 'axios'
205
+ ? `${AppColors.purple}30`
206
+ : selected.client === 'apollo' || selected.client === 'graphql'
207
+ ? `${AppColors.pink500}30`
208
+ : selected.client === 'xhr'
209
+ ? `${AppColors.amber500}30`
210
+ : `${AppColors.sky500}30`,
211
+ paddingHorizontal: 6,
212
+ paddingVertical: 3,
213
+ borderRadius: 6,
214
+ },
215
+ ]}>
216
+ <Text
217
+ style={[
218
+ styles.chipText,
219
+ {
220
+ fontFamily: AppFonts.interBold,
221
+ fontSize: 9.5,
222
+ color:
223
+ selected.client === 'axios'
224
+ ? AppColors.purple
225
+ : selected.client === 'apollo' || selected.client === 'graphql'
226
+ ? AppColors.pink500
227
+ : selected.client === 'xhr'
228
+ ? AppColors.amber700
229
+ : AppColors.sky600,
230
+ },
231
+ ]}>
232
+ {selected.client.toUpperCase()}
233
+ </Text>
234
+ </View>
235
+ )}
236
+
237
+ {/* Status Pill */}
238
+ {selected.status != null && (
239
+ <View
240
+ style={[
241
+ styles.chip,
242
+ {
243
+ backgroundColor:
244
+ selected.status === 0
245
+ ? `${AppColors.errorColor}18`
246
+ : `${getStatusColor(
247
+ selected.status,
248
+ )}18`,
249
+ borderColor:
250
+ selected.status === 0
251
+ ? `${AppColors.errorColor}40`
252
+ : `${getStatusColor(
253
+ selected.status,
254
+ )}40`,
255
+ paddingHorizontal: 7,
256
+ paddingVertical: 3.5,
257
+ borderRadius: 7,
258
+ },
259
+ ]}>
260
+ {selected.status === 0 ? (
261
+ <FailIcon
262
+ size={8}
263
+ color={AppColors.errorColor}
264
+ />
265
+ ) : (
266
+ <View
267
+ style={{
268
+ width: 6,
269
+ height: 6,
270
+ borderRadius: 3,
271
+ backgroundColor: getStatusColor(
272
+ selected.status,
273
+ ),
274
+ }}
275
+ />
276
+ )}
277
+ <Text
278
+ style={[
279
+ styles.chipText,
280
+ {
281
+ color:
282
+ selected.status === 0
283
+ ? AppColors.errorColor
284
+ : getStatusColor(
285
+ selected.status,
286
+ ),
287
+ fontFamily: AppFonts.interBold,
288
+ fontSize: 10.5,
289
+ },
290
+ ]}>
291
+ {selected.status === 0
292
+ ? 'Failed'
293
+ : `${selected.status} ${selected.status === 200 ? 'OK' : ''}`}
294
+ </Text>
295
+ </View>
296
+ )}
297
+
298
+ {/* Duration Pill */}
299
+ {selected.duration != null && (
300
+ <View
301
+ style={[
302
+ styles.chip,
303
+ {
304
+ backgroundColor: `${AppColors.brandPurple}14`,
305
+ borderColor: `${AppColors.brandPurple}30`,
306
+ paddingHorizontal: 7,
307
+ paddingVertical: 3.5,
308
+ borderRadius: 7,
309
+ gap: 4,
310
+ },
311
+ ]}>
312
+ <ClockIcon color={AppColors.brandPurple} size={10} />
313
+ <Text
314
+ style={[
315
+ styles.chipText,
316
+ {
317
+ color: AppColors.brandPurple,
318
+ fontFamily: AppFonts.interBold,
319
+ fontSize: 10.5,
320
+ },
321
+ ]}>
322
+ {selected.duration}ms
323
+ </Text>
324
+ </View>
325
+ )}
326
+
327
+ {/* Size Pill */}
328
+ {selected.response != null && (
329
+ <View
330
+ style={[
331
+ styles.chip,
332
+ {
333
+ backgroundColor: `${AppColors.sky600}14`,
334
+ borderColor: `${AppColors.sky600}30`,
335
+ paddingHorizontal: 7,
336
+ paddingVertical: 3.5,
337
+ borderRadius: 7,
338
+ gap: 4,
339
+ },
340
+ ]}>
341
+ <SizeIcon color={AppColors.sky600} size={10} />
342
+ <Text
343
+ style={[
344
+ styles.chipText,
345
+ {
346
+ color: AppColors.sky600,
347
+ fontFamily: AppFonts.interBold,
348
+ fontSize: 10.5,
349
+ },
350
+ ]}>
351
+ {getSize(selected.response)}
352
+ </Text>
353
+ </View>
354
+ )}
355
+ </View>
356
+
357
+ <View style={styles.detailInfoRight}>
358
+ <TouchableScale
359
+ style={[styles.iconSquareBtn, {backgroundColor: `${AppColors.sky600}15`}]}
360
+ onPress={handleOpenUrl}
361
+ hitSlop={12}>
362
+ <ExternalLinkIcon
363
+ color={AppColors.sky600}
364
+ size={14}
365
+ />
366
+ </TouchableScale>
367
+ <CopyButton
368
+ value={getFetchCommand(selected)}
369
+ label="fetch()"
370
+ iconType="fetch"
371
+ />
372
+ <CopyButton
373
+ value={getCurlCommand(selected)}
374
+ label="cURL"
375
+ iconType="terminal"
376
+ />
377
+ <CopyButton
378
+ value={detailDisplayUrl}
379
+ label="URL"
380
+ />
381
+ </View>
382
+ </View>
383
+
384
+ {/* Full Live URL Analysis Card */}
385
+ <Pressable
386
+ style={{
387
+ backgroundColor: AppColors.grayBackground,
388
+ borderRadius: 10,
389
+ borderWidth: 1,
390
+ borderColor: AppColors.dividerColor,
391
+ padding: 10,
392
+ marginTop: 8,
393
+ gap: 6,
394
+ }}
395
+ onPress={handleOpenUrl}>
396
+ <View
397
+ style={{
398
+ flexDirection: 'row',
399
+ alignItems: 'center',
400
+ justifyContent: 'space-between',
401
+ }}>
402
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
403
+ {schemeStr ? (
404
+ <View
405
+ style={{
406
+ backgroundColor: schemeStr.startsWith('https')
407
+ ? `${AppColors.green600}18`
408
+ : `${AppColors.amber500}18`,
409
+ paddingHorizontal: 6,
410
+ paddingVertical: 2,
411
+ borderRadius: 4,
412
+ }}>
413
+ <Text
414
+ style={{
415
+ fontFamily: AppFonts.interBold,
416
+ fontSize: 9,
417
+ color: schemeStr.startsWith('https')
418
+ ? AppColors.green600
419
+ : AppColors.amber500,
420
+ }}>
421
+ {schemeStr.replace('://', '').toUpperCase()}
422
+ </Text>
423
+ </View>
424
+ ) : null}
425
+
426
+ <Text
427
+ style={{
428
+ fontFamily: AppFonts.interBold,
429
+ fontSize: 10,
430
+ color: AppColors.grayTextWeak,
431
+ letterSpacing: 0.4,
432
+ textTransform: 'uppercase',
433
+ }}>
434
+ ENDPOINT URL ↗
435
+ </Text>
436
+ </View>
437
+
438
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
439
+ {queryStr ? (
440
+ <View
441
+ style={{
442
+ backgroundColor: `${AppColors.purple}14`,
443
+ paddingHorizontal: 6,
444
+ paddingVertical: 2,
445
+ borderRadius: 4,
446
+ }}>
447
+ <Text
448
+ style={{
449
+ fontFamily: AppFonts.interBold,
450
+ fontSize: 8.5,
451
+ color: AppColors.purple,
452
+ }}>
453
+ {t('network.queryParams')}
454
+ </Text>
455
+ </View>
456
+ ) : null}
457
+
458
+ <View
459
+ style={{
460
+ flexDirection: 'row',
461
+ alignItems: 'center',
462
+ gap: 3,
463
+ backgroundColor: `${AppColors.sky600}14`,
464
+ paddingHorizontal: 6,
465
+ paddingVertical: 2,
466
+ borderRadius: 4,
467
+ }}>
468
+ <ExternalLinkIcon color={AppColors.sky600} size={10} />
469
+ <Text
470
+ style={{
471
+ fontFamily: AppFonts.interBold,
472
+ fontSize: 9,
473
+ color: AppColors.sky600,
474
+ }}>
475
+ Open ↗
476
+ </Text>
477
+ </View>
478
+ </View>
479
+ </View>
480
+
481
+ {/* Full Clickable Hyperlink */}
482
+ <Text
483
+ selectable={true}
484
+ style={{
485
+ fontFamily: AppFonts.interMedium,
486
+ fontSize: 12.5,
487
+ color: AppColors.skyBlue,
488
+ textDecorationLine: 'underline',
489
+ lineHeight: 18,
490
+ }}>
491
+ {detailDisplayUrl}
492
+ </Text>
493
+ </Pressable>
494
+ </>
495
+ );
496
+ })()}
497
+ </View>
498
+ </View>
499
+
500
+ {/* Sticky Segment Control */}
501
+ <SegmentedTabs
502
+ tabs={[
503
+ {
504
+ key: 'metadata',
505
+ label: t('network.detailTabs.metadata'),
506
+ icon: (isActive: boolean) => (
507
+ <StatusIcon color={isActive ? AppColors.white : AppColors.grayText} />
508
+ ),
509
+ },
510
+ {
511
+ key: 'headers',
512
+ label: t('network.detailTabs.headers'),
513
+ icon: (isActive: boolean) => (
514
+ <HeadersIcon color={isActive ? AppColors.white : AppColors.grayText} />
515
+ ),
516
+ },
517
+ ...(selected.request != null
518
+ ? [
519
+ {
520
+ key: 'request',
521
+ label: t('network.detailTabs.request'),
522
+ icon: (isActive: boolean) => (
523
+ <RequestIcon color={isActive ? AppColors.white : AppColors.grayText} />
524
+ ),
525
+ },
526
+ ]
527
+ : []),
528
+ {
529
+ key: 'response',
530
+ label: t('network.detailTabs.response'),
531
+ icon: (isActive: boolean) => (
532
+ <ResponseIcon color={isActive ? AppColors.white : AppColors.grayText} />
533
+ ),
534
+ },
535
+ ]}
536
+ activeKey={apiDetailActiveTab}
537
+ onChange={tab => {
538
+ animateNextLayout();
539
+ setApiDetailActiveTab(
540
+ tab as 'metadata' | 'headers' | 'request' | 'response',
541
+ );
542
+ }}
543
+ style={{marginHorizontal: 6, marginBottom: 10, marginTop: 6}}
544
+ />
545
+
546
+ {/* Scrollable Tab Content */}
547
+ <ScrollView
548
+ style={styles.detailScroll}
549
+ contentContainerStyle={{
550
+ paddingHorizontal: 6,
551
+ paddingBottom: 24,
552
+ }}
553
+ showsVerticalScrollIndicator={true}>
554
+ {apiDetailActiveTab === 'metadata' && (
555
+ <>
556
+ <MetaAccordion
557
+ status={selected.status}
558
+ statusColor={getStatusColor(selected.status)}
559
+ duration={selected.duration}
560
+ size={getSize(selected.response)}
561
+ triggeredAt={formatDateTime(selected.startTime)}
562
+ method={selected.method}
563
+ contentType={
564
+ selected.responseHeaders?.['content-type'] ||
565
+ selected.responseHeaders?.['Content-Type']
566
+ }
567
+ url={selected.url}
568
+ />
569
+
570
+ {(() => {
571
+ const routeInfo = logRouteMapRef.current.get(
572
+ selected.id,
573
+ );
574
+ if (!routeInfo || routeInfo.path === 'Navigators')
575
+ return null;
576
+ return <SourcePageCard routeInfo={routeInfo} />;
577
+ })()}
578
+
579
+ {(() => {
580
+ const cType =
581
+ selected.responseHeaders?.['content-type'] ||
582
+ selected.responseHeaders?.['Content-Type'];
583
+ if (cType?.includes('image/')) {
584
+ return (
585
+ <View style={styles.imagePreviewWrapper}>
586
+ <Image
587
+ source={{uri: selected.url}}
588
+ style={styles.imagePreview}
589
+ resizeMode="contain"
590
+ />
591
+ <TouchableScale
592
+ style={styles.imageDownloadBtn}
593
+ onPress={() =>
594
+ Linking.openURL(selected.url)
595
+ }
596
+ hitSlop={10}>
597
+ <DownloadIcon
598
+ color={AppColors.purple}
599
+ size={18}
600
+ />
601
+ </TouchableScale>
602
+ </View>
603
+ );
604
+ }
605
+ return null;
606
+ })()}
607
+ </>
608
+ )}
609
+
610
+ {apiDetailActiveTab === 'headers' && (
611
+ <>
612
+ <View style={styles.detailSearchRow}>
613
+ <View style={styles.detailSearchBox}>
614
+ <TextInput
615
+ placeholder={t('network.searchHeaders')}
616
+ placeholderTextColor={AppColors.grayTextWeak}
617
+ value={detailSearch}
618
+ onChangeText={setDetailSearch}
619
+ style={styles.detailSearchInput}
620
+ autoCorrect={false}
621
+ autoCapitalize="none"
622
+ />
623
+ {detailSearch.length > 0 && (
624
+ <Pressable
625
+ onPress={() => setDetailSearch('')}
626
+ hitSlop={10}
627
+ style={{padding: 8}}>
628
+ <ClearIcon
629
+ color={AppColors.grayTextWeak}
630
+ size={14}
631
+ />
632
+ </Pressable>
633
+ )}
634
+ </View>
635
+ </View>
636
+
637
+ <HeadersSection
638
+ title="Request Headers"
639
+ headers={selected.requestHeaders}
640
+ search={detailSearch}
641
+ resetKey={selected.id}
642
+ />
643
+ <HeadersSection
644
+ title="Response Headers"
645
+ headers={selected.responseHeaders}
646
+ search={detailSearch}
647
+ resetKey={selected.id}
648
+ />
649
+ </>
650
+ )}
651
+
652
+ {apiDetailActiveTab === 'request' &&
653
+ selected.request != null && (
654
+ <>
655
+ <View style={styles.detailSearchRow}>
656
+ <View style={styles.detailSearchBox}>
657
+ <TextInput
658
+ placeholder={t('network.searchRequest')}
659
+ placeholderTextColor={
660
+ AppColors.grayTextWeak
661
+ }
662
+ value={detailSearch}
663
+ onChangeText={setDetailSearch}
664
+ style={styles.detailSearchInput}
665
+ autoCorrect={false}
666
+ autoCapitalize="none"
667
+ />
668
+ {detailSearch.length > 0 && (
669
+ <Pressable
670
+ onPress={() => setDetailSearch('')}
671
+ hitSlop={10}
672
+ style={{padding: 8}}>
673
+ <ClearIcon
674
+ color={AppColors.grayTextWeak}
675
+ size={14}
676
+ />
677
+ </Pressable>
678
+ )}
679
+ </View>
680
+ </View>
681
+
682
+ <View style={styles.sectionContainer}>
683
+ <SectionHeader
684
+ title={t('network.requestTitle')}
685
+ value={selected.request}
686
+ expanded={reqExpanded}
687
+ onToggleExpand={() => setReqExpanded(v => !v)}
688
+ showDiff={prevRequestData != null}
689
+ isDiffing={showReqDiff}
690
+ onToggleDiff={() => {
691
+ setShowReqDiff(v => !v);
692
+ if (!reqExpanded && !showReqDiff)
693
+ setReqExpanded(true);
694
+ }}
695
+ />
696
+ {showReqDiff ? (
697
+ <DiffViewer
698
+ oldData={prevRequestData}
699
+ newData={selected.request}
700
+ forceOpen={reqExpanded}
701
+ />
702
+ ) : reqExpanded ? (
703
+ <JsonViewer
704
+ data={selected.request}
705
+ search={detailSearch}
706
+ />
707
+ ) : (
708
+ <View style={styles.codeBlock}>
709
+ <Text
710
+ style={[
711
+ styles.codeText,
712
+ {color: AppColors.grayTextWeak},
713
+ ]}>
714
+ {t('network.bodyHidden')}
715
+ </Text>
716
+ </View>
717
+ )}
718
+ </View>
719
+ </>
720
+ )}
721
+
722
+ {apiDetailActiveTab === 'response' && (
723
+ <>
724
+ <View style={styles.detailSearchRow}>
725
+ <View style={styles.detailSearchBox}>
726
+ <TextInput
727
+ placeholder={t('network.searchResponse')}
728
+ placeholderTextColor={AppColors.grayTextWeak}
729
+ value={detailSearch}
730
+ onChangeText={setDetailSearch}
731
+ style={styles.detailSearchInput}
732
+ autoCorrect={false}
733
+ autoCapitalize="none"
734
+ />
735
+ {detailSearch.length > 0 && (
736
+ <Pressable
737
+ onPress={() => setDetailSearch('')}
738
+ hitSlop={10}
739
+ style={{padding: 8}}>
740
+ <ClearIcon
741
+ color={AppColors.grayTextWeak}
742
+ size={14}
743
+ />
744
+ </Pressable>
745
+ )}
746
+ </View>
747
+ </View>
748
+
749
+ <View style={styles.sectionContainer}>
750
+ <SectionHeader
751
+ title={t('network.responseTitle')}
752
+ value={selected.response}
753
+ expanded={resExpanded}
754
+ onToggleExpand={() => setResExpanded(v => !v)}
755
+ showDiff={prevResponseData != null}
756
+ isDiffing={showResDiff}
757
+ onToggleDiff={() => {
758
+ setShowResDiff(v => !v);
759
+ if (!resExpanded && !showResDiff)
760
+ setResExpanded(true);
761
+ }}
762
+ />
763
+ {showResDiff ? (
764
+ <DiffViewer
765
+ oldData={prevResponseData}
766
+ newData={selected.response}
767
+ forceOpen={resExpanded}
768
+ />
769
+ ) : resExpanded ? (
770
+ <JsonViewer
771
+ data={selected.response}
772
+ search={detailSearch}
773
+ wrap
774
+ />
775
+ ) : (
776
+ <View style={styles.codeBlock}>
777
+ <Text
778
+ style={[
779
+ styles.codeText,
780
+ {color: AppColors.grayTextWeak},
781
+ ]}>
782
+ {t('network.bodyHidden')}
783
+ </Text>
784
+ </View>
785
+ )}
786
+ </View>
787
+ </>
788
+ )}
789
+ </ScrollView>
790
+ </View>
791
+ );
792
+ });
793
+
794
+ export default NetworkDetail;