react-native-inapp-inspector 2.2.9 → 2.3.1

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 (105) hide show
  1. package/dist/commonjs/components/AppHeaderLogo.js +45 -12
  2. package/dist/commonjs/components/BrandSquareIcon.js +92 -49
  3. package/dist/commonjs/components/EmptyState.d.ts +7 -2
  4. package/dist/commonjs/components/EmptyState.js +11 -5
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +29 -18
  6. package/dist/commonjs/components/Inspector/ConsoleTab.js +6 -1
  7. package/dist/commonjs/components/Inspector/CrashDetail.js +43 -9
  8. package/dist/commonjs/components/Inspector/CrashTab.js +34 -3
  9. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +80 -16
  10. package/dist/commonjs/components/Inspector/InspectorHeader.js +16 -3
  11. package/dist/commonjs/components/Inspector/MainScreen.js +7 -11
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +3 -2
  13. package/dist/commonjs/components/Inspector/NetworkTab.js +103 -4
  14. package/dist/commonjs/components/Inspector/ReduxTab.js +19 -3
  15. package/dist/commonjs/components/Inspector/SettingsPanel.js +4 -4
  16. package/dist/commonjs/components/Inspector/StorageTab.js +123 -92
  17. package/dist/commonjs/components/JsonViewer.js +2 -0
  18. package/dist/commonjs/components/MetaAccordion.js +15 -3
  19. package/dist/commonjs/components/SkeletonPlaceholder.d.ts +6 -0
  20. package/dist/commonjs/components/SkeletonPlaceholder.js +208 -0
  21. package/dist/commonjs/constants/version.d.ts +1 -1
  22. package/dist/commonjs/constants/version.js +1 -1
  23. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  24. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  25. package/dist/commonjs/customHooks/storageInspector.d.ts +2 -2
  26. package/dist/commonjs/customHooks/storageInspector.js +17 -13
  27. package/dist/commonjs/helpers/index.d.ts +6 -0
  28. package/dist/commonjs/helpers/index.js +196 -6
  29. package/dist/commonjs/helpers/searchQueryParser.js +62 -20
  30. package/dist/commonjs/i18n/index.d.ts +13 -0
  31. package/dist/commonjs/i18n/index.js +35 -2
  32. package/dist/commonjs/index.d.ts +3 -0
  33. package/dist/commonjs/index.js +108 -35
  34. package/dist/commonjs/styles/AppColors.d.ts +4 -0
  35. package/dist/commonjs/styles/AppColors.js +11 -1
  36. package/dist/commonjs/styles/AppFonts.d.ts +11 -0
  37. package/dist/commonjs/styles/AppFonts.js +11 -1
  38. package/dist/commonjs/types/interfaces.d.ts +2 -2
  39. package/dist/esm/components/AppHeaderLogo.js +45 -12
  40. package/dist/esm/components/BrandSquareIcon.js +92 -49
  41. package/dist/esm/components/EmptyState.d.ts +7 -2
  42. package/dist/esm/components/EmptyState.js +11 -5
  43. package/dist/esm/components/Inspector/AnalyticsTab.js +30 -19
  44. package/dist/esm/components/Inspector/ConsoleTab.js +6 -1
  45. package/dist/esm/components/Inspector/CrashDetail.js +43 -9
  46. package/dist/esm/components/Inspector/CrashTab.js +34 -3
  47. package/dist/esm/components/Inspector/DeviceInfoTab.js +80 -16
  48. package/dist/esm/components/Inspector/InspectorHeader.js +17 -4
  49. package/dist/esm/components/Inspector/MainScreen.js +8 -12
  50. package/dist/esm/components/Inspector/NetworkDetail.js +3 -2
  51. package/dist/esm/components/Inspector/NetworkTab.js +103 -4
  52. package/dist/esm/components/Inspector/ReduxTab.js +19 -3
  53. package/dist/esm/components/Inspector/SettingsPanel.js +4 -4
  54. package/dist/esm/components/Inspector/StorageTab.js +124 -93
  55. package/dist/esm/components/JsonViewer.js +2 -0
  56. package/dist/esm/components/MetaAccordion.js +15 -3
  57. package/dist/esm/components/SkeletonPlaceholder.d.ts +6 -0
  58. package/dist/esm/components/SkeletonPlaceholder.js +173 -0
  59. package/dist/esm/constants/version.d.ts +1 -1
  60. package/dist/esm/constants/version.js +1 -1
  61. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  62. package/dist/esm/customHooks/networkLogger.js +8 -0
  63. package/dist/esm/customHooks/storageInspector.d.ts +2 -2
  64. package/dist/esm/customHooks/storageInspector.js +17 -13
  65. package/dist/esm/helpers/index.d.ts +6 -0
  66. package/dist/esm/helpers/index.js +193 -5
  67. package/dist/esm/helpers/searchQueryParser.js +62 -20
  68. package/dist/esm/i18n/index.d.ts +13 -0
  69. package/dist/esm/i18n/index.js +33 -3
  70. package/dist/esm/index.d.ts +3 -0
  71. package/dist/esm/index.js +97 -37
  72. package/dist/esm/styles/AppColors.d.ts +4 -0
  73. package/dist/esm/styles/AppColors.js +9 -0
  74. package/dist/esm/styles/AppFonts.d.ts +11 -0
  75. package/dist/esm/styles/AppFonts.js +9 -0
  76. package/dist/esm/types/interfaces.d.ts +2 -2
  77. package/package.json +1 -1
  78. package/src/components/AppHeaderLogo.tsx +46 -12
  79. package/src/components/BrandSquareIcon.tsx +127 -78
  80. package/src/components/EmptyState.tsx +27 -5
  81. package/src/components/Inspector/AnalyticsTab.tsx +48 -27
  82. package/src/components/Inspector/ConsoleTab.tsx +10 -0
  83. package/src/components/Inspector/CrashDetail.tsx +65 -9
  84. package/src/components/Inspector/CrashTab.tsx +41 -3
  85. package/src/components/Inspector/DeviceInfoTab.tsx +87 -16
  86. package/src/components/Inspector/InspectorHeader.tsx +24 -4
  87. package/src/components/Inspector/MainScreen.tsx +7 -11
  88. package/src/components/Inspector/NetworkDetail.tsx +4 -4
  89. package/src/components/Inspector/NetworkTab.tsx +129 -4
  90. package/src/components/Inspector/ReduxTab.tsx +26 -8
  91. package/src/components/Inspector/SettingsPanel.tsx +4 -4
  92. package/src/components/Inspector/StorageTab.tsx +191 -117
  93. package/src/components/JsonViewer.tsx +2 -0
  94. package/src/components/MetaAccordion.tsx +18 -3
  95. package/src/components/SkeletonPlaceholder.tsx +206 -0
  96. package/src/constants/version.ts +1 -1
  97. package/src/customHooks/networkLogger.ts +15 -0
  98. package/src/customHooks/storageInspector.ts +18 -14
  99. package/src/helpers/index.ts +213 -5
  100. package/src/helpers/searchQueryParser.ts +63 -20
  101. package/src/i18n/index.ts +38 -3
  102. package/src/index.tsx +124 -37
  103. package/src/styles/AppColors.ts +9 -0
  104. package/src/styles/AppFonts.ts +16 -0
  105. package/src/types/interfaces.ts +2 -2
