react-native-inapp-inspector 1.1.27 → 1.1.29
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.
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -46
- package/dist/commonjs/components/AppHeaderLogo.js +12 -15
- package/dist/commonjs/components/ConsoleLogCard.js +31 -7
- package/dist/commonjs/components/CopyButton.js +15 -1
- package/dist/commonjs/components/DomainHeader.js +56 -28
- package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
- package/dist/commonjs/components/ErrorBoundary.js +530 -100
- package/dist/commonjs/components/HighlightText.js +7 -1
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +190 -126
- package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
- package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
- package/dist/commonjs/components/Inspector/InspectorHeader.js +17 -3
- package/dist/commonjs/components/Inspector/LogDetail.js +8 -5
- package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
- package/dist/commonjs/components/Inspector/NetworkDetail.js +65 -24
- package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
- package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
- package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
- package/dist/commonjs/components/Inspector/TabBar.js +13 -1
- package/dist/commonjs/components/JsonViewer.js +25 -3
- package/dist/commonjs/components/LogCard.js +173 -40
- package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
- package/dist/commonjs/components/Toast.d.ts +3 -0
- package/dist/commonjs/components/Toast.js +152 -0
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- package/dist/commonjs/components/TouchableScale.js +3 -3
- package/dist/commonjs/components/TreeNode.js +1 -1
- package/dist/commonjs/constants/index.js +6 -6
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
- package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
- package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
- package/dist/commonjs/customHooks/networkLogger.js +16 -0
- package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
- package/dist/commonjs/customHooks/performanceTracker.js +546 -392
- package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
- package/dist/commonjs/customHooks/reduxLogger.js +3 -1
- package/dist/commonjs/helpers/index.d.ts +4 -0
- package/dist/commonjs/helpers/index.js +92 -69
- package/dist/commonjs/helpers/toast.d.ts +4 -0
- package/dist/commonjs/helpers/toast.js +20 -0
- package/dist/commonjs/i18n/locales/en.json +229 -5
- package/dist/commonjs/index.d.ts +1 -1
- package/dist/commonjs/index.js +36 -2
- package/dist/commonjs/styles/index.d.ts +63 -15
- package/dist/commonjs/styles/index.js +79 -32
- package/dist/commonjs/types/interfaces.d.ts +3 -0
- package/dist/esm/components/AppHeaderLogo.js +12 -15
- package/dist/esm/components/ConsoleLogCard.js +33 -9
- package/dist/esm/components/CopyButton.js +15 -1
- package/dist/esm/components/DomainHeader.js +56 -28
- package/dist/esm/components/ErrorBoundary.d.ts +1 -1
- package/dist/esm/components/ErrorBoundary.js +498 -101
- package/dist/esm/components/HighlightText.js +7 -1
- package/dist/esm/components/Inspector/AnalyticsTab.js +191 -127
- package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/esm/components/Inspector/BundleTab.js +145 -23
- package/dist/esm/components/Inspector/CrashTab.js +0 -8
- package/dist/esm/components/Inspector/InspectorHeader.js +18 -4
- package/dist/esm/components/Inspector/LogDetail.js +10 -7
- package/dist/esm/components/Inspector/MainScreen.js +4 -0
- package/dist/esm/components/Inspector/NetworkDetail.js +66 -25
- package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
- package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
- package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
- package/dist/esm/components/Inspector/TabBar.js +13 -1
- package/dist/esm/components/JsonViewer.js +25 -3
- package/dist/esm/components/LogCard.js +175 -42
- package/dist/esm/components/NetworkIcons.d.ts +112 -102
- package/dist/esm/components/Toast.d.ts +3 -0
- package/dist/esm/components/Toast.js +117 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- package/dist/esm/components/TouchableScale.js +3 -3
- package/dist/esm/components/TreeNode.js +1 -1
- package/dist/esm/constants/index.js +6 -6
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
- package/dist/esm/customHooks/analyticsLogger.js +6 -0
- package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
- package/dist/esm/customHooks/networkLogger.d.ts +1 -0
- package/dist/esm/customHooks/networkLogger.js +16 -0
- package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
- package/dist/esm/customHooks/performanceTracker.js +531 -391
- package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
- package/dist/esm/customHooks/reduxLogger.js +1 -0
- package/dist/esm/helpers/index.d.ts +4 -0
- package/dist/esm/helpers/index.js +87 -69
- package/dist/esm/helpers/toast.d.ts +4 -0
- package/dist/esm/helpers/toast.js +15 -0
- package/dist/esm/i18n/locales/en.json +229 -5
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +24 -3
- package/dist/esm/styles/index.d.ts +63 -15
- package/dist/esm/styles/index.js +79 -32
- package/dist/esm/types/interfaces.d.ts +3 -0
- package/package.json +4 -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 {
|
|
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
|
|
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
|
-
<
|
|
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 (
|
|
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' &&
|
|
1376
|
-
|
|
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' && {
|
|
1384
|
-
|
|
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
|
-
|
|
1632
|
-
|
|
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' && {
|
|
1636
|
-
|
|
1732
|
+
file.status === 'warning' && {
|
|
1733
|
+
color: AppColors.amber800Warm,
|
|
1734
|
+
},
|
|
1735
|
+
file.status === 'optimal' && {
|
|
1736
|
+
color: AppColors.emerald700,
|
|
1737
|
+
},
|
|
1637
1738
|
]}>
|
|
1638
|
-
|
|
1639
|
-
|
|
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:
|
|
2703
|
+
paddingVertical: 5,
|
|
2584
2704
|
borderRadius: 6,
|
|
2585
|
-
marginTop:
|
|
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;
|
|
@@ -10,7 +10,7 @@ import { AppFonts } from '../../styles/AppFonts';
|
|
|
10
10
|
import { METHOD_COLORS } from '../../constants';
|
|
11
11
|
import { LIB_VERSION } from '../../constants';
|
|
12
12
|
import { getStatusColor, getAppName, getBundleIdentifier, formatTime, getSize } from '../../helpers';
|
|
13
|
-
import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, AppleIcon, AndroidIcon, PackageBoxIcon, } from '../NetworkIcons';
|
|
13
|
+
import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, PackageBoxIcon, } from '../NetworkIcons';
|
|
14
14
|
const InspectorHeader = React.memo(() => {
|
|
15
15
|
const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, activeTab, environment, selectedCrash, setSelectedCrash, } = useInspector();
|
|
16
16
|
const envConfig = useMemo(() => {
|
|
@@ -108,9 +108,9 @@ const InspectorHeader = React.memo(() => {
|
|
|
108
108
|
flex: 1,
|
|
109
109
|
}}>
|
|
110
110
|
<AppHeaderLogo size={46} customIcon={appIcon}/>
|
|
111
|
-
<View style={{ gap: 2, flex: 1 }}>
|
|
112
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: '
|
|
113
|
-
<Text style={[styles.headerTitle]} numberOfLines={1}>
|
|
111
|
+
<View style={{ gap: 2, flex: 1, minWidth: 0 }}>
|
|
112
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'nowrap', minWidth: 0 }}>
|
|
113
|
+
<Text style={[styles.headerTitle, { flexShrink: 1 }]} numberOfLines={1}>
|
|
114
114
|
{getAppName()}
|
|
115
115
|
</Text>
|
|
116
116
|
<View style={[
|
|
@@ -334,6 +334,20 @@ const InspectorHeader = React.memo(() => {
|
|
|
334
334
|
: '—'}
|
|
335
335
|
</Text>
|
|
336
336
|
</View>
|
|
337
|
+
{selected.response != null && (<View style={{
|
|
338
|
+
flexDirection: 'row',
|
|
339
|
+
alignItems: 'center',
|
|
340
|
+
gap: 4,
|
|
341
|
+
paddingHorizontal: 8,
|
|
342
|
+
paddingVertical: 3,
|
|
343
|
+
borderRadius: 20,
|
|
344
|
+
backgroundColor: `${AppColors.white}29`,
|
|
345
|
+
}}>
|
|
346
|
+
<SizeIcon color={AppColors.white} size={11}/>
|
|
347
|
+
<Text style={styles.headerSubTitle}>
|
|
348
|
+
{getSize(selected.response)}
|
|
349
|
+
</Text>
|
|
350
|
+
</View>)}
|
|
337
351
|
</View>
|
|
338
352
|
</View>) : activeTab === 'analytics' && selectedEvent != null ? (<View style={styles.headerDetailCenter}>
|
|
339
353
|
<View style={styles.headerDetailRow}>
|
|
@@ -6,11 +6,11 @@ import HighlightText from '../HighlightText';
|
|
|
6
6
|
import JsonViewer from '../JsonViewer';
|
|
7
7
|
import CopyButton from '../CopyButton';
|
|
8
8
|
import SegmentedTabs from '../SegmentedTabs';
|
|
9
|
-
import { ClearIcon, SearchIcon, PrettyIcon, RawIcon, TableIcon, LayersIcon, TerminalIcon, RequestIcon, InfoCircleIcon, DocIcon, } from '../NetworkIcons';
|
|
9
|
+
import { ClearIcon, SearchIcon, PrettyIcon, RawIcon, TableIcon, LayersIcon, TerminalIcon, RequestIcon, InfoCircleIcon, DocIcon, ExternalLinkIcon, } from '../NetworkIcons';
|
|
10
10
|
import { AppColors } from '../../styles/AppColors';
|
|
11
11
|
import { AppFonts } from '../../styles/AppFonts';
|
|
12
12
|
import styles from '../../styles';
|
|
13
|
-
import { getJsonContent, getSize, formatDateTime, parseStackLine, } from '../../helpers';
|
|
13
|
+
import { getJsonContent, getSize, formatDateTime, parseStackLine, openInVSCode, } from '../../helpers';
|
|
14
14
|
const getRelativeTime = (timestamp) => {
|
|
15
15
|
const now = Date.now();
|
|
16
16
|
const diff = now - timestamp;
|
|
@@ -269,16 +269,18 @@ const LogDetail = React.memo(() => {
|
|
|
269
269
|
: originFrame?.fileName || t('console.consoleLog')}
|
|
270
270
|
</Text>
|
|
271
271
|
|
|
272
|
-
{originFrame?.fileName && (<
|
|
272
|
+
{originFrame?.fileName && (<Pressable onPress={() => openInVSCode(originFrame.rawFilePath ||
|
|
273
|
+
originFrame.fullPath ||
|
|
274
|
+
originFrame.fileName, originFrame.lineNumber, originFrame.columnNumber)} hitSlop={8} style={[
|
|
273
275
|
styles.metaChip,
|
|
274
276
|
{
|
|
275
|
-
backgroundColor: `${AppColors.brandPurple}
|
|
276
|
-
borderColor: `${AppColors.brandPurple}
|
|
277
|
+
backgroundColor: `${AppColors.brandPurple}15`,
|
|
278
|
+
borderColor: `${AppColors.brandPurple}40`,
|
|
277
279
|
paddingHorizontal: 7,
|
|
278
280
|
paddingVertical: 2,
|
|
279
281
|
},
|
|
280
282
|
]}>
|
|
281
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap:
|
|
283
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flexShrink: 1 }}>
|
|
282
284
|
<DocIcon color={AppColors.brandPurple} size={10}/>
|
|
283
285
|
<Text style={[
|
|
284
286
|
styles.metaChipText,
|
|
@@ -288,8 +290,9 @@ const LogDetail = React.memo(() => {
|
|
|
288
290
|
{originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
|
|
289
291
|
{originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
|
|
290
292
|
</Text>
|
|
293
|
+
<ExternalLinkIcon color={AppColors.brandPurple} size={9}/>
|
|
291
294
|
</View>
|
|
292
|
-
</
|
|
295
|
+
</Pressable>)}
|
|
293
296
|
</View>
|
|
294
297
|
|
|
295
298
|
{/* Enhanced metadata metrics row */}
|
|
@@ -18,6 +18,7 @@ import PerformanceTab from './PerformanceTab';
|
|
|
18
18
|
import CrashTab from './CrashTab';
|
|
19
19
|
import CrashDetail from './CrashDetail';
|
|
20
20
|
import SettingsPanel from './SettingsPanel';
|
|
21
|
+
import Toast from '../Toast';
|
|
21
22
|
import styles from '../../styles';
|
|
22
23
|
import { AppColors } from '../../styles/AppColors';
|
|
23
24
|
import NavigationTracker from './NavigationTracker';
|
|
@@ -115,6 +116,9 @@ const MainScreen = () => {
|
|
|
115
116
|
]}>
|
|
116
117
|
<SettingsPanel />
|
|
117
118
|
</View>)}
|
|
119
|
+
|
|
120
|
+
{/* Bottom floating toast notification */}
|
|
121
|
+
<Toast />
|
|
118
122
|
</View>
|
|
119
123
|
</View>
|
|
120
124
|
</ErrorBoundary>)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from '../../i18n';
|
|
3
|
-
import { Image, Linking, Pressable, ScrollView, Text, TextInput, View, } from 'react-native';
|
|
3
|
+
import { Alert, Image, Linking, Pressable, ScrollView, Text, TextInput, View, } from 'react-native';
|
|
4
4
|
import { animateNextLayout, useInspector } from './InspectorContext';
|
|
5
5
|
import TouchableScale from '../TouchableScale';
|
|
6
6
|
import SegmentedTabs from '../SegmentedTabs';
|
|
@@ -20,6 +20,26 @@ import { FailIcon, StatusIcon, HeadersIcon, RequestIcon, ResponseIcon, DownloadI
|
|
|
20
20
|
const NetworkDetail = React.memo(() => {
|
|
21
21
|
const { t } = useTranslation();
|
|
22
22
|
const { selected, detailDisplayUrl, apiDetailActiveTab, setApiDetailActiveTab, detailSearch, setDetailSearch, reqExpanded, setReqExpanded, resExpanded, setResExpanded, showReqDiff, setShowReqDiff, showResDiff, setShowResDiff, prevRequestData, prevResponseData, logRouteMapRef, } = useInspector();
|
|
23
|
+
const handleOpenUrl = () => {
|
|
24
|
+
Alert.alert(t('common.openInBrowser') || 'Open in Browser', `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${detailDisplayUrl}`, [
|
|
25
|
+
{ text: t('common.cancel') || 'Cancel', style: 'cancel' },
|
|
26
|
+
{
|
|
27
|
+
text: t('common.open') || 'Open',
|
|
28
|
+
onPress: () => {
|
|
29
|
+
Linking.canOpenURL(detailDisplayUrl)
|
|
30
|
+
.then(supported => {
|
|
31
|
+
if (supported) {
|
|
32
|
+
Linking.openURL(detailDisplayUrl);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
Linking.openURL(detailDisplayUrl).catch(() => { });
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.catch(() => { });
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
};
|
|
23
43
|
if (!selected)
|
|
24
44
|
return null;
|
|
25
45
|
return (<View style={{ flex: 1 }}>
|
|
@@ -97,6 +117,47 @@ const NetworkDetail = React.memo(() => {
|
|
|
97
117
|
</Text>
|
|
98
118
|
</View>
|
|
99
119
|
|
|
120
|
+
{/* Client Chip (AXIOS / FETCH / XHR) */}
|
|
121
|
+
{selected.client && (<View style={[
|
|
122
|
+
styles.chip,
|
|
123
|
+
{
|
|
124
|
+
backgroundColor: selected.client === 'axios'
|
|
125
|
+
? `${AppColors.purple}14`
|
|
126
|
+
: selected.client === 'apollo' || selected.client === 'graphql'
|
|
127
|
+
? `${AppColors.pink500}14`
|
|
128
|
+
: selected.client === 'xhr'
|
|
129
|
+
? `${AppColors.amber500}14`
|
|
130
|
+
: `${AppColors.sky500}14`,
|
|
131
|
+
borderColor: selected.client === 'axios'
|
|
132
|
+
? `${AppColors.purple}30`
|
|
133
|
+
: selected.client === 'apollo' || selected.client === 'graphql'
|
|
134
|
+
? `${AppColors.pink500}30`
|
|
135
|
+
: selected.client === 'xhr'
|
|
136
|
+
? `${AppColors.amber500}30`
|
|
137
|
+
: `${AppColors.sky500}30`,
|
|
138
|
+
paddingHorizontal: 6,
|
|
139
|
+
paddingVertical: 3,
|
|
140
|
+
borderRadius: 6,
|
|
141
|
+
},
|
|
142
|
+
]}>
|
|
143
|
+
<Text style={[
|
|
144
|
+
styles.chipText,
|
|
145
|
+
{
|
|
146
|
+
fontFamily: AppFonts.interBold,
|
|
147
|
+
fontSize: 9.5,
|
|
148
|
+
color: selected.client === 'axios'
|
|
149
|
+
? AppColors.purple
|
|
150
|
+
: selected.client === 'apollo' || selected.client === 'graphql'
|
|
151
|
+
? AppColors.pink500
|
|
152
|
+
: selected.client === 'xhr'
|
|
153
|
+
? AppColors.amber700
|
|
154
|
+
: AppColors.sky600,
|
|
155
|
+
},
|
|
156
|
+
]}>
|
|
157
|
+
{selected.client.toUpperCase()}
|
|
158
|
+
</Text>
|
|
159
|
+
</View>)}
|
|
160
|
+
|
|
100
161
|
{/* Status Pill */}
|
|
101
162
|
{selected.status != null && (<View style={[
|
|
102
163
|
styles.chip,
|
|
@@ -186,7 +247,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
186
247
|
</View>
|
|
187
248
|
|
|
188
249
|
<View style={styles.detailInfoRight}>
|
|
189
|
-
<TouchableScale style={[styles.iconSquareBtn, { backgroundColor: `${AppColors.sky600}15` }]} onPress={
|
|
250
|
+
<TouchableScale style={[styles.iconSquareBtn, { backgroundColor: `${AppColors.sky600}15` }]} onPress={handleOpenUrl} hitSlop={12}>
|
|
190
251
|
<ExternalLinkIcon color={AppColors.sky600} size={14}/>
|
|
191
252
|
</TouchableScale>
|
|
192
253
|
<CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
|
|
@@ -204,7 +265,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
204
265
|
padding: 10,
|
|
205
266
|
marginTop: 8,
|
|
206
267
|
gap: 6,
|
|
207
|
-
}} onPress={
|
|
268
|
+
}} onPress={handleOpenUrl}>
|
|
208
269
|
<View style={{
|
|
209
270
|
flexDirection: 'row',
|
|
210
271
|
alignItems: 'center',
|
|
@@ -387,7 +448,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
387
448
|
if (!reqExpanded && !showReqDiff)
|
|
388
449
|
setReqExpanded(true);
|
|
389
450
|
}}/>
|
|
390
|
-
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer data={selected.request} search={detailSearch}
|
|
451
|
+
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer data={selected.request} search={detailSearch}/>) : (<View style={styles.codeBlock}>
|
|
391
452
|
<Text style={[
|
|
392
453
|
styles.codeText,
|
|
393
454
|
{ color: AppColors.grayTextWeak },
|
|
@@ -406,26 +467,6 @@ const NetworkDetail = React.memo(() => {
|
|
|
406
467
|
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
407
468
|
</Pressable>)}
|
|
408
469
|
</View>
|
|
409
|
-
{selected.response != null && (<View style={{
|
|
410
|
-
flexDirection: 'row',
|
|
411
|
-
alignItems: 'center',
|
|
412
|
-
gap: 4,
|
|
413
|
-
paddingHorizontal: 10,
|
|
414
|
-
height: 34,
|
|
415
|
-
borderRadius: 8,
|
|
416
|
-
borderWidth: 1,
|
|
417
|
-
borderColor: `${AppColors.purple}30`,
|
|
418
|
-
backgroundColor: `${AppColors.purple}10`,
|
|
419
|
-
}}>
|
|
420
|
-
<SizeIcon color={AppColors.purple} size={12}/>
|
|
421
|
-
<Text style={{
|
|
422
|
-
fontFamily: AppFonts.interBold,
|
|
423
|
-
fontSize: 10.5,
|
|
424
|
-
color: AppColors.purple,
|
|
425
|
-
}}>
|
|
426
|
-
{getSize(selected.response)}
|
|
427
|
-
</Text>
|
|
428
|
-
</View>)}
|
|
429
470
|
</View>
|
|
430
471
|
|
|
431
472
|
<View style={styles.sectionContainer}>
|
|
@@ -434,7 +475,7 @@ const NetworkDetail = React.memo(() => {
|
|
|
434
475
|
if (!resExpanded && !showResDiff)
|
|
435
476
|
setResExpanded(true);
|
|
436
477
|
}}/>
|
|
437
|
-
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer data={selected.response} search={detailSearch}
|
|
478
|
+
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer data={selected.response} search={detailSearch} wrap/>) : (<View style={styles.codeBlock}>
|
|
438
479
|
<Text style={[
|
|
439
480
|
styles.codeText,
|
|
440
481
|
{ color: AppColors.grayTextWeak },
|
|
@@ -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;
|