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
@@ -92,7 +92,7 @@ export const NpmUpdateToast = () => {
92
92
  Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { });
93
93
  handleDismiss();
94
94
  };
95
- if (!showUpdateToast || !visible || !updateAvailable || !latestNpmVersion)
95
+ if (typeof __DEV__ === 'undefined' || !__DEV__ || !showUpdateToast || !visible || !updateAvailable || !latestNpmVersion)
96
96
  return null;
97
97
  const progressWidth = progressAnim.interpolate({
98
98
  inputRange: [0, 1],
@@ -5,6 +5,7 @@ import TouchableScale from '../TouchableScale';
5
5
  import CopyButton from '../CopyButton';
6
6
  import HighlightText from '../HighlightText';
7
7
  import EndOfListFooter from '../EndOfListFooter';
8
+ import FeatureUnderDevNotice from './FeatureUnderDevNotice';
8
9
  import { AppColors } from '../../styles/AppColors';
9
10
  import { AppFonts } from '../../styles/AppFonts';
10
11
  import { useTranslation } from '../../i18n';
@@ -356,6 +357,8 @@ const PerformanceTab = React.memo(() => {
356
357
  </TouchableScale>
357
358
  </View>
358
359
 
360
+ <FeatureUnderDevNotice featureName="Performance & FPS Profiler"/>
361
+
359
362
 
360
363
  <View style={perfStyles.categoryScrollWrapper}>
361
364
  <ScrollView horizontal showsHorizontalScrollIndicator={false} style={perfStyles.categoryScrollView} contentContainerStyle={perfStyles.categoryScroll}>
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useMemo, useState } from 'react';
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
2
  import { FlatList, Pressable, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
3
3
  import { useTranslation } from '../../i18n';
4
4
  import { useInspector } from './InspectorContext';
@@ -12,7 +12,7 @@ import styles from '../../styles';
12
12
  import { AppColors } from '../../styles/AppColors';
13
13
  import { AppFonts } from '../../styles/AppFonts';
14
14
  import { getActionHistory, getReduxAutoRefresh, setReduxAutoRefresh, } from '../../customHooks/reduxLogger';
15
- import { TerminalIcon, SearchIcon, ClearIcon, LayersIcon, ClockIcon, ForwardChevronIcon, SortArrowIcon, BoltIcon, AtomIcon, ScreenIcon, ListenerIcon, DocIcon, LoadingSpinnerIcon, CircleAlertIcon, TextAaIcon, HeaderPauseIcon, } from '../NetworkIcons';
15
+ import { TerminalIcon, SearchIcon, ClearIcon, LayersIcon, ClockIcon, ForwardChevronIcon, SortArrowIcon, BoltIcon, AtomIcon, ScreenIcon, ListenerIcon, DocIcon, LoadingSpinnerIcon, CircleAlertIcon, TextAaIcon, HeaderPauseIcon, ChevronIcon, } from '../NetworkIcons';
16
16
  const getOriginBadge = (originType) => {
17
17
  switch (originType) {
18
18
  case 'saga':
@@ -60,6 +60,7 @@ const getOriginBadge = (originType) => {
60
60
  const ReduxTab = React.memo(() => {
61
61
  const { t } = useTranslation();
62
62
  const { reduxState, reduxLastActionMap, reduxSearch, setReduxSearch, setSelectedReduxSlice, } = useInspector();
63
+ const listRef = useRef(null);
63
64
  const [sortMode, setSortMode] = useState('latest');
64
65
  const [isReduxPaused, setIsReduxPaused] = useState(!getReduxAutoRefresh());
65
66
  const handleTogglePause = useCallback(() => {
@@ -453,10 +454,32 @@ const ReduxTab = React.memo(() => {
453
454
  </View>
454
455
 
455
456
 
456
- <FlatList data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={reduxSearch.length > 0}/>} ListFooterComponent={filteredSlices.length > 0 ? (<EndOfListFooter count={filteredSlices.length} label="slices"/>) : null} contentContainerStyle={[
457
+ <FlatList ref={listRef} data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={reduxSearch.length > 0}/>} ListFooterComponent={filteredSlices.length > 0 ? (<EndOfListFooter count={filteredSlices.length} label="slices"/>) : null} contentContainerStyle={[
457
458
  reduxTabStyles.listContent,
458
459
  filteredSlices.length === 0 && { flexGrow: 1 },
459
460
  ]} keyboardShouldPersistTaps="handled"/>
461
+
462
+ <TouchableScale onPress={() => {
463
+ try {
464
+ listRef.current?.scrollToOffset({
465
+ offset: 0,
466
+ animated: true,
467
+ });
468
+ }
469
+ catch {
470
+ try {
471
+ listRef.current?.scrollToIndex({
472
+ index: 0,
473
+ animated: true,
474
+ });
475
+ }
476
+ catch { }
477
+ }
478
+ }} hitSlop={12} style={styles.scrollTopBtn}>
479
+ <View style={{ transform: [{ rotate: '180deg' }] }}>
480
+ <ChevronIcon color={AppColors.white} size={18}/>
481
+ </View>
482
+ </TouchableScale>
460
483
  </View>);
461
484
  });
462
485
  const reduxTabStyles = StyleSheet.create({
@@ -275,11 +275,12 @@ const SettingsPanel = () => {
275
275
  }}>
276
276
  <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 14 }} showsVerticalScrollIndicator={false}>
277
277
 
278
+
278
279
  <View style={{
279
- flexDirection: 'row',
280
280
  backgroundColor: AppColors.primaryLight,
281
- borderRadius: 12,
282
- padding: 4,
281
+ borderRadius: 13,
282
+ paddingVertical: 4,
283
+ paddingHorizontal: 4,
283
284
  borderWidth: 1,
284
285
  borderColor: AppColors.grayBorderSecondary,
285
286
  shadowColor: AppColors.black,
@@ -287,95 +288,79 @@ const SettingsPanel = () => {
287
288
  shadowRadius: 4,
288
289
  shadowOffset: { width: 0, height: 2 },
289
290
  }}>
290
- <TouchableScale accessible={true} accessibilityRole="tab" accessibilityLabel={t('settings.modulesAndTools')} accessibilityState={{ selected: settingsActiveSubTab === 'module' }} onPress={() => {
291
- animateNextLayout();
292
- setSettingsActiveSubTab('module');
293
- }} style={{
294
- flex: 1,
295
- paddingVertical: 9,
291
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
296
292
  flexDirection: 'row',
297
293
  alignItems: 'center',
298
- justifyContent: 'center',
299
294
  gap: 6,
300
- borderRadius: 9,
301
- backgroundColor: settingsActiveSubTab === 'module'
302
- ? AppColors.purple
303
- : 'transparent',
304
- }}>
305
- <LayersIcon color={settingsActiveSubTab === 'module'
306
- ? AppColors.white
307
- : AppColors.grayText} size={13}/>
308
- <Text style={{
309
- fontFamily: AppFonts.interBold,
310
- fontSize: 12.5,
311
- lineHeight: 16,
312
- color: settingsActiveSubTab === 'module'
313
- ? AppColors.white
314
- : AppColors.grayText,
315
- }}>
316
- {t('settings.modulesAndTools')}
317
- </Text>
318
- </TouchableScale>
319
-
320
- <TouchableScale accessible={true} accessibilityRole="tab" accessibilityLabel={t('settings.uiPreferences')} accessibilityState={{ selected: settingsActiveSubTab === 'ui' }} onPress={() => {
321
- animateNextLayout();
322
- setSettingsActiveSubTab('ui');
323
- }} style={{
324
- flex: 1,
325
- paddingVertical: 9,
326
- flexDirection: 'row',
327
- alignItems: 'center',
328
- justifyContent: 'center',
329
- gap: 5,
330
- borderRadius: 9,
331
- backgroundColor: settingsActiveSubTab === 'ui'
332
- ? AppColors.purple
333
- : 'transparent',
334
- }}>
335
- <ScreenIcon color={settingsActiveSubTab === 'ui'
336
- ? AppColors.white
337
- : AppColors.grayText} size={13}/>
338
- <Text numberOfLines={1} style={{
339
- fontFamily: AppFonts.interBold,
340
- fontSize: 12,
341
- lineHeight: 16,
342
- color: settingsActiveSubTab === 'ui'
343
- ? AppColors.white
344
- : AppColors.grayText,
345
- }}>
346
- {t('settings.uiPreferences')}
347
- </Text>
348
- </TouchableScale>
349
-
350
- <TouchableScale accessible={true} accessibilityRole="tab" accessibilityLabel={t('settings.ramLimits')} accessibilityState={{ selected: settingsActiveSubTab === 'limits' }} onPress={() => {
351
- animateNextLayout();
352
- setSettingsActiveSubTab('limits');
353
- }} style={{
354
- flex: 1,
355
- paddingVertical: 9,
356
- flexDirection: 'row',
357
- alignItems: 'center',
358
- justifyContent: 'center',
359
- gap: 5,
360
- borderRadius: 9,
361
- backgroundColor: settingsActiveSubTab === 'limits'
362
- ? AppColors.purple
363
- : 'transparent',
364
- }}>
365
- <BrainIcon color={settingsActiveSubTab === 'limits'
366
- ? AppColors.white
367
- : AppColors.grayText} size={13}/>
368
- <Text numberOfLines={1} style={{
369
- fontFamily: AppFonts.interBold,
370
- fontSize: 12,
371
- lineHeight: 16,
372
- color: settingsActiveSubTab === 'limits'
373
- ? AppColors.white
374
- : AppColors.grayText,
295
+ paddingHorizontal: 2,
375
296
  }}>
376
- {t('settings.ramLimits')}
377
- </Text>
378
- </TouchableScale>
297
+ {[
298
+ {
299
+ key: 'module',
300
+ label: t('settings.modulesAndTools', 'Modules & Tools'),
301
+ Icon: LayersIcon,
302
+ badge: `${allModules.filter(m => stagedTabVisibility?.[m.key] || m.key === 'apis').length}/${allModules.length}`,
303
+ },
304
+ {
305
+ key: 'ui',
306
+ label: t('settings.uiPreferences', 'UI Preferences'),
307
+ Icon: ScreenIcon,
308
+ },
309
+ {
310
+ key: 'limits',
311
+ label: t('settings.ramLimits', 'RAM & Limits'),
312
+ Icon: BrainIcon,
313
+ },
314
+ ].map(tab => {
315
+ const isActive = settingsActiveSubTab === tab.key;
316
+ const IconComp = tab.Icon;
317
+ return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="tab" accessibilityLabel={tab.label} accessibilityState={{ selected: isActive }} onPress={() => {
318
+ animateNextLayout();
319
+ setSettingsActiveSubTab(tab.key);
320
+ }} style={{
321
+ paddingVertical: 8,
322
+ paddingHorizontal: 14,
323
+ flexDirection: 'row',
324
+ alignItems: 'center',
325
+ justifyContent: 'center',
326
+ gap: 6,
327
+ borderRadius: 9,
328
+ backgroundColor: isActive
329
+ ? AppColors.purple
330
+ : 'transparent',
331
+ }}>
332
+ <IconComp color={isActive ? AppColors.white : AppColors.grayText} size={13}/>
333
+ <Text style={{
334
+ fontFamily: AppFonts.interBold,
335
+ fontSize: 12.5,
336
+ lineHeight: 16,
337
+ color: isActive
338
+ ? AppColors.white
339
+ : AppColors.grayText,
340
+ }}>
341
+ {tab.label}
342
+ </Text>
343
+ {tab.badge && (<View style={{
344
+ backgroundColor: isActive
345
+ ? 'rgba(255,255,255,0.25)'
346
+ : `${AppColors.purple}18`,
347
+ paddingHorizontal: 5.5,
348
+ paddingVertical: 1,
349
+ borderRadius: 10,
350
+ }}>
351
+ <Text style={{
352
+ fontFamily: AppFonts.interBold,
353
+ fontSize: 9.5,
354
+ color: isActive
355
+ ? AppColors.white
356
+ : AppColors.purple,
357
+ }}>
358
+ {tab.badge}
359
+ </Text>
360
+ </View>)}
361
+ </TouchableScale>);
362
+ })}
363
+ </ScrollView>
379
364
  </View>
380
365
 
381
366
  {settingsActiveSubTab === 'module' && (<View style={{ gap: 12 }}>
@@ -1,10 +1,11 @@
1
- import React, { useState, useEffect, useMemo, useCallback } from 'react';
1
+ import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
2
2
  import { View, Text, ScrollView, FlatList, TextInput, Modal, Alert, StyleSheet, ActivityIndicator, Platform, } from 'react-native';
3
3
  import TouchableScale from '../TouchableScale';
4
+ import globalStyles from '../../styles';
4
5
  import { AppColors } from '../../styles/AppColors';
5
6
  import { AppFonts } from '../../styles/AppFonts';
6
7
  import { useTranslation } from '../../i18n';
7
- import { DatabaseIcon, SearchIcon, ClearIcon, PlusIcon, PencilIcon, TrashIcon, CopyIcon, CheckIcon, LayersIcon, ResetIcon, } from '../NetworkIcons';
8
+ import { DatabaseIcon, SearchIcon, ClearIcon, PlusIcon, PencilIcon, TrashIcon, CopyIcon, CheckIcon, LayersIcon, ResetIcon, ChevronIcon, } from '../NetworkIcons';
8
9
  import { fetchStorageEntries, setStorageEntry, removeStorageEntry, clearStorageDriver, isAsyncStorageConnected, isMMKVConnected, getRegisteredMMKVInstanceIds, subscribeToStorageChanges, } from '../../customHooks/storageInspector';
9
10
  import { copyToClipboard } from '../../helpers';
10
11
  import { showToast } from '../../helpers/toast';
@@ -95,6 +96,7 @@ const StorageEntryCard = React.memo(function StorageEntryCard({ entry, isExpande
95
96
  });
96
97
  export const StorageTab = React.memo(() => {
97
98
  const { t } = useTranslation();
99
+ const listRef = useRef(null);
98
100
  const [activeDriver, setActiveDriver] = useState('asyncStorage');
99
101
  const [activeMMKVId, setActiveMMKVId] = useState('default');
100
102
  const [entries, setEntries] = useState([]);
@@ -360,19 +362,43 @@ export const StorageTab = React.memo(() => {
360
362
  {isLoading ? (<View style={styles.loadingContainer}>
361
363
  <ActivityIndicator size="small" color={AppColors.purple}/>
362
364
  <Text style={styles.loadingText}>Loading storage entries...</Text>
363
- </View>) : (<FlatList data={filteredEntries} keyExtractor={item => item.key} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={Platform.OS === 'android'} style={styles.scrollArea} contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false} ListEmptyComponent={<View style={styles.emptyContainer}>
364
- <View style={styles.emptyIconWrap}>
365
- <DatabaseIcon size={28} color={AppColors.grayTextWeak}/>
366
- </View>
367
- <Text style={styles.emptyTitle}>
368
- {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
369
- </Text>
370
- <Text style={styles.emptySubtitle}>
371
- {search
365
+ </View>) : (<>
366
+ <FlatList ref={listRef} data={filteredEntries} keyExtractor={item => item.key} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={Platform.OS === 'android'} style={styles.scrollArea} contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false} ListEmptyComponent={<View style={styles.emptyContainer}>
367
+ <View style={styles.emptyIconWrap}>
368
+ <DatabaseIcon size={28} color={AppColors.grayTextWeak}/>
369
+ </View>
370
+ <Text style={styles.emptyTitle}>
371
+ {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
372
+ </Text>
373
+ <Text style={styles.emptySubtitle}>
374
+ {search
372
375
  ? 'Try modifying your search query'
373
376
  : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
374
- </Text>
375
- </View>} ListFooterComponent={<View style={{ height: 60 }}/>}/>)}
377
+ </Text>
378
+ </View>} ListFooterComponent={<View style={{ height: 60 }}/>}/>
379
+
380
+ <TouchableScale onPress={() => {
381
+ try {
382
+ listRef.current?.scrollToOffset({
383
+ offset: 0,
384
+ animated: true,
385
+ });
386
+ }
387
+ catch {
388
+ try {
389
+ listRef.current?.scrollToIndex({
390
+ index: 0,
391
+ animated: true,
392
+ });
393
+ }
394
+ catch { }
395
+ }
396
+ }} hitSlop={12} style={globalStyles.scrollTopBtn}>
397
+ <View style={{ transform: [{ rotate: '180deg' }] }}>
398
+ <ChevronIcon color={AppColors.white} size={18}/>
399
+ </View>
400
+ </TouchableScale>
401
+ </>)}
376
402
 
377
403
 
378
404
  <Modal visible={modalVisible} transparent animationType="fade" onRequestClose={() => setModalVisible(false)}>
@@ -32,6 +32,8 @@ export const UpdateAvailableModal = ({ visible, latestVersion, onClose, }) => {
32
32
  const fadeAnim = useState(new Animated.Value(0))[0];
33
33
  const scaleAnim = useState(new Animated.Value(0.92))[0];
34
34
  const installCommand = `npm install react-native-inapp-inspector@latest`;
35
+ if (typeof __DEV__ === 'undefined' || !__DEV__)
36
+ return null;
35
37
  useEffect(() => {
36
38
  if (visible) {
37
39
  setCopied(false);
@@ -4,7 +4,7 @@ import Svg, { Path } from 'react-native-svg';
4
4
  import { AppColors } from '../styles/AppColors';
5
5
  import { METHOD_COLORS } from '../constants';
6
6
  import { getStatusColor, getDurationColor, formatDateTime, getSize, } from '../helpers';
7
- import { CalendarIcon, ClockIcon, SizeIcon, GlobeIcon, CircleCheckIcon, CircleAlertIcon, CircleXIcon, RepeatIcon, } from './NetworkIcons';
7
+ import { CalendarIcon, ClockIcon, SizeIcon, GlobeIcon, CircleCheckIcon, CircleAlertIcon, CircleXIcon, RepeatIcon, PinIcon, } from './NetworkIcons';
8
8
  import { AppFonts } from '../styles/AppFonts';
9
9
  import styles from '../styles';
10
10
  import HighlightText from './HighlightText';
@@ -310,9 +310,39 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
310
310
 
311
311
 
312
312
  <View style={styles.cardFooterRow}>
313
- <View style={styles.cardDateRow}>
314
- <CalendarIcon color={AppColors.grayTextWeak} size={10}/>
315
- <Text style={styles.cardDateText}>{triggeredAt}</Text>
313
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexShrink: 1, minWidth: 0 }}>
314
+ <View style={styles.cardDateRow}>
315
+ <CalendarIcon color={AppColors.grayTextWeak} size={10}/>
316
+ <Text style={styles.cardDateText}>{triggeredAt}</Text>
317
+ </View>
318
+
319
+ {item.caller && item.caller !== 'Unknown' && (<View style={[
320
+ styles.chip,
321
+ {
322
+ flexDirection: 'row',
323
+ alignItems: 'center',
324
+ gap: 3,
325
+ backgroundColor: `${AppColors.skyBlue}14`,
326
+ borderColor: `${AppColors.skyBlue}30`,
327
+ paddingHorizontal: 5,
328
+ paddingVertical: 1,
329
+ borderRadius: 4,
330
+ flexShrink: 1,
331
+ maxWidth: 160,
332
+ },
333
+ ]}>
334
+ <PinIcon color={AppColors.skyBlue} size={8.5}/>
335
+ <Text style={[
336
+ styles.chipText,
337
+ {
338
+ color: AppColors.skyBlue,
339
+ fontSize: 8.5,
340
+ fontFamily: AppFonts.interMedium,
341
+ },
342
+ ]} numberOfLines={1} ellipsizeMode="middle">
343
+ {item.caller}
344
+ </Text>
345
+ </View>)}
316
346
  </View>
317
347
 
318
348
  <View style={styles.cardFooterRight}>
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "2.3.7";
1
+ export declare const LIB_VERSION = "2.3.9";
@@ -1 +1 @@
1
- export const LIB_VERSION = '2.3.7';
1
+ export const LIB_VERSION = '2.3.9';
@@ -22,6 +22,8 @@ const IGNORED_URL_PATTERNS = [
22
22
  /\/status(?:\?|$)/i,
23
23
  /127\.0\.0\.1:(?:8081|8082|8083|19000|19001|19002|3000|8000)/i,
24
24
  /localhost:(?:8081|8082|8083|19000|19001|19002|3000|8000)\/index\.bundle/i,
25
+ /google-analytics\.com\/mp\/collect/i,
26
+ /analytics\.google\.com/i,
25
27
  ];
26
28
  function shouldIgnoreUrl(url) {
27
29
  if (!url)
@@ -79,6 +79,36 @@ async function getOrCreateClientId() {
79
79
  return `${Math.floor(100000000 + Math.random() * 900000000)}.${Math.floor(1000000000 + Math.random() * 9000000000)}`;
80
80
  }
81
81
  }
82
+ function isDeviceEmulator() {
83
+ try {
84
+ if (Platform.OS === 'android') {
85
+ const c = Platform.constants || {};
86
+ const model = String(c.Model || '').toLowerCase();
87
+ const brand = String(c.Brand || '').toLowerCase();
88
+ const fingerprint = String(c.Fingerprint || '').toLowerCase();
89
+ const hardware = String(c.Hardware || '').toLowerCase();
90
+ const manufacturer = String(c.Manufacturer || '').toLowerCase();
91
+ return (model.includes('emulator') ||
92
+ model.includes('sdk') ||
93
+ model.includes('google_sdk') ||
94
+ model.includes('vbox') ||
95
+ brand.includes('generic') ||
96
+ fingerprint.startsWith('generic') ||
97
+ hardware.includes('goldfish') ||
98
+ hardware.includes('ranchu') ||
99
+ manufacturer.includes('genymotion'));
100
+ }
101
+ if (Platform.OS === 'ios') {
102
+ const c = Platform.constants || {};
103
+ const model = String(c.Model || '').toLowerCase();
104
+ return model.includes('simulator') || Boolean(c.isSimulator);
105
+ }
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ return false;
111
+ }
82
112
  function getSystemMetadata(environment) {
83
113
  const rnConstants = Platform.constants;
84
114
  const rnVersion = rnConstants?.reactNativeVersion
@@ -87,6 +117,7 @@ function getSystemMetadata(environment) {
87
117
  const isHermes = Boolean(typeof global.HermesInternal === 'object' && global.HermesInternal !== null);
88
118
  const isFabric = Boolean(global?.nativeFabricUIManager !== undefined);
89
119
  const isDev = Boolean(typeof __DEV__ !== 'undefined' && __DEV__);
120
+ const isEmulator = isDeviceEmulator();
90
121
  return {
91
122
  lib_version: LIB_VERSION,
92
123
  platform: Platform.OS,
@@ -95,6 +126,9 @@ function getSystemMetadata(environment) {
95
126
  is_hermes: isHermes,
96
127
  is_fabric: isFabric,
97
128
  is_dev: isDev,
129
+ is_emulator: isEmulator,
130
+ device_type: isEmulator ? 'emulator' : 'real_device',
131
+ app_mode: isDev ? 'local_mode' : 'bundled_application',
98
132
  environment: environment || (isDev ? 'DEV' : 'PROD'),
99
133
  engagement_time_msec: 1000,
100
134
  };
package/dist/esm/index.js CHANGED
@@ -362,6 +362,9 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, appIcon, env
362
362
  return false;
363
363
  }, [latestNpmVersion]);
364
364
  useEffect(() => {
365
+ if (typeof __DEV__ === 'undefined' || !__DEV__) {
366
+ return;
367
+ }
365
368
  let cancelled = false;
366
369
  fetch('https://registry.npmjs.org/react-native-inapp-inspector/latest')
367
370
  .then(res => (res.ok ? res.json() : null))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-inapp-inspector",
3
- "version": "2.3.7",
3
+ "version": "2.3.9",
4
4
  "description": "The zero-config, all-in-one in-app debugger for React Native & Expo. Inspect Network (fetch/axios), Console logs, Stack Traces, Redux State, Firebase Analytics, and JS Bundle size directly on device.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,4 +1,4 @@
1
- import React, {useCallback, useState, useMemo, useEffect} from 'react';
1
+ import React, {useCallback, useState, useMemo, useEffect, useRef} from 'react';
2
2
  import {
3
3
  FlatList,
4
4
  Pressable,
@@ -55,6 +55,7 @@ import {
55
55
  PinIcon,
56
56
  MoneyIcon,
57
57
  UserCheckIcon,
58
+ ChevronIcon,
58
59
  } from '../NetworkIcons';
59
60
 
60
61
  const AnalyticsHeader = React.memo(() => {
@@ -1090,6 +1091,7 @@ const AnalyticsTab = React.memo(() => {
1090
1091
  isAnalyticsFilterApplied,
1091
1092
  } = useInspector();
1092
1093
 
1094
+ const listRef = useRef<FlatList>(null);
1093
1095
  const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
1094
1096
 
1095
1097
  const keyExtractor = useCallback(
@@ -1305,49 +1307,75 @@ const AnalyticsTab = React.memo(() => {
1305
1307
  style={{flex: 1}}
1306
1308
  onLayout={() => setIsAnalyticsLayoutReady(true)}>
1307
1309
  {isAnalyticsLayoutReady ? (
1308
- <FlatList
1309
- data={filteredAnalyticsEvents}
1310
- keyExtractor={keyExtractor}
1311
- ListHeaderComponent={AnalyticsHeader}
1312
- renderItem={renderItem}
1313
- initialNumToRender={12}
1314
- maxToRenderPerBatch={8}
1315
- windowSize={5}
1316
- removeClippedSubviews={true}
1317
- renderToHardwareTextureAndroid={true}
1318
- ListEmptyComponent={
1319
- <EmptyState
1320
- isSearch={analyticsSearch.length > 0}
1321
- searchQuery={analyticsSearch}
1322
- customTitle={
1323
- analyticsSearch.length > 0
1324
- ? 'No matching analytics events'
1325
- : 'No analytics events yet'
1326
- }
1327
- customSub={
1328
- analyticsSearch.length > 0
1329
- ? `No events matched "${analyticsSearch}"`
1330
- : 'Call setupAnalyticsLogger(analytics()) at app start.'
1331
- }
1332
- onClearSearch={() => setAnalyticsSearch('')}
1333
- />
1334
- }
1335
- ListFooterComponent={
1336
- filteredAnalyticsEvents.length > 0 ? (
1337
- <EndOfListFooter
1338
- count={filteredAnalyticsEvents.length}
1339
- label="events"
1310
+ <>
1311
+ <FlatList
1312
+ ref={listRef}
1313
+ data={filteredAnalyticsEvents}
1314
+ keyExtractor={keyExtractor}
1315
+ ListHeaderComponent={AnalyticsHeader}
1316
+ renderItem={renderItem}
1317
+ initialNumToRender={12}
1318
+ maxToRenderPerBatch={8}
1319
+ windowSize={5}
1320
+ removeClippedSubviews={true}
1321
+ renderToHardwareTextureAndroid={true}
1322
+ ListEmptyComponent={
1323
+ <EmptyState
1324
+ isSearch={analyticsSearch.length > 0}
1325
+ searchQuery={analyticsSearch}
1326
+ customTitle={
1327
+ analyticsSearch.length > 0
1328
+ ? 'No matching analytics events'
1329
+ : 'No analytics events yet'
1330
+ }
1331
+ customSub={
1332
+ analyticsSearch.length > 0
1333
+ ? `No events matched "${analyticsSearch}"`
1334
+ : 'Call setupAnalyticsLogger(analytics()) at app start.'
1335
+ }
1336
+ onClearSearch={() => setAnalyticsSearch('')}
1340
1337
  />
1341
- ) : null
1342
- }
1343
- contentContainerStyle={[
1344
- styles.listContent,
1345
- filteredAnalyticsEvents.length === 0 && {
1346
- flexGrow: 1,
1347
- },
1348
- ]}
1349
- keyboardShouldPersistTaps="handled"
1350
- />
1338
+ }
1339
+ ListFooterComponent={
1340
+ filteredAnalyticsEvents.length > 0 ? (
1341
+ <EndOfListFooter
1342
+ count={filteredAnalyticsEvents.length}
1343
+ label="events"
1344
+ />
1345
+ ) : null
1346
+ }
1347
+ contentContainerStyle={[
1348
+ styles.listContent,
1349
+ filteredAnalyticsEvents.length === 0 && {
1350
+ flexGrow: 1,
1351
+ },
1352
+ ]}
1353
+ keyboardShouldPersistTaps="handled"
1354
+ />
1355
+
1356
+ <TouchableScale
1357
+ onPress={() => {
1358
+ try {
1359
+ listRef.current?.scrollToOffset({
1360
+ offset: 0,
1361
+ animated: true,
1362
+ });
1363
+ } catch {
1364
+ try {
1365
+ listRef.current?.scrollToIndex({
1366
+ index: 0,
1367
+ animated: true,
1368
+ });
1369
+ } catch {}
1370
+ }
1371
+ }}
1372
+ hitSlop={12}
1373
+ style={styles.scrollTopBtn}>
1374
+ <View style={{transform: [{rotate: '180deg'}]}}>
1375
+ <ChevronIcon color={AppColors.white} size={18} />
1376
+ </View>
1377
+ </TouchableScale>
1378
+ </>
1351
1379
  ) : null}
1352
1380
  </View>
1353
1381
  </>
@@ -19,6 +19,7 @@ import HighlightText from '../HighlightText';
19
19
  import SegmentedTabs from '../SegmentedTabs';
20
20
  import AnimatedEntrance from '../AnimatedEntrance';
21
21
  import EndOfListFooter from '../EndOfListFooter';
22
+ import FeatureUnderDevNotice from './FeatureUnderDevNotice';
22
23
  import {AppColors} from '../../styles/AppColors';
23
24
  import {AppFonts} from '../../styles/AppFonts';
24
25
  import styles from '../../styles';
@@ -1400,6 +1401,8 @@ const BundleTab = React.memo(() => {
1400
1401
  </TouchableScale>
1401
1402
  </View>
1402
1403
 
1404
+ <FeatureUnderDevNotice featureName="Metro Bundle Analyzer" />
1405
+
1403
1406
  {/* ══════════════════════════════════════════════════════════════════════ */}
1404
1407
  {/* ── 1. TAB: OVERVIEW & TREEMAP ───────────────────────────────────────── */}
1405
1408
  {/* ══════════════════════════════════════════════════════════════════════ */}