react-native-inapp-inspector 1.1.26 → 1.1.28

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 (67) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +34 -0
  3. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  4. package/dist/commonjs/components/ErrorBoundary.js +530 -100
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +176 -126
  6. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
  7. package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
  8. package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
  9. package/dist/commonjs/components/Inspector/NetworkDetail.js +41 -0
  10. package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
  13. package/dist/commonjs/components/Inspector/TabBar.js +13 -1
  14. package/dist/commonjs/components/LogCard.js +75 -34
  15. package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
  16. package/dist/commonjs/constants/version.d.ts +1 -1
  17. package/dist/commonjs/constants/version.js +1 -1
  18. package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
  19. package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
  20. package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
  21. package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
  22. package/dist/commonjs/customHooks/networkLogger.js +16 -0
  23. package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
  24. package/dist/commonjs/customHooks/performanceTracker.js +546 -392
  25. package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
  26. package/dist/commonjs/customHooks/reduxLogger.js +3 -1
  27. package/dist/commonjs/helpers/index.js +27 -3
  28. package/dist/commonjs/i18n/locales/en.json +225 -4
  29. package/dist/commonjs/index.d.ts +1 -1
  30. package/dist/commonjs/index.js +36 -2
  31. package/dist/commonjs/styles/index.d.ts +3 -1
  32. package/dist/commonjs/styles/index.js +8 -6
  33. package/dist/commonjs/types/interfaces.d.ts +3 -0
  34. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  35. package/dist/esm/components/ErrorBoundary.js +498 -101
  36. package/dist/esm/components/Inspector/AnalyticsTab.js +177 -127
  37. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
  38. package/dist/esm/components/Inspector/BundleTab.js +145 -23
  39. package/dist/esm/components/Inspector/CrashTab.js +0 -8
  40. package/dist/esm/components/Inspector/NetworkDetail.js +41 -0
  41. package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
  42. package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
  43. package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
  44. package/dist/esm/components/Inspector/TabBar.js +13 -1
  45. package/dist/esm/components/LogCard.js +75 -34
  46. package/dist/esm/components/NetworkIcons.d.ts +112 -102
  47. package/dist/esm/constants/version.d.ts +1 -1
  48. package/dist/esm/constants/version.js +1 -1
  49. package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
  50. package/dist/esm/customHooks/analyticsLogger.js +6 -0
  51. package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
  52. package/dist/esm/customHooks/networkLogger.d.ts +1 -0
  53. package/dist/esm/customHooks/networkLogger.js +16 -0
  54. package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
  55. package/dist/esm/customHooks/performanceTracker.js +531 -391
  56. package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
  57. package/dist/esm/customHooks/reduxLogger.js +1 -0
  58. package/dist/esm/helpers/index.js +27 -3
  59. package/dist/esm/i18n/locales/en.json +225 -4
  60. package/dist/esm/index.d.ts +1 -1
  61. package/dist/esm/index.js +24 -3
  62. package/dist/esm/styles/index.d.ts +3 -1
  63. package/dist/esm/styles/index.js +8 -6
  64. package/dist/esm/types/interfaces.d.ts +3 -0
  65. package/ios/NetworkInspectorModule.h +2 -0
  66. package/ios/NetworkInspectorModule.m +10 -0
  67. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import React, { useState, useEffect, useMemo, useCallback } from 'react';
2
- import { ActivityIndicator, Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
2
+ import { ActivityIndicator, Image, Linking, Platform, Pressable, ScrollView, Share, StyleSheet, Text, TextInput, View, } from 'react-native';
3
3
  import { useTranslation, t } from '../../i18n';
4
4
  import TouchableScale from '../TouchableScale';
5
5
  import CopyButton from '../CopyButton';
6
6
  import HighlightText from '../HighlightText';
7
7
  import { AppColors } from '../../styles/AppColors';
8
8
  import { AppFonts } from '../../styles/AppFonts';
9
- import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, TimelineIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, RefreshCcwIcon, ExternalLinkIcon, } from '../NetworkIcons';
9
+ import { copyToClipboard } from '../../helpers';
10
+ import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, TimelineIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, RefreshCcwIcon, ExternalLinkIcon, DownloadIcon, } from '../NetworkIcons';
10
11
  import Svg, { Path, Rect, Circle, Ellipse, G, Defs, LinearGradient, Stop, } from 'react-native-svg';
