react-native-inapp-inspector 2.2.1 → 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 (181) hide show
  1. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  2. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  3. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  4. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  5. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  10. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  12. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/9.2.0/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build/reports/problems/problems-report.html +659 -0
  18. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +28 -6
  19. package/dist/commonjs/analytics.d.ts +7 -0
  20. package/dist/commonjs/analytics.js +28 -0
  21. package/dist/commonjs/bundle.d.ts +6 -0
  22. package/dist/commonjs/bundle.js +20 -0
  23. package/dist/commonjs/console.d.ts +7 -0
  24. package/dist/commonjs/console.js +20 -0
  25. package/dist/commonjs/constants/version.d.ts +1 -1
  26. package/dist/commonjs/constants/version.js +1 -1
  27. package/dist/commonjs/crash.d.ts +7 -0
  28. package/dist/commonjs/crash.js +27 -0
  29. package/dist/commonjs/customHooks/analyticsLogger.d.ts +3 -0
  30. package/dist/commonjs/customHooks/analyticsLogger.js +23 -2
  31. package/dist/commonjs/customHooks/consoleLogger.d.ts +3 -0
  32. package/dist/commonjs/customHooks/consoleLogger.js +24 -4
  33. package/dist/commonjs/customHooks/crashHandler.d.ts +2 -0
  34. package/dist/commonjs/customHooks/crashHandler.js +15 -1
  35. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  36. package/dist/commonjs/customHooks/networkLogger.js +23 -2
  37. package/dist/commonjs/customHooks/performanceTracker.d.ts +3 -0
  38. package/dist/commonjs/customHooks/performanceTracker.js +34 -3
  39. package/dist/commonjs/customHooks/reduxLogger.d.ts +3 -0
  40. package/dist/commonjs/customHooks/reduxLogger.js +24 -4
  41. package/dist/commonjs/helpers/index.d.ts +1 -0
  42. package/dist/commonjs/helpers/index.js +1 -0
  43. package/dist/commonjs/helpers/memoryManager.d.ts +29 -0
  44. package/dist/commonjs/helpers/memoryManager.js +112 -0
  45. package/dist/commonjs/helpers/telemetry.js +4 -4
  46. package/dist/commonjs/index.d.ts +7 -1
  47. package/dist/commonjs/index.js +36 -2
  48. package/dist/commonjs/network.d.ts +6 -0
  49. package/dist/commonjs/network.js +17 -0
  50. package/dist/commonjs/performance.d.ts +6 -0
  51. package/dist/commonjs/performance.js +33 -0
  52. package/dist/commonjs/redux.d.ts +5 -0
  53. package/dist/commonjs/redux.js +24 -0
  54. package/dist/esm/analytics.d.ts +7 -0
  55. package/dist/esm/analytics.js +6 -0
  56. package/dist/esm/bundle.d.ts +6 -0
  57. package/dist/esm/bundle.js +6 -0
  58. package/dist/esm/console.d.ts +7 -0
  59. package/dist/esm/console.js +6 -0
  60. package/dist/esm/constants/version.d.ts +1 -1
  61. package/dist/esm/constants/version.js +1 -1
  62. package/dist/esm/crash.d.ts +7 -0
  63. package/dist/esm/crash.js +6 -0
  64. package/dist/esm/customHooks/analyticsLogger.d.ts +3 -0
  65. package/dist/esm/customHooks/analyticsLogger.js +19 -1
  66. package/dist/esm/customHooks/consoleLogger.d.ts +3 -0
  67. package/dist/esm/customHooks/consoleLogger.js +20 -3
  68. package/dist/esm/customHooks/crashHandler.d.ts +2 -0
  69. package/dist/esm/customHooks/crashHandler.js +12 -0
  70. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  71. package/dist/esm/customHooks/networkLogger.js +19 -1
  72. package/dist/esm/customHooks/performanceTracker.d.ts +3 -0
  73. package/dist/esm/customHooks/performanceTracker.js +30 -2
  74. package/dist/esm/customHooks/reduxLogger.d.ts +3 -0
  75. package/dist/esm/customHooks/reduxLogger.js +20 -3
  76. package/dist/esm/helpers/index.d.ts +1 -0
  77. package/dist/esm/helpers/index.js +1 -0
  78. package/dist/esm/helpers/memoryManager.d.ts +29 -0
  79. package/dist/esm/helpers/memoryManager.js +104 -0
  80. package/dist/esm/helpers/telemetry.js +4 -4
  81. package/dist/esm/index.d.ts +7 -1
  82. package/dist/esm/index.js +17 -3
  83. package/dist/esm/network.d.ts +6 -0
  84. package/dist/esm/network.js +5 -0
  85. package/dist/esm/performance.d.ts +6 -0
  86. package/dist/esm/performance.js +6 -0
  87. package/dist/esm/redux.d.ts +5 -0
  88. package/dist/esm/redux.js +5 -0
  89. package/package.json +53 -2
  90. package/src/analytics.ts +35 -0
  91. package/src/bundle.ts +25 -0
  92. package/src/components/AnalyticsDetail.tsx +865 -0
  93. package/src/components/AnalyticsEventCard.tsx +441 -0
  94. package/src/components/AnalyticsGraph.tsx +583 -0
  95. package/src/components/AnimatedEntrance.tsx +64 -0
  96. package/src/components/AppHeaderLogo.tsx +109 -0
  97. package/src/components/BrandCircleIcon.tsx +144 -0
  98. package/src/components/BrandSquareIcon.tsx +144 -0
  99. package/src/components/CodeSnippet.tsx +725 -0
  100. package/src/components/ConsoleLogCard.tsx +628 -0
  101. package/src/components/CopyButton.tsx +87 -0
  102. package/src/components/DiffViewer.tsx +82 -0
  103. package/src/components/DomainHeader.tsx +237 -0
  104. package/src/components/EmptyState.tsx +73 -0
  105. package/src/components/EndOfListFooter.tsx +100 -0
  106. package/src/components/ErrorBoundary.tsx +688 -0
  107. package/src/components/HeadersSection.tsx +192 -0
  108. package/src/components/HighlightText.tsx +100 -0
  109. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  110. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  111. package/src/components/Inspector/BundleTab.tsx +4731 -0
  112. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  113. package/src/components/Inspector/CrashDetail.tsx +941 -0
  114. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  115. package/src/components/Inspector/CrashTab.tsx +871 -0
  116. package/src/components/Inspector/FabLauncher.tsx +80 -0
  117. package/src/components/Inspector/InspectorContext.tsx +28 -0
  118. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  119. package/src/components/Inspector/LogDetail.tsx +1427 -0
  120. package/src/components/Inspector/MainScreen.tsx +258 -0
  121. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  122. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  123. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  124. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  125. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  126. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  127. package/src/components/Inspector/ReduxTab.tsx +954 -0
  128. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  129. package/src/components/Inspector/TabBar.tsx +187 -0
  130. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  131. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  132. package/src/components/JsonViewer.tsx +486 -0
  133. package/src/components/LogCard.tsx +491 -0
  134. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  135. package/src/components/MetaAccordion.tsx +340 -0
  136. package/src/components/MiniBarChart.tsx +42 -0
  137. package/src/components/MiniLineChart.tsx +33 -0
  138. package/src/components/NetworkIcons.tsx +2118 -0
  139. package/src/components/SectionHeader.tsx +113 -0
  140. package/src/components/SegmentedTabs.tsx +83 -0
  141. package/src/components/Slider.tsx +300 -0
  142. package/src/components/SourcePageCard.tsx +148 -0
  143. package/src/components/Toast.tsx +131 -0
  144. package/src/components/TouchableScale.tsx +91 -0
  145. package/src/components/TreeNode.tsx +186 -0
  146. package/src/console.ts +24 -0
  147. package/src/constants/index.ts +38 -0
  148. package/src/constants/version.ts +3 -0
  149. package/src/crash.ts +32 -0
  150. package/src/customHooks/analyticsLogger.ts +336 -0
  151. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  152. package/src/customHooks/consoleLogger.ts +497 -0
  153. package/src/customHooks/crashHandler.ts +944 -0
  154. package/src/customHooks/logFilters.ts +32 -0
  155. package/src/customHooks/networkLogger.ts +419 -0
  156. package/src/customHooks/performanceTracker.ts +1014 -0
  157. package/src/customHooks/reduxLogger.ts +406 -0
  158. package/src/customHooks/useAccordion.tsx +60 -0
  159. package/src/decorators/index.ts +184 -0
  160. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  161. package/src/helpers/index.ts +860 -0
  162. package/src/helpers/memoryManager.ts +138 -0
  163. package/src/helpers/searchQueryParser.ts +283 -0
  164. package/src/helpers/settingsStore.ts +171 -0
  165. package/src/helpers/telemetry.ts +505 -0
  166. package/src/helpers/toast.ts +17 -0
  167. package/src/i18n/index.ts +69 -0
  168. package/src/i18n/locales/en.json +1052 -0
  169. package/src/index.tsx +2336 -0
  170. package/src/native/NativeInspector.ts +397 -0
  171. package/src/native/NativeNetworkInspector.ts +24 -0
  172. package/src/network.ts +22 -0
  173. package/src/performance.ts +38 -0
  174. package/src/redux.ts +23 -0
  175. package/src/styles/AppColors.ts +408 -0
  176. package/src/styles/AppFonts.ts +8 -0
  177. package/src/styles/common.ts +209 -0
  178. package/src/styles/index.ts +1570 -0
  179. package/src/types/enums.ts +194 -0
  180. package/src/types/index.ts +34 -0
  181. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,491 @@
