react-native-inapp-inspector 2.2.9 → 2.3.1

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 (105) hide show
  1. package/dist/commonjs/components/AppHeaderLogo.js +45 -12
  2. package/dist/commonjs/components/BrandSquareIcon.js +92 -49
  3. package/dist/commonjs/components/EmptyState.d.ts +7 -2
  4. package/dist/commonjs/components/EmptyState.js +11 -5
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +29 -18
  6. package/dist/commonjs/components/Inspector/ConsoleTab.js +6 -1
  7. package/dist/commonjs/components/Inspector/CrashDetail.js +43 -9
  8. package/dist/commonjs/components/Inspector/CrashTab.js +34 -3
  9. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +80 -16
  10. package/dist/commonjs/components/Inspector/InspectorHeader.js +16 -3
  11. package/dist/commonjs/components/Inspector/MainScreen.js +7 -11
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +3 -2
  13. package/dist/commonjs/components/Inspector/NetworkTab.js +103 -4
  14. package/dist/commonjs/components/Inspector/ReduxTab.js +19 -3
  15. package/dist/commonjs/components/Inspector/SettingsPanel.js +4 -4
  16. package/dist/commonjs/components/Inspector/StorageTab.js +123 -92
  17. package/dist/commonjs/components/JsonViewer.js +2 -0
  18. package/dist/commonjs/components/MetaAccordion.js +15 -3
  19. package/dist/commonjs/components/SkeletonPlaceholder.d.ts +6 -0
  20. package/dist/commonjs/components/SkeletonPlaceholder.js +208 -0
  21. package/dist/commonjs/constants/version.d.ts +1 -1
  22. package/dist/commonjs/constants/version.js +1 -1
  23. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  24. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  25. package/dist/commonjs/customHooks/storageInspector.d.ts +2 -2
  26. package/dist/commonjs/customHooks/storageInspector.js +17 -13
  27. package/dist/commonjs/helpers/index.d.ts +6 -0
  28. package/dist/commonjs/helpers/index.js +196 -6
  29. package/dist/commonjs/helpers/searchQueryParser.js +62 -20
  30. package/dist/commonjs/i18n/index.d.ts +13 -0
  31. package/dist/commonjs/i18n/index.js +35 -2
  32. package/dist/commonjs/index.d.ts +3 -0
  33. package/dist/commonjs/index.js +108 -35
  34. package/dist/commonjs/styles/AppColors.d.ts +4 -0
  35. package/dist/commonjs/styles/AppColors.js +11 -1
  36. package/dist/commonjs/styles/AppFonts.d.ts +11 -0
  37. package/dist/commonjs/styles/AppFonts.js +11 -1
  38. package/dist/commonjs/types/interfaces.d.ts +2 -2
  39. package/dist/esm/components/AppHeaderLogo.js +45 -12
  40. package/dist/esm/components/BrandSquareIcon.js +92 -49
  41. package/dist/esm/components/EmptyState.d.ts +7 -2
  42. package/dist/esm/components/EmptyState.js +11 -5
  43. package/dist/esm/components/Inspector/AnalyticsTab.js +30 -19
  44. package/dist/esm/components/Inspector/ConsoleTab.js +6 -1
  45. package/dist/esm/components/Inspector/CrashDetail.js +43 -9
  46. package/dist/esm/components/Inspector/CrashTab.js +34 -3
  47. package/dist/esm/components/Inspector/DeviceInfoTab.js +80 -16
  48. package/dist/esm/components/Inspector/InspectorHeader.js +17 -4
  49. package/dist/esm/components/Inspector/MainScreen.js +8 -12
  50. package/dist/esm/components/Inspector/NetworkDetail.js +3 -2
  51. package/dist/esm/components/Inspector/NetworkTab.js +103 -4
  52. package/dist/esm/components/Inspector/ReduxTab.js +19 -3
  53. package/dist/esm/components/Inspector/SettingsPanel.js +4 -4
  54. package/dist/esm/components/Inspector/StorageTab.js +124 -93
  55. package/dist/esm/components/JsonViewer.js +2 -0
  56. package/dist/esm/components/MetaAccordion.js +15 -3
  57. package/dist/esm/components/SkeletonPlaceholder.d.ts +6 -0
  58. package/dist/esm/components/SkeletonPlaceholder.js +173 -0
  59. package/dist/esm/constants/version.d.ts +1 -1
  60. package/dist/esm/constants/version.js +1 -1
  61. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  62. package/dist/esm/customHooks/networkLogger.js +8 -0
  63. package/dist/esm/customHooks/storageInspector.d.ts +2 -2
  64. package/dist/esm/customHooks/storageInspector.js +17 -13
  65. package/dist/esm/helpers/index.d.ts +6 -0
  66. package/dist/esm/helpers/index.js +193 -5
  67. package/dist/esm/helpers/searchQueryParser.js +62 -20
  68. package/dist/esm/i18n/index.d.ts +13 -0
  69. package/dist/esm/i18n/index.js +33 -3
  70. package/dist/esm/index.d.ts +3 -0
  71. package/dist/esm/index.js +97 -37
  72. package/dist/esm/styles/AppColors.d.ts +4 -0
  73. package/dist/esm/styles/AppColors.js +9 -0
  74. package/dist/esm/styles/AppFonts.d.ts +11 -0
  75. package/dist/esm/styles/AppFonts.js +9 -0
  76. package/dist/esm/types/interfaces.d.ts +2 -2
  77. package/package.json +1 -1
  78. package/src/components/AppHeaderLogo.tsx +46 -12
  79. package/src/components/BrandSquareIcon.tsx +127 -78
  80. package/src/components/EmptyState.tsx +27 -5
  81. package/src/components/Inspector/AnalyticsTab.tsx +48 -27
  82. package/src/components/Inspector/ConsoleTab.tsx +10 -0
  83. package/src/components/Inspector/CrashDetail.tsx +65 -9
  84. package/src/components/Inspector/CrashTab.tsx +41 -3
  85. package/src/components/Inspector/DeviceInfoTab.tsx +87 -16
  86. package/src/components/Inspector/InspectorHeader.tsx +24 -4
  87. package/src/components/Inspector/MainScreen.tsx +7 -11
  88. package/src/components/Inspector/NetworkDetail.tsx +4 -4
  89. package/src/components/Inspector/NetworkTab.tsx +129 -4
  90. package/src/components/Inspector/ReduxTab.tsx +26 -8
  91. package/src/components/Inspector/SettingsPanel.tsx +4 -4
  92. package/src/components/Inspector/StorageTab.tsx +191 -117
  93. package/src/components/JsonViewer.tsx +2 -0
  94. package/src/components/MetaAccordion.tsx +18 -3
  95. package/src/components/SkeletonPlaceholder.tsx +206 -0
  96. package/src/constants/version.ts +1 -1
  97. package/src/customHooks/networkLogger.ts +15 -0
  98. package/src/customHooks/storageInspector.ts +18 -14
  99. package/src/helpers/index.ts +213 -5
  100. package/src/helpers/searchQueryParser.ts +63 -20
  101. package/src/i18n/index.ts +38 -3
  102. package/src/index.tsx +124 -37
  103. package/src/styles/AppColors.ts +9 -0
  104. package/src/styles/AppFonts.ts +16 -0
  105. package/src/types/interfaces.ts +2 -2
