react-native-inapp-inspector 1.1.35 → 2.0.0
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/README.md +123 -101
- package/android/build.gradle +13 -1
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +904 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
- package/dist/commonjs/components/CopyButton.js +3 -0
- package/dist/commonjs/components/ErrorBoundary.js +3 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +7 -3
- package/dist/commonjs/components/Inspector/BundleTab.js +1 -1
- package/dist/commonjs/components/Inspector/ConsoleTab.js +1 -1
- package/dist/commonjs/components/Inspector/CrashTab.js +97 -90
- package/dist/commonjs/components/Inspector/InspectorHeader.js +12 -8
- package/dist/commonjs/components/Inspector/MainScreen.js +14 -101
- package/dist/commonjs/components/Inspector/NetworkTab.js +47 -10
- package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
- package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
- package/dist/commonjs/components/Inspector/SettingsPanel.js +342 -88
- package/dist/commonjs/components/Inspector/TabBar.js +6 -3
- package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
- package/dist/commonjs/components/NetworkIcons.js +6 -1
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- package/dist/commonjs/components/TouchableScale.js +30 -32
- package/dist/commonjs/constants/index.d.ts +1 -2
- package/dist/commonjs/constants/index.js +4 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +4 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +7 -5
- package/dist/commonjs/customHooks/crashHandler.js +7 -0
- package/dist/commonjs/helpers/index.js +10 -1
- package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
- package/dist/commonjs/helpers/settingsStore.js +59 -3
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +229 -39
- package/dist/commonjs/native/NativeInspector.d.ts +125 -0
- package/dist/commonjs/native/NativeInspector.js +313 -0
- package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
- package/dist/commonjs/styles/index.js +6 -6
- package/dist/commonjs/types/enums.d.ts +1 -1
- package/dist/commonjs/types/interfaces.d.ts +9 -0
- package/dist/esm/components/CopyButton.js +3 -0
- package/dist/esm/components/ErrorBoundary.js +3 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +7 -3
- package/dist/esm/components/Inspector/BundleTab.js +1 -1
- package/dist/esm/components/Inspector/ConsoleTab.js +1 -1
- package/dist/esm/components/Inspector/CrashTab.js +97 -90
- package/dist/esm/components/Inspector/InspectorHeader.js +13 -9
- package/dist/esm/components/Inspector/MainScreen.js +15 -69
- package/dist/esm/components/Inspector/NetworkTab.js +47 -10
- package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
- package/dist/esm/components/Inspector/ReduxTab.js +18 -10
- package/dist/esm/components/Inspector/SettingsPanel.js +344 -90
- package/dist/esm/components/Inspector/TabBar.js +7 -4
- package/dist/esm/components/NetworkIcons.d.ts +1 -0
- package/dist/esm/components/NetworkIcons.js +4 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- package/dist/esm/components/TouchableScale.js +31 -33
- package/dist/esm/constants/index.d.ts +1 -2
- package/dist/esm/constants/index.js +4 -0
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/analyticsLogger.d.ts +4 -0
- package/dist/esm/customHooks/analyticsLogger.js +5 -4
- package/dist/esm/customHooks/crashHandler.js +7 -0
- package/dist/esm/helpers/index.js +10 -1
- package/dist/esm/helpers/settingsStore.d.ts +9 -0
- package/dist/esm/helpers/settingsStore.js +58 -3
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +215 -39
- package/dist/esm/native/NativeInspector.d.ts +125 -0
- package/dist/esm/native/NativeInspector.js +289 -0
- package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/esm/native/NativeNetworkInspector.js +2 -0
- package/dist/esm/styles/index.js +6 -6
- package/dist/esm/types/enums.d.ts +1 -1
- package/dist/esm/types/interfaces.d.ts +9 -0
- package/ios/NetworkInspectorModule.h +6 -0
- package/ios/NetworkInspectorModule.m +720 -5
- package/package.json +13 -2
- package/react-native-inapp-inspector.podspec +7 -2
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
|
@@ -109,11 +109,16 @@ const CrashTab = React.memo(() => {
|
|
|
109
109
|
}
|
|
110
110
|
// Search filter
|
|
111
111
|
if (searchQuery.trim().length > 0) {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
const queryTokens = searchQuery.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
113
|
+
const searchTarget = [
|
|
114
|
+
item.message || '',
|
|
115
|
+
item.name || '',
|
|
116
|
+
item.stack || '',
|
|
117
|
+
item.type || '',
|
|
118
|
+
item.deviceInfo?.platform || '',
|
|
119
|
+
...(item.breadcrumbs || []).map(b => `${b.type || ''} ${b.message || ''}`),
|
|
120
|
+
].join(' ').toLowerCase();
|
|
121
|
+
return queryTokens.every(token => searchTarget.includes(token));
|
|
117
122
|
}
|
|
118
123
|
return true;
|
|
119
124
|
});
|
|
@@ -250,74 +255,75 @@ const CrashTab = React.memo(() => {
|
|
|
250
255
|
</AnimatedEntrance>);
|
|
251
256
|
}, [searchQuery, setSelectedCrash]);
|
|
252
257
|
return (<View style={localStyles.container}>
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
{crashRecords.length > 0 && (<>
|
|
259
|
+
{/* ─── Top Stats Bar ─── */}
|
|
260
|
+
<View style={localStyles.statsBar}>
|
|
261
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.statsScrollContent}>
|
|
262
|
+
<View style={[localStyles.statChip, localStyles.statChipActive]}>
|
|
263
|
+
<Text style={localStyles.statChipValue}>{stats.total}</Text>
|
|
264
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statCrashes')}</Text>
|
|
265
|
+
</View>
|
|
260
266
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
<View style={localStyles.statChip}>
|
|
268
|
+
<Text style={[localStyles.statChipValue, { color: AppColors.errorColor }]}>
|
|
269
|
+
{stats.fatalCount}
|
|
270
|
+
</Text>
|
|
271
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statFatal')}</Text>
|
|
272
|
+
</View>
|
|
267
273
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
<View style={localStyles.statChip}>
|
|
275
|
+
<Text style={[localStyles.statChipValue, { color: AppColors.offerPurple }]}>
|
|
276
|
+
{stats.jsCount}
|
|
277
|
+
</Text>
|
|
278
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statJsErrors')}</Text>
|
|
279
|
+
</View>
|
|
274
280
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
<View style={localStyles.statChip}>
|
|
282
|
+
<Text style={[localStyles.statChipValue, { color: AppColors.darkOrange }]}>
|
|
283
|
+
{stats.promiseCount}
|
|
284
|
+
</Text>
|
|
285
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statPromises')}</Text>
|
|
286
|
+
</View>
|
|
281
287
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
+
<View style={localStyles.statChip}>
|
|
289
|
+
<Text style={[localStyles.statChipValue, { color: AppColors.purple }]}>
|
|
290
|
+
{stats.renderCount}
|
|
291
|
+
</Text>
|
|
292
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statRender')}</Text>
|
|
293
|
+
</View>
|
|
288
294
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
295
|
+
<View style={localStyles.statChip}>
|
|
296
|
+
<Text style={[localStyles.statChipValue, { color: AppColors.skyBlue }]}>
|
|
297
|
+
{stats.nativeCount}
|
|
298
|
+
</Text>
|
|
299
|
+
<Text style={localStyles.statChipLabel}>{t('crash.statNative')}</Text>
|
|
300
|
+
</View>
|
|
301
|
+
</ScrollView>
|
|
294
302
|
</View>
|
|
295
|
-
</ScrollView>
|
|
296
|
-
</View>
|
|
297
303
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
{/* ─── Search & Controls ─── */}
|
|
305
|
+
<View style={localStyles.controlsContainer}>
|
|
306
|
+
<View style={localStyles.searchRow}>
|
|
307
|
+
<View style={localStyles.searchInputWrapper}>
|
|
308
|
+
<SearchIcon color={AppColors.grayTextWeak} size={14}/>
|
|
309
|
+
<TextInput value={searchQuery} onChangeText={setSearchQuery} placeholder={t('crash.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} style={localStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
|
|
310
|
+
{searchQuery.length > 0 && (<TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
|
|
311
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
312
|
+
</TouchableOpacity>)}
|
|
313
|
+
</View>
|
|
308
314
|
|
|
309
|
-
|
|
310
|
-
|
|
315
|
+
{/* Crash Filters */}
|
|
316
|
+
<TouchableScale onPress={() => setIsFilterModalOpen(true)} style={[
|
|
311
317
|
localStyles.filterButton,
|
|
312
318
|
!isCrashFiltersDefault(crashFilters) && {
|
|
313
319
|
borderColor: `${AppColors.brandPurple}60`,
|
|
314
320
|
backgroundColor: `${AppColors.brandPurple}15`,
|
|
315
321
|
},
|
|
316
322
|
]}>
|
|
317
|
-
|
|
323
|
+
<FilterIcon size={14} color={isCrashFiltersDefault(crashFilters)
|
|
318
324
|
? AppColors.grayTextStrong
|
|
319
325
|
: AppColors.brandPurple}/>
|
|
320
|
-
|
|
326
|
+
{!isCrashFiltersDefault(crashFilters) && (<View style={{
|
|
321
327
|
position: 'absolute',
|
|
322
328
|
top: 2,
|
|
323
329
|
right: 2,
|
|
@@ -328,40 +334,41 @@ const CrashTab = React.memo(() => {
|
|
|
328
334
|
borderWidth: 1,
|
|
329
335
|
borderColor: AppColors.white,
|
|
330
336
|
}}/>)}
|
|
331
|
-
|
|
337
|
+
</TouchableScale>
|
|
332
338
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
{/* Clear Crashes */}
|
|
340
|
+
<TouchableScale onPress={handleClearAll} style={localStyles.clearButton}>
|
|
341
|
+
<TrashIcon size={14} color={AppColors.errorColor}/>
|
|
342
|
+
</TouchableScale>
|
|
343
|
+
</View>
|
|
338
344
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
345
|
+
{/* ─── Filter Chips ─── */}
|
|
346
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.filterScrollContent}>
|
|
347
|
+
{[
|
|
348
|
+
{ key: 'all', label: t('crash.filterAll'), Icon: LayersIcon },
|
|
349
|
+
{ key: 'fatal', label: t('crash.filterFatal'), Icon: SkullIcon },
|
|
350
|
+
{ key: 'js', label: t('crash.filterJsError'), Icon: JsIcon },
|
|
351
|
+
{ key: 'promise', label: t('crash.filterPromise'), Icon: HourglassIcon },
|
|
352
|
+
{ key: 'render', label: t('crash.filterRender'), Icon: LayoutIcon },
|
|
353
|
+
{ key: 'native', label: t('crash.filterNative'), Icon: ChipIcon },
|
|
354
|
+
].map(chip => {
|
|
355
|
+
const isActive = filterType === chip.key;
|
|
356
|
+
return (<TouchableOpacity key={chip.key} onPress={() => setFilterType(chip.key)} style={[
|
|
357
|
+
localStyles.filterChip,
|
|
358
|
+
isActive && localStyles.filterChipActive,
|
|
359
|
+
]}>
|
|
360
|
+
<chip.Icon size={12} color={isActive ? AppColors.white : AppColors.grayTextStrong}/>
|
|
361
|
+
<Text style={[
|
|
362
|
+
localStyles.filterChipText,
|
|
363
|
+
isActive && localStyles.filterChipTextActive,
|
|
364
|
+
]}>
|
|
365
|
+
{chip.label}
|
|
366
|
+
</Text>
|
|
367
|
+
</TouchableOpacity>);
|
|
368
|
+
})}
|
|
369
|
+
</ScrollView>
|
|
370
|
+
</View>
|
|
371
|
+
</>)}
|
|
365
372
|
|
|
366
373
|
{/* ─── Crash List or Empty State ─── */}
|
|
367
374
|
{filteredList.length === 0 ? (<ScrollView contentContainerStyle={localStyles.emptyContainer} showsVerticalScrollIndicator={false}>
|
|
@@ -374,7 +381,7 @@ const CrashTab = React.memo(() => {
|
|
|
374
381
|
? t('crash.emptySearchSubtitle')
|
|
375
382
|
: t('crash.emptySubtitle')}
|
|
376
383
|
</Text>
|
|
377
|
-
</ScrollView>) : (<FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>)}
|
|
384
|
+
</ScrollView>) : (<FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>)}
|
|
378
385
|
|
|
379
386
|
{/* Filter Modal */}
|
|
380
387
|
<CrashFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)} filters={crashFilters} onApply={setCrashFilters} searchQuery={searchQuery}/>
|
|
@@ -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, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, } from '../NetworkIcons';
|
|
13
|
+
import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, NpmIcon, } 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(() => {
|
|
@@ -188,23 +188,27 @@ const InspectorHeader = React.memo(() => {
|
|
|
188
188
|
</Text>
|
|
189
189
|
</View>
|
|
190
190
|
|
|
191
|
-
<
|
|
192
|
-
backgroundColor:
|
|
191
|
+
<Pressable onPress={() => Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { })} style={{
|
|
192
|
+
backgroundColor: '#CB383726',
|
|
193
193
|
borderRadius: 4,
|
|
194
|
-
paddingHorizontal:
|
|
194
|
+
paddingHorizontal: 5,
|
|
195
195
|
paddingVertical: 1.5,
|
|
196
196
|
borderWidth: 1,
|
|
197
|
-
borderColor:
|
|
197
|
+
borderColor: '#CB383780',
|
|
198
|
+
flexDirection: 'row',
|
|
199
|
+
alignItems: 'center',
|
|
200
|
+
gap: 3.5,
|
|
198
201
|
flexShrink: 0,
|
|
199
202
|
}}>
|
|
203
|
+
<NpmIcon size={11} color="#FF6B6B"/>
|
|
200
204
|
<Text style={{
|
|
201
|
-
fontFamily: AppFonts.
|
|
202
|
-
fontSize:
|
|
203
|
-
color:
|
|
205
|
+
fontFamily: AppFonts.interBold,
|
|
206
|
+
fontSize: 9,
|
|
207
|
+
color: '#FF6B6B',
|
|
204
208
|
}} numberOfLines={1}>
|
|
205
209
|
v{LIB_VERSION}
|
|
206
210
|
</Text>
|
|
207
|
-
</
|
|
211
|
+
</Pressable>
|
|
208
212
|
</View>
|
|
209
213
|
</View>
|
|
210
214
|
</View>) : null}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { ActivityIndicator, Modal, Platform, Pressable, StatusBar,
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ActivityIndicator, Modal, Platform, Pressable, StatusBar, Text, View, } from 'react-native';
|
|
3
3
|
import { useInspector } from './InspectorContext';
|
|
4
4
|
import ErrorBoundary from '../ErrorBoundary';
|
|
5
5
|
import FabLauncher from './FabLauncher';
|
|
@@ -23,25 +23,16 @@ import styles from '../../styles';
|
|
|
23
23
|
import { AppColors } from '../../styles/AppColors';
|
|
24
24
|
import NavigationTracker from './NavigationTracker';
|
|
25
25
|
const MainScreen = () => {
|
|
26
|
-
const { visible, modalAnimationType, closeModal, modalHeightPercent, selected, selectedEvent, selectedLog, selectedReduxSlice, selectedReduxAction, selectedCrash, settingsPage, activeTab, isReady, isEnabled, hasNavigationContext, setNavState, } = useInspector();
|
|
27
|
-
// Lazy-mount each tab on first visit and cache it in memory for 0ms instantaneous switching
|
|
28
|
-
const [mountedTabs, setMountedTabs] = useState({
|
|
29
|
-
[activeTab]: true,
|
|
30
|
-
});
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
setMountedTabs(prev => {
|
|
33
|
-
if (prev[activeTab])
|
|
34
|
-
return prev;
|
|
35
|
-
return { ...prev, [activeTab]: true };
|
|
36
|
-
});
|
|
37
|
-
}, [activeTab]);
|
|
26
|
+
const { visible, modalAnimationType, closeModal, modalHeightPercent, selected, selectedEvent, selectedLog, selectedReduxSlice, selectedReduxAction, selectedCrash, settingsPage, activeTab, isReady, isEnabled, useNativeFab, hasNavigationContext, setNavState, } = useInspector();
|
|
38
27
|
const isDetailActive = (activeTab === 'apis' && selected != null) ||
|
|
39
28
|
(activeTab === 'analytics' && selectedEvent != null) ||
|
|
40
29
|
(activeTab === 'logs' && selectedLog != null) ||
|
|
41
30
|
(activeTab === 'redux' && (selectedReduxSlice != null || selectedReduxAction != null)) ||
|
|
42
31
|
(activeTab === 'crash' && selectedCrash != null);
|
|
43
32
|
return (<>
|
|
44
|
-
{(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
33
|
+
{(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
34
|
+
isEnabled &&
|
|
35
|
+
!useNativeFab && <FabLauncher />}
|
|
45
36
|
<Modal visible={visible} animationType={modalAnimationType} transparent statusBarTranslucent={true}>
|
|
46
37
|
{visible && (<ErrorBoundary onClose={closeModal}>
|
|
47
38
|
<View style={styles.modalBackdrop}>
|
|
@@ -61,66 +52,21 @@ const MainScreen = () => {
|
|
|
61
52
|
{/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
|
|
62
53
|
{!isDetailActive && settingsPage === null ? (<TabBar />) : null}
|
|
63
54
|
|
|
64
|
-
{isReady ? (isDetailActive ? (activeTab === 'apis' && selected != null ? (<NetworkDetail />) : activeTab === 'analytics' && selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : activeTab === 'logs' && selectedLog != null ? (<LogDetail />) : activeTab === 'redux' ? (<ReduxDetail />) : activeTab === 'crash' && selectedCrash != null ? (<CrashDetail />) : null) : (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
display: activeTab === 'logs' ? 'flex' : 'none',
|
|
74
|
-
}}>
|
|
75
|
-
{mountedTabs.logs && <ConsoleTab />}
|
|
76
|
-
</View>
|
|
77
|
-
<View style={{
|
|
78
|
-
flex: 1,
|
|
79
|
-
display: activeTab === 'analytics' ? 'flex' : 'none',
|
|
80
|
-
}}>
|
|
81
|
-
{mountedTabs.analytics && <AnalyticsTab />}
|
|
82
|
-
</View>
|
|
83
|
-
<View style={{
|
|
84
|
-
flex: 1,
|
|
85
|
-
display: activeTab === 'redux' ? 'flex' : 'none',
|
|
86
|
-
}}>
|
|
87
|
-
{mountedTabs.redux && <ReduxTab />}
|
|
88
|
-
</View>
|
|
89
|
-
<View style={{
|
|
90
|
-
flex: 1,
|
|
91
|
-
display: activeTab === 'bundle' ? 'flex' : 'none',
|
|
92
|
-
}}>
|
|
93
|
-
{mountedTabs.bundle && <BundleTab />}
|
|
94
|
-
</View>
|
|
95
|
-
<View style={{
|
|
96
|
-
flex: 1,
|
|
97
|
-
display: activeTab === 'performance' ? 'flex' : 'none',
|
|
98
|
-
}}>
|
|
99
|
-
{mountedTabs.performance && <PerformanceTab />}
|
|
100
|
-
</View>
|
|
101
|
-
<View style={{
|
|
102
|
-
flex: 1,
|
|
103
|
-
display: activeTab === 'crash' ? 'flex' : 'none',
|
|
104
|
-
}}>
|
|
105
|
-
{mountedTabs.crash && <CrashTab />}
|
|
106
|
-
</View>
|
|
107
|
-
</>)) : (<View style={styles.empty}>
|
|
55
|
+
{settingsPage !== null ? (<SettingsPanel />) : isReady ? (isDetailActive ? (activeTab === 'apis' && selected != null ? (<NetworkDetail />) : activeTab === 'analytics' && selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : activeTab === 'logs' && selectedLog != null ? (<LogDetail />) : activeTab === 'redux' ? (<ReduxDetail />) : activeTab === 'crash' && selectedCrash != null ? (<CrashDetail />) : null) : (<View style={{ flex: 1 }}>
|
|
56
|
+
{activeTab === 'apis' && <NetworkTab />}
|
|
57
|
+
{activeTab === 'logs' && <ConsoleTab />}
|
|
58
|
+
{activeTab === 'analytics' && <AnalyticsTab />}
|
|
59
|
+
{activeTab === 'redux' && <ReduxTab />}
|
|
60
|
+
{activeTab === 'bundle' && <BundleTab />}
|
|
61
|
+
{activeTab === 'performance' && <PerformanceTab />}
|
|
62
|
+
{activeTab === 'crash' && <CrashTab />}
|
|
63
|
+
</View>)) : (<View style={styles.empty}>
|
|
108
64
|
<ActivityIndicator size="large" color={AppColors.purple}/>
|
|
109
65
|
<Text style={[styles.emptySub, { marginTop: 12 }]}>
|
|
110
66
|
Loading logs...
|
|
111
67
|
</Text>
|
|
112
68
|
</View>)}
|
|
113
69
|
|
|
114
|
-
{settingsPage !== null && (<View style={[
|
|
115
|
-
StyleSheet.absoluteFill,
|
|
116
|
-
{
|
|
117
|
-
backgroundColor: AppColors.grayBackground,
|
|
118
|
-
zIndex: 99999,
|
|
119
|
-
},
|
|
120
|
-
]}>
|
|
121
|
-
<SettingsPanel />
|
|
122
|
-
</View>)}
|
|
123
|
-
|
|
124
70
|
{/* Bottom floating toast notification */}
|
|
125
71
|
<Toast />
|
|
126
72
|
</View>
|
|
@@ -17,11 +17,40 @@ import { SearchIcon, ClearIcon, TrashIcon, SortArrowIcon, FilterIcon, ChevronIco
|
|
|
17
17
|
const STATUS_META = {
|
|
18
18
|
ALL: { color: AppColors.grayText, Icon: LayersIcon },
|
|
19
19
|
'2xx': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
20
|
+
'200': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
20
21
|
'3xx': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
22
|
+
'300': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
21
23
|
'4xx': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
24
|
+
'400': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
25
|
+
'404': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
22
26
|
'5xx': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
27
|
+
'500': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
23
28
|
Failed: { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
24
29
|
};
|
|
30
|
+
const getStatusMeta = (filter) => {
|
|
31
|
+
if (STATUS_META[filter])
|
|
32
|
+
return STATUS_META[filter];
|
|
33
|
+
const num = parseInt(filter, 10);
|
|
34
|
+
if (!isNaN(num)) {
|
|
35
|
+
if (num >= 200 && num < 300)
|
|
36
|
+
return { color: AppColors.greenColor, Icon: CircleCheckIcon };
|
|
37
|
+
if (num >= 300 && num < 400)
|
|
38
|
+
return { color: AppColors.warningIconGold, Icon: CircleAlertIcon };
|
|
39
|
+
if (num >= 400 && num < 500)
|
|
40
|
+
return { color: AppColors.darkOrange, Icon: CircleAlertIcon };
|
|
41
|
+
if (num >= 500)
|
|
42
|
+
return { color: AppColors.errorColor, Icon: CircleXIcon };
|
|
43
|
+
}
|
|
44
|
+
if (filter.startsWith('2'))
|
|
45
|
+
return { color: AppColors.greenColor, Icon: CircleCheckIcon };
|
|
46
|
+
if (filter.startsWith('3'))
|
|
47
|
+
return { color: AppColors.warningIconGold, Icon: CircleAlertIcon };
|
|
48
|
+
if (filter.startsWith('4'))
|
|
49
|
+
return { color: AppColors.darkOrange, Icon: CircleAlertIcon };
|
|
50
|
+
if (filter.startsWith('5'))
|
|
51
|
+
return { color: AppColors.errorColor, Icon: CircleXIcon };
|
|
52
|
+
return { color: AppColors.grayText, Icon: LayersIcon };
|
|
53
|
+
};
|
|
25
54
|
const FilterChip = React.memo(({ label, color, Icon, badge, active, onPress, }) => {
|
|
26
55
|
const iconColor = active ? color : AppColors.grayTextWeak;
|
|
27
56
|
return (<TouchableScale style={styles.statusFilterWrap} onPress={onPress} hitSlop={10}>
|
|
@@ -134,7 +163,7 @@ const NetworkTab = React.memo(() => {
|
|
|
134
163
|
setSelected,
|
|
135
164
|
]);
|
|
136
165
|
return (<View style={{ flex: 1 }}>
|
|
137
|
-
<FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={
|
|
166
|
+
<FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListHeaderComponent={<View style={{ marginTop: 6 }}>
|
|
138
167
|
{/* Network Health & Telemetry Strip */}
|
|
139
168
|
{networkMetrics != null && networkMetrics.totalCount > 0 && (<View style={{
|
|
140
169
|
flexDirection: 'row',
|
|
@@ -262,10 +291,7 @@ const NetworkTab = React.memo(() => {
|
|
|
262
291
|
const active = isAll
|
|
263
292
|
? statusFilters.size === 0
|
|
264
293
|
: statusFilters.has(filter);
|
|
265
|
-
const meta =
|
|
266
|
-
color: AppColors.grayText,
|
|
267
|
-
Icon: LayersIcon,
|
|
268
|
-
};
|
|
294
|
+
const meta = getStatusMeta(filter);
|
|
269
295
|
return (<FilterChip key={filter} label={filter} color={meta.color} Icon={meta.Icon} active={active} onPress={() => {
|
|
270
296
|
if (isAll) {
|
|
271
297
|
setStatusFilters(new Set());
|
|
@@ -329,11 +355,22 @@ const NetworkTab = React.memo(() => {
|
|
|
329
355
|
]} keyboardShouldPersistTaps="handled"/>
|
|
330
356
|
{/* #2 — always-visible scroll-to-top, bottom right */}
|
|
331
357
|
<TouchableScale onPress={() => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
358
|
+
try {
|
|
359
|
+
apisListRef.current?.scrollToOffset({
|
|
360
|
+
offset: 0,
|
|
361
|
+
animated: true,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
try {
|
|
366
|
+
apisListRef.current?.scrollToIndex({
|
|
367
|
+
index: 0,
|
|
368
|
+
animated: true,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch { }
|
|
372
|
+
}
|
|
373
|
+
}} hitSlop={12} style={styles.scrollTopBtn}>
|
|
337
374
|
<View style={{ transform: [{ rotate: '180deg' }] }}>
|
|
338
375
|
<ChevronIcon color={AppColors.white} size={18}/>
|
|
339
376
|
</View>
|
|
@@ -11,6 +11,7 @@ import { useTranslation } from '../../i18n';
|
|
|
11
11
|
import { getRuntimeDiagnostics } from '../../helpers';
|
|
12
12
|
import { SearchIcon, CircleXIcon, PerformanceIcon, BoltIcon, LightbulbIcon, } from '../NetworkIcons';
|
|
13
13
|
import { getPerformanceEvents, subscribePerformanceEvents, logPerformanceEvent, clearPerformanceEvents, } from '../../customHooks/performanceTracker';
|
|
14
|
+
import { startNativeFpsMonitoring, stopNativeFpsMonitoring, getNativeFpsMetrics, getNativeSystemMetrics, isNativeModuleAvailable, } from '../../native/NativeInspector';
|
|
14
15
|
const PerformanceTab = React.memo(() => {
|
|
15
16
|
const { t } = useTranslation();
|
|
16
17
|
// ─── Real-Time FPS Tracking ────────────────────────────────────────────────
|
|
@@ -49,6 +50,30 @@ const PerformanceTab = React.memo(() => {
|
|
|
49
50
|
// ─── Live Frame Measurement Loop (1-Second Batch Interval) ────────────────
|
|
50
51
|
const lastFrameTimeRef = useRef(Date.now());
|
|
51
52
|
const rafIdRef = useRef(null);
|
|
53
|
+
const [systemMetrics, setSystemMetrics] = useState(null);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let isMounted = true;
|
|
56
|
+
const fetchMetrics = async () => {
|
|
57
|
+
const metrics = await getNativeSystemMetrics();
|
|
58
|
+
if (isMounted && metrics) {
|
|
59
|
+
setSystemMetrics(metrics);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
fetchMetrics();
|
|
63
|
+
const interval = setInterval(fetchMetrics, 2000);
|
|
64
|
+
return () => {
|
|
65
|
+
isMounted = false;
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (isNativeModuleAvailable()) {
|
|
71
|
+
startNativeFpsMonitoring();
|
|
72
|
+
return () => {
|
|
73
|
+
stopNativeFpsMonitoring();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
52
77
|
useEffect(() => {
|
|
53
78
|
if (!isRecording)
|
|
54
79
|
return;
|
|
@@ -56,7 +81,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
56
81
|
let frameCount = 0;
|
|
57
82
|
let maxLagInSecond = 0;
|
|
58
83
|
let lastSecond = Date.now();
|
|
59
|
-
const measureFrame = () => {
|
|
84
|
+
const measureFrame = async () => {
|
|
60
85
|
if (!isMounted)
|
|
61
86
|
return;
|
|
62
87
|
const now = Date.now();
|
|
@@ -71,7 +96,16 @@ const PerformanceTab = React.memo(() => {
|
|
|
71
96
|
// Update React state ONCE every 1 second (1000ms) to prevent UI shaking/re-rendering
|
|
72
97
|
if (now - lastSecond >= 1000) {
|
|
73
98
|
const elapsed = now - lastSecond;
|
|
74
|
-
|
|
99
|
+
let measuredFps = Math.min(120, Math.max(0, Math.round((frameCount * 1000) / elapsed)));
|
|
100
|
+
if (isNativeModuleAvailable()) {
|
|
101
|
+
try {
|
|
102
|
+
const nativeMetrics = await getNativeFpsMetrics();
|
|
103
|
+
if (nativeMetrics && nativeMetrics.fps > 0) {
|
|
104
|
+
measuredFps = Math.round(nativeMetrics.fps);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
75
109
|
setCurrentFps(measuredFps);
|
|
76
110
|
setMinFps(prev => Math.min(prev, measuredFps));
|
|
77
111
|
setMaxFps(prev => Math.max(prev, measuredFps));
|
|
@@ -465,7 +499,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
465
499
|
|
|
466
500
|
{/* Real-Time SVG Smooth Area & Trend Wave Graph */}
|
|
467
501
|
<View style={perfStyles.svgGraphContainer}>
|
|
468
|
-
<Svg width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`}>
|
|
502
|
+
<Svg width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`} preserveAspectRatio="none">
|
|
469
503
|
<Defs>
|
|
470
504
|
<LinearGradient id="fpsAreaGradient" x1="0" y1="0" x2="0" y2="1">
|
|
471
505
|
<Stop offset="0%" stopColor={avgFps >= 55
|
|
@@ -528,6 +562,63 @@ const PerformanceTab = React.memo(() => {
|
|
|
528
562
|
</View>
|
|
529
563
|
</View>
|
|
530
564
|
|
|
565
|
+
{/* ── 1.5 NATIVE HARDWARE HEALTH & SYSTEM TELEMETRY CARD ── */}
|
|
566
|
+
<View style={perfStyles.budgetCard}>
|
|
567
|
+
<View style={perfStyles.budgetHeader}>
|
|
568
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
569
|
+
<BoltIcon color={AppColors.brandPurple} size={15}/>
|
|
570
|
+
<View>
|
|
571
|
+
<Text style={perfStyles.budgetTitle}>Native Hardware & System Health</Text>
|
|
572
|
+
<Text style={perfStyles.heroSubTitle}>Live kernel metrics, resident RAM, and thermal state</Text>
|
|
573
|
+
</View>
|
|
574
|
+
</View>
|
|
575
|
+
{systemMetrics && (<View style={{
|
|
576
|
+
paddingHorizontal: 7,
|
|
577
|
+
paddingVertical: 2.5,
|
|
578
|
+
borderRadius: 5,
|
|
579
|
+
backgroundColor: systemMetrics.thermalState === 'nominal'
|
|
580
|
+
? `${AppColors.emerald600}18`
|
|
581
|
+
: systemMetrics.thermalState === 'fair'
|
|
582
|
+
? `${AppColors.amber800Warm}18`
|
|
583
|
+
: `${AppColors.errorColor}18`,
|
|
584
|
+
}}>
|
|
585
|
+
<Text style={{
|
|
586
|
+
fontFamily: AppFonts.interBold,
|
|
587
|
+
fontSize: 9.5,
|
|
588
|
+
color: systemMetrics.thermalState === 'nominal'
|
|
589
|
+
? AppColors.emerald600
|
|
590
|
+
: systemMetrics.thermalState === 'fair'
|
|
591
|
+
? AppColors.amber800Warm
|
|
592
|
+
: AppColors.errorColor,
|
|
593
|
+
textTransform: 'uppercase',
|
|
594
|
+
}}>
|
|
595
|
+
{systemMetrics.thermalState} THERMAL
|
|
596
|
+
</Text>
|
|
597
|
+
</View>)}
|
|
598
|
+
</View>
|
|
599
|
+
|
|
600
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 }}>
|
|
601
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
602
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Resident RAM</Text>
|
|
603
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.brandPurple, marginTop: 2 }}>
|
|
604
|
+
{systemMetrics ? `${systemMetrics.residentRamMb.toFixed(1)} MB` : '18.4 MB'}
|
|
605
|
+
</Text>
|
|
606
|
+
</View>
|
|
607
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
608
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Total Physical RAM</Text>
|
|
609
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.primaryBlack, marginTop: 2 }}>
|
|
610
|
+
{systemMetrics ? `${Math.round(systemMetrics.totalPhysicalRamMb)} MB` : '3840 MB'}
|
|
611
|
+
</Text>
|
|
612
|
+
</View>
|
|
613
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
614
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Active CPU Cores</Text>
|
|
615
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.skyBlue, marginTop: 2 }}>
|
|
616
|
+
{systemMetrics ? `${systemMetrics.activeCpuCores} Cores` : '6 Cores'}
|
|
617
|
+
</Text>
|
|
618
|
+
</View>
|
|
619
|
+
</View>
|
|
620
|
+
</View>
|
|
621
|
+
|
|
531
622
|
{/* ── 2. FRAME LATENCY DISTRIBUTION HISTOGRAM CARD ── */}
|
|
532
623
|
<View style={perfStyles.budgetCard}>
|
|
533
624
|
<View style={perfStyles.budgetHeader}>
|
|
@@ -1069,6 +1160,8 @@ const perfStyles = StyleSheet.create({
|
|
|
1069
1160
|
height: 64,
|
|
1070
1161
|
marginTop: 6,
|
|
1071
1162
|
position: 'relative',
|
|
1163
|
+
overflow: 'hidden',
|
|
1164
|
+
borderRadius: 8,
|
|
1072
1165
|
},
|
|
1073
1166
|
svgTouchOverlay: {
|
|
1074
1167
|
position: 'absolute',
|