react-native-inapp-inspector 2.3.7 → 2.3.9

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 (56) hide show
  1. package/dist/commonjs/components/Inspector/AnalyticsTab.js +27 -2
  2. package/dist/commonjs/components/Inspector/BundleTab.js +3 -0
  3. package/dist/commonjs/components/Inspector/ConsoleTab.js +24 -1
  4. package/dist/commonjs/components/Inspector/CrashTab.js +30 -1
  5. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.d.ts +7 -0
  6. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.js +111 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +225 -108
  8. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +1 -1
  9. package/dist/commonjs/components/Inspector/PerformanceTab.js +3 -0
  10. package/dist/commonjs/components/Inspector/ReduxTab.js +24 -1
  11. package/dist/commonjs/components/Inspector/SettingsPanel.js +73 -88
  12. package/dist/commonjs/components/Inspector/StorageTab.js +37 -11
  13. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +2 -0
  14. package/dist/commonjs/components/LogCard.js +33 -3
  15. package/dist/commonjs/constants/version.d.ts +1 -1
  16. package/dist/commonjs/constants/version.js +1 -1
  17. package/dist/commonjs/customHooks/networkLogger.js +2 -0
  18. package/dist/commonjs/helpers/telemetry.js +34 -0
  19. package/dist/commonjs/index.js +3 -0
  20. package/dist/esm/components/Inspector/AnalyticsTab.js +29 -4
  21. package/dist/esm/components/Inspector/BundleTab.js +3 -0
  22. package/dist/esm/components/Inspector/ConsoleTab.js +26 -3
  23. package/dist/esm/components/Inspector/CrashTab.js +32 -3
  24. package/dist/esm/components/Inspector/FeatureUnderDevNotice.d.ts +7 -0
  25. package/dist/esm/components/Inspector/FeatureUnderDevNotice.js +74 -0
  26. package/dist/esm/components/Inspector/InspectorHeader.js +226 -109
  27. package/dist/esm/components/Inspector/NpmUpdateToast.js +1 -1
  28. package/dist/esm/components/Inspector/PerformanceTab.js +3 -0
  29. package/dist/esm/components/Inspector/ReduxTab.js +26 -3
  30. package/dist/esm/components/Inspector/SettingsPanel.js +73 -88
  31. package/dist/esm/components/Inspector/StorageTab.js +39 -13
  32. package/dist/esm/components/Inspector/UpdateAvailableModal.js +2 -0
  33. package/dist/esm/components/LogCard.js +34 -4
  34. package/dist/esm/constants/version.d.ts +1 -1
  35. package/dist/esm/constants/version.js +1 -1
  36. package/dist/esm/customHooks/networkLogger.js +2 -0
  37. package/dist/esm/helpers/telemetry.js +34 -0
  38. package/dist/esm/index.js +3 -0
  39. package/package.json +1 -1
  40. package/src/components/Inspector/AnalyticsTab.tsx +71 -43
  41. package/src/components/Inspector/BundleTab.tsx +3 -0
  42. package/src/components/Inspector/ConsoleTab.tsx +28 -1
  43. package/src/components/Inspector/CrashTab.tsx +45 -14
  44. package/src/components/Inspector/FeatureUnderDevNotice.tsx +94 -0
  45. package/src/components/Inspector/InspectorHeader.tsx +257 -115
  46. package/src/components/Inspector/NpmUpdateToast.tsx +1 -1
  47. package/src/components/Inspector/PerformanceTab.tsx +3 -0
  48. package/src/components/Inspector/ReduxTab.tsx +28 -1
  49. package/src/components/Inspector/SettingsPanel.tsx +93 -132
  50. package/src/components/Inspector/StorageTab.tsx +56 -27
  51. package/src/components/Inspector/UpdateAvailableModal.tsx +2 -0
  52. package/src/components/LogCard.tsx +41 -4
  53. package/src/constants/version.ts +1 -1
  54. package/src/customHooks/networkLogger.ts +2 -0
  55. package/src/helpers/telemetry.ts +36 -0
  56. package/src/index.tsx +5 -0
