react-native-inapp-inspector 2.2.8 → 2.3.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.
Files changed (100) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +22 -7
  2. package/dist/commonjs/components/AppHeaderLogo.js +45 -12
  3. package/dist/commonjs/components/BrandSquareIcon.js +92 -49
  4. package/dist/commonjs/components/EmptyState.d.ts +7 -2
  5. package/dist/commonjs/components/EmptyState.js +11 -5
  6. package/dist/commonjs/components/Inspector/AnalyticsTab.js +29 -18
  7. package/dist/commonjs/components/Inspector/ConsoleTab.js +6 -1
  8. package/dist/commonjs/components/Inspector/CrashDetail.js +43 -9
  9. package/dist/commonjs/components/Inspector/CrashTab.js +34 -3
  10. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +80 -16
  11. package/dist/commonjs/components/Inspector/InspectorHeader.js +16 -3
  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 +19 -3
  17. package/dist/commonjs/components/JsonViewer.js +2 -0
  18. package/dist/commonjs/components/MetaAccordion.js +15 -3
  19. package/dist/commonjs/constants/version.d.ts +1 -1
  20. package/dist/commonjs/constants/version.js +1 -1
  21. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  22. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  23. package/dist/commonjs/helpers/index.d.ts +6 -0
  24. package/dist/commonjs/helpers/index.js +196 -6
  25. package/dist/commonjs/helpers/searchQueryParser.js +62 -20
  26. package/dist/commonjs/i18n/index.d.ts +13 -0
  27. package/dist/commonjs/i18n/index.js +35 -2
  28. package/dist/commonjs/index.d.ts +3 -0
  29. package/dist/commonjs/index.js +80 -11
  30. package/dist/commonjs/native/NativeInspector.js +13 -0
  31. package/dist/commonjs/native/NativeNetworkInspector.d.ts +1 -1
  32. package/dist/commonjs/styles/AppColors.d.ts +4 -0
  33. package/dist/commonjs/styles/AppColors.js +11 -1
  34. package/dist/commonjs/styles/AppFonts.d.ts +11 -0
  35. package/dist/commonjs/styles/AppFonts.js +11 -1
  36. package/dist/commonjs/types/interfaces.d.ts +1 -0
  37. package/dist/esm/components/AppHeaderLogo.js +45 -12
  38. package/dist/esm/components/BrandSquareIcon.js +92 -49
  39. package/dist/esm/components/EmptyState.d.ts +7 -2
  40. package/dist/esm/components/EmptyState.js +11 -5
  41. package/dist/esm/components/Inspector/AnalyticsTab.js +30 -19
  42. package/dist/esm/components/Inspector/ConsoleTab.js +6 -1
  43. package/dist/esm/components/Inspector/CrashDetail.js +43 -9
  44. package/dist/esm/components/Inspector/CrashTab.js +34 -3
  45. package/dist/esm/components/Inspector/DeviceInfoTab.js +80 -16
  46. package/dist/esm/components/Inspector/InspectorHeader.js +17 -4
  47. package/dist/esm/components/Inspector/NetworkDetail.js +3 -2
  48. package/dist/esm/components/Inspector/NetworkTab.js +103 -4
  49. package/dist/esm/components/Inspector/ReduxTab.js +19 -3
  50. package/dist/esm/components/Inspector/SettingsPanel.js +4 -4
  51. package/dist/esm/components/Inspector/StorageTab.js +19 -3
  52. package/dist/esm/components/JsonViewer.js +2 -0
  53. package/dist/esm/components/MetaAccordion.js +15 -3
  54. package/dist/esm/constants/version.d.ts +1 -1
  55. package/dist/esm/constants/version.js +1 -1
  56. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  57. package/dist/esm/customHooks/networkLogger.js +8 -0
  58. package/dist/esm/helpers/index.d.ts +6 -0
  59. package/dist/esm/helpers/index.js +193 -5
  60. package/dist/esm/helpers/searchQueryParser.js +62 -20
  61. package/dist/esm/i18n/index.d.ts +13 -0
  62. package/dist/esm/i18n/index.js +33 -3
  63. package/dist/esm/index.d.ts +3 -0
  64. package/dist/esm/index.js +68 -12
  65. package/dist/esm/native/NativeInspector.js +13 -0
  66. package/dist/esm/native/NativeNetworkInspector.d.ts +1 -1
  67. package/dist/esm/styles/AppColors.d.ts +4 -0
  68. package/dist/esm/styles/AppColors.js +9 -0
  69. package/dist/esm/styles/AppFonts.d.ts +11 -0
  70. package/dist/esm/styles/AppFonts.js +9 -0
  71. package/dist/esm/types/interfaces.d.ts +1 -0
  72. package/ios/NetworkInspectorModule.mm +11 -1
  73. package/package.json +1 -1
  74. package/src/components/AppHeaderLogo.tsx +46 -12
  75. package/src/components/BrandSquareIcon.tsx +127 -78
  76. package/src/components/EmptyState.tsx +27 -5
  77. package/src/components/Inspector/AnalyticsTab.tsx +48 -27
  78. package/src/components/Inspector/ConsoleTab.tsx +10 -0
  79. package/src/components/Inspector/CrashDetail.tsx +65 -9
  80. package/src/components/Inspector/CrashTab.tsx +41 -3
  81. package/src/components/Inspector/DeviceInfoTab.tsx +87 -16
  82. package/src/components/Inspector/InspectorHeader.tsx +24 -4
  83. package/src/components/Inspector/NetworkDetail.tsx +4 -4
  84. package/src/components/Inspector/NetworkTab.tsx +129 -4
  85. package/src/components/Inspector/ReduxTab.tsx +26 -8
  86. package/src/components/Inspector/SettingsPanel.tsx +4 -4
  87. package/src/components/Inspector/StorageTab.tsx +21 -3
  88. package/src/components/JsonViewer.tsx +2 -0
  89. package/src/components/MetaAccordion.tsx +18 -3
  90. package/src/constants/version.ts +1 -1
  91. package/src/customHooks/networkLogger.ts +15 -0
  92. package/src/helpers/index.ts +213 -5
  93. package/src/helpers/searchQueryParser.ts +63 -20
  94. package/src/i18n/index.ts +38 -3
  95. package/src/index.tsx +89 -11
  96. package/src/native/NativeInspector.ts +13 -0
  97. package/src/native/NativeNetworkInspector.ts +1 -1
  98. package/src/styles/AppColors.ts +9 -0
  99. package/src/styles/AppFonts.ts +16 -0
  100. package/src/types/interfaces.ts +1 -0