@@ -3,6 +3,7 @@ import {
3
3
  View,
4
4
  Text,
5
5
  ScrollView,
6
+ FlatList,
6
7
  TextInput,
7
8
  Modal,
8
9
  Alert,
@@ -43,6 +44,131 @@ import {
43
44
  import {copyToClipboard} from '../../helpers';
44
45
  import {showToast} from '../../helpers/toast';
45
46
 
47
+ const getTypeBadge = (type: StorageEntry['type']) => {
48
+ switch (type) {
49
+ case 'json':
50
+ return {label: 'JSON', color: AppColors.purple, bg: `${AppColors.purple}16`};
51
+ case 'number':
52
+ return {label: 'NUM', color: AppColors.warningIconGold, bg: `${AppColors.warningIconGold}16`};
53
+ case 'boolean':
54
+ return {label: 'BOOL', color: AppColors.emerald500, bg: `${AppColors.emerald500}16`};
55
+ case 'null':
56
+ return {label: 'NULL', color: AppColors.grayTextWeak, bg: `${AppColors.grayTextWeak}16`};
57
+ default:
58
+ return {label: 'STR', color: AppColors.blue500, bg: `${AppColors.blue500}16`};
59
+ }
60
+ };
61
+
62
+ const StorageEntryCard = React.memo(function StorageEntryCard({
63
+ entry,
64
+ isExpanded,
65
+ onToggleExpand,
66
+ onCopy,
67
+ onEdit,
68
+ onDelete,
69
+ badge,
70
+ }: {
71
+ entry: StorageEntry;
72
+ isExpanded: boolean;
73
+ onToggleExpand: (key: string) => void;
74
+ onCopy: (entry: StorageEntry) => void;
75
+ onEdit: (entry: StorageEntry) => void;
76
+ onDelete: (key: string) => void;
77
+ badge: {label: string; color: string; bg: string};
78
+ }) {
79
+ const formattedBytes = useMemo(() => {
80
+ return entry.byteSize < 1024
81
+ ? `${entry.byteSize} B`
82
+ : `${(entry.byteSize / 1024).toFixed(1)} KB`;
83
+ }, [entry.byteSize]);
84
+
85
+ // Lazy compute displayed value to avoid layout thrashing on large JSON strings
86
+ const displayValue = useMemo(() => {
87
+ if (!isExpanded) {
88
+ if (entry.value.length > 250) {
89
+ return entry.value.slice(0, 250) + '...';
90
+ }
91
+ return entry.value;
92
+ }
93
+
94
+ if (entry.type === 'json') {
95
+ try {
96
+ const parsed = entry.parsedValue ?? JSON.parse(entry.value);
97
+ return JSON.stringify(parsed, null, 2);
98
+ } catch {
99
+ return entry.value;
100
+ }
101
+ }
102
+ return entry.value;
103
+ }, [isExpanded, entry.value, entry.type, entry.parsedValue]);
104
+
105
+ return (
106
+ <View style={styles.entryCard}>
107
+ {/* Entry Header: Key name, Type Badge, Size, Actions */}
108
+ <View style={styles.entryHeader}>
109
+ <View style={{flex: 1, marginRight: 8}}>
110
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
111
+ <Text style={styles.entryKey} numberOfLines={1} selectable>
112
+ {entry.key}
113
+ </Text>
114
+ <View
115
+ style={[
116
+ styles.typeBadge,
117
+ {backgroundColor: badge.bg, borderColor: `${badge.color}33`},
118
+ ]}>
119
+ <Text style={[styles.typeBadgeText, {color: badge.color}]}>
120
+ {badge.label}
121
+ </Text>
122
+ </View>
123
+ <Text style={styles.sizeText}>{formattedBytes}</Text>
124
+ </View>
125
+ </View>
126
+
127
+ {/* Actions: Copy, Edit, Delete */}
128
+ <View style={styles.entryActions}>
129
+ <TouchableScale
130
+ hitSlop={6}
131
+ onPress={() => onCopy(entry)}
132
+ style={styles.entryActionBtn}>
133
+ <CopyIcon size={12} color={AppColors.grayText} />
134
+ </TouchableScale>
135
+
136
+ <TouchableScale
137
+ hitSlop={6}
138
+ onPress={() => onEdit(entry)}
139
+ style={styles.entryActionBtn}>
140
+ <PencilIcon size={12} color={AppColors.purple} />
141
+ </TouchableScale>
142
+
143
+ <TouchableScale
144
+ hitSlop={6}
145
+ onPress={() => onDelete(entry.key)}
146
+ style={[styles.entryActionBtn, {backgroundColor: `${AppColors.errorColor}12`}]}>
147
+ <TrashIcon size={12} color={AppColors.errorColor} />
148
+ </TouchableScale>
149
+ </View>
150
+ </View>
151
+
152
+ {/* Entry Value Preview / Viewer */}
153
+ <TouchableScale
154
+ onPress={() => onToggleExpand(entry.key)}
155
+ style={styles.valuePreviewBox}>
156
+ <Text
157
+ style={styles.valuePreviewText}
158
+ numberOfLines={isExpanded ? undefined : 3}
159
+ selectable={isExpanded}>
160
+ {displayValue}
161
+ </Text>
162
+ {entry.value.length > 80 && (
163
+ <Text style={styles.expandHint}>
164
+ {isExpanded ? '▲ Collapse' : '▼ Expand'}
165
+ </Text>
166
+ )}
167
+ </TouchableScale>
168
+ </View>
169
+ );
170
+ });
171
+
46
172
  export const StorageTab = React.memo(() => {
47
173
  const {t} = useTranslation();
48
174
  const [activeDriver, setActiveDriver] = useState<StorageDriver>('asyncStorage');
@@ -235,20 +361,25 @@ export const StorageTab = React.memo(() => {
235
361
  return `${(totalBytes / (1024 * 1024)).toFixed(2)} MB`;
236
362
  }, [totalBytes]);
237
363
 
238
- const getTypeBadge = (type: StorageEntry['type']) => {
239
- switch (type) {
240
- case 'json':
241
- return {label: 'JSON', color: AppColors.purple, bg: `${AppColors.purple}16`};
242
- case 'number':
243
- return {label: 'NUM', color: AppColors.warningIconGold, bg: `${AppColors.warningIconGold}16`};
244
- case 'boolean':
245
- return {label: 'BOOL', color: AppColors.emerald500, bg: `${AppColors.emerald500}16`};
246
- case 'null':
247
- return {label: 'NULL', color: AppColors.grayTextWeak, bg: `${AppColors.grayTextWeak}16`};
248
- default:
249
- return {label: 'STR', color: AppColors.blue500, bg: `${AppColors.blue500}16`};
250
- }
251
- };
364
+ const handleCopyEntry = useCallback((entry: StorageEntry) => {
365
+ copyToClipboard(entry.value, entry.key);
366
+ showToast(`Copied value of "${entry.key}"`);
367
+ }, []);
368
+
369
+ const renderItem = useCallback(
370
+ ({item}: {item: StorageEntry}) => (
371
+ <StorageEntryCard
372
+ entry={item}
373
+ isExpanded={Boolean(expandedKeys[item.key])}
374
+ onToggleExpand={toggleExpand}
375
+ onCopy={handleCopyEntry}
376
+ onEdit={handleOpenEdit}
377
+ onDelete={handleDeleteKey}
378
+ badge={getTypeBadge(item.type)}
379
+ />
380
+ ),
381
+ [expandedKeys, handleCopyEntry],
382
+ );
252
383
 
253
384
  const isConnected =
254
385
  activeDriver === 'asyncStorage'
@@ -357,9 +488,27 @@ export const StorageTab = React.memo(() => {
357
488
  clearButtonMode="while-editing"
358
489
  />
359
490
  {search.length > 0 && (
360
- <TouchableScale onPress={() => setSearch('')} hitSlop={8}>
361
- <ClearIcon size={13} color={AppColors.grayTextWeak} />
362
- </TouchableScale>
491
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
492
+ <View
493
+ style={{
494
+ backgroundColor: `${AppColors.purple}20`,
495
+ borderRadius: 10,
496
+ paddingHorizontal: 6,
497
+ paddingVertical: 1.5,
498
+ }}>
499
+ <Text
500
+ style={{
501
+ color: AppColors.purple,
502
+ fontSize: 9.5,
503
+ fontFamily: AppFonts.interBold,
504
+ }}>
505
+ {filteredEntries.length}
506
+ </Text>
507
+ </View>
508
+ <TouchableScale onPress={() => setSearch('')} hitSlop={8}>
509
+ <ClearIcon size={13} color={AppColors.grayTextWeak} />
510
+ </TouchableScale>
511
+ </View>
363
512
  )}
364
513
  </View>
365
514
 
@@ -405,110 +554,35 @@ export const StorageTab = React.memo(() => {
405
554
  <ActivityIndicator size="small" color={AppColors.purple} />
406
555
  <Text style={styles.loadingText}>Loading storage entries...</Text>
407
556
  </View>
408
- ) : filteredEntries.length === 0 ? (
409
- <ScrollView
410
- contentContainerStyle={styles.emptyContainer}
411
- showsVerticalScrollIndicator={false}>
412
- <View style={styles.emptyIconWrap}>
413
- <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
414
- </View>
415
- <Text style={styles.emptyTitle}>
416
- {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
417
- </Text>
418
- <Text style={styles.emptySubtitle}>
419
- {search
420
- ? 'Try modifying your search query'
421
- : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
422
- </Text>
423
- </ScrollView>
424
557
  ) : (
425
- <ScrollView
558
+ <FlatList
559
+ data={filteredEntries}
560
+ keyExtractor={item => item.key}
561
+ renderItem={renderItem}
562
+ initialNumToRender={10}
563
+ maxToRenderPerBatch={10}
564
+ windowSize={5}
565
+ removeClippedSubviews={Platform.OS === 'android'}
426
566
  style={styles.scrollArea}
427
567
  contentContainerStyle={styles.scrollContent}
428
- showsVerticalScrollIndicator={false}>
429
- {filteredEntries.map(entry => {
430
- const isExpanded = Boolean(expandedKeys[entry.key]);
431
- const badge = getTypeBadge(entry.type);
432
- const formattedBytes =
433
- entry.byteSize < 1024
434
- ? `${entry.byteSize} B`
435
- : `${(entry.byteSize / 1024).toFixed(1)} KB`;
436
-
437
- return (
438
- <View key={entry.key} style={styles.entryCard}>
439
- {/* Entry Header: Key name, Type Badge, Size, Actions */}
440
- <View style={styles.entryHeader}>
441
- <View style={{flex: 1, marginRight: 8}}>
442
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
443
- <Text style={styles.entryKey} numberOfLines={1} selectable>
444
- {entry.key}
445
- </Text>
446
- <View
447
- style={[
448
- styles.typeBadge,
449
- {backgroundColor: badge.bg, borderColor: `${badge.color}33`},
450
- ]}>
451
- <Text style={[styles.typeBadgeText, {color: badge.color}]}>
452
- {badge.label}
453
- </Text>
454
- </View>
455
- <Text style={styles.sizeText}>{formattedBytes}</Text>
456
- </View>
457
- </View>
458
-
459
- {/* Actions: Copy, Edit, Delete */}
460
- <View style={styles.entryActions}>
461
- <TouchableScale
462
- hitSlop={6}
463
- onPress={() => {
464
- copyToClipboard(entry.value, entry.key);
465
- showToast(`Copied value of "${entry.key}"`);
466
- }}
467
- style={styles.entryActionBtn}>
468
- <CopyIcon size={12} color={AppColors.grayText} />
469
- </TouchableScale>
470
-
471
- <TouchableScale
472
- hitSlop={6}
473
- onPress={() => handleOpenEdit(entry)}
474
- style={styles.entryActionBtn}>
475
- <PencilIcon size={12} color={AppColors.purple} />
476
- </TouchableScale>
477
-
478
- <TouchableScale
479
- hitSlop={6}
480
- onPress={() => handleDeleteKey(entry.key)}
481
- style={[styles.entryActionBtn, {backgroundColor: `${AppColors.errorColor}12`}]}>
482
- <TrashIcon size={12} color={AppColors.errorColor} />
483
- </TouchableScale>
484
- </View>
485
- </View>
486
-
487
- {/* Entry Value Preview / Viewer */}
488
- <TouchableScale
489
- onPress={() => toggleExpand(entry.key)}
490
- style={styles.valuePreviewBox}>
491
- <Text
492
- style={styles.valuePreviewText}
493
- numberOfLines={isExpanded ? undefined : 3}
494
- selectable>
495
- {entry.type === 'json' && entry.parsedValue
496
- ? isExpanded
497
- ? JSON.stringify(entry.parsedValue, null, 2)
498
- : entry.value
499
- : entry.value}
500
- </Text>
501
- {entry.value.length > 80 && (
502
- <Text style={styles.expandHint}>
503
- {isExpanded ? '▲ Collapse' : '▼ Expand'}
504
- </Text>
505
- )}
506
- </TouchableScale>
568
+ showsVerticalScrollIndicator={false}
569
+ ListEmptyComponent={
570
+ <View style={styles.emptyContainer}>
571
+ <View style={styles.emptyIconWrap}>
572
+ <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
507
573
  </View>
508
- );
509
- })}
510
- <View style={{height: 60}} />
511
- </ScrollView>
574
+ <Text style={styles.emptyTitle}>
575
+ {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
576
+ </Text>
577
+ <Text style={styles.emptySubtitle}>
578
+ {search
579
+ ? 'Try modifying your search query'
580
+ : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
581
+ </Text>
582
+ </View>
583
+ }
584
+ ListFooterComponent={<View style={{height: 60}} />}
585
+ />
512
586
  )}
513
587
 
514
588
  {/* ── Create / Edit Key Modal ── */}
@@ -6,6 +6,7 @@ import {useTranslation} from '../i18n';
6
6
  import TreeNode from './TreeNode';
7
7
  import SegmentedTabs from './SegmentedTabs';
8
8
  import HighlightText from './HighlightText';
9
+ import CopyButton from './CopyButton';
9
10
  import {PrettyIcon, RawIcon, TableIcon} from './NetworkIcons';
10
11
 
11
12
  // Styles
@@ -260,6 +261,7 @@ const JsonViewer = React.memo(({
260
261
  setMode(key as 'pretty' | 'raw' | 'table')
261
262
  }
262
263
  />
264
+ <CopyButton value={data} label="JSON" />
263
265
  </View>
264
266
  )}
265
267
 
@@ -27,6 +27,7 @@ import {
27
27
 
28
28
  // Components
29
29
  import TouchableScale from './TouchableScale';
30
+ import CopyButton from './CopyButton';
30
31
 
31
32
  // Stylesheet
32
33
  import {AppColors} from '../styles/AppColors';
@@ -97,9 +98,23 @@ const MetaAccordion = ({
97
98
  <Pressable onPress={toggleOpen} hitSlop={12}>
98
99
  <View style={styles.metaHeader}>
99
100
  <Text style={styles.metaTitle}>Metadata</Text>
100
- <Animated.View style={chevronStyle}>
101
- <ChevronIcon color={AppColors.grayTextWeak} size={14} />
102
- </Animated.View>
101
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
102
+ <CopyButton
103
+ value={{
104
+ status: status ?? 0,
105
+ duration: duration != null ? `${duration} ms` : '—',
106
+ size: size || '—',
107
+ triggeredAt: triggeredAt || '',
108
+ method: method || 'GET',
109
+ contentType: contentType || 'application/json',
110
+ url: url || '',
111
+ }}
112
+ label="Metadata"
113
+ />
114
+ <Animated.View style={chevronStyle}>
115
+ <ChevronIcon color={AppColors.grayTextWeak} size={14} />
116
+ </Animated.View>
117
+ </View>
103
118
  </View>
104
119
  </Pressable>
105
120
 
@@ -0,0 +1,206 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {Animated, StyleSheet, View} from 'react-native';
3
+ import {AppColors} from '../styles/AppColors';
4
+
5
+ interface SkeletonPlaceholderProps {
6
+ cardCount?: number;
7
+ }
8
+
9
+ const SkeletonPlaceholder = React.memo(function SkeletonPlaceholder({
10
+ cardCount = 4,
11
+ }: SkeletonPlaceholderProps) {
12
+ const shimmerAnim = useRef(new Animated.Value(0.35)).current;
13
+
14
+ useEffect(() => {
15
+ const animation = Animated.loop(
16
+ Animated.sequence([
17
+ Animated.timing(shimmerAnim, {
18
+ toValue: 0.85,
19
+ duration: 750,
20
+ useNativeDriver: true,
21
+ }),
22
+ Animated.timing(shimmerAnim, {
23
+ toValue: 0.35,
24
+ duration: 750,
25
+ useNativeDriver: true,
26
+ }),
27
+ ]),
28
+ );
29
+ animation.start();
30
+ return () => animation.stop();
31
+ }, [shimmerAnim]);
32
+
33
+ return (
34
+ <View style={skeletonStyles.container}>
35
+ {/* ─── Search & Scope Toolbar Skeleton ─── */}
36
+ <View style={skeletonStyles.toolbarSkeleton}>
37
+ <Animated.View
38
+ style={[
39
+ skeletonStyles.searchBarSkeleton,
40
+ {opacity: shimmerAnim},
41
+ ]}
42
+ />
43
+ <View style={skeletonStyles.actionButtonsRow}>
44
+ <Animated.View
45
+ style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
46
+ />
47
+ <Animated.View
48
+ style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
49
+ />
50
+ </View>
51
+ </View>
52
+
53
+ {/* ─── Quick Filter Chips Skeleton Strip ─── */}
54
+ <View style={skeletonStyles.chipStripSkeleton}>
55
+ <Animated.View
56
+ style={[skeletonStyles.chipSkeleton, {width: 48, opacity: shimmerAnim}]}
57
+ />
58
+ <Animated.View
59
+ style={[skeletonStyles.chipSkeleton, {width: 68, opacity: shimmerAnim}]}
60
+ />
61
+ <Animated.View
62
+ style={[skeletonStyles.chipSkeleton, {width: 76, opacity: shimmerAnim}]}
63
+ />
64
+ <Animated.View
65
+ style={[skeletonStyles.chipSkeleton, {width: 58, opacity: shimmerAnim}]}
66
+ />
67
+ </View>
68
+
69
+ {/* ─── List Cards Skeleton ─── */}
70
+ {Array.from({length: cardCount}).map((_, i) => (
71
+ <Animated.View
72
+ key={`skeleton_card_${i}`}
73
+ style={[skeletonStyles.cardSkeleton, {opacity: shimmerAnim}]}>
74
+ {/* Top row: Status pill + Method + Time */}
75
+ <View style={skeletonStyles.cardTopRow}>
76
+ <View style={skeletonStyles.badgeGroup}>
77
+ <View style={skeletonStyles.statusBadgeSkeleton} />
78
+ <View style={skeletonStyles.methodBadgeSkeleton} />
79
+ </View>
80
+ <View style={skeletonStyles.timeSkeleton} />
81
+ </View>
82
+
83
+ {/* Middle row: URL lines */}
84
+ <View style={skeletonStyles.urlLineLong} />
85
+ <View style={skeletonStyles.urlLineShort} />
86
+
87
+ {/* Bottom row: Latency & Size */}
88
+ <View style={skeletonStyles.cardBottomRow}>
89
+ <View style={skeletonStyles.metaPillSkeleton} />
90
+ <View style={skeletonStyles.metaPillSkeleton} />
91
+ </View>
92
+ </Animated.View>
93
+ ))}
94
+ </View>
95
+ );
96
+ });
97
+
98
+ const skeletonStyles = StyleSheet.create({
99
+ container: {
100
+ flex: 1,
101
+ paddingHorizontal: 12,
102
+ paddingTop: 8,
103
+ },
104
+ toolbarSkeleton: {
105
+ flexDirection: 'row',
106
+ alignItems: 'center',
107
+ gap: 8,
108
+ marginBottom: 8,
109
+ },
110
+ searchBarSkeleton: {
111
+ flex: 1,
112
+ height: 36,
113
+ borderRadius: 8,
114
+ backgroundColor: AppColors.graySurface,
115
+ borderWidth: 1,
116
+ borderColor: AppColors.dividerColor,
117
+ },
118
+ actionButtonsRow: {
119
+ flexDirection: 'row',
120
+ gap: 6,
121
+ },
122
+ iconButtonSkeleton: {
123
+ width: 36,
124
+ height: 36,
125
+ borderRadius: 8,
126
+ backgroundColor: AppColors.graySurface,
127
+ borderWidth: 1,
128
+ borderColor: AppColors.dividerColor,
129
+ },
130
+ chipStripSkeleton: {
131
+ flexDirection: 'row',
132
+ gap: 6,
133
+ marginBottom: 10,
134
+ },
135
+ chipSkeleton: {
136
+ height: 24,
137
+ borderRadius: 6,
138
+ backgroundColor: AppColors.graySurface,
139
+ borderWidth: 1,
140
+ borderColor: AppColors.dividerColor,
141
+ },
142
+ cardSkeleton: {
143
+ backgroundColor: AppColors.primaryLight,
144
+ borderRadius: 10,
145
+ padding: 12,
146
+ marginBottom: 8,
147
+ borderWidth: 1,
148
+ borderColor: AppColors.dividerColor,
149
+ },
150
+ cardTopRow: {
151
+ flexDirection: 'row',
152
+ justifyContent: 'space-between',
153
+ alignItems: 'center',
154
+ marginBottom: 8,
155
+ },
156
+ badgeGroup: {
157
+ flexDirection: 'row',
158
+ alignItems: 'center',
159
+ gap: 6,
160
+ },
161
+ statusBadgeSkeleton: {
162
+ width: 38,
163
+ height: 18,
164
+ borderRadius: 4,
165
+ backgroundColor: AppColors.graySurface,
166
+ },
167
+ methodBadgeSkeleton: {
168
+ width: 44,
169
+ height: 18,
170
+ borderRadius: 4,
171
+ backgroundColor: AppColors.graySurface,
172
+ },
173
+ timeSkeleton: {
174
+ width: 48,
175
+ height: 12,
176
+ borderRadius: 4,
177
+ backgroundColor: AppColors.graySurface,
178
+ },
179
+ urlLineLong: {
180
+ height: 13,
181
+ borderRadius: 4,
182
+ backgroundColor: AppColors.graySurface,
183
+ marginBottom: 5,
184
+ width: '90%',
185
+ },
186
+ urlLineShort: {
187
+ height: 11,
188
+ borderRadius: 4,
189
+ backgroundColor: AppColors.graySurface,
190
+ marginBottom: 8,
191
+ width: '55%',
192
+ },
193
+ cardBottomRow: {
194
+ flexDirection: 'row',
195
+ gap: 8,
196
+ marginTop: 2,
197
+ },
198
+ metaPillSkeleton: {
199
+ width: 52,
200
+ height: 14,
201
+ borderRadius: 4,
202
+ backgroundColor: AppColors.graySurface,
203
+ },
204
+ });
205
+
206
+ export default SkeletonPlaceholder;
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED FILE — do not edit by hand.
2
2
  // Regenerated from package.json on every build by scripts/gen-version.js.
3
- export const LIB_VERSION = '2.2.9';
3
+ export const LIB_VERSION = '2.3.1';
@@ -1,5 +1,6 @@
1
1
  import axios from "axios";
2
2
  import {setupGlobalCrashHandler} from "./crashHandler";
3
+ import {RouteInfo} from "../types";
3
4
 
4
5
  type NetworkLog = {
5
6
  id: number;
@@ -14,12 +15,20 @@ type NetworkLog = {
14
15
  client?: string; // ✅ Captures request client: axios, fetch, xhr, apollo, etc.
15
16
  requestHeaders?: Record<string, string>;
16
17
  responseHeaders?: Record<string, string>;
18
+ routeInfo?: RouteInfo;
17
19
  };
18
20
 
19
21
  let logs: NetworkLog[] = [];
20
22
  let listeners: ((logs: NetworkLog[]) => void)[] = [];
21
23
  let counter = 0;
22
24
  let isNetworkModuleEnabled = true;
25
+ let currentRouteProvider: (() => RouteInfo | null) | null = null;
26
+
27
+ export const setRouteInfoProvider = (
28
+ provider: (() => RouteInfo | null) | null,
29
+ ) => {
30
+ currentRouteProvider = provider;
31
+ };
23
32
 
24
33
  export const setNetworkModuleEnabled = (enabled: boolean) => {
25
34
  isNetworkModuleEnabled = enabled;
@@ -232,6 +241,8 @@ export const setupNetworkLogger = () => {
232
241
  client = 'axios';
233
242
  }
234
243
 
244
+ const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
245
+
235
246
  addOrUpdateLog({
236
247
  id,
237
248
  url: finalUrl,
@@ -239,6 +250,7 @@ export const setupNetworkLogger = () => {
239
250
  startTime: start,
240
251
  caller,
241
252
  client,
253
+ routeInfo: currentRoute || undefined,
242
254
  request: method === "GET" ? undefined : parseRequestData(options?.body),
243
255
  requestHeaders,
244
256
  });
@@ -347,6 +359,8 @@ export const addAxiosInterceptors = (axiosInstance: any) => {
347
359
  config.__logStart = start;
348
360
  config.__logCaller = caller;
349
361
 
362
+ const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
363
+
350
364
  addOrUpdateLog({
351
365
  id,
352
366
  url,
@@ -354,6 +368,7 @@ export const addAxiosInterceptors = (axiosInstance: any) => {
354
368
  startTime: start,
355
369
  caller,
356
370
  client: 'axios',
371
+ routeInfo: currentRoute || undefined,
357
372
  request: method === "GET" ? undefined : parseRequestData(config.data),
358
373
  requestHeaders: normaliseHeaders(config.headers),
359
374
  });