1
+ import {Method} from '../types';
2
+ import React, {useEffect, useRef} from 'react';
3
+ import {
4
+ Alert,
5
+ Animated,
6
+ Linking,
7
+ View,
8
+ Pressable,
9
+ Text,
10
+ StyleSheet,
11
+ } from 'react-native';
12
+ import Svg, {Path} from 'react-native-svg';
13
+ import {AppColors} from '../styles/AppColors';
14
+ import {METHOD_COLORS} from '../constants';
15
+ import {
16
+ getStatusColor,
17
+ getDurationColor,
18
+ formatDateTime,
19
+ getSize,
20
+ } from '../helpers';
21
+ import {
22
+ CalendarIcon,
23
+ ClockIcon,
24
+ SizeIcon,
25
+ GlobeIcon,
26
+ CircleCheckIcon,
27
+ CircleAlertIcon,
28
+ CircleXIcon,
29
+ RepeatIcon,
30
+ } from './NetworkIcons';
31
+ import {AppFonts} from '../styles/AppFonts';
32
+ import styles from '../styles';
33
+ import {LogCardProps} from '../types';
34
+ import HighlightText from './HighlightText';
35
+ import TouchableScale from './TouchableScale';
36
+ import {useTranslation} from '../i18n';
37
+
38
+ const LogCard = React.memo(function LogCard({
39
+ item,
40
+ onPress,
41
+ timelineMinStart,
42
+ timelineTotalRange,
43
+ isNew,
44
+ isSelected,
45
+ onToggleSelect,
46
+ searchStr,
47
+ }: LogCardProps) {
48
+ const {t} = useTranslation();
49
+ const methodColor = METHOD_COLORS[item.method as Method] ?? METHOD_COLORS.ALL;
50
+
51
+ const handleOpenUrl = (e?: any) => {
52
+ e?.stopPropagation?.();
53
+ Alert.alert(
54
+ t('common.openInBrowser') || 'Open in Browser',
55
+ `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${item.url}`,
56
+ [
57
+ {text: t('common.cancel') || 'Cancel', style: 'cancel'},
58
+ {
59
+ text: t('common.open') || 'Open',
60
+ onPress: () => {
61
+ Linking.canOpenURL(item.url)
62
+ .then(supported => {
63
+ if (supported) {
64
+ Linking.openURL(item.url);
65
+ } else {
66
+ Linking.openURL(item.url).catch(() => {});
67
+ }
68
+ })
69
+ .catch(() => {});
70
+ },
71
+ },
72
+ ],
73
+ );
74
+ };
75
+
76
+ const isFailed =
77
+ item.status === 0 || (item.status != null && item.status >= 400);
78
+ const isLoading = item.status == null;
79
+ const statusColor = getStatusColor(item.status);
80
+ const durationColor = getDurationColor(item.duration);
81
+
82
+ const cardStatusColor = isLoading
83
+ ? AppColors.darkOrange
84
+ : isFailed
85
+ ? AppColors.errorColor
86
+ : AppColors.greenColor;
87
+
88
+ const leftPercent =
89
+ timelineTotalRange > 0
90
+ ? ((item.startTime - timelineMinStart) / timelineTotalRange) * 100
91
+ : 0;
92
+ const widthPercent =
93
+ timelineTotalRange > 0
94
+ ? ((item.duration || 10) / timelineTotalRange) * 100
95
+ : 100;
96
+
97
+ const shimmerOpacity = useRef(new Animated.Value(isNew ? 0.35 : 0)).current;
98
+ useEffect(() => {
99
+ if (isNew) {
100
+ Animated.timing(shimmerOpacity, {
101
+ toValue: 0,
102
+ duration: 1200,
103
+ useNativeDriver: true,
104
+ }).start();
105
+ }
106
+ }, [isNew]);
107
+
108
+ const renderStatusIcon = () => {
109
+ if (isLoading) {
110
+ return <ClockIcon color={AppColors.darkOrange} size={9} />;
111
+ }
112
+ const num = typeof item.status === 'number' ? item.status : parseInt(String(item.status || 0), 10);
113
+ if (num >= 200 && num < 300) {
114
+ return <CircleCheckIcon color={AppColors.greenColor} size={9} />;
115
+ }
116
+ if (num >= 300 && num < 400) {
117
+ return <RepeatIcon color={AppColors.amber700} size={9} />;
118
+ }
119
+ if (num >= 400 && num < 500) {
120
+ return <CircleAlertIcon color={AppColors.darkOrange} size={9} />;
121
+ }
122
+ return <CircleXIcon color={AppColors.errorColor} size={9} />;
123
+ };
124
+
125
+ const triggeredAt = formatDateTime(item.startTime);
126
+ const isJson = item.url.split('?')[0].toLowerCase().endsWith('.json');
127
+
128
+ return (
129
+ <TouchableScale
130
+ onPress={onPress}
131
+ style={[
132
+ styles.card,
133
+ {
134
+ borderLeftWidth: 3.5,
135
+ borderLeftColor: cardStatusColor,
136
+ },
137
+ ]}>
138
+ <View style={styles.cardBody}>
139
+ {/* Row 1: Header (Checkbox, Serial, Method Badge, Client Tag, Status Pill) */}
140
+ <View style={styles.cardHeaderRow}>
141
+ <View style={styles.cardHeaderLeft}>
142
+ <Pressable
143
+ onPress={() => onToggleSelect(item.id)}
144
+ hitSlop={12}
145
+ style={[
146
+ styles.smallCheckbox,
147
+ isSelected && styles.smallCheckboxChecked,
148
+ ]}>
149
+ {isSelected && (
150
+ <Svg width={9} height={9} viewBox="0 0 24 24" fill="none">
151
+ <Path
152
+ d="M20 6L9 17l-5-5"
153
+ stroke={AppColors.white}
154
+ strokeWidth="4"
155
+ strokeLinecap="round"
156
+ strokeLinejoin="round"
157
+ />
158
+ </Svg>
159
+ )}
160
+ </Pressable>
161
+
162
+ <Text style={styles.serialNumber}>#{item.id + 1}</Text>
163
+
164
+ <View
165
+ style={[styles.methodBadge, {backgroundColor: methodColor}]}>
166
+ <Text style={[styles.methodBadgeText, {color: AppColors.white}]}>
167
+ {item.method}
168
+ </Text>
169
+ </View>
170
+
171
+ {item.client && (
172
+ <View
173
+ style={[
174
+ styles.chip,
175
+ {
176
+ backgroundColor:
177
+ item.client === 'axios'
178
+ ? `${AppColors.purple}14`
179
+ : item.client === 'apollo' || item.client === 'graphql'
180
+ ? `${AppColors.pink500}14`
181
+ : item.client === 'xhr'
182
+ ? `${AppColors.amber500}14`
183
+ : `${AppColors.sky500}14`,
184
+ borderColor:
185
+ item.client === 'axios'
186
+ ? `${AppColors.purple}30`
187
+ : item.client === 'apollo' || item.client === 'graphql'
188
+ ? `${AppColors.pink500}30`
189
+ : item.client === 'xhr'
190
+ ? `${AppColors.amber500}30`
191
+ : `${AppColors.sky500}30`,
192
+ paddingHorizontal: 4.5,
193
+ paddingVertical: 1,
194
+ borderRadius: 4,
195
+ },
196
+ ]}>
197
+ <Text
198
+ style={[
199
+ styles.chipText,
200
+ {
201
+ fontFamily: AppFonts.interBold,
202
+ fontSize: 8.5,
203
+ color:
204
+ item.client === 'axios'
205
+ ? AppColors.purple
206
+ : item.client === 'apollo' || item.client === 'graphql'
207
+ ? AppColors.pink500
208
+ : item.client === 'xhr'
209
+ ? AppColors.amber700
210
+ : AppColors.sky600,
211
+ },
212
+ ]}>
213
+ {item.client.toUpperCase()}
214
+ </Text>
215
+ </View>
216
+ )}
217
+
218
+ {/* Protocol / HTTPS Badge */}
219
+ <View
220
+ style={[
221
+ styles.chip,
222
+ {
223
+ backgroundColor: item.url.toLowerCase().startsWith('https')
224
+ ? AppColors.mintGreenBg
225
+ : AppColors.amberWarmBg,
226
+ borderColor: item.url.toLowerCase().startsWith('https')
227
+ ? AppColors.mintGreenBorder
228
+ : AppColors.amberWarmBorder,
229
+ paddingHorizontal: 4,
230
+ paddingVertical: 1,
231
+ borderRadius: 4,
232
+ },
233
+ ]}>
234
+ <Text
235
+ style={[
236
+ styles.chipText,
237
+ {
238
+ fontFamily: AppFonts.interBold,
239
+ fontSize: 8,
240
+ color: item.url.toLowerCase().startsWith('https')
241
+ ? AppColors.mintGreenText
242
+ : AppColors.amber700,
243
+ },
244
+ ]}>
245
+ {item.url.toLowerCase().startsWith('https') ? 'HTTPS' : 'HTTP'}
246
+ </Text>
247
+ </View>
248
+
249
+ {/* GraphQL Indicator */}
250
+ {(item.url.toLowerCase().includes('graphql') ||
251
+ item.client === 'apollo' ||
252
+ item.client === 'graphql') && (
253
+ <View
254
+ style={[
255
+ styles.chip,
256
+ {
257
+ backgroundColor: AppColors.roseBg,
258
+ borderColor: AppColors.roseBorder,
259
+ paddingHorizontal: 4,
260
+ paddingVertical: 1,
261
+ borderRadius: 4,
262
+ },
263
+ ]}>
264
+ <Text
265
+ style={[
266
+ styles.chipText,
267
+ {
268
+ fontFamily: AppFonts.interBold,
269
+ fontSize: 8,
270
+ color: AppColors.pink600,
271
+ },
272
+ ]}>
273
+ GQL
274
+ </Text>
275
+ </View>
276
+ )}
277
+
278
+ {/* In-Line Duration / Latency Pill */}
279
+ {item.duration != null && !isFailed && (
280
+ <View
281
+ style={[
282
+ styles.chip,
283
+ {
284
+ backgroundColor: `${durationColor}12`,
285
+ borderColor: `${durationColor}2E`,
286
+ paddingHorizontal: 4.5,
287
+ paddingVertical: 1,
288
+ borderRadius: 4,
289
+ },
290
+ ]}>
291
+ <Text
292
+ style={[
293
+ styles.chipText,
294
+ {
295
+ fontFamily: AppFonts.interBold,
296
+ fontSize: 8.5,
297
+ color: durationColor,
298
+ },
299
+ ]}>
300
+ {item.duration}ms
301
+ </Text>
302
+ </View>
303
+ )}
304
+ </View>
305
+
306
+ <View
307
+ style={[
308
+ styles.statusPill,
309
+ {
310
+ flexDirection: 'row',
311
+ alignItems: 'center',
312
+ gap: 3.5,
313
+ backgroundColor: isFailed
314
+ ? `${AppColors.errorColor}15`
315
+ : isLoading
316
+ ? `${AppColors.darkOrange}15`
317
+ : `${statusColor}15`,
318
+ borderColor: isFailed
319
+ ? `${AppColors.errorColor}30`
320
+ : isLoading
321
+ ? `${AppColors.darkOrange}30`
322
+ : `${statusColor}30`,
323
+ },
324
+ ]}>
325
+ {renderStatusIcon()}
326
+ <Text
327
+ style={[
328
+ styles.statusPillText,
329
+ {
330
+ color: isFailed
331
+ ? AppColors.errorColor
332
+ : isLoading
333
+ ? AppColors.darkOrange
334
+ : statusColor,
335
+ },
336
+ ]}>
337
+ {isLoading
338
+ ? '...'
339
+ : isFailed
340
+ ? (item.status ? `${item.status}` : t('network.statusFailed'))
341
+ : item.status}
342
+ </Text>
343
+ </View>
344
+ </View>
345
+
346
+ {/* Row 2: Full URL Capsule Container (Clickable with link prompt) */}
347
+ <View style={styles.cardSlugBox}>
348
+ <Pressable
349
+ onPress={handleOpenUrl}
350
+ style={styles.slugLeft}
351
+ hitSlop={6}>
352
+ <View
353
+ style={{
354
+ width: 17,
355
+ height: 17,
356
+ borderRadius: 4,
357
+ backgroundColor: `${AppColors.skyBlue}1A`,
358
+ alignItems: 'center',
359
+ justifyContent: 'center',
360
+ marginRight: 4,
361
+ }}>
362
+ <GlobeIcon color={AppColors.skyBlue} size={11} />
363
+ </View>
364
+ <HighlightText
365
+ text={item.url}
366
+ search={searchStr}
367
+ style={[styles.slugText, {color: AppColors.skyBlue, textDecorationLine: 'underline'}]}
368
+ highlightStyle={styles.highlight}
369
+ numberOfLines={2}
370
+ ellipsizeMode="middle"
371
+ />
372
+ </Pressable>
373
+
374
+ <View style={styles.slugRight}>
375
+ {isJson && (
376
+ <View
377
+ style={[
378
+ styles.chip,
379
+ {
380
+ backgroundColor: `${AppColors.darkOrange}12`,
381
+ borderColor: `${AppColors.darkOrange}28`,
382
+ },
383
+ ]}>
384
+ <Text style={[styles.chipText, {color: AppColors.darkOrange}]}>
385
+ .json
386
+ </Text>
387
+ </View>
388
+ )}
389
+
390
+ {item.duplicateCount != null && item.duplicateCount > 1 && (
391
+ <View
392
+ style={[
393
+ styles.chip,
394
+ {
395
+ backgroundColor: `${AppColors.purple}12`,
396
+ borderColor: `${AppColors.purple}28`,
397
+ },
398
+ ]}>
399
+ <Text
400
+ style={[
401
+ styles.chipText,
402
+ {color: AppColors.purple, fontWeight: '700'},
403
+ ]}>
404
+ ×{item.duplicateCount}
405
+ </Text>
406
+ </View>
407
+ )}
408
+ </View>
409
+ </View>
410
+
411
+ {/* Row 3: Footer (Timestamp on Left, Response Size & Duration on Right) */}
412
+ <View style={styles.cardFooterRow}>
413
+ <View style={styles.cardDateRow}>
414
+ <CalendarIcon color={AppColors.grayTextWeak} size={10} />
415
+ <Text style={styles.cardDateText}>{triggeredAt}</Text>
416
+ </View>
417
+
418
+ <View style={styles.cardFooterRight}>
419
+ {item.response != null && (
420
+ <View
421
+ style={[
422
+ styles.chip,
423
+ {
424
+ backgroundColor: `${AppColors.purple}12`,
425
+ borderColor: `${AppColors.purple}2E`,
426
+ },
427
+ ]}>
428
+ <SizeIcon color={AppColors.purple} size={9} />
429
+ <Text
430
+ style={[
431
+ styles.chipText,
432
+ {color: AppColors.purple, fontSize: 9.5},
433
+ ]}>
434
+ {getSize(item.response)}
435
+ </Text>
436
+ </View>
437
+ )}
438
+
439
+ {item.duration != null && !isFailed && (
440
+ <View
441
+ style={[
442
+ styles.chip,
443
+ {
444
+ backgroundColor: `${durationColor}15`,
445
+ borderColor: `${durationColor}30`,
446
+ },
447
+ ]}>
448
+ <ClockIcon color={durationColor} size={9} />
449
+ <Text style={[styles.chipText, {color: durationColor, fontSize: 9.5}]}>
450
+ {item.duration}ms
451
+ </Text>
452
+ </View>
453
+ )}
454
+ </View>
455
+ </View>
456
+
457
+ {/* Mini Latency Timing Waterfall Bar */}
458
+ {typeof item.duration === 'number' && item.duration > 0 && !isFailed && (
459
+ <View style={{marginTop: 6, gap: 2}}>
460
+ <View
461
+ style={{
462
+ height: 2.5,
463
+ width: '100%',
464
+ backgroundColor: AppColors.grayBorderSecondary,
465
+ borderRadius: 1.5,
466
+ overflow: 'hidden',
467
+ }}>
468
+ <View
469
+ style={{
470
+ height: '100%',
471
+ width: `${Math.min(100, Math.max(6, (item.duration / 1200) * 100))}%`,
472
+ backgroundColor: durationColor,
473
+ borderRadius: 1.5,
474
+ }}
475
+ />
476
+ </View>
477
+ </View>
478
+ )}
479
+ </View>
480
+ <Animated.View
481
+ pointerEvents="none"
482
+ style={[
483
+ StyleSheet.absoluteFill,
484
+ {backgroundColor: methodColor, opacity: shimmerOpacity},
485
+ ]}
486
+ />
487
+ </TouchableScale>
488
+ );
489
+ });
490
+
491
+ export default LogCard;
@@ -0,0 +1,178 @@
1
+ import React from 'react';
2
+ import {StyleSheet, Text, View, TextStyle, Linking} from 'react-native';
3
+ import {AppColors} from '../styles/AppColors';
4
+ import {AppFonts} from '../styles/AppFonts';
5
+ import {escapeRegex, handleOpenExternalLink} from '../helpers';
6
+
7
+ export interface LogSyntaxHighlighterProps {
8
+ text: string;
9
+ search?: string;
10
+ numberOfLines?: number;
11
+ style?: TextStyle | TextStyle[];
12
+ detectLinks?: boolean;
13
+ }
14
+
15
+ interface Token {
16
+ type:
17
+ | 'key'
18
+ | 'string'
19
+ | 'number'
20
+ | 'boolean'
21
+ | 'null'
22
+ | 'url'
23
+ | 'bracket'
24
+ | 'tag'
25
+ | 'plain';
26
+ value: string;
27
+ }
28
+
29
+ const TOKEN_REGEX =
30
+ /("(?:\\.|[^"\\])*"(?=\s*:))|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')|(\b-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(\b(?:true|false)\b)|(\b(?:null|undefined|NaN)\b)|(https?:\/\/[^\s"',]+)|(\[[\w\s_-]+\])|([{}[\],:])|([^"'\d\s{}[\],:]+|[ \t\r\n]+)/g;
31
+
32
+ export const tokenizeText = (input: string): Token[] => {
33
+ if (!input) return [];
34
+ const tokens: Token[] = [];
35
+ let match: RegExpExecArray | null;
36
+ TOKEN_REGEX.lastIndex = 0;
37
+
38
+ while ((match = TOKEN_REGEX.exec(input)) !== null) {
39
+ if (match[1]) {
40
+ tokens.push({type: 'key', value: match[1]});
41
+ } else if (match[2]) {
42
+ tokens.push({type: 'string', value: match[2]});
43
+ } else if (match[3]) {
44
+ tokens.push({type: 'number', value: match[3]});
45
+ } else if (match[4]) {
46
+ tokens.push({type: 'boolean', value: match[4]});
47
+ } else if (match[5]) {
48
+ tokens.push({type: 'null', value: match[5]});
49
+ } else if (match[6]) {
50
+ tokens.push({type: 'url', value: match[6]});
51
+ } else if (match[7]) {
52
+ tokens.push({type: 'tag', value: match[7]});
53
+ } else if (match[8]) {
54
+ tokens.push({type: 'bracket', value: match[8]});
55
+ } else if (match[9]) {
56
+ tokens.push({type: 'plain', value: match[9]});
57
+ }
58
+ }
59
+
60
+ return tokens;
61
+ };
62
+
63
+ const getTokenColor = (type: Token['type']): string => {
64
+ switch (type) {
65
+ case 'key':
66
+ return AppColors.sky600 || '#0369a1';
67
+ case 'string':
68
+ return AppColors.emerald600 || '#059669';
69
+ case 'number':
70
+ return AppColors.amber600 || '#d97706';
71
+ case 'boolean':
72
+ return AppColors.purple || '#7c3aed';
73
+ case 'null':
74
+ return AppColors.red600 || '#dc2626';
75
+ case 'url':
76
+ return '#0284c7';
77
+ case 'tag':
78
+ return AppColors.violet600 || '#7c3aed';
79
+ case 'bracket':
80
+ return AppColors.slate500 || '#64748b';
81
+ case 'plain':
82
+ default:
83
+ return AppColors.primaryBlack || '#1e293b';
84
+ }
85
+ };
86
+
87
+ export const LogSyntaxHighlighter: React.FC<LogSyntaxHighlighterProps> = React.memo(
88
+ ({text, search, numberOfLines, style, detectLinks = true}) => {
89
+ if (!text) return null;
90
+
91
+ const tokens = React.useMemo(() => tokenizeText(text), [text]);
92
+
93
+ const renderToken = (token: Token, tokenIdx: number) => {
94
+ const baseColor = getTokenColor(token.type);
95
+ const isUrl = token.type === 'url';
96
+ const isKey = token.type === 'key';
97
+
98
+ const tokenStyle: TextStyle = {
99
+ color: baseColor,
100
+ fontFamily: isKey ? AppFonts.interBold : AppFonts.interRegular,
101
+ textDecorationLine: isUrl ? 'underline' : 'none',
102
+ };
103
+
104
+ if (!search || search.trim().length === 0) {
105
+ return (
106
+ <Text
107
+ key={tokenIdx}
108
+ style={tokenStyle}
109
+ onPress={
110
+ isUrl && detectLinks
111
+ ? () => handleOpenExternalLink(token.value)
112
+ : undefined
113
+ }>
114
+ {token.value}
115
+ </Text>
116
+ );
117
+ }
118
+
119
+ const regex = new RegExp(`(${escapeRegex(search.trim())})`, 'gi');
120
+ const parts = token.value.split(regex);
121
+
122
+ return (
123
+ <Text key={tokenIdx} style={tokenStyle}>
124
+ {parts.map((part, partIdx) => {
125
+ const isMatch =
126
+ part.toLowerCase() === search.trim().toLowerCase();
127
+ return isMatch ? (
128
+ <Text
129
+ key={partIdx}
130
+ style={[
131
+ tokenStyle,
132
+ styles.searchHighlight,
133
+ ]}>
134
+ {part}
135
+ </Text>
136
+ ) : (
137
+ <Text
138
+ key={partIdx}
139
+ style={tokenStyle}
140
+ onPress={
141
+ isUrl && detectLinks
142
+ ? () => handleOpenExternalLink(token.value)
143
+ : undefined
144
+ }>
145
+ {part}
146
+ </Text>
147
+ );
148
+ })}
149
+ </Text>
150
+ );
151
+ };
152
+
153
+ return (
154
+ <Text
155
+ style={[styles.containerText, style]}
156
+ numberOfLines={numberOfLines}>
157
+ {tokens.map(renderToken)}
158
+ </Text>
159
+ );
160
+ },
161
+ );
162
+
163
+ const styles = StyleSheet.create({
164
+ containerText: {
165
+ fontFamily: AppFonts.interRegular,
166
+ fontSize: 12,
167
+ lineHeight: 18,
168
+ color: AppColors.primaryBlack,
169
+ },
170
+ searchHighlight: {
171
+ backgroundColor: '#fef08a',
172
+ color: '#854d0e',
173
+ fontFamily: AppFonts.interBold,
174
+ borderRadius: 2,
175
+ },
176
+ });
177
+
178
+ export default LogSyntaxHighlighter;