react-native-inapp-inspector 1.1.2 → 1.1.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 (36) hide show
  1. package/README.md +14 -0
  2. package/dist/commonjs/components/ConsoleLogCard.js +18 -0
  3. package/dist/commonjs/components/LogCard.js +17 -0
  4. package/dist/commonjs/components/NetworkIcons.d.ts +9 -2
  5. package/dist/commonjs/components/NetworkIcons.js +59 -3
  6. package/dist/commonjs/constants/version.d.ts +1 -1
  7. package/dist/commonjs/constants/version.js +1 -1
  8. package/dist/commonjs/customHooks/reduxLogger.d.ts +21 -7
  9. package/dist/commonjs/customHooks/reduxLogger.js +147 -48
  10. package/dist/commonjs/customHooks/webViewLogger.js +13 -8
  11. package/dist/commonjs/helpers/settingsStore.d.ts +24 -0
  12. package/dist/commonjs/helpers/settingsStore.js +74 -0
  13. package/dist/commonjs/index.d.ts +1 -1
  14. package/dist/commonjs/index.js +572 -46
  15. package/dist/commonjs/styles/index.d.ts +17 -1
  16. package/dist/commonjs/styles/index.js +25 -3
  17. package/dist/commonjs/types/index.d.ts +4 -0
  18. package/dist/esm/components/ConsoleLogCard.js +18 -0
  19. package/dist/esm/components/LogCard.js +17 -0
  20. package/dist/esm/components/NetworkIcons.d.ts +9 -2
  21. package/dist/esm/components/NetworkIcons.js +51 -2
  22. package/dist/esm/constants/version.d.ts +1 -1
  23. package/dist/esm/constants/version.js +1 -1
  24. package/dist/esm/customHooks/reduxLogger.d.ts +21 -7
  25. package/dist/esm/customHooks/reduxLogger.js +145 -47
  26. package/dist/esm/customHooks/webViewLogger.js +13 -8
  27. package/dist/esm/helpers/settingsStore.d.ts +24 -0
  28. package/dist/esm/helpers/settingsStore.js +67 -0
  29. package/dist/esm/index.d.ts +1 -1
  30. package/dist/esm/index.js +570 -47
  31. package/dist/esm/styles/index.d.ts +17 -1
  32. package/dist/esm/styles/index.js +25 -3
  33. package/dist/esm/types/index.d.ts +4 -0
  34. package/example/ios/example.xcodeproj/project.pbxproj +0 -8
  35. package/example/package-lock.json +4 -3
  36. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -23,8 +23,10 @@ import CodeSnippet from './components/CodeSnippet';
23
23
  import AnimatedEntrance from './components/AnimatedEntrance';
24
24
  // Helpers
25
25
  import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
26
+ // #5 — settings persistence
27
+ import { loadSettings, saveSettings } from './helpers/settingsStore';
26
28
  // Assets
27
- import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, BrandSquareIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, } from './components/NetworkIcons';
29
+ import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, BrandSquareIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, WipeIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, TrendingUpIcon, } from './components/NetworkIcons';
28
30
  import ErrorBoundary from './components/ErrorBoundary';
29
31
  // Stylesheet
30
32
  import { AppColors } from './styles/AppColors';