@@ -245,9 +245,25 @@ export const StorageTab = React.memo(() => {
245
245
  <View style={styles.searchBar}>
246
246
  <SearchIcon size={14} color={AppColors.grayTextWeak}/>
247
247
  <TextInput value={search} onChangeText={setSearch} placeholder={`Search ${entries.length} keys & values...`} placeholderTextColor={AppColors.grayTextWeak} style={styles.searchInput} clearButtonMode="while-editing"/>
248
- {search.length > 0 && (<TouchableScale onPress={() => setSearch('')} hitSlop={8}>
249
- <ClearIcon size={13} color={AppColors.grayTextWeak}/>
250
- </TouchableScale>)}
248
+ {search.length > 0 && (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
249
+ <View style={{
250
+ backgroundColor: `${AppColors.purple}20`,
251
+ borderRadius: 10,
252
+ paddingHorizontal: 6,
253
+ paddingVertical: 1.5,
254
+ }}>
255
+ <Text style={{
256
+ color: AppColors.purple,
257
+ fontSize: 9.5,
258
+ fontFamily: AppFonts.interBold,
259
+ }}>
260
+ {filteredEntries.length}
261
+ </Text>
262
+ </View>
263
+ <TouchableScale onPress={() => setSearch('')} hitSlop={8}>
264
+ <ClearIcon size={13} color={AppColors.grayTextWeak}/>
265
+ </TouchableScale>
266
+ </View>)}
251
267
  </View>
252
268
 
253
269
  {/* Action Buttons: Add Key & Refresh */}
@@ -5,6 +5,7 @@ import { useTranslation } from '../i18n';
5
5
  import TreeNode from './TreeNode';
6
6
  import SegmentedTabs from './SegmentedTabs';
7
7
  import HighlightText from './HighlightText';
8
+ import CopyButton from './CopyButton';
8
9
  import { PrettyIcon, RawIcon, TableIcon } from './NetworkIcons';
9
10
  // Styles
10
11
  import { AppColors } from '../styles/AppColors';
@@ -151,6 +152,7 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth = 1
151
152
  icon: (isActive) => (<TableIcon color={isActive ? AppColors.white : AppColors.slate400} size={12}/>),
152
153
  },
153
154
  ]} activeKey={mode} onChange={key => setMode(key)}/>