11
12
  import { analyzeHostAppBundle, getCachedBundleAnalysis, } from '../../customHooks/bundleAnalyzer';
12
13
  // ─── High-Fidelity Vector Package & NPM Logos ────────────────────────────────
@@ -304,7 +305,72 @@ function buildBundleFileTree(files) {
304
305
  }
305
306
  return convertMapToNodes(rootMap);
306
307
  }
307
- const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, }) => {
308
+ export const downloadBundleFile = async (file, scriptURL) => {
309
+ let content = '';
310
+ // 1. Attempt to fetch real source code from Metro dev server if available
311
+ try {
312
+ let devServerOrigin = 'http://localhost:8081';
313
+ if (scriptURL && scriptURL.startsWith('http')) {
314
+ const match = scriptURL.match(/^(https?:\/\/[^/]+)/);
315
+ if (match) {
316
+ devServerOrigin = match[1];
317
+ }
318
+ }
319
+ const cleanPath = file.path.replace(/^\/+/, '');
320
+ const url = `${devServerOrigin}/${cleanPath}`;
321
+ const res = await fetch(url);
322
+ if (res.ok) {
323
+ const text = await res.text();
324
+ if (text && text.length > 0) {
325
+ content = text;
326
+ }
327
+ }
328
+ }
329
+ catch { }
330
+ // 2. If dev server direct fetch is unavailable, provide structured module descriptor with full path
331
+ if (!content) {
332
+ content = `// ─────────────────────────────────────────────────────────────
333
+ // File: ${file.name}
334
+ // Path: ${file.path}
335
+ // Category: ${file.category.toUpperCase()}
336
+ // Size: ${file.sizeKb} KB
337
+ // Status: ${file.status === 'optimal' ? 'Active in Dependency Graph' : 'Not consumed / Static'}
338
+ // Meta: ${file.meta}
339
+ // ─────────────────────────────────────────────────────────────
340
+
341
+ export default {
342
+ id: ${JSON.stringify(file.id)},
343
+ name: ${JSON.stringify(file.name)},
344
+ path: ${JSON.stringify(file.path)},
345
+ category: ${JSON.stringify(file.category)},
346
+ sizeKb: ${file.sizeKb},
347
+ meta: ${JSON.stringify(file.meta)},
348
+ status: ${JSON.stringify(file.status || 'optimal')},
349
+ };
350
+ `;
351
+ }
352
+ // 3. Trigger native Share / Download sheet
353
+ try {
354
+ await Share.share(Platform.OS === 'ios'
355
+ ? {
356
+ title: file.name,
357
+ message: content,
358
+ }
359
+ : {
360
+ title: `Download ${file.name}`,
361
+ message: content,
362
+ }, {
363
+ dialogTitle: `Download / Save ${file.name}`,
364
+ });
365
+ }
366
+ catch { }
367
+ // 4. Also copy directly to clipboard for quick paste in VS Code or external editor
368
+ try {
369
+ copyToClipboard(content, file.name);
370
+ }
371
+ catch { }
372
+ };
373
+ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, onDownload, }) => {
308
374
  const isExpanded = search ? true : !!expandedFolders[node.fullPath];
309
375
  const isCollapsed = !isExpanded;
310
376
  const indentLeft = level * 16 + 10;
@@ -342,7 +408,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
342
408
  </TouchableScale>
343
409
 
344
410
  {!isCollapsed && (<View style={bundleStyles.treeChildrenContainer}>
345
- {node.children.map((child, cIdx) => (<BundleTreeNodeView key={child.id} node={child} level={level + 1} search={search} totalBundleKb={totalBundleKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={cIdx === node.children.length - 1}/>))}
411
+ {node.children.map((child, cIdx) => (<BundleTreeNodeView key={child.id} node={child} level={level + 1} search={search} totalBundleKb={totalBundleKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={cIdx === node.children.length - 1} onDownload={onDownload}/>))}
346
412
  </View>)}
347
413
  </View>);
348
414
  }
@@ -383,7 +449,13 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
383
449
  <Text style={bundleStyles.treeFilePctText}>{pctOfTotal}%</Text>
384
450
  </View>
385
451
 
386
- <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
452
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
453
+ {onDownload && (<TouchableScale onPress={() => onDownload(file)} style={bundleStyles.treeActionBtn} hitSlop={8}>
454
+ <DownloadIcon size={13} color={AppColors.sky500}/>
455
+ </TouchableScale>)}
456
+
457
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
458
+ </View>
387
459
  </View>
388
460
  </View>);