@@ -237,6 +239,141 @@ const NetworkInspector = ({ enabled = true, }) => {
237
239
  const [reduxExpandDepth, setReduxExpandDepth] = useState(1);
238
240
  const [slowRequestThreshold, setSlowRequestThreshold] = useState(1000);
239
241
  const [insightsShowConsoleAlerts, setInsightsShowConsoleAlerts] = useState(true);
242
+ // #6 — tab the inspector opens on. Shown with a DEFAULT badge in Settings.
243
+ const [defaultTab, setDefaultTab] = useState('apis');
244
+ // #9 — when false (default), consecutive identical entries in the API and
245
+ // Console lists are collapsed into one row with a ×N counter.
246
+ const [showDuplicateLogs, setShowDuplicateLogs] = useState(false);
247
+ // #5 — hydrate persisted settings once, then auto-save on any change.
248
+ const settingsHydratedRef = useRef(false);
249
+ useEffect(() => {
250
+ let cancelled = false;
251
+ loadSettings().then(saved => {
252
+ if (cancelled)
253
+ return;
254
+ if (saved.isDark != null) {
255
+ setIsDark(saved.isDark);
256
+ toggleGlobalTheme(saved.isDark);
257
+ }
258
+ if (saved.modalHeightPercent != null)
259
+ setModalHeightPercent(saved.modalHeightPercent);
260
+ if (saved.tabVisibility)
261
+ setTabVisibility(prev => ({
262
+ ...prev,
263
+ ...saved.tabVisibility,
264
+ apis: true, // APIs is always required
265
+ }));
266
+ if (saved.defaultTab)
267
+ setDefaultTab(saved.defaultTab);
268
+ if (saved.maxNetworkLogs != null)
269
+ setMaxNetworkLogs(saved.maxNetworkLogs);
270
+ if (saved.maxConsoleLogs != null)
271
+ setMaxConsoleLogs(saved.maxConsoleLogs);
272
+ if (saved.showConsoleLevels)
273
+ setShowConsoleLevels(saved.showConsoleLevels);
274
+ if (saved.webViewCaptureCssJs != null)
275
+ setWebViewCaptureCssJs(saved.webViewCaptureCssJs);
276
+ if (saved.reduxAutoRefresh != null)
277
+ setReduxAutoRefreshState(saved.reduxAutoRefresh);
278
+ if (saved.reduxExpandDepth != null)
279
+ setReduxExpandDepth(saved.reduxExpandDepth);
280
+ if (saved.slowRequestThreshold != null)
281
+ setSlowRequestThreshold(saved.slowRequestThreshold);
282
+ if (saved.insightsShowConsoleAlerts != null)
283
+ setInsightsShowConsoleAlerts(saved.insightsShowConsoleAlerts);
284
+ if (saved.showDuplicateLogs != null)
285
+ setShowDuplicateLogs(saved.showDuplicateLogs);
286
+ if (saved.defaultTab) {
287
+ const dt = saved.defaultTab;
288
+ const vis = {
289
+ ...{
290
+ insights: true,
291
+ apis: true,
292
+ logs: true,
293
+ analytics: true,
294
+ webview: true,
295
+ redux: true,
296
+ },
297
+ ...(saved.tabVisibility || {}),
298
+ apis: true,
299
+ };
300
+ setActiveTab(vis[dt] ? dt : 'apis');
301
+ }
302
+ settingsHydratedRef.current = true;
303
+ });
304
+ return () => {
305
+ cancelled = true;
306
+ };
307
+ }, []);
308
+ useEffect(() => {
309
+ if (!settingsHydratedRef.current)
310
+ return;
311
+ saveSettings({
312
+ isDark,
313
+ modalHeightPercent,
314
+ tabVisibility,
315
+ defaultTab,
316
+ maxNetworkLogs,
317
+ maxConsoleLogs,
318
+ showConsoleLevels,
319
+ webViewCaptureCssJs,
320
+ reduxAutoRefresh,
321
+ reduxExpandDepth,
322
+ slowRequestThreshold,
323
+ insightsShowConsoleAlerts,
324
+ showDuplicateLogs,
325
+ });
326
+ }, [
327
+ isDark,
328
+ modalHeightPercent,
329
+ tabVisibility,
330
+ defaultTab,
331
+ maxNetworkLogs,
332
+ maxConsoleLogs,
333
+ showConsoleLevels,
334
+ webViewCaptureCssJs,
335
+ reduxAutoRefresh,
336
+ reduxExpandDepth,
337
+ slowRequestThreshold,
338
+ insightsShowConsoleAlerts,
339
+ showDuplicateLogs,
340
+ ]);
341
+ // #1 — check NPM for a newer published version; surfaces an animated dot
342
+ // in the header next to the npm chip when an update is available.
343
+ const [latestNpmVersion, setLatestNpmVersion] = useState(null);
344
+ const updateAvailable = useMemo(() => {
345
+ if (!latestNpmVersion)
346
+ return false;
347
+ const parse = (v) => v
348
+ .replace(/^v/, '')
349
+ .split('.')
350
+ .map(n => parseInt(n, 10) || 0);
351
+ const cur = parse(LIB_VERSION);
352
+ const latest = parse(latestNpmVersion);
353
+ for (let i = 0; i < 3; i++) {
354
+ if ((latest[i] || 0) > (cur[i] || 0))
355
+ return true;
356
+ if ((latest[i] || 0) < (cur[i] || 0))
357
+ return false;
358
+ }
359
+ return false;
360
+ }, [latestNpmVersion]);
361
+ useEffect(() => {
362
+ let cancelled = false;
363
+ fetch('https://registry.npmjs.org/react-native-inapp-inspector/latest')
364
+ .then(res => (res.ok ? res.json() : null))
365
+ .then(data => {
366
+ if (!cancelled && data && typeof data.version === 'string') {
367
+ setLatestNpmVersion(data.version);
368
+ }
369
+ })
370
+ .catch(() => {
371
+ // Offline / blocked — silently skip the update check.
372
+ });
373
+ return () => {
374
+ cancelled = true;
375
+ };
376
+ }, []);
240
377
  useEffect(() => {
241
378
  setReduxAutoRefresh(reduxAutoRefresh);
242
379
  }, [reduxAutoRefresh]);
@@ -250,6 +387,10 @@ const NetworkInspector = ({ enabled = true, }) => {
250
387
  animateNextLayout();
251
388
  setActiveTab('apis');
252
389
  }
390
+ // #6 — a hidden module can't be the default landing tab.
391
+ if (!nextVal && defaultTab === key) {
392
+ setDefaultTab('apis');
393
+ }
253
394
  return newVisibility;
254
395
  });