155
+ <CopyButton value={data} label="JSON"/>
154
156
  </View>)}
155
157
 
156
158
  {/* ── Main View Content Area (Royal Monospace Theme) ── */}
@@ -11,6 +11,7 @@ import { getDurationColor, handleOpenExternalLink } from '../helpers';
11
11
  import { ChevronIcon, CalendarIcon, StatusIcon, ClockIcon, SizeIcon, TerminalIcon, GlobeIcon, } from './NetworkIcons';
12
12
  // Components
13
13
  import TouchableScale from './TouchableScale';
14
+ import CopyButton from './CopyButton';
14
15
  // Stylesheet
15
16
  import { AppColors } from '../styles/AppColors';
16
17
  import { AppFonts } from '../styles/AppFonts';
@@ -60,9 +61,20 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
60
61
  <Pressable onPress={toggleOpen} hitSlop={12}>
61
62
  <View style={styles.metaHeader}>
62
63
  <Text style={styles.metaTitle}>Metadata</Text>
63
- <Animated.View style={chevronStyle}>
64
- <ChevronIcon color={AppColors.grayTextWeak} size={14}/>
65
- </Animated.View>
64
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
65
+ <CopyButton value={{
66
+ status: status ?? 0,
67
+ duration: duration != null ? `${duration} ms` : '—',
68
+ size: size || '—',
69
+ triggeredAt: triggeredAt || '',
70
+ method: method || 'GET',
71
+ contentType: contentType || 'application/json',
72
+ url: url || '',
73
+ }} label="Metadata"/>
74
+ <Animated.View style={chevronStyle}>
75
+ <ChevronIcon color={AppColors.grayTextWeak} size={14}/>
76
+ </Animated.View>
77
+ </View>
66
78
  </View>
67
79
  </Pressable>
68
80
 
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "2.2.8";
1
+ export declare const LIB_VERSION = "2.3.0";
@@ -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.8';
3
+ export const LIB_VERSION = '2.3.0';
@@ -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
  });
@@ -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;
@@ -5,7 +5,8 @@
5
5
  export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
6
6
  if (!searchQuery || searchQuery.trim().length === 0)
7
7
  return true;