@@ -0,0 +1,173 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Animated, StyleSheet, View } from 'react-native';
3
+ import { AppColors } from '../styles/AppColors';
4
+ const SkeletonPlaceholder = React.memo(function SkeletonPlaceholder({ cardCount = 4, }) {
5
+ const shimmerAnim = useRef(new Animated.Value(0.35)).current;
6
+ useEffect(() => {
7
+ const animation = Animated.loop(Animated.sequence([
8
+ Animated.timing(shimmerAnim, {
9
+ toValue: 0.85,
10
+ duration: 750,
11
+ useNativeDriver: true,
12
+ }),
13
+ Animated.timing(shimmerAnim, {
14
+ toValue: 0.35,
15
+ duration: 750,
16
+ useNativeDriver: true,
17
+ }),
18
+ ]));
19
+ animation.start();
20
+ return () => animation.stop();
21
+ }, [shimmerAnim]);
22
+ return (<View style={skeletonStyles.container}>
23
+ {/* ─── Search & Scope Toolbar Skeleton ─── */}
24
+ <View style={skeletonStyles.toolbarSkeleton}>
25
+ <Animated.View style={[
26
+ skeletonStyles.searchBarSkeleton,
27
+ { opacity: shimmerAnim },
28
+ ]}/>
29
+ <View style={skeletonStyles.actionButtonsRow}>
30
+ <Animated.View style={[skeletonStyles.iconButtonSkeleton, { opacity: shimmerAnim }]}/>
31
+ <Animated.View style={[skeletonStyles.iconButtonSkeleton, { opacity: shimmerAnim }]}/>
32
+ </View>
33
+ </View>
34
+
35
+ {/* ─── Quick Filter Chips Skeleton Strip ─── */}
36
+ <View style={skeletonStyles.chipStripSkeleton}>
37
+ <Animated.View style={[skeletonStyles.chipSkeleton, { width: 48, opacity: shimmerAnim }]}/>
38
+ <Animated.View style={[skeletonStyles.chipSkeleton, { width: 68, opacity: shimmerAnim }]}/>
39
+ <Animated.View style={[skeletonStyles.chipSkeleton, { width: 76, opacity: shimmerAnim }]}/>
40
+ <Animated.View style={[skeletonStyles.chipSkeleton, { width: 58, opacity: shimmerAnim }]}/>
41
+ </View>
42
+
43
+ {/* ─── List Cards Skeleton ─── */}
44
+ {Array.from({ length: cardCount }).map((_, i) => (<Animated.View key={`skeleton_card_${i}`} style={[skeletonStyles.cardSkeleton, { opacity: shimmerAnim }]}>
45
+ {/* Top row: Status pill + Method + Time */}
46
+ <View style={skeletonStyles.cardTopRow}>
47
+ <View style={skeletonStyles.badgeGroup}>
48
+ <View style={skeletonStyles.statusBadgeSkeleton}/>
49
+ <View style={skeletonStyles.methodBadgeSkeleton}/>
50
+ </View>
51
+ <View style={skeletonStyles.timeSkeleton}/>
52
+ </View>
53
+
54
+ {/* Middle row: URL lines */}
55
+ <View style={skeletonStyles.urlLineLong}/>
56
+ <View style={skeletonStyles.urlLineShort}/>
57
+
58
+ {/* Bottom row: Latency & Size */}
59
+ <View style={skeletonStyles.cardBottomRow}>
60
+ <View style={skeletonStyles.metaPillSkeleton}/>
61
+ <View style={skeletonStyles.metaPillSkeleton}/>
62
+ </View>
63
+ </Animated.View>))}
64
+ </View>);
65
+ });
66
+ const skeletonStyles = StyleSheet.create({
67
+ container: {
68
+ flex: 1,
69
+ paddingHorizontal: 12,
70
+ paddingTop: 8,
71
+ },
72
+ toolbarSkeleton: {
73
+ flexDirection: 'row',
74
+ alignItems: 'center',
75
+ gap: 8,
76
+ marginBottom: 8,
77
+ },
78
+ searchBarSkeleton: {
79
+ flex: 1,
80
+ height: 36,
81
+ borderRadius: 8,
82
+ backgroundColor: AppColors.graySurface,
83
+ borderWidth: 1,
84
+ borderColor: AppColors.dividerColor,
85
+ },
86
+ actionButtonsRow: {
87
+ flexDirection: 'row',
88
+ gap: 6,
89
+ },
90
+ iconButtonSkeleton: {
91
+ width: 36,
92
+ height: 36,
93
+ borderRadius: 8,
94
+ backgroundColor: AppColors.graySurface,
95
+ borderWidth: 1,
96
+ borderColor: AppColors.dividerColor,
97
+ },
98
+ chipStripSkeleton: {
99
+ flexDirection: 'row',
100
+ gap: 6,
101
+ marginBottom: 10,
102
+ },
103
+ chipSkeleton: {
104
+ height: 24,
105
+ borderRadius: 6,
106
+ backgroundColor: AppColors.graySurface,
107
+ borderWidth: 1,
108
+ borderColor: AppColors.dividerColor,
109
+ },
110
+ cardSkeleton: {
111
+ backgroundColor: AppColors.primaryLight,
112
+ borderRadius: 10,
113
+ padding: 12,
114
+ marginBottom: 8,
115
+ borderWidth: 1,
116
+ borderColor: AppColors.dividerColor,
117
+ },
118
+ cardTopRow: {
119
+ flexDirection: 'row',
120
+ justifyContent: 'space-between',
121
+ alignItems: 'center',
122
+ marginBottom: 8,
123
+ },
124
+ badgeGroup: {
125
+ flexDirection: 'row',
126
+ alignItems: 'center',
127
+ gap: 6,
128
+ },
129
+ statusBadgeSkeleton: {
130
+ width: 38,
131
+ height: 18,
132
+ borderRadius: 4,
133
+ backgroundColor: AppColors.graySurface,
134
+ },
135
+ methodBadgeSkeleton: {
136
+ width: 44,
137
+ height: 18,
138
+ borderRadius: 4,
139
+ backgroundColor: AppColors.graySurface,
140
+ },
141
+ timeSkeleton: {
142
+ width: 48,
143
+ height: 12,
144
+ borderRadius: 4,
145
+ backgroundColor: AppColors.graySurface,
146
+ },
147
+ urlLineLong: {
148
+ height: 13,
149
+ borderRadius: 4,
150
+ backgroundColor: AppColors.graySurface,
151
+ marginBottom: 5,
152
+ width: '90%',
153
+ },
154
+ urlLineShort: {
155
+ height: 11,
156
+ borderRadius: 4,
157
+ backgroundColor: AppColors.graySurface,
158
+ marginBottom: 8,
159
+ width: '55%',
160
+ },
161
+ cardBottomRow: {
162
+ flexDirection: 'row',
163
+ gap: 8,
164
+ marginTop: 2,
165
+ },
166
+ metaPillSkeleton: {
167
+ width: 52,
168
+ height: 14,
169
+ borderRadius: 4,
170
+ backgroundColor: AppColors.graySurface,
171
+ },
172
+ });
173
+ export default SkeletonPlaceholder;
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "2.2.9";
1
+ export declare const LIB_VERSION = "2.3.1";
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED FILE — do not edit by hand.
2
2
  // Regenerated from package.json on every build by scripts/gen-version.js.