389
461
  };
@@ -560,12 +632,28 @@ const BundleTab = React.memo(() => {
560
632
  const filteredTotalKb = useMemo(() => {
561
633
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
562
634
  }, [filteredFiles]);
563
- // Tree View State & Node Generation (Collapsed by default)
635
+ // Tree View State & Node Generation (Top folders expanded by default)
564
636
  const [filesViewMode, setFilesViewMode] = useState('tree');
565
637
  const [expandedFolders, setExpandedFolders] = useState({});
566
638
  const fileTreeNodes = useMemo(() => {
567
639
  return buildBundleFileTree(filteredFiles);
568
640
  }, [filteredFiles]);
641
+ useEffect(() => {
642
+ if (fileTreeNodes.length > 0 && Object.keys(expandedFolders).length === 0) {
643
+ const initial = {};
644
+ fileTreeNodes.forEach(node => {
645
+ if (node.isFolder) {
646
+ initial[node.fullPath] = true;
647
+ node.children.forEach(child => {
648
+ if (child.isFolder) {
649
+ initial[child.fullPath] = true;
650
+ }
651
+ });
652
+ }
653
+ });
654
+ setExpandedFolders(initial);
655
+ }
656
+ }, [fileTreeNodes]);
569
657
  const toggleFolder = useCallback((path) => {
570
658
  setExpandedFolders(prev => ({
571
659
  ...prev,
@@ -1318,7 +1406,7 @@ const BundleTab = React.memo(() => {
1318
1406
 
1319
1407
  {/* ── MODE 1: DIRECTORY TREE VIEW ── */}
1320
1408
  {filesViewMode === 'tree' ? (<View style={bundleStyles.treeRootCard}>
1321
- {fileTreeNodes.map((node, nIdx) => (<BundleTreeNodeView key={node.id} node={node} level={0} search={search} totalBundleKb={summary.totalKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={nIdx === fileTreeNodes.length - 1}/>))}
1409
+ {fileTreeNodes.map((node, nIdx) => (<BundleTreeNodeView key={node.id} node={node} level={0} search={search} totalBundleKb={summary.totalKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={nIdx === fileTreeNodes.length - 1} onDownload={f => downloadBundleFile(f, analysis?.scriptURL)}/>))}
1322
1410
  </View>) : (
1323
1411
  /* ── MODE 2: FLAT LIST VIEW ── */
1324
1412
  filteredFiles.map((file, index) => {
@@ -1354,6 +1442,9 @@ const BundleTab = React.memo(() => {
1354
1442
  </Text>
1355
1443
  <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
1356
1444
  </View>
1445
+ <TouchableScale onPress={() => downloadBundleFile(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1446
+ <DownloadIcon size={14} color={AppColors.sky500}/>
1447
+ </TouchableScale>
1357
1448
  <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
1358
1449
  </View>
1359
1450
  </View>
@@ -1372,21 +1463,25 @@ const BundleTab = React.memo(() => {
1372
1463
  <Text style={bundleStyles.fileMetaText}>{file.meta}</Text>
1373
1464
  {file.advice && (<View style={[
1374
1465
  bundleStyles.adviceBadge,
1375
- file.status === 'warning' && bundleStyles.adviceWarning,
1376
- file.status === 'optimal' && bundleStyles.adviceOptimal,
1466
+ file.status === 'warning' &&
1467
+ bundleStyles.adviceWarning,
1468
+ file.status === 'optimal' &&
1469
+ bundleStyles.adviceOptimal,
1377
1470
  isUnused && bundleStyles.adviceUnused,
1378
1471
  ]}>
1379
- <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1380
1472
  {isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
1381
1473
  <Text style={[
1382
1474
  bundleStyles.adviceText,
1383
- file.status === 'warning' && { color: AppColors.amber800Warm },
1384
- file.status === 'optimal' && { color: AppColors.emerald700 },
1475
+ file.status === 'warning' && {
1476
+ color: AppColors.amber800Warm,
1477
+ },
1478
+ file.status === 'optimal' && {
1479
+ color: AppColors.emerald700,
1480
+ },
1385
1481
  isUnused && { color: AppColors.red600 },
1386
1482
  ]}>
1387
1483
  {file.advice}
1388
1484
  </Text>
1389
- </View>
1390
1485
  </View>)}
1391
1486
  </View>
1392
1487
  </View>);
@@ -1617,6 +1712,9 @@ const BundleTab = React.memo(() => {
1617
1712
  {t('bundle.fileSizeKb', { size: file.sizeKb })}
1618
1713
  </Text>
1619
1714
  </View>
1715
+ <TouchableScale onPress={() => downloadBundleFile(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1716
+ <DownloadIcon size={14} color={AppColors.sky500}/>
1717
+ </TouchableScale>
1620
1718
  <CopyButton value={() => file} label={t('bundle.mediaItemJson')}/>
1621
1719
  </View>
1622
1720
  </View>
@@ -1628,16 +1726,18 @@ const BundleTab = React.memo(() => {
1628
1726
  file.status === 'warning' && bundleStyles.adviceWarning,
1629
1727
  file.status === 'optimal' && bundleStyles.adviceOptimal,
1630
1728
  ]}>
1631
- <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1632
- {file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
1633
- <Text style={[
1729
+ {file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
1730
+ <Text style={[
1634
1731
  bundleStyles.adviceText,
1635
- file.status === 'warning' && { color: AppColors.amber800Warm },
1636
- file.status === 'optimal' && { color: AppColors.emerald700 },
1732
+ file.status === 'warning' && {
1733
+ color: AppColors.amber800Warm,
1734
+ },
1735
+ file.status === 'optimal' && {
1736
+ color: AppColors.emerald700,
1737
+ },
1637
1738
  ]}>
1638
- {file.advice}
1639
- </Text>
1640
- </View>
1739
+ {file.advice}
1740
+ </Text>
1641
1741
  </View>)}
1642
1742
  </View>
1643
1743
  </View>))}
@@ -2408,6 +2508,24 @@ const bundleStyles = StyleSheet.create({
2408
2508
  fileSizeBox: {
2409
2509
  alignItems: 'flex-end',
2410
2510
  },
2511
+ downloadFileBtn: {
2512
+ padding: 6,
2513
+ borderRadius: 6,
2514
+ backgroundColor: `${AppColors.sky500}14`,
2515
+ alignItems: 'center',
2516
+ justifyContent: 'center',
2517
+ borderWidth: 1,
2518
+ borderColor: `${AppColors.sky500}28`,
2519
+ },
2520
+ treeActionBtn: {
2521
+ padding: 4,
2522
+ borderRadius: 4,
2523
+ backgroundColor: `${AppColors.sky500}14`,
2524
+ alignItems: 'center',
2525
+ justifyContent: 'center',
2526
+ borderWidth: 1,
2527
+ borderColor: `${AppColors.sky500}28`,
2528
+ },
2411
2529
  fileSizeKb: {
2412
2530
  fontFamily: AppFonts.interBold,
2413
2531
  fontSize: 12,
@@ -2579,10 +2697,13 @@ const bundleStyles = StyleSheet.create({
2579
2697
  color: AppColors.amber800Warm,
2580
2698
  },
2581
2699
  adviceBadge: {
2700
+ flexDirection: 'row',
2701
+ alignItems: 'center',
2582
2702
  paddingHorizontal: 8,
2583
- paddingVertical: 4,
2703
+ paddingVertical: 5,
2584
2704
  borderRadius: 6,
2585
- marginTop: 2,
2705
+ marginTop: 4,
2706
+ gap: 6,
2586
2707
  },
2587
2708
  adviceWarning: {
2588
2709
  backgroundColor: AppColors.amber100,
@@ -2598,6 +2719,7 @@ const bundleStyles = StyleSheet.create({
2598
2719
  fontFamily: AppFonts.interMedium,
2599
2720
  fontSize: 10.5,
2600
2721
  lineHeight: 14,
2722
+ flex: 1,
2601
2723
  },
2602
2724
  impactBadge: {
2603
2725
  paddingHorizontal: 6,
@@ -34,14 +34,6 @@ const CrashTab = React.memo(() => {
34
34
  const [crashFilters, setCrashFilters] = useState(DEFAULT_CRASH_FILTERS);
35
35
  const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
36
36
  const [detailSubTab, setDetailSubTab] = useState('stack');
37
- // Auto-open the latest crash detail on first visit if crashes exist
38
- const hasAutoOpened = React.useRef(false);
39
- React.useEffect(() => {
40
- if (!hasAutoOpened.current && !selectedCrash && crashRecords.length > 0) {
41
- setSelectedCrash(crashRecords[0]);
42
- hasAutoOpened.current = true;
43
- }
44
- }, [crashRecords, selectedCrash, setSelectedCrash]);
45
37
  // Calculate statistics
46
38
  const stats = useMemo(() => {
47
39
  const total = crashRecords.length;
@@ -97,6 +97,47 @@ const NetworkDetail = React.memo(() => {
97
97
  </Text>
98
98
  </View>
99
99
 
100
+ {/* Client Chip (AXIOS / FETCH / XHR) */}
101
+ {selected.client && (<View style={[
102
+ styles.chip,
103
+ {
104
+ backgroundColor: selected.client === 'axios'
105
+ ? `${AppColors.purple}14`
106
+ : selected.client === 'apollo' || selected.client === 'graphql'
107
+ ? `${AppColors.pink500}14`
108
+ : selected.client === 'xhr'
109
+ ? `${AppColors.amber500}14`
110
+ : `${AppColors.sky500}14`,
111
+ borderColor: selected.client === 'axios'
112
+ ? `${AppColors.purple}30`
113
+ : selected.client === 'apollo' || selected.client === 'graphql'
114
+ ? `${AppColors.pink500}30`
115
+ : selected.client === 'xhr'
116
+ ? `${AppColors.amber500}30`
117
+ : `${AppColors.sky500}30`,
118
+ paddingHorizontal: 6,
119
+ paddingVertical: 3,
120
+ borderRadius: 6,
121
+ },
122
+ ]}>
123
+ <Text style={[
124
+ styles.chipText,
125
+ {
126
+ fontFamily: AppFonts.interBold,
127
+ fontSize: 9.5,
128
+ color: selected.client === 'axios'
129
+ ? AppColors.purple
130
+ : selected.client === 'apollo' || selected.client === 'graphql'
131
+ ? AppColors.pink500
132
+ : selected.client === 'xhr'
133
+ ? AppColors.amber700
134
+ : AppColors.sky600,
135
+ },
136
+ ]}>
137
+ {selected.client.toUpperCase()}
138
+ </Text>
139
+ </View>)}
140
+
100
141
  {/* Status Pill */}
101
142
  {selected.status != null && (<View style={[
102
143
  styles.chip,
@@ -6,6 +6,11 @@ export interface PerformanceEvent {
6
6
  category: 'render' | 'navigation' | 'memory' | 'io' | 'bridge';
7
7
  fps: number;
8
8
  durationMs: number;
9
+ droppedFrames?: number;
10
+ frameTimeMs?: number;
11
+ frameBudgetPct?: number;
12
+ bottleneckThread?: 'JS Thread' | 'UI Thread' | 'Bridge' | 'Balanced';
13
+ screenName?: string;
9
14
  label: string;
10
15
  detail: string;
11
16
  source?: string;