8
- const rawTokens = searchQuery.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
8
+ // Split tokens by spaces or commas while respecting quotes
9
+ const rawTokens = searchQuery.trim().match(/(?:[^\s,"']+|"[^"]*"|'[^']*')+/g) || [];
9
10
  if (rawTokens.length === 0)
10
11
  return true;
11
12
  const isCaseSensitive = Boolean(options?.isCaseSensitive);
@@ -15,24 +16,35 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
15
16
  const urlRaw = log.url || '';
16
17
  const statusNum = typeof log.status === 'number'
17
18
  ? log.status
18
- : parseInt(String(log.status || 0), 10);
19
- const statusStr = String(log.status ?? '');
19
+ : log.status != null
20
+ ? parseInt(String(log.status), 10)
21
+ : null;
22
+ const statusStr = log.status != null ? String(log.status) : 'pending';
20
23
  const durationNum = log.duration || 0;
21
24
  const clientRaw = log.client || '';
25
+ const callerRaw = log.caller || '';
22
26
  const reqRaw = typeof log.request === 'string'
23
27
  ? log.request
24
- : JSON.stringify(log.request || '');
28
+ : log.request != null
29
+ ? JSON.stringify(log.request)
30
+ : '';
25
31
  const resRaw = typeof log.response === 'string'
26
32
  ? log.response
27
- : JSON.stringify(log.response || '');
28
- const reqHeadersRaw = JSON.stringify(log.requestHeaders || '');
29
- const resHeadersRaw = JSON.stringify(log.responseHeaders || '');
30
- const pathRaw = routePath || '';
33
+ : log.response != null
34
+ ? JSON.stringify(log.response)
35
+ : '';
36
+ const reqHeadersRaw = log.requestHeaders
37
+ ? JSON.stringify(log.requestHeaders)
38
+ : '';
39
+ const resHeadersRaw = log.responseHeaders
40
+ ? JSON.stringify(log.responseHeaders)
41
+ : '';
42
+ const pathRaw = routePath || log?.routeInfo?.path || '';
31
43
  // Construct search corpus based on active scope
32
44
  const getScopedCorpus = (caseSens) => {
33
45
  let parts = [];
34
46
  if (scope === 'url') {
35
- parts = [methodRaw, urlRaw, pathRaw];
47
+ parts = [methodRaw, urlRaw, pathRaw, callerRaw];
36
48
  }
37
49
  else if (scope === 'reqBody') {
38
50
  parts = [reqRaw];
@@ -49,6 +61,7 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
49
61
  urlRaw,
50
62
  statusStr,
51
63
  pathRaw,
64
+ callerRaw,
52
65
  clientRaw,
53
66
  reqRaw,
54
67
  resRaw,
@@ -63,6 +76,7 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
63
76
  const methodStr = methodRaw.toLowerCase();
64
77
  const urlStr = urlRaw.toLowerCase();
65
78
  const clientStr = clientRaw.toLowerCase();
79
+ const callerStr = callerRaw.toLowerCase();
66
80
  const reqStr = reqRaw.toLowerCase();
67
81
  const resStr = resRaw.toLowerCase();
68
82
  const reqHeadersStr = reqHeadersRaw.toLowerCase();
@@ -89,24 +103,28 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
89
103
  // 2. status:<VAL> or s:<VAL>
90
104
  if (lowerToken.startsWith('status:') || lowerToken.startsWith('s:')) {
91
105
  const val = lowerToken.replace(/^(status:|s:)/, '').trim();
92
- if (val.startsWith('>=')) {
106
+ if (val === 'pending' || val === 'loading') {
107
+ if (log.status != null)
108
+ return false;
109
+ }
110
+ else if (val.startsWith('>=')) {
93
111
  const threshold = parseInt(val.slice(2), 10);
94
- if (isNaN(threshold) || statusNum < threshold)
112
+ if (isNaN(threshold) || statusNum === null || statusNum < threshold)
95
113
  return false;
96
114
  }
97
115
  else if (val.startsWith('>')) {
98
116
  const threshold = parseInt(val.slice(1), 10);
99
- if (isNaN(threshold) || statusNum <= threshold)
117
+ if (isNaN(threshold) || statusNum === null || statusNum <= threshold)
100
118
  return false;
101
119
  }
102
120
  else if (val.startsWith('<=')) {
103
121
  const threshold = parseInt(val.slice(2), 10);
104
- if (isNaN(threshold) || statusNum > threshold)
122
+ if (isNaN(threshold) || statusNum === null || statusNum > threshold)
105
123
  return false;
106
124
  }
107
125
  else if (val.startsWith('<')) {
108
126
  const threshold = parseInt(val.slice(1), 10);
109
- if (isNaN(threshold) || statusNum >= threshold)
127
+ if (isNaN(threshold) || statusNum === null || statusNum >= threshold)
110
128
  return false;
111
129
  }
112
130
  else if (val.endsWith('xx') || val.endsWith('x')) {
@@ -133,15 +151,19 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
133
151
  flag === 'failed' ||
134
152
  flag === 'err' ||
135
153
  flag === 'fail') {
136
- const isErr = log.status === 0 || log.status == null || statusNum >= 400;
154
+ const isErr = log.status === 0 || (statusNum !== null && statusNum >= 400);
137
155
  if (!isErr)
138
156
  return false;
139
157
  }
140
158
  else if (flag === 'success' || flag === 'ok' || flag === '2xx') {
141
- const isSuccess = statusNum >= 200 && statusNum < 300;
159
+ const isSuccess = statusNum !== null && statusNum >= 200 && statusNum < 300;
142
160
  if (!isSuccess)
143
161
  return false;
144
162
  }
163
+ else if (flag === 'pending' || flag === 'loading') {
164
+ if (log.status != null)
165
+ return false;
166
+ }
145
167
  else if (flag === 'slow') {
146
168
  if (durationNum < 1000)
147
169
  return false;
@@ -172,6 +194,12 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
172
194
  if (!isJson)
173
195
  return false;
174
196
  }
197
+ else if (flag === 'image') {
198
+ const isImg = resHeadersStr.includes('image/') ||
199
+ /\.(png|jpe?g|gif|webp|svg)/i.test(urlStr);
200
+ if (!isImg)
201
+ return false;
202
+ }
175
203
  continue;
176
204
  }
177
205
  // 4. slow:<DURATION> or dur:<DURATION> or duration:<DURATION>
@@ -233,10 +261,16 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
233
261
  return false;
234
262
  continue;
235
263
  }
236
- // 7. path:<PATH> or p:<PATH>
237
- if (lowerToken.startsWith('path:') || lowerToken.startsWith('p:')) {
238
- const targetPath = lowerToken.replace(/^(path:|p:)/, '').trim();
239
- if (!urlStr.includes(targetPath) && !pathStr.includes(targetPath)) {
264
+ // 7. path:<PATH> or p:<PATH> or page:<PAGE>
265
+ if (lowerToken.startsWith('path:') ||
266
+ lowerToken.startsWith('p:') ||
267
+ lowerToken.startsWith('page:')) {
268
+ const targetPath = lowerToken
269
+ .replace(/^(path:|p:|page:)/, '')
270
+ .trim();
271
+ if (!urlStr.includes(targetPath) &&
272
+ !pathStr.includes(targetPath) &&
273
+ !callerStr.includes(targetPath)) {
240
274
  return false;
241
275
  }
242
276
  continue;
@@ -279,6 +313,14 @@ export function matchNetworkLogQuery(log, searchQuery, routePath, options) {
279
313
  else {
280
314
  const queryTerm = isCaseSensitive ? token : lowerToken;
281
315
  if (!targetCorpus.includes(queryTerm)) {
316
+ // Special case: if user typed "error" / "failed", check error status
317
+ if (queryTerm === 'error' ||
318
+ queryTerm === 'failed' ||
319
+ queryTerm === 'fail') {
320
+ const isErr = log.status === 0 || (statusNum !== null && statusNum >= 400);
321
+ if (isErr)
322
+ continue;
323
+ }
282
324
  return false;
283
325
  }
284
326
  }
@@ -4,9 +4,17 @@ type TranslationParams = Record<string, any>;
4
4
  * Standalone zero-dependency translation function
5
5
  */
6
6
  export declare function t(key: string, optionsOrFallback?: TranslationParams | string): string;
7
+ export declare function setLanguage(lang: string): void;
8
+ export declare function addTranslations(lang: string, translations: Record<string, any>): void;
9
+ export declare function setTranslations(translations: Record<string, any>, lang?: string): void;
7
10
  export declare const i18n: {
8
11
  t: typeof t;
9
12
  language: string;
13
+ changeLanguage: typeof setLanguage;
14
+ addResourceBundle: (lng: string, _ns: string, resources: any) => void;
15
+ setLanguage: typeof setLanguage;
16
+ addTranslations: typeof addTranslations;
17
+ setTranslations: typeof setTranslations;
10
18
  };
11
19
  /**
12
20
  * React hook compatible with react-i18next useTranslation()
@@ -16,6 +24,11 @@ export declare function useTranslation(): {
16
24
  i18n: {
17
25
  t: typeof t;
18
26
  language: string;
27
+ changeLanguage: typeof setLanguage;
28
+ addResourceBundle: (lng: string, _ns: string, resources: any) => void;
29
+ setLanguage: typeof setLanguage;
30
+ addTranslations: typeof addTranslations;
31
+ setTranslations: typeof setTranslations;
19
32
  };
20
33
  };
21
34
  /**
@@ -1,5 +1,9 @@
1
1
  import React from 'react';
2
- import en from './locales/en.json';
2
+ import enLocale from './locales/en.json';
3
+ const localeRegistry = {
4
+ en: enLocale,
5
+ };
6
+ let currentLanguage = 'en';
3
7
  /**
4
8
  * Lightweight nested lookup for translation keys (e.g. "network.headers")
5
9
  */
@@ -29,7 +33,11 @@ function interpolate(template, params) {
29
33
  * Standalone zero-dependency translation function
30
34
  */
31
35
  export function t(key, optionsOrFallback) {
32
- const raw = lookupKey(en, key);
36
+ const activeBundle = localeRegistry[currentLanguage] || localeRegistry.en;
37
+ let raw = lookupKey(activeBundle, key);
38
+ if (raw === undefined && currentLanguage !== 'en') {
39
+ raw = lookupKey(localeRegistry.en, key);
40
+ }
33
41
  if (typeof raw === 'string') {
34
42
  if (typeof optionsOrFallback === 'object' && optionsOrFallback !== null) {
35
43
  return interpolate(raw, optionsOrFallback);
@@ -41,9 +49,31 @@ export function t(key, optionsOrFallback) {
41
49
  }
42
50
  return key;
43
51
  }
52
+ export function setLanguage(lang) {
53
+ if (typeof lang === 'string' && lang.trim()) {
54
+ currentLanguage = lang.trim();
55
+ i18n.language = currentLanguage;
56
+ }
57
+ }
58
+ export function addTranslations(lang, translations) {
59
+ if (!lang || !translations || typeof translations !== 'object')
60
+ return;
61
+ localeRegistry[lang] = {
62
+ ...(localeRegistry[lang] || {}),
63
+ ...translations,
64
+ };
65
+ }
66
+ export function setTranslations(translations, lang = 'en') {
67
+ addTranslations(lang, translations);
68
+ }
44
69
  export const i18n = {
45
70
  t,
46
- language: 'en',
71
+ language: currentLanguage,
72
+ changeLanguage: setLanguage,
73
+ addResourceBundle: (lng, _ns, resources) => addTranslations(lng, resources),
74
+ setLanguage,
75
+ addTranslations,
76
+ setTranslations,
47
77
  };
48
78
  /**
49
79
  * React hook compatible with react-i18next useTranslation()
@@ -23,3 +23,6 @@ export { setMaxPerformanceEventsLimit, getMaxPerformanceEventsLimit, prunePerfor
23
23
  export { BrandSquareIcon, BrandCircleIcon, } from './components/NetworkIcons';
24
24
  export { connectAsyncStorage, connectMMKV, isAsyncStorageConnected, isMMKVConnected, getRegisteredMMKVInstanceIds, fetchStorageEntries, setStorageEntry, removeStorageEntry, clearStorageDriver, subscribeToStorageChanges, type StorageEntry, type StorageDriver, } from './customHooks/storageInspector';
25
25
  export { ActiveTab, Method, StatusFilter, SortOrder, LocalFilter, ModalAnimationType, SettingsPage, SettingsSubTab, LogFilter, ConsoleLogType, AnalyticsEventSource, GAEventCategory, StackFrameType, DiffResultType, BundleSubTab, PerformanceSubTab, CrashType, CrashExportFormat, CrashDetailSubTab, CrashFilterType, BreadcrumbType, } from './types';
26
+ export { AppFonts, setAppFonts, type AppFontConfig, } from './styles/AppFonts';
27
+ export { AppColors, setAppColors, getThemeColors, updateAppColorsTheme, } from './styles/AppColors';
28
+ export { t, i18n, useTranslation, setLanguage, addTranslations, setTranslations, I18nextProvider, } from './i18n';