@@ -169,7 +169,7 @@ export const NpmUpdateToast = () => {
169
169
  handleDismiss();
170
170
  };
171
171
 
172
- if (!showUpdateToast || !visible || !updateAvailable || !latestNpmVersion) return null;
172
+ if (typeof __DEV__ === 'undefined' || !__DEV__ || !showUpdateToast || !visible || !updateAvailable || !latestNpmVersion) return null;
173
173
 
174
174
  const progressWidth = progressAnim.interpolate({
175
175
  inputRange: [0, 1],
@@ -22,6 +22,7 @@ import TouchableScale from '../TouchableScale';
22
22
  import CopyButton from '../CopyButton';
23
23
  import HighlightText from '../HighlightText';
24
24
  import EndOfListFooter from '../EndOfListFooter';
25
+ import FeatureUnderDevNotice from './FeatureUnderDevNotice';
25
26
  import {AppColors} from '../../styles/AppColors';
26
27
  import {AppFonts} from '../../styles/AppFonts';
27
28
  import {useTranslation} from '../../i18n';
@@ -500,6 +501,8 @@ const PerformanceTab = React.memo(() => {
500
501
  </TouchableScale>
501
502
  </View>
502
503
 
504
+ <FeatureUnderDevNotice featureName="Performance & FPS Profiler" />
505
+
503
506
  {/* Filter Category Tabs Carousel */}
504
507
  <View style={perfStyles.categoryScrollWrapper}>
505
508
  <ScrollView
@@ -1,4 +1,4 @@
1
- import React, {useCallback, useMemo, useState} from 'react';
1
+ import React, {useCallback, useMemo, useRef, useState} from 'react';
2
2
  import {
3
3
  FlatList,
4
4
  Pressable,
@@ -42,6 +42,7 @@ import {
42
42
  TextAaIcon,
43
43
  StorageIcon,
44
44
  HeaderPauseIcon,
45
+ ChevronIcon,
45
46
  } from '../NetworkIcons';
46
47
 
47
48
  interface ReduxSliceItem {
@@ -121,6 +122,8 @@ const ReduxTab = React.memo(() => {
121
122
  setSelectedReduxSlice,
122
123
  } = useInspector();
123
124
 
125
+ const listRef = useRef<FlatList>(null);
126
+
124
127
  // Sort mode: 'latest' (newest updated first) vs 'alpha' (A-Z)
125
128
  const [sortMode, setSortMode] = useState<'latest' | 'alpha'>('latest');
126
129
 
@@ -656,6 +659,7 @@ const ReduxTab = React.memo(() => {
656
659
 
657
660
  {/* ─── FlatList of Slices ─── */}
658
661
  <FlatList
662
+ ref={listRef}
659
663
  data={filteredSlices}
660
664
  keyExtractor={item => item.id}
661
665
  renderItem={renderSliceItem}
@@ -683,6 +687,29 @@ const ReduxTab = React.memo(() => {
683
687
  ]}
684
688
  keyboardShouldPersistTaps="handled"
685
689
  />
690
+
691
+ <TouchableScale
692
+ onPress={() => {
693
+ try {
694
+ listRef.current?.scrollToOffset({
695
+ offset: 0,
696
+ animated: true,
697
+ });
698
+ } catch {
699
+ try {
700
+ listRef.current?.scrollToIndex({
701
+ index: 0,
702
+ animated: true,
703
+ });
704
+ } catch {}
705
+ }
706
+ }}
707
+ hitSlop={12}
708
+ style={styles.scrollTopBtn}>
709
+ <View style={{transform: [{rotate: '180deg'}]}}>
710
+ <ChevronIcon color={AppColors.white} size={18} />
711
+ </View>
712
+ </TouchableScale>
686
713
  </View>
687
714
  );
688
715
  });
@@ -457,12 +457,13 @@ const SettingsPanel = () => {
457
457
  contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 14}}
458
458
  showsVerticalScrollIndicator={false}>
459
459
  {/* Segmented Top Navigation Sub-Tabs */}
460
+ {/* ─── Modern Scrollable Settings Sub-Tabs Bar ─── */}
460
461
  <View
461
462
  style={{
462
- flexDirection: 'row',
463
463
  backgroundColor: AppColors.primaryLight,
464
- borderRadius: 12,
465
- padding: 4,
464
+ borderRadius: 13,
465
+ paddingVertical: 4,
466
+ paddingHorizontal: 4,
466
467
  borderWidth: 1,
467
468
  borderColor: AppColors.grayBorderSecondary,
468
469
  shadowColor: AppColors.black,
@@ -470,139 +471,99 @@ const SettingsPanel = () => {
470
471
  shadowRadius: 4,
471
472
  shadowOffset: {width: 0, height: 2},
472
473
  }}>
473
- <TouchableScale
474
- accessible={true}
475
- accessibilityRole="tab"
476
- accessibilityLabel={t('settings.modulesAndTools')}
477
- accessibilityState={{selected: settingsActiveSubTab === 'module'}}
478
- onPress={() => {
479
- animateNextLayout();
480
- setSettingsActiveSubTab('module');
481
- }}
482
- style={{
483
- flex: 1,
484
- paddingVertical: 9,
474
+ <ScrollView
475
+ horizontal
476
+ showsHorizontalScrollIndicator={false}
477
+ contentContainerStyle={{
485
478
  flexDirection: 'row',
486
479
  alignItems: 'center',
487
- justifyContent: 'center',
488
480
  gap: 6,
489
- borderRadius: 9,
490
- backgroundColor:
491
- settingsActiveSubTab === 'module'
492
- ? AppColors.purple
493
- : 'transparent',
494
- }}>
495
- <LayersIcon
496
- color={
497
- settingsActiveSubTab === 'module'
498
- ? AppColors.white
499
- : AppColors.grayText
500
- }
501
- size={13}
502
- />
503
- <Text
504
- style={{
505
- fontFamily: AppFonts.interBold,
506
- fontSize: 12.5,
507
- lineHeight: 16,
508
- color:
509
- settingsActiveSubTab === 'module'
510
- ? AppColors.white
511
- : AppColors.grayText,
512
- }}>
513
- {t('settings.modulesAndTools')}
514
- </Text>
515
- </TouchableScale>
516
-
517
- <TouchableScale
518
- accessible={true}
519
- accessibilityRole="tab"
520
- accessibilityLabel={t('settings.uiPreferences')}
521
- accessibilityState={{selected: settingsActiveSubTab === 'ui'}}
522
- onPress={() => {
523
- animateNextLayout();
524
- setSettingsActiveSubTab('ui');
525
- }}
526
- style={{
527
- flex: 1,
528
- paddingVertical: 9,
529
- flexDirection: 'row',
530
- alignItems: 'center',
531
- justifyContent: 'center',
532
- gap: 5,
533
- borderRadius: 9,
534
- backgroundColor:
535
- settingsActiveSubTab === 'ui'
536
- ? AppColors.purple
537
- : 'transparent',
538
- }}>
539
- <ScreenIcon
540
- color={
541
- settingsActiveSubTab === 'ui'
542
- ? AppColors.white
543
- : AppColors.grayText
544
- }
545
- size={13}
546
- />
547
- <Text
548
- numberOfLines={1}
549
- style={{
550
- fontFamily: AppFonts.interBold,
551
- fontSize: 12,
552
- lineHeight: 16,
553
- color:
554
- settingsActiveSubTab === 'ui'
555
- ? AppColors.white
556
- : AppColors.grayText,
557
- }}>
558
- {t('settings.uiPreferences')}
559
- </Text>
560
- </TouchableScale>
561
-
562
- <TouchableScale
563
- accessible={true}
564
- accessibilityRole="tab"
565
- accessibilityLabel={t('settings.ramLimits')}
566
- accessibilityState={{selected: settingsActiveSubTab === 'limits'}}
567
- onPress={() => {
568
- animateNextLayout();
569
- setSettingsActiveSubTab('limits');
570
- }}
571
- style={{
572
- flex: 1,
573
- paddingVertical: 9,
574
- flexDirection: 'row',
575
- alignItems: 'center',
576
- justifyContent: 'center',
577
- gap: 5,
578
- borderRadius: 9,
579
- backgroundColor:
580
- settingsActiveSubTab === 'limits'
581
- ? AppColors.purple
582
- : 'transparent',
481
+ paddingHorizontal: 2,
583
482
  }}>
584
- <BrainIcon
585
- color={
586
- settingsActiveSubTab === 'limits'
587
- ? AppColors.white
588
- : AppColors.grayText
589
- }
590
- size={13}
591
- />
592
- <Text
593
- numberOfLines={1}
594
- style={{
595
- fontFamily: AppFonts.interBold,
596
- fontSize: 12,
597
- lineHeight: 16,
598
- color:
599
- settingsActiveSubTab === 'limits'
600
- ? AppColors.white
601
- : AppColors.grayText,
602
- }}>
603
- {t('settings.ramLimits')}
604
- </Text>
605
- </TouchableScale>
483
+ {[
484
+ {
485
+ key: 'module' as const,
486
+ label: t('settings.modulesAndTools', 'Modules & Tools'),
487
+ Icon: LayersIcon,
488
+ badge: `${allModules.filter(m => stagedTabVisibility?.[m.key as ActiveTab] || m.key === 'apis').length}/${allModules.length}`,
489
+ },
490
+ {
491
+ key: 'ui' as const,
492
+ label: t('settings.uiPreferences', 'UI Preferences'),
493
+ Icon: ScreenIcon,
494
+ },
495
+ {
496
+ key: 'limits' as const,
497
+ label: t('settings.ramLimits', 'RAM & Limits'),
498
+ Icon: BrainIcon,
499
+ },
500
+ ].map(tab => {
501
+ const isActive = settingsActiveSubTab === tab.key;
502
+ const IconComp = tab.Icon;
503
+ return (
504
+ <TouchableScale
505
+ key={tab.key}
506
+ accessible={true}
507
+ accessibilityRole="tab"
508
+ accessibilityLabel={tab.label}
509
+ accessibilityState={{selected: isActive}}
510
+ onPress={() => {
511
+ animateNextLayout();
512
+ setSettingsActiveSubTab(tab.key);
513
+ }}
514
+ style={{
515
+ paddingVertical: 8,
516
+ paddingHorizontal: 14,
517
+ flexDirection: 'row',
518
+ alignItems: 'center',
519
+ justifyContent: 'center',
520
+ gap: 6,
521
+ borderRadius: 9,
522
+ backgroundColor: isActive
523
+ ? AppColors.purple
524
+ : 'transparent',
525
+ }}>
526
+ <IconComp
527
+ color={isActive ? AppColors.white : AppColors.grayText}
528
+ size={13}
529
+ />
530
+ <Text
531
+ style={{
532
+ fontFamily: AppFonts.interBold,
533
+ fontSize: 12.5,
534
+ lineHeight: 16,
535
+ color: isActive
536
+ ? AppColors.white
537
+ : AppColors.grayText,
538
+ }}>
539
+ {tab.label}
540
+ </Text>
541
+ {tab.badge && (
542
+ <View
543
+ style={{
544
+ backgroundColor: isActive
545
+ ? 'rgba(255,255,255,0.25)'
546
+ : `${AppColors.purple}18`,
547
+ paddingHorizontal: 5.5,
548
+ paddingVertical: 1,
549
+ borderRadius: 10,
550
+ }}>
551
+ <Text
552
+ style={{
553
+ fontFamily: AppFonts.interBold,
554
+ fontSize: 9.5,
555
+ color: isActive
556
+ ? AppColors.white
557
+ : AppColors.purple,
558
+ }}>
559
+ {tab.badge}
560
+ </Text>
561
+ </View>
562
+ )}
563
+ </TouchableScale>
564
+ );
565
+ })}
566
+ </ScrollView>
606
567
  </View>
607
568
 
608
569
  {settingsActiveSubTab === 'module' && (
@@ -1,4 +1,4 @@
1
- import React, {useState, useEffect, useMemo, useCallback} from 'react';
1
+ import React, {useState, useEffect, useMemo, useCallback, useRef} from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -13,6 +13,7 @@ import {
13
13
  } from 'react-native';
14
14
  import TouchableScale from '../TouchableScale';
15
15
  import CopyButton from '../CopyButton';
16
+ import globalStyles from '../../styles';
16
17
  import {AppColors} from '../../styles/AppColors';
17
18
  import {AppFonts} from '../../styles/AppFonts';
18
19
  import {useTranslation} from '../../i18n';
@@ -28,6 +29,7 @@ import {
28
29
  CircleAlertIcon,
29
30
  LayersIcon,
30
31
  ResetIcon,
32
+ ChevronIcon,
31
33
  } from '../NetworkIcons';
32
34
  import {
33
35
  fetchStorageEntries,
@@ -171,6 +173,7 @@ const StorageEntryCard = React.memo(function StorageEntryCard({
171
173
 
172
174
  export const StorageTab = React.memo(() => {
173
175
  const {t} = useTranslation();
176
+ const listRef = useRef<FlatList>(null);
174
177
  const [activeDriver, setActiveDriver] = useState<StorageDriver>('asyncStorage');
175
178
  const [activeMMKVId, setActiveMMKVId] = useState<string>('default');
176
179
  const [entries, setEntries] = useState<StorageEntry[]>([]);
@@ -555,34 +558,60 @@ export const StorageTab = React.memo(() => {
555
558
  <Text style={styles.loadingText}>Loading storage entries...</Text>
556
559
  </View>
557
560
  ) : (
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'}
566
- style={styles.scrollArea}
567
- contentContainerStyle={styles.scrollContent}
568
- showsVerticalScrollIndicator={false}
569
- ListEmptyComponent={
570
- <View style={styles.emptyContainer}>
571
- <View style={styles.emptyIconWrap}>
572
- <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
561
+ <>
562
+ <FlatList
563
+ ref={listRef}
564
+ data={filteredEntries}
565
+ keyExtractor={item => item.key}
566
+ renderItem={renderItem}
567
+ initialNumToRender={10}
568
+ maxToRenderPerBatch={10}
569
+ windowSize={5}
570
+ removeClippedSubviews={Platform.OS === 'android'}
571
+ style={styles.scrollArea}
572
+ contentContainerStyle={styles.scrollContent}
573
+ showsVerticalScrollIndicator={false}
574
+ ListEmptyComponent={
575
+ <View style={styles.emptyContainer}>
576
+ <View style={styles.emptyIconWrap}>
577
+ <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
578
+ </View>
579
+ <Text style={styles.emptyTitle}>
580
+ {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
581
+ </Text>
582
+ <Text style={styles.emptySubtitle}>
583
+ {search
584
+ ? 'Try modifying your search query'
585
+ : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
586
+ </Text>
573
587
  </View>
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>
588
+ }
589
+ ListFooterComponent={<View style={{height: 60}} />}
590
+ />
591
+
592
+ <TouchableScale
593
+ onPress={() => {
594
+ try {
595
+ listRef.current?.scrollToOffset({
596
+ offset: 0,
597
+ animated: true,
598
+ });
599
+ } catch {
600
+ try {
601
+ listRef.current?.scrollToIndex({
602
+ index: 0,
603
+ animated: true,
604
+ });
605
+ } catch {}
606
+ }
607
+ }}
608
+ hitSlop={12}
609
+ style={globalStyles.scrollTopBtn}>
610
+ <View style={{transform: [{rotate: '180deg'}]}}>
611
+ <ChevronIcon color={AppColors.white} size={18} />
582
612
  </View>
583
- }
584
- ListFooterComponent={<View style={{height: 60}} />}
585
- />
613
+ </TouchableScale>
614
+ </>
586
615
  )}
587
616
 
588
617
  {/* ── Create / Edit Key Modal ── */}
@@ -125,6 +125,8 @@ export const UpdateAvailableModal: React.FC<UpdateAvailableModalProps> = ({
125
125
 
126
126
  const installCommand = `npm install react-native-inapp-inspector@latest`;
127
127
 
128
+ if (typeof __DEV__ === 'undefined' || !__DEV__) return null;
129
+
128
130
  useEffect(() => {
129
131
  if (visible) {
130
132
  setCopied(false);
@@ -27,6 +27,7 @@ import {
27
27
  CircleAlertIcon,
28
28
  CircleXIcon,
29
29
  RepeatIcon,
30
+ PinIcon,
30
31
  } from './NetworkIcons';
31
32
  import {AppFonts} from '../styles/AppFonts';
32
33
  import styles from '../styles';
@@ -408,11 +409,47 @@ const LogCard = React.memo(function LogCard({
408
409
  </View>
409
410
  </View>
410
411
 
411
- {/* Row 3: Footer (Timestamp on Left, Response Size & Duration on Right) */}
412
+ {/* Row 3: Footer (Timestamp & Origin on Left, Response Size & Duration on Right) */}
412
413
  <View style={styles.cardFooterRow}>
413
- <View style={styles.cardDateRow}>
414
- <CalendarIcon color={AppColors.grayTextWeak} size={10} />
415
- <Text style={styles.cardDateText}>{triggeredAt}</Text>
414
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexShrink: 1, minWidth: 0}}>
415
+ <View style={styles.cardDateRow}>
416
+ <CalendarIcon color={AppColors.grayTextWeak} size={10} />
417
+ <Text style={styles.cardDateText}>{triggeredAt}</Text>
418
+ </View>
419
+
420
+ {item.caller && item.caller !== 'Unknown' && (
421
+ <View
422
+ style={[
423
+ styles.chip,
424
+ {
425
+ flexDirection: 'row',
426
+ alignItems: 'center',
427
+ gap: 3,
428
+ backgroundColor: `${AppColors.skyBlue}14`,
429
+ borderColor: `${AppColors.skyBlue}30`,
430
+ paddingHorizontal: 5,
431
+ paddingVertical: 1,
432
+ borderRadius: 4,
433
+ flexShrink: 1,
434
+ maxWidth: 160,
435
+ },
436
+ ]}>
437
+ <PinIcon color={AppColors.skyBlue} size={8.5} />
438
+ <Text
439
+ style={[
440
+ styles.chipText,
441
+ {
442
+ color: AppColors.skyBlue,
443
+ fontSize: 8.5,
444
+ fontFamily: AppFonts.interMedium,
445
+ },
446
+ ]}
447
+ numberOfLines={1}
448
+ ellipsizeMode="middle">
449
+ {item.caller}
450
+ </Text>
451
+ </View>
452
+ )}
416
453
  </View>
417
454
 
418
455
  <View style={styles.cardFooterRight}>
@@ -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.3.7';
3
+ export const LIB_VERSION = '2.3.9';
@@ -47,6 +47,8 @@ const IGNORED_URL_PATTERNS: RegExp[] = [
47
47
  /\/status(?:\?|$)/i,
48
48
  /127\.0\.0\.1:(?:8081|8082|8083|19000|19001|19002|3000|8000)/i,
49
49
  /localhost:(?:8081|8082|8083|19000|19001|19002|3000|8000)\/index\.bundle/i,
50
+ /google-analytics\.com\/mp\/collect/i,
51
+ /analytics\.google\.com/i,
50
52
  ];
51
53
 
52
54
  function shouldIgnoreUrl(url: string | undefined | null): boolean {
@@ -98,6 +98,38 @@ async function getOrCreateClientId(): Promise<string> {
98
98
  }
99
99
  }
100
100
 
101
+ function isDeviceEmulator(): boolean {
102
+ try {
103
+ if (Platform.OS === 'android') {
104
+ const c = (Platform.constants as any) || {};
105
+ const model = String(c.Model || '').toLowerCase();
106
+ const brand = String(c.Brand || '').toLowerCase();
107
+ const fingerprint = String(c.Fingerprint || '').toLowerCase();
108
+ const hardware = String(c.Hardware || '').toLowerCase();
109
+ const manufacturer = String(c.Manufacturer || '').toLowerCase();
110
+ return (
111
+ model.includes('emulator') ||
112
+ model.includes('sdk') ||
113
+ model.includes('google_sdk') ||
114
+ model.includes('vbox') ||
115
+ brand.includes('generic') ||
116
+ fingerprint.startsWith('generic') ||
117
+ hardware.includes('goldfish') ||
118
+ hardware.includes('ranchu') ||
119
+ manufacturer.includes('genymotion')
120
+ );
121
+ }
122
+ if (Platform.OS === 'ios') {
123
+ const c = (Platform.constants as any) || {};
124
+ const model = String(c.Model || '').toLowerCase();
125
+ return model.includes('simulator') || Boolean(c.isSimulator);
126
+ }
127
+ } catch {
128
+ return false;
129
+ }
130
+ return false;
131
+ }
132
+
101
133
  function getSystemMetadata(environment?: string) {
102
134
  const rnConstants = Platform.constants as any;
103
135
  const rnVersion = rnConstants?.reactNativeVersion
@@ -107,6 +139,7 @@ function getSystemMetadata(environment?: string) {
107
139
  const isHermes = Boolean(typeof (global as any).HermesInternal === 'object' && (global as any).HermesInternal !== null);
108
140
  const isFabric = Boolean((global as any)?.nativeFabricUIManager !== undefined);
109
141
  const isDev = Boolean(typeof __DEV__ !== 'undefined' && __DEV__);
142
+ const isEmulator = isDeviceEmulator();
110
143
 
111
144
  return {
112
145
  lib_version: LIB_VERSION,
@@ -116,6 +149,9 @@ function getSystemMetadata(environment?: string) {
116
149
  is_hermes: isHermes,
117
150
  is_fabric: isFabric,
118
151
  is_dev: isDev,
152
+ is_emulator: isEmulator,
153
+ device_type: isEmulator ? 'emulator' : 'real_device',
154
+ app_mode: isDev ? 'local_mode' : 'bundled_application',
119
155
  environment: environment || (isDev ? 'DEV' : 'PROD'),
120
156
  engagement_time_msec: 1000,
121
157
  };
package/src/index.tsx CHANGED
@@ -567,6 +567,11 @@ const NetworkInspector = ({
567
567
  }, [latestNpmVersion]);
568
568
 
569
569
  useEffect(() => {
570
+ // Only check for npm updates when running in local development / debug mode (__DEV__)
571
+ // Never show or check in production / bundled application
572
+ if (typeof __DEV__ === 'undefined' || !__DEV__) {
573
+ return;
574
+ }
570
575
  let cancelled = false;
571
576
  fetch('https://registry.npmjs.org/react-native-inapp-inspector/latest')
572
577
  .then(res => (res.ok ? res.json() : null))