255
396
  };
@@ -331,6 +472,8 @@ const NetworkInspector = ({ enabled = true, }) => {
331
472
  const badgeAnim = useRef(new Animated.Value(1)).current;
332
473
  const activePulseAnim = useRef(new Animated.Value(0.4)).current;
333
474
  const unreadPulseAnim = useRef(new Animated.Value(1)).current;
475
+ // #4 — diagonal light streak sweeping across the floating launcher
476
+ const fabShineAnim = useRef(new Animated.Value(0)).current;
334
477
  // #11 — header "clear all" icon spin/scale animation
335
478
  const clearAnim = useRef(new Animated.Value(0)).current;
336
479
  // #4 — draggable floating launcher (drag anywhere on screen)
@@ -373,9 +516,9 @@ const NetworkInspector = ({ enabled = true, }) => {
373
516
  fabDraggedRef.current = false;
374
517
  },
375
518
  })).current;
376
- // #10 — scroll-to-top affordance for the main APIs list
519
+ // #2 — scroll-to-top button for the main APIs list, always visible at the
520
+ // bottom right of the list.
377
521
  const apisListRef = useRef(null);
378
- const [showScrollTop, setShowScrollTop] = useState(false);
379
522
  const runClearAllWithAnimation = useCallback(() => {
380
523
  Animated.sequence([
381
524
  Animated.timing(clearAnim, {
@@ -412,6 +555,24 @@ const NetworkInspector = ({ enabled = true, }) => {
412
555
  loop.start();
413
556
  return () => loop.stop();
414
557
  }, [pulseAnim]);
558
+ // #4 — sweep the shine streak across the launcher, pause, repeat.
559
+ useEffect(() => {
560
+ const loop = Animated.loop(Animated.sequence([
561
+ Animated.timing(fabShineAnim, {
562
+ toValue: 1,
563
+ duration: 1100,
564
+ useNativeDriver: true,
565
+ }),
566
+ Animated.delay(1600),
567
+ Animated.timing(fabShineAnim, {
568
+ toValue: 0,
569
+ duration: 0,
570
+ useNativeDriver: true,
571
+ }),
572
+ ]));
573
+ loop.start();
574
+ return () => loop.stop();
575
+ }, [fabShineAnim]);
415
576
  useEffect(() => {
416
577
  const loop = Animated.loop(Animated.sequence([
417
578
  Animated.timing(activePulseAnim, {
@@ -455,6 +616,16 @@ const NetworkInspector = ({ enabled = true, }) => {
455
616
  }).start();
456
617
  }
457
618
  }, [newLogIds]);
619
+ // #6 — every time the inspector is opened, land on the chosen default tab.
620
+ useEffect(() => {
621
+ if (visible) {
622
+ const target = defaultTab === 'apis' || tabVisibility[defaultTab]
623
+ ? defaultTab
624
+ : 'apis';
625
+ setActiveTab(target);
626
+ }
627
+ // eslint-disable-next-line react-hooks/exhaustive-deps
628
+ }, [visible]);
458
629
  useEffect(() => {
459
630
  if (visible) {
460
631
  const task = InteractionManager.runAfterInteractions(() => {
@@ -608,8 +779,38 @@ const NetworkInspector = ({ enabled = true, }) => {
608
779
  if (sortOrder === 'oldest') {
609
780
  result = [...result].reverse();
610
781
  }
782
+ // #9 — collapse consecutive identical requests (same method + url +
783
+ // status) into one row carrying a ×N counter, unless the user opted in
784
+ // to seeing every duplicate via Settings → "Show Duplicate Logs".
785
+ if (!showDuplicateLogs) {
786
+ const collapsed = [];
787
+ for (const log of result) {
788
+ const last = collapsed[collapsed.length - 1];
789
+ if (last &&
790
+ last.method === log.method &&
791
+ last.url === log.url &&
792
+ last.status === log.status) {
793
+ collapsed[collapsed.length - 1] = {
794
+ ...last,
795
+ duplicateCount: (last.duplicateCount || 1) + 1,
796
+ };
797
+ }
798
+ else {
799
+ collapsed.push({ ...log, duplicateCount: 1 });
800
+ }
801
+ }
802
+ result = collapsed;
803
+ }
611
804
  return result.slice(0, maxNetworkLogs);
612
- }, [logs, search, statusFilters, methodFilters, sortOrder, maxNetworkLogs]);
805
+ }, [
806
+ logs,
807
+ search,
808
+ statusFilters,
809
+ methodFilters,
810
+ sortOrder,
811
+ maxNetworkLogs,
812
+ showDuplicateLogs,
813
+ ]);
613
814
  const availableMethods = useMemo(() => {
614
815
  const methods = new Set();
615
816
  logs.forEach(log => {
@@ -819,8 +1020,35 @@ const NetworkInspector = ({ enabled = true, }) => {
819
1020
  result = [...result].sort((a, b) => logSortOrder === 'newest'
820
1021
  ? b.timestamp - a.timestamp
821
1022
  : a.timestamp - b.timestamp);
1023
+ // #9 — collapse consecutive identical messages into one row with a ×N
1024
+ // counter unless duplicates are explicitly enabled in Settings.
1025
+ if (!showDuplicateLogs) {
1026
+ const collapsed = [];
1027
+ for (const log of result) {
1028
+ const last = collapsed[collapsed.length - 1];
1029
+ if (last &&
1030
+ last.type === log.type &&
1031
+ last.sourceMethod === log.sourceMethod &&
1032
+ last.message === log.message) {
1033
+ collapsed[collapsed.length - 1] = {
1034
+ ...last,
1035
+ duplicateCount: (last.duplicateCount || 1) + 1,
1036
+ };
1037
+ }
1038
+ else {
1039
+ collapsed.push({ ...log, duplicateCount: 1 });
1040
+ }
1041
+ }
1042
+ result = collapsed;
1043
+ }
822
1044
  return result;
823
- }, [visibleConsoleLogs, logFilters, logSearch, logSortOrder]);
1045
+ }, [
1046
+ visibleConsoleLogs,
1047
+ logFilters,
1048
+ logSearch,
1049
+ logSortOrder,
1050
+ showDuplicateLogs,
1051
+ ]);
824
1052
  const filteredWebViewLogs = useMemo(() => {
825
1053
  let result = webViewLogs;
826
1054
  if (webViewSearch) {
@@ -969,7 +1197,11 @@ const NetworkInspector = ({ enabled = true, }) => {
969
1197
  else {
970
1198
  Alert.alert('Clear Logs', 'Are you sure you want to clear all network logs?', [
971
1199
  { text: 'Cancel', style: 'cancel' },
972
- { text: 'Clear All', onPress: clearNetworkOnly, style: 'destructive' },
1200
+ {
1201
+ text: 'Clear All',
1202
+ onPress: clearNetworkOnly,
1203
+ style: 'destructive',
1204
+ },
973
1205
  ]);
974
1206
  }
975
1207
  }
@@ -1213,7 +1445,8 @@ const NetworkInspector = ({ enabled = true, }) => {
1213
1445
  }}>
1214
1446
  {tab.label}
1215
1447
  </Text>
1216
- {isLocked && (<View style={{
1448
+ {/* #6 badge marks the configured default tab */}
1449
+ {tab.key === defaultTab && (<View style={{
1217
1450
  flexDirection: 'row',
1218
1451
  alignItems: 'center',
1219
1452
  backgroundColor: 'rgba(104,75,155,0.08)',
@@ -1481,6 +1714,169 @@ const NetworkInspector = ({ enabled = true, }) => {
1481
1714
  })}
1482
1715
  </View>
1483
1716
  </View>
1717
+
1718
+ {/* Divider */}
1719
+ <View style={{
1720
+ height: 1,
1721
+ backgroundColor: AppColors.dividerColor,
1722
+ }}/>
1723
+
1724
+ {/* #6 — Default Tab */}
1725
+ <View style={{
1726
+ paddingVertical: 12,
1727
+ paddingHorizontal: 14,
1728
+ }}>
1729
+ <View style={{
1730
+ flexDirection: 'row',
1731
+ alignItems: 'center',
1732
+ gap: 8,
1733
+ }}>
1734
+ <View style={{
1735
+ width: 20,
1736
+ height: 20,
1737
+ borderRadius: 6,
1738
+ backgroundColor: AppColors.purpleShade50,
1739
+ borderWidth: 1,
1740
+ borderColor: 'rgba(104,75,155,0.2)',
1741
+ alignItems: 'center',
1742
+ justifyContent: 'center',
1743
+ }}>
1744
+ <LayersIcon color={AppColors.purple} size={11}/>
1745
+ </View>
1746
+ <View style={{ flex: 1 }}>
1747
+ <Text style={{
1748
+ fontFamily: AppFonts.interBold,
1749
+ fontSize: 13,
1750
+ color: AppColors.primaryBlack,
1751
+ }}>
1752
+ Default Tab
1753
+ </Text>
1754
+ <Text style={{
1755
+ fontFamily: AppFonts.interRegular,
1756
+ fontSize: 11,
1757
+ color: AppColors.grayText,
1758
+ marginTop: 1,
1759
+ }}>
1760
+ Tab shown when the inspector opens
1761
+ </Text>
1762
+ </View>
1763
+ </View>
1764
+
1765
+ {/* Segmented picker — only visible tabs are offered */}
1766
+ <View style={{
1767
+ flexDirection: 'row',
1768
+ flexWrap: 'wrap',
1769
+ backgroundColor: AppColors.grayBackground,
1770
+ borderRadius: 8,
1771
+ padding: 2.5,
1772
+ marginTop: 10,
1773
+ borderWidth: 1,
1774
+ borderColor: AppColors.dividerColor,
1775
+ gap: 2,
1776
+ }}>
1777
+ {settingsTabs
1778
+ .filter(tab => tab.key === 'apis' || tabVisibility[tab.key])
1779
+ .map(tab => {
1780
+ const isActive = defaultTab === tab.key;
1781
+ return (<TouchableScale key={tab.key} onPress={() => setDefaultTab(tab.key)} style={{
1782
+ paddingVertical: 6,
1783
+ paddingHorizontal: 10,
1784
+ alignItems: 'center',
1785
+ borderRadius: 6,
1786
+ backgroundColor: isActive
1787
+ ? AppColors.purple
1788
+ : 'transparent',
1789
+ }}>
1790
+ <Text style={{
1791
+ fontFamily: AppFonts.interBold,
1792
+ fontSize: 11,
1793
+ color: isActive
1794
+ ? '#FFFFFF'
1795
+ : AppColors.grayText,
1796
+ }}>
1797
+ {tab.label}
1798
+ </Text>
1799
+ </TouchableScale>);
1800
+ })}
1801
+ </View>
1802
+ </View>
1803
+
1804
+ {/* Divider */}
1805
+ <View style={{
1806
+ height: 1,
1807
+ backgroundColor: AppColors.dividerColor,
1808
+ }}/>
1809
+
1810
+ {/* #9 — Show Duplicate Logs */}
1811
+ <View style={{
1812
+ flexDirection: 'row',
1813
+ alignItems: 'center',
1814
+ paddingVertical: 12,
1815
+ paddingHorizontal: 14,
1816
+ gap: 12,
1817
+ }}>
1818
+ <View style={{
1819
+ flex: 1,
1820
+ flexDirection: 'row',
1821
+ alignItems: 'center',
1822
+ gap: 8,
1823
+ }}>
1824
+ <View style={{
1825
+ width: 20,
1826
+ height: 20,
1827
+ borderRadius: 6,
1828
+ backgroundColor: AppColors.purpleShade50,
1829
+ borderWidth: 1,
1830
+ borderColor: 'rgba(104,75,155,0.2)',
1831
+ alignItems: 'center',
1832
+ justifyContent: 'center',
1833
+ }}>
1834
+ <EyeIcon color={AppColors.purple} size={11}/>
1835
+ </View>
1836
+ <View style={{ flex: 1 }}>
1837
+ <Text style={{
1838
+ fontFamily: AppFonts.interBold,
1839
+ fontSize: 13,
1840
+ color: AppColors.primaryBlack,
1841
+ }}>
1842
+ Show Duplicate Logs
1843
+ </Text>
1844
+ <Text style={{
1845
+ fontFamily: AppFonts.interRegular,
1846
+ fontSize: 11,
1847
+ color: AppColors.grayText,
1848
+ marginTop: 1,
1849
+ }}>
1850
+ Off: repeated identical entries collapse into one row
1851
+ with a ×N count
1852
+ </Text>
1853
+ </View>
1854
+ </View>
1855
+
1856
+ {/* Toggle Switch */}
1857
+ <TouchableScale onPress={() => setShowDuplicateLogs(prev => !prev)} style={{
1858
+ width: 38,
1859
+ height: 22,
1860
+ borderRadius: 11,
1861
+ backgroundColor: showDuplicateLogs
1862
+ ? AppColors.purple
1863
+ : AppColors.grayBorderSecondary,
1864
+ padding: 2,
1865
+ justifyContent: 'center',
1866
+ alignItems: showDuplicateLogs ? 'flex-end' : 'flex-start',
1867
+ }}>
1868
+ <View style={{
1869
+ width: 18,
1870
+ height: 18,
1871
+ borderRadius: 9,
1872
+ backgroundColor: '#FFFFFF',
1873
+ shadowColor: '#000',
1874
+ shadowOpacity: 0.15,
1875
+ shadowRadius: 1.5,
1876
+ shadowOffset: { width: 0, height: 1 },
1877
+ }}/>
1878
+ </TouchableScale>
1879
+ </View>
1484
1880
  </View>
1485
1881
  </View>
1486
1882
  </ScrollView>
@@ -2751,6 +3147,29 @@ const NetworkInspector = ({ enabled = true, }) => {
2751
3147
  }} hitSlop={10}>
2752
3148
  <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
2753
3149
  <BrandCircleIcon size={62}/>
3150
+ {/* #4 — shining sweep, clipped inside the circular launcher */}
3151
+ <View pointerEvents="none" style={styles.fabShineClip}>
3152
+ <Animated.View style={[
3153
+ styles.fabShineStreak,
3154
+ {
3155
+ transform: [
3156
+ {
3157
+ translateX: fabShineAnim.interpolate({
3158
+ inputRange: [0, 1],
3159
+ outputRange: [-48, 96],
3160
+ }),
3161
+ },
3162
+ { rotate: '25deg' },
3163
+ ],
3164
+ },
3165
+ ]}>
3166
+ <LinearGradient colors={[
3167
+ 'rgba(255,255,255,0)',
3168
+ 'rgba(255,255,255,0.55)',
3169
+ 'rgba(255,255,255,0)',
3170
+ ]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
3171
+ </Animated.View>
3172
+ </View>
2754
3173
  {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
2755
3174
  styles.fabBadge,
2756
3175
  hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
@@ -2923,6 +3342,29 @@ const NetworkInspector = ({ enabled = true, }) => {
2923
3342
  </Text>
2924
3343
  </View>
2925
3344
  </View>
3345
+
3346
+ {/* #1 — pulsing dot when a newer version is on NPM */}
3347
+ {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
3348
+ { text: 'Later', style: 'cancel' },
3349
+ {
3350
+ text: 'View on NPM',
3351
+ onPress: () => Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { }),
3352
+ },
3353
+ ])} style={{
3354
+ alignItems: 'center',
3355
+ justifyContent: 'center',
3356
+ }}>
3357
+ <Animated.View style={{
3358
+ width: 8,
3359
+ height: 8,
3360
+ borderRadius: 4,
3361
+ backgroundColor: '#4ADE80',
3362
+ borderWidth: 1,
3363
+ borderColor: 'rgba(255,255,255,0.9)',
3364
+ opacity: activePulseAnim,
3365
+ transform: [{ scale: unreadPulseAnim }],
3366
+ }}/>
3367
+ </Pressable>)}
2926
3368
  </View>
2927
3369
  </View>
2928
3370
  </View>) : null}
@@ -3071,7 +3513,7 @@ const NetworkInspector = ({ enabled = true, }) => {
3071
3513
  },
3072
3514
  ],
3073
3515
  }}>
3074
- <TrashIcon color="#FFFFFF" size={15}/>
3516
+ <WipeIcon color="#FFFFFF" size={16}/>
3075
3517
  </Animated.View>
3076
3518
  </TouchableScale>)}
3077
3519
 
@@ -3251,7 +3693,16 @@ const NetworkInspector = ({ enabled = true, }) => {
3251
3693
  animateNextLayout();
3252
3694
  setAnalyticsSubTab('ga_events');
3253
3695
  }}>
3254
- <Text style={[
3696
+ <View style={{
3697
+ flexDirection: 'row',
3698
+ alignItems: 'center',
3699
+ gap: 6,
3700
+ }}>
3701
+ {/* #7 */}
3702
+ <AnalyticsIcon size={13} color={analyticsSubTab === 'ga_events'
3703
+ ? AppColors.purple
3704
+ : AppColors.grayTextStrong}/>
3705
+ <Text style={[
3255
3706
  {
3256
3707
  fontFamily: AppFonts.interMedium,
3257
3708
  fontSize: 13,
@@ -3262,12 +3713,13 @@ const NetworkInspector = ({ enabled = true, }) => {
3262
3713
  color: AppColors.purple,
3263
3714
  },
3264
3715
  ]}>
3265
- GA Events (
3266
- {analyticsSearch
3716
+ GA Events (
3717
+ {analyticsSearch
3267
3718
  ? filteredAnalyticsEvents.length
3268
3719
  : analyticsEvents.length}
3269
- )
3270
- </Text>
3720
+ )
3721
+ </Text>
3722
+ </View>
3271
3723
  </Pressable>
3272
3724
  <Pressable style={[
3273
3725
  {
@@ -3288,7 +3740,16 @@ const NetworkInspector = ({ enabled = true, }) => {
3288
3740
  animateNextLayout();
3289
3741
  setAnalyticsSubTab('top_events');
3290
3742
  }}>
3291
- <Text style={[
3743
+ <View style={{
3744
+ flexDirection: 'row',
3745
+ alignItems: 'center',
3746
+ gap: 6,
3747
+ }}>
3748
+ {/* #7 */}
3749
+ <TrendingUpIcon size={13} color={analyticsSubTab === 'top_events'
3750
+ ? AppColors.purple
3751
+ : AppColors.grayTextStrong}/>
3752
+ <Text style={[
3292
3753
  {
3293
3754
  fontFamily: AppFonts.interMedium,
3294
3755
  fontSize: 13,
@@ -3299,8 +3760,9 @@ const NetworkInspector = ({ enabled = true, }) => {
3299
3760
  color: AppColors.purple,
3300
3761
  },
3301
3762
  ]}>
3302
- Top Events ({topEventsArray.length})
3303
- </Text>
3763
+ Top Events ({topEventsArray.length})
3764
+ </Text>
3765
+ </View>
3304
3766
  </Pressable>
3305
3767
  </View>
3306
3768
  </View>)}
@@ -3410,7 +3872,7 @@ const NetworkInspector = ({ enabled = true, }) => {
3410
3872
  flexGrow: 1,
3411
3873
  },
3412
3874
  ]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (<View style={{ flex: 1 }}>
3413
- <FlatList ref={apisListRef} onScroll={e => setShowScrollTop(e.nativeEvent.contentOffset.y > 320)} scrollEventThrottle={16} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
3875
+ <FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
3414
3876
  <View style={styles.toolbarRow}>
3415
3877
  <View style={styles.searchContainer}>
3416
3878
  <SearchIcon color={AppColors.grayTextWeak} size={16}/>
@@ -3561,17 +4023,17 @@ const NetworkInspector = ({ enabled = true, }) => {
3561
4023
  styles.listContent,
3562
4024
  filteredLogs.length === 0 && { flexGrow: 1 },
3563
4025
  ]} keyboardShouldPersistTaps="handled"/>
3564
- {showScrollTop && (<TouchableScale onPress={() => {
3565
- apisListRef.current?.scrollToOffset({
3566
- offset: 0,
3567
- animated: true,
3568
- });
3569
- setShowScrollTop(false);
3570
- }} hitSlop={10} style={styles.scrollTopBtn}>
3571
- <View style={{ transform: [{ rotate: '180deg' }] }}>
3572
- <ChevronIcon color="#FFFFFF" size={18}/>
3573
- </View>
3574
- </TouchableScale>)}
4026
+ {/* #2 always-visible scroll-to-top, bottom right */}
4027
+ <TouchableScale onPress={() => {
4028
+ apisListRef.current?.scrollToOffset({
4029
+ offset: 0,
4030
+ animated: true,
4031
+ });
4032
+ }} hitSlop={10} style={styles.scrollTopBtn}>
4033
+ <View style={{ transform: [{ rotate: '180deg' }] }}>
4034
+ <ChevronIcon color="#FFFFFF" size={18}/>
4035
+ </View>
4036
+ </TouchableScale>
3575
4037
  </View>) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
3576
4038
  <View style={{
3577
4039
  backgroundColor: '#FFFFFF',
@@ -3620,15 +4082,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3620
4082
  backgroundColor: '#F4EBFF',
3621
4083
  },
3622
4084
  ]}>
3623
- <Text numberOfLines={1} style={[
4085
+ {/* #7 */}
4086
+ <View style={{
4087
+ flexDirection: 'row',
4088
+ alignItems: 'center',
4089
+ gap: 5,
4090
+ }}>
4091
+ <LayersIcon size={12} color={active
4092
+ ? AppColors.purpleShade700
4093
+ : AppColors.grayTextStrong}/>
4094
+ <Text numberOfLines={1} style={[
3624
4095
  styles.statusFilterText,
3625
4096
  active && {
3626
4097
  color: AppColors.purpleShade700,
3627
4098
  fontFamily: AppFonts.interBold,
3628
4099
  },
3629
4100
  ]}>
3630
- All ({logCounts.all})
3631
- </Text>
4101
+ All ({logCounts.all})
4102
+ </Text>
4103
+ </View>
3632
4104
  </View>
3633
4105
  </TouchableScale>);
3634
4106
  })()}
@@ -3654,15 +4126,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3654
4126
  backgroundColor: '#F1F5F9',
3655
4127
  },
3656
4128
  ]}>
3657
- <Text numberOfLines={1} style={[
4129
+ {/* #7 */}
4130
+ <View style={{
4131
+ flexDirection: 'row',
4132
+ alignItems: 'center',
4133
+ gap: 5,
4134
+ }}>
4135
+ <UserIcon size={12} color={active
4136
+ ? '#334155'
4137
+ : AppColors.grayTextStrong}/>
4138
+ <Text numberOfLines={1} style={[
3658
4139
  styles.statusFilterText,
3659
4140
  active && {
3660
4141
  color: '#334155',
3661
4142
  fontFamily: AppFonts.interBold,
3662
4143
  },
3663
4144
  ]}>
3664
- User Log ({logCounts['user-log']})
3665
- </Text>
4145
+ User Log ({logCounts['user-log']})
4146
+ </Text>
4147
+ </View>
3666
4148
  </View>
3667
4149
  </TouchableScale>);
3668
4150
  })()}
@@ -3688,15 +4170,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3688
4170
  backgroundColor: AppColors.purpleShade50,
3689
4171
  },
3690
4172
  ]}>
3691
- <Text numberOfLines={1} style={[
4173
+ {/* #7 */}
4174
+ <View style={{
4175
+ flexDirection: 'row',
4176
+ alignItems: 'center',
4177
+ gap: 5,
4178
+ }}>
4179
+ <InfoCircleIcon size={12} color={active
4180
+ ? AppColors.purple
4181
+ : AppColors.grayTextStrong}/>
4182
+ <Text numberOfLines={1} style={[
3692
4183
  styles.statusFilterText,
3693
4184
  active && {
3694
4185
  color: AppColors.purple,
3695
4186
  fontFamily: AppFonts.interBold,
3696
4187
  },
3697
4188
  ]}>
3698
- Info ({logCounts.info})
3699
- </Text>
4189
+ Info ({logCounts.info})
4190
+ </Text>
4191
+ </View>
3700
4192
  </View>
3701
4193
  </TouchableScale>);
3702
4194
  })()}