3
- export const LIB_VERSION = '2.2.9';
3
+ export const LIB_VERSION = '2.3.1';
@@ -1,3 +1,4 @@
1
+ import { RouteInfo } from "../types";
1
2
  type NetworkLog = {
2
3
  id: number;
3
4
  url: string;
@@ -11,7 +12,9 @@ type NetworkLog = {
11
12
  client?: string;
12
13
  requestHeaders?: Record<string, string>;
13
14
  responseHeaders?: Record<string, string>;
15
+ routeInfo?: RouteInfo;
14
16
  };
17
+ export declare const setRouteInfoProvider: (provider: (() => RouteInfo | null) | null) => void;
15
18
  export declare const setNetworkModuleEnabled: (enabled: boolean) => void;
16
19
  export declare const getNetworkModuleEnabled: () => boolean;
17
20
  export declare const subscribeNetworkLogs: (callback: (logs: NetworkLog[]) => void) => () => void;
@@ -4,6 +4,10 @@ let logs = [];
4
4
  let listeners = [];
5
5
  let counter = 0;
6
6
  let isNetworkModuleEnabled = true;
7
+ let currentRouteProvider = null;
8
+ export const setRouteInfoProvider = (provider) => {
9
+ currentRouteProvider = provider;
10
+ };
7
11
  export const setNetworkModuleEnabled = (enabled) => {
8
12
  isNetworkModuleEnabled = enabled;
9
13
  };
@@ -185,6 +189,7 @@ export const setupNetworkLogger = () => {
185
189
  else if (caller?.toLowerCase().includes('axios')) {
186
190
  client = 'axios';
187
191
  }
192
+ const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
188
193
  addOrUpdateLog({
189
194
  id,
190
195
  url: finalUrl,
@@ -192,6 +197,7 @@ export const setupNetworkLogger = () => {
192
197
  startTime: start,
193
198
  caller,
194
199
  client,
200
+ routeInfo: currentRoute || undefined,
195
201
  request: method === "GET" ? undefined : parseRequestData(options?.body),
196
202
  requestHeaders,
197
203
  });
@@ -298,6 +304,7 @@ export const addAxiosInterceptors = (axiosInstance) => {
298
304
  config.__logId = id;
299
305
  config.__logStart = start;
300
306
  config.__logCaller = caller;
307
+ const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
301
308
  addOrUpdateLog({
302
309
  id,
303
310
  url,
@@ -305,6 +312,7 @@ export const addAxiosInterceptors = (axiosInstance) => {
305
312
  startTime: start,
306
313
  caller,
307
314
  client: 'axios',
315
+ routeInfo: currentRoute || undefined,
308
316
  request: method === "GET" ? undefined : parseRequestData(config.data),
309
317
  requestHeaders: normaliseHeaders(config.headers),
310
318
  });
@@ -37,11 +37,11 @@ export declare const getRegisteredMMKVInstanceIds: () => string[];
37
37
  */
38
38
  export declare const calculateByteSize: (str: string) => number;
39
39
  /**
40
- * Determine type and format value safely
40
+ * Determine type and format value safely without blocking JS thread
41
41
  */
42
42
  export declare const analyzeStorageValue: (rawVal: any) => {
43
43
  strVal: string;
44
- parsedVal?: any;
44
+ parsedValue?: any;
45
45
  type: "json" | "string" | "number" | "boolean" | "null";
46
46
  byteSize: number;
47
47
  };
@@ -255,34 +255,38 @@ export const calculateByteSize = (str) => {
255
255
  return str.length * 2;
256
256
  };
257
257
  /**
258
- * Determine type and format value safely
258
+ * Determine type and format value safely without blocking JS thread
259
259
  */
260
260
  export const analyzeStorageValue = (rawVal) => {
261
261
  if (rawVal === null || rawVal === undefined) {
262
262
  return { strVal: 'null', type: 'null', byteSize: 4 };
263
263
  }
264
264
  const strVal = typeof rawVal === 'string' ? rawVal : String(rawVal);
265
- const byteSize = calculateByteSize(strVal);
265
+ const byteSize = strVal.length * 2;
266
266
  if (typeof rawVal === 'number') {
267
- return { strVal, parsedVal: rawVal, type: 'number', byteSize };
267
+ return { strVal, parsedValue: rawVal, type: 'number', byteSize };
268
268
  }
269
269
  if (typeof rawVal === 'boolean') {
270
- return { strVal, parsedVal: rawVal, type: 'boolean', byteSize };
270
+ return { strVal, parsedValue: rawVal, type: 'boolean', byteSize };
271
271
  }
272
272
  const trimmed = strVal.trim();
273
273
  if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
274
274
  (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
275
- try {
276
- const parsed = JSON.parse(trimmed);
277
- return { strVal, parsedVal: parsed, type: 'json', byteSize };
275
+ // For small/medium JSON, parse eagerly; for huge payloads (>10KB), parse lazily on expand
276
+ if (trimmed.length < 10000) {
277
+ try {
278
+ const parsed = JSON.parse(trimmed);
279
+ return { strVal, parsedValue: parsed, type: 'json', byteSize };
280
+ }
281
+ catch { }
278
282
  }
279
- catch { }
283
+ return { strVal, type: 'json', byteSize };
280
284
  }
281
285
  if (trimmed === 'true' || trimmed === 'false') {
282
- return { strVal, parsedVal: trimmed === 'true', type: 'boolean', byteSize };
286
+ return { strVal, parsedValue: trimmed === 'true', type: 'boolean', byteSize };
283
287
  }
284
288
  if (!isNaN(Number(trimmed)) && trimmed !== '') {
285
- return { strVal, parsedVal: Number(trimmed), type: 'number', byteSize };
289
+ return { strVal, parsedValue: Number(trimmed), type: 'number', byteSize };
286
290
  }
287
291
  return { strVal, type: 'string', byteSize };
288
292
  };
@@ -307,7 +311,7 @@ export const fetchStorageEntries = async (driver, instanceId) => {
307
311
  entries.push({
308
312
  key: k,
309
313
  value: analysis.strVal,
310
- parsedValue: analysis.parsedVal,
314
+ parsedValue: analysis.parsedValue,
311
315
  type: analysis.type,
312
316
  byteSize: analysis.byteSize,
313
317
  });
@@ -320,7 +324,7 @@ export const fetchStorageEntries = async (driver, instanceId) => {
320
324
  entries.push({
321
325
  key: k,
322
326
  value: analysis.strVal,
323
- parsedValue: analysis.parsedVal,
327
+ parsedValue: analysis.parsedValue,
324
328
  type: analysis.type,
325
329
  byteSize: analysis.byteSize,
326
330
  });
@@ -361,7 +365,7 @@ export const fetchStorageEntries = async (driver, instanceId) => {
361
365
  entries.push({
362
366
  key: k,
363
367
  value: analysis.strVal,
364
- parsedValue: analysis.parsedVal,
368
+ parsedValue: analysis.parsedValue,
365
369
  type: analysis.type,
366
370
  byteSize: analysis.byteSize,
367
371
  });
@@ -15,6 +15,7 @@ export declare const getFetchCommand: (log: NetworkLog) => string;
15
15
  export declare const deduplicateLogs: (raw: NetworkLog[]) => NetworkLog[];
16
16
  export declare const escapeRegex: (str: string) => string;
17
17
  export declare const getNavigationInfo: (state: any, path?: string[]) => RouteInfo;
18
+ export declare const getLogPageName: (log: NetworkLog, routeInfo?: RouteInfo | null) => string;
18
19
  export declare const flattenObject: (obj: any, prefix?: string) => Record<string, any>;
19
20
  export declare const getDiff: (oldObj: any, newObj: any) => DiffResult[];
20
21
  export declare const formatDisplayUrl: (url: string) => string;
@@ -24,6 +25,11 @@ export declare const isAllValuesEmpty: (obj: Record<string, any>) => boolean;
24
25
  export declare const formatDateTimeToAnalytics: (ts: number) => string;
25
26
  export declare const getBundleIdentifier: () => string;
26
27
  export declare const getAppName: () => string;
28
+ export declare const getAppVersionAndBuild: () => {
29
+ version: string;
30
+ build: string;
31
+ formatted: string;
32
+ };
27
33
  export declare const handleOpenExternalLink: (url: string) => void;
28
34
  /** Formats a timestamp as HH:MM:SS (no milliseconds). */
29
35
  export declare const formatTimeShort: (ts: number) => string;
@@ -177,16 +177,33 @@ export const escapeRegex = (str) => {
177
177
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
178
178
  };
179
179
  export const getNavigationInfo = (state, path = []) => {
180
- if (!state?.routes) {
180
+ if (!state) {
181
181
  return {
182
- path: path.length > 0 ? path.join(' ➔ ') : 'Navigators',
182
+ path: path.length > 0 ? path.join(' ➔ ') : '',
183
183
  params: null,
184
184
  };
185
185
  }
186
- const route = state.routes[state.index ?? 0];
186
+ if (state.name && !state.routes) {
187
+ const fullPath = path.length > 0 ? [...path, state.name].join(' ➔ ') : state.name;
188
+ return { path: fullPath, params: state.params || null };
189
+ }
190
+ if (!state.routes ||
191
+ !Array.isArray(state.routes) ||
192
+ state.routes.length === 0) {
193
+ return {
194
+ path: path.length > 0 ? path.join(' ➔ ') : '',
195
+ params: null,
196
+ };
197
+ }
198
+ const index = typeof state.index === 'number' &&
199
+ state.index >= 0 &&
200
+ state.index < state.routes.length
201
+ ? state.index
202
+ : 0;
203
+ const route = state.routes[index];
187
204
  if (!route) {
188
205
  return {
189
- path: path.length > 0 ? path.join(' ➔ ') : 'Navigators',
206
+ path: path.length > 0 ? path.join(' ➔ ') : '',
190
207
  params: null,
191
208
  };
192
209
  }
@@ -194,9 +211,87 @@ export const getNavigationInfo = (state, path = []) => {
194
211
  if (route?.state) {
195
212
  return getNavigationInfo(route.state, currentPath);
196
213
  }
197
- const resolved = currentPath.length > 0 ? currentPath.join(' ➔ ') : 'Navigators';
214
+ const resolved = currentPath.length > 0 ? currentPath.join(' ➔ ') : route.name || '';
198
215
  return { path: resolved, params: route.params || null };
199
216
  };
217
+ export const getLogPageName = (log, routeInfo) => {
218
+ // 1. If routeInfo has a valid path, resolve the leaf screen name
219
+ const effectiveRoute = routeInfo || log?.routeInfo;
220
+ if (effectiveRoute &&
221
+ effectiveRoute.path &&
222
+ effectiveRoute.path !== 'Navigators') {
223
+ const parts = effectiveRoute.path
224
+ .split(' ➔ ')
225
+ .map((s) => s.trim())
226
+ .filter(Boolean);
227
+ if (parts.length > 0) {
228
+ return parts[parts.length - 1]; // Leaf screen name, e.g. "HomeScreen", "ProfileScreen"
229
+ }
230
+ return effectiveRoute.path;
231
+ }
232
+ // 2. Extract screen / component name from log.caller stack trace
233
+ if (log.caller && log.caller !== 'Unknown') {
234
+ try {
235
+ const match = log.caller.match(/([^/\\]+)\.(tsx|jsx|ts|js)/i);
236
+ if (match && match[1]) {
237
+ const file = match[1];
238
+ const ignored = [
239
+ 'networklogger',
240
+ 'fetch',
241
+ 'axios',
242
+ 'apiclient',
243
+ 'request',
244
+ 'http',
245
+ 'index',
246
+ 'nativeinspector',
247
+ 'inappinspector',
248
+ 'bundleanalyzer',
249
+ ];
250
+ if (!ignored.includes(file.toLowerCase())) {
251
+ return file;
252
+ }
253
+ }
254
+ }
255
+ catch (e) { }
256
+ }
257
+ // 3. Fallback to API resource / endpoint category from URL
258
+ if (log.url) {
259
+ try {
260
+ const cleanUrl = log.url.split('?')[0].split('#')[0];
261
+ const withoutProto = cleanUrl.replace(/^https?:\/\//i, '');
262
+ const slashIdx = withoutProto.indexOf('/');
263
+ if (slashIdx !== -1) {
264
+ const pathSegments = withoutProto
265
+ .substring(slashIdx + 1)
266
+ .split('/')
267
+ .filter(Boolean);
268
+ const ignoredSegments = [
269
+ 'api',
270
+ 'v1',
271
+ 'v2',
272
+ 'v3',
273
+ 'v4',
274
+ 'rest',
275
+ 'graphql',
276
+ 'json',
277
+ 'data',
278
+ 'service',
279
+ 'app',
280
+ ];
281
+ const meaningful = pathSegments.filter(s => !ignoredSegments.includes(s.toLowerCase()));
282
+ if (meaningful.length > 0) {
283
+ const seg = meaningful[0];
284
+ return seg.charAt(0).toUpperCase() + seg.slice(1);
285
+ }
286
+ }
287
+ const host = withoutProto.split('/')[0];
288
+ if (host)
289
+ return host;
290
+ }
291
+ catch (e) { }
292
+ }
293
+ return 'General';
294
+ };
200
295
  export const flattenObject = (obj, prefix = '') => {
201
296
  let result = {};
202
297
  if (typeof obj === 'object' && obj !== null) {
@@ -351,6 +446,99 @@ export const getAppName = () => {
351
446
  }
352
447
  return 'App';
353
448
  };
449
+ export const getAppVersionAndBuild = () => {
450
+ let version = '';
451
+ let build = '';
452
+ // 1. Try ExpoApplication / ExponentConstants
453
+ const ExpoApplication = NativeModules.ExpoApplication;
454
+ if (ExpoApplication) {
455
+ if (typeof ExpoApplication.nativeAppVersion === 'string') {
456
+ version = ExpoApplication.nativeAppVersion;
457
+ }
458
+ if (typeof ExpoApplication.nativeBuildVersion === 'string') {
459
+ build = ExpoApplication.nativeBuildVersion;
460
+ }
461
+ }
462
+ const ExponentConstants = NativeModules.ExponentConstants;
463
+ if (ExponentConstants) {
464
+ const manifest = ExponentConstants.manifest || ExponentConstants.expoConfig;
465
+ if (manifest) {
466
+ if (!version && manifest.version) {
467
+ version = String(manifest.version);
468
+ }
469
+ if (!build) {
470
+ if (manifest.ios?.buildNumber) {
471
+ build = String(manifest.ios.buildNumber);
472
+ }
473
+ else if (manifest.android?.versionCode) {
474
+ build = String(manifest.android.versionCode);
475
+ }
476
+ }
477
+ }
478
+ }
479
+ // 2. Try react-native-device-info
480
+ const RNDeviceInfo = NativeModules.RNDeviceInfo;
481
+ if (RNDeviceInfo) {
482
+ if (!version) {
483
+ if (typeof RNDeviceInfo.appVersion === 'string') {
484
+ version = RNDeviceInfo.appVersion;
485
+ }
486
+ else if (typeof RNDeviceInfo.getVersion === 'function') {
487
+ try {
488
+ version = RNDeviceInfo.getVersion();
489
+ }
490
+ catch (e) { }
491
+ }
492
+ }
493
+ if (!build) {
494
+ if (typeof RNDeviceInfo.buildNumber === 'string') {
495
+ build = RNDeviceInfo.buildNumber;
496
+ }
497
+ else if (typeof RNDeviceInfo.getBuildNumber === 'function') {
498
+ try {
499
+ build = RNDeviceInfo.getBuildNumber();
500
+ }
501
+ catch (e) { }
502
+ }
503
+ }
504
+ }
505
+ // 3. Try PlatformConstants / Platform.constants
506
+ const constants = Platform.constants || NativeModules.PlatformConstants;
507
+ if (constants) {
508
+ if (!version &&
509
+ constants.Version &&
510
+ typeof constants.Version === 'string' &&
511
+ constants.Version.includes('.')) {
512
+ version = constants.Version;
513
+ }
514
+ if (!version && constants.reactNativeVersion) {
515
+ const rnv = constants.reactNativeVersion;
516
+ if (rnv.major !== undefined) {
517
+ version = `${rnv.major}.${rnv.minor}.${rnv.patch}`;
518
+ }
519
+ }
520
+ }
521
+ // 4. Try Android AppInfo
522
+ const AppInfo = NativeModules.AppInfo;
523
+ if (AppInfo) {
524
+ if (!version && typeof AppInfo.versionName === 'string') {
525
+ version = AppInfo.versionName;
526
+ }
527
+ if (!build && typeof AppInfo.versionCode === 'string') {
528
+ build = AppInfo.versionCode;
529
+ }
530
+ }
531
+ // Fallbacks
532
+ if (!version)
533
+ version = '1.0.0';
534
+ if (!build)
535
+ build = '1';
536
+ return {
537
+ version,
538
+ build,
539
+ formatted: `${version} (${build})`,
540
+ };
541
+ };
354
542
  export const handleOpenExternalLink = (url) => {
355
543
  if (!url)
356
544
  return;