@@ -3722,7 +4214,17 @@ const NetworkInspector = ({ enabled = true, }) => {
3722
4214
  backgroundColor: '#FFFDF6',
3723
4215
  },
3724
4216
  ]}>
3725
- <Text numberOfLines={1} style={[
4217
+ {/* #7 */}
4218
+ <View style={{
4219
+ flexDirection: 'row',
4220
+ alignItems: 'center',
4221
+ gap: 5,
4222
+ }}>
4223
+ <WarningTriangleIcon size={12} color={active
4224
+ ? AppColors.darkOrange ||
4225
+ AppColors.lightOrange
4226
+ : AppColors.grayTextStrong}/>
4227
+ <Text numberOfLines={1} style={[
3726
4228
  styles.statusFilterText,
3727
4229
  active && {
3728
4230
  color: AppColors.darkOrange ||
@@ -3730,8 +4232,9 @@ const NetworkInspector = ({ enabled = true, }) => {
3730
4232
  fontFamily: AppFonts.interBold,
3731
4233
  },
3732
4234
  ]}>
3733
- Warning ({logCounts.warn})
3734
- </Text>
4235
+ Warning ({logCounts.warn})
4236
+ </Text>
4237
+ </View>
3735
4238
  </View>
3736
4239
  </TouchableScale>);
3737
4240
  })()}
@@ -3757,15 +4260,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3757
4260
  backgroundColor: '#FFF5F6',
3758
4261
  },
3759
4262
  ]}>
3760
- <Text numberOfLines={1} style={[
4263
+ {/* #7 */}
4264
+ <View style={{
4265
+ flexDirection: 'row',
4266
+ alignItems: 'center',
4267
+ gap: 5,
4268
+ }}>
4269
+ <ErrorCircleIcon size={12} color={active
4270
+ ? AppColors.errorColor
4271
+ : AppColors.grayTextStrong}/>
4272
+ <Text numberOfLines={1} style={[
3761
4273
  styles.statusFilterText,
3762
4274
  active && {
3763
4275
  color: AppColors.errorColor,
3764
4276
  fontFamily: AppFonts.interBold,
3765
4277
  },
3766
4278
  ]}>
3767
- Error ({logCounts.error})
3768
- </Text>
4279
+ Error ({logCounts.error})
4280
+ </Text>
4281
+ </View>
3769
4282
  </View>
3770
4283
  </TouchableScale>);
3771
4284
  })()}
@@ -3791,15 +4304,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3791
4304
  backgroundColor: `${AppColors.skyBlue}15`,
3792
4305
  },
3793
4306
  ]}>
3794
- <Text numberOfLines={1} style={[
4307
+ {/* #7 */}
4308
+ <View style={{
4309
+ flexDirection: 'row',
4310
+ alignItems: 'center',
4311
+ gap: 5,
4312
+ }}>
4313
+ <AnalyticsIcon size={12} color={active
4314
+ ? AppColors.skyBlue
4315
+ : AppColors.grayTextStrong}/>
4316
+ <Text numberOfLines={1} style={[
3795
4317
  styles.statusFilterText,
3796
4318
  active && {
3797
4319
  color: AppColors.skyBlue,
3798
4320
  fontFamily: AppFonts.interBold,
3799
4321
  },
3800
4322
  ]}>
3801
- Analytics ({logCounts.analytics})
3802
- </Text>
4323
+ Analytics ({logCounts.analytics})
4324
+ </Text>
4325
+ </View>
3803
4326
  </View>
3804
4327
  </TouchableScale>);
3805
4328
  })()}
@@ -4872,4 +5395,4 @@ export { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './c
4872
5395
  export { setupAnalyticsLogger, logAnalyticsEvent, subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
4873
5396
  export { WebView, getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
4874
5397
  export { default as ErrorBoundary } from './components/ErrorBoundary';
4875
- export { connectReduxStore, getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
5398
+ export { connectReduxStore, inspectorReduxMiddleware, getReduxState, subscribeReduxState, getActionHistory, clearActionHistory, } from './customHooks/reduxLogger';