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
@@ -273,13 +273,13 @@ export const calculateByteSize = (str: string): number => {
273
273
  };
274
274
 
275
275
  /**
276
- * Determine type and format value safely
276
+ * Determine type and format value safely without blocking JS thread
277
277
  */
278
278
  export const analyzeStorageValue = (
279
279
  rawVal: any,
280
280
  ): {
281
281
  strVal: string;
282
- parsedVal?: any;
282
+ parsedValue?: any;
283
283
  type: 'json' | 'string' | 'number' | 'boolean' | 'null';
284
284
  byteSize: number;
285
285
  } => {
@@ -288,14 +288,14 @@ export const analyzeStorageValue = (
288
288
  }
289
289
 
290
290
  const strVal = typeof rawVal === 'string' ? rawVal : String(rawVal);
291
- const byteSize = calculateByteSize(strVal);
291
+ const byteSize = strVal.length * 2;
292
292
 
293
293
  if (typeof rawVal === 'number') {
294
- return {strVal, parsedVal: rawVal, type: 'number', byteSize};
294
+ return {strVal, parsedValue: rawVal, type: 'number', byteSize};
295
295
  }
296
296
 
297
297
  if (typeof rawVal === 'boolean') {
298
- return {strVal, parsedVal: rawVal, type: 'boolean', byteSize};
298
+ return {strVal, parsedValue: rawVal, type: 'boolean', byteSize};
299
299
  }
300
300
 
301
301
  const trimmed = strVal.trim();
@@ -303,18 +303,22 @@ export const analyzeStorageValue = (
303
303
  (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
304
304
  (trimmed.startsWith('[') && trimmed.endsWith(']'))
305
305
  ) {
306
- try {
307
- const parsed = JSON.parse(trimmed);
308
- return {strVal, parsedVal: parsed, type: 'json', byteSize};
309
- } catch {}
306
+ // For small/medium JSON, parse eagerly; for huge payloads (>10KB), parse lazily on expand
307
+ if (trimmed.length < 10000) {
308
+ try {
309
+ const parsed = JSON.parse(trimmed);
310
+ return {strVal, parsedValue: parsed, type: 'json', byteSize};
311
+ } catch {}
312
+ }
313
+ return {strVal, type: 'json', byteSize};
310
314
  }
311
315
 
312
316
  if (trimmed === 'true' || trimmed === 'false') {
313
- return {strVal, parsedVal: trimmed === 'true', type: 'boolean', byteSize};
317
+ return {strVal, parsedValue: trimmed === 'true', type: 'boolean', byteSize};
314
318
  }
315
319
 
316
320
  if (!isNaN(Number(trimmed)) && trimmed !== '') {
317
- return {strVal, parsedVal: Number(trimmed), type: 'number', byteSize};
321
+ return {strVal, parsedValue: Number(trimmed), type: 'number', byteSize};
318
322
  }
319
323
 
320
324
  return {strVal, type: 'string', byteSize};
@@ -346,7 +350,7 @@ export const fetchStorageEntries = async (
346
350
  entries.push({
347
351
  key: k,
348
352
  value: analysis.strVal,
349
- parsedValue: analysis.parsedVal,
353
+ parsedValue: analysis.parsedValue,
350
354
  type: analysis.type,
351
355
  byteSize: analysis.byteSize,
352
356
  });
@@ -358,7 +362,7 @@ export const fetchStorageEntries = async (
358
362
  entries.push({
359
363
  key: k,
360
364
  value: analysis.strVal,
361
- parsedValue: analysis.parsedVal,
365
+ parsedValue: analysis.parsedValue,
362
366
  type: analysis.type,
363
367
  byteSize: analysis.byteSize,
364
368
  });
@@ -397,7 +401,7 @@ export const fetchStorageEntries = async (
397
401
  entries.push({
398
402
  key: k,
399
403
  value: analysis.strVal,
400
- parsedValue: analysis.parsedVal,
404
+ parsedValue: analysis.parsedValue,
401
405
  type: analysis.type,
402
406
  byteSize: analysis.byteSize,
403
407
  });
@@ -198,16 +198,37 @@ export const getNavigationInfo = (
198
198
  state: any,
199
199
  path: string[] = [],
200
200
  ): RouteInfo => {
201
- if (!state?.routes) {
201
+ if (!state) {
202
202
  return {
203
- path: path.length > 0 ? path.join(' ➔ ') : 'Navigators',
203
+ path: path.length > 0 ? path.join(' ➔ ') : '',
204
204
  params: null,
205
205
  };
206
206
  }
207
- const route = state.routes[state.index ?? 0];
207
+ if (state.name && !state.routes) {
208
+ const fullPath =
209
+ path.length > 0 ? [...path, state.name].join(' ➔ ') : state.name;
210
+ return {path: fullPath, params: state.params || null};
211
+ }
212
+ if (
213
+ !state.routes ||
214
+ !Array.isArray(state.routes) ||
215
+ state.routes.length === 0
216
+ ) {
217
+ return {
218
+ path: path.length > 0 ? path.join(' ➔ ') : '',
219
+ params: null,
220
+ };
221
+ }
222
+ const index =
223
+ typeof state.index === 'number' &&
224
+ state.index >= 0 &&
225
+ state.index < state.routes.length
226
+ ? state.index
227
+ : 0;
228
+ const route = state.routes[index];
208
229
  if (!route) {
209
230
  return {
210
- path: path.length > 0 ? path.join(' ➔ ') : 'Navigators',
231
+ path: path.length > 0 ? path.join(' ➔ ') : '',
211
232
  params: null,
212
233
  };
213
234
  }
@@ -217,10 +238,96 @@ export const getNavigationInfo = (
217
238
  return getNavigationInfo(route.state, currentPath);
218
239
  }
219
240
  const resolved =
220
- currentPath.length > 0 ? currentPath.join(' ➔ ') : 'Navigators';
241
+ currentPath.length > 0 ? currentPath.join(' ➔ ') : route.name || '';
221
242
  return {path: resolved, params: route.params || null};
222
243
  };
223
244
 
245
+ export const getLogPageName = (
246
+ log: NetworkLog,
247
+ routeInfo?: RouteInfo | null,
248
+ ): string => {
249
+ // 1. If routeInfo has a valid path, resolve the leaf screen name
250
+ const effectiveRoute = routeInfo || (log as any)?.routeInfo;
251
+ if (
252
+ effectiveRoute &&
253
+ effectiveRoute.path &&
254
+ effectiveRoute.path !== 'Navigators'
255
+ ) {
256
+ const parts = effectiveRoute.path
257
+ .split(' ➔ ')
258
+ .map((s: string) => s.trim())
259
+ .filter(Boolean);
260
+ if (parts.length > 0) {
261
+ return parts[parts.length - 1]; // Leaf screen name, e.g. "HomeScreen", "ProfileScreen"
262
+ }
263
+ return effectiveRoute.path;
264
+ }
265
+
266
+ // 2. Extract screen / component name from log.caller stack trace
267
+ if (log.caller && log.caller !== 'Unknown') {
268
+ try {
269
+ const match = log.caller.match(/([^/\\]+)\.(tsx|jsx|ts|js)/i);
270
+ if (match && match[1]) {
271
+ const file = match[1];
272
+ const ignored = [
273
+ 'networklogger',
274
+ 'fetch',
275
+ 'axios',
276
+ 'apiclient',
277
+ 'request',
278
+ 'http',
279
+ 'index',
280
+ 'nativeinspector',
281
+ 'inappinspector',
282
+ 'bundleanalyzer',
283
+ ];
284
+ if (!ignored.includes(file.toLowerCase())) {
285
+ return file;
286
+ }
287
+ }
288
+ } catch (e) {}
289
+ }
290
+
291
+ // 3. Fallback to API resource / endpoint category from URL
292
+ if (log.url) {
293
+ try {
294
+ const cleanUrl = log.url.split('?')[0].split('#')[0];
295
+ const withoutProto = cleanUrl.replace(/^https?:\/\//i, '');
296
+ const slashIdx = withoutProto.indexOf('/');
297
+ if (slashIdx !== -1) {
298
+ const pathSegments = withoutProto
299
+ .substring(slashIdx + 1)
300
+ .split('/')
301
+ .filter(Boolean);
302
+ const ignoredSegments = [
303
+ 'api',
304
+ 'v1',
305
+ 'v2',
306
+ 'v3',
307
+ 'v4',
308
+ 'rest',
309
+ 'graphql',
310
+ 'json',
311
+ 'data',
312
+ 'service',
313
+ 'app',
314
+ ];
315
+ const meaningful = pathSegments.filter(
316
+ s => !ignoredSegments.includes(s.toLowerCase()),
317
+ );
318
+ if (meaningful.length > 0) {
319
+ const seg = meaningful[0];
320
+ return seg.charAt(0).toUpperCase() + seg.slice(1);
321
+ }
322
+ }
323
+ const host = withoutProto.split('/')[0];
324
+ if (host) return host;
325
+ } catch (e) {}
326
+ }
327
+
328
+ return 'General';
329
+ };
330
+
224
331
  export const flattenObject = (obj: any, prefix = ''): Record<string, any> => {
225
332
  let result: Record<string, any> = {};
226
333
  if (typeof obj === 'object' && obj !== null) {
@@ -409,6 +516,107 @@ export const getAppName = (): string => {
409
516
  return 'App';
410
517
  };
411
518
 
519
+ export const getAppVersionAndBuild = (): {
520
+ version: string;
521
+ build: string;
522
+ formatted: string;
523
+ } => {
524
+ let version = '';
525
+ let build = '';
526
+
527
+ // 1. Try ExpoApplication / ExponentConstants
528
+ const ExpoApplication = NativeModules.ExpoApplication;
529
+ if (ExpoApplication) {
530
+ if (typeof ExpoApplication.nativeAppVersion === 'string') {
531
+ version = ExpoApplication.nativeAppVersion;
532
+ }
533
+ if (typeof ExpoApplication.nativeBuildVersion === 'string') {
534
+ build = ExpoApplication.nativeBuildVersion;
535
+ }
536
+ }
537
+
538
+ const ExponentConstants = NativeModules.ExponentConstants;
539
+ if (ExponentConstants) {
540
+ const manifest = ExponentConstants.manifest || ExponentConstants.expoConfig;
541
+ if (manifest) {
542
+ if (!version && manifest.version) {
543
+ version = String(manifest.version);
544
+ }
545
+ if (!build) {
546
+ if (manifest.ios?.buildNumber) {
547
+ build = String(manifest.ios.buildNumber);
548
+ } else if (manifest.android?.versionCode) {
549
+ build = String(manifest.android.versionCode);
550
+ }
551
+ }
552
+ }
553
+ }
554
+
555
+ // 2. Try react-native-device-info
556
+ const RNDeviceInfo = NativeModules.RNDeviceInfo;
557
+ if (RNDeviceInfo) {
558
+ if (!version) {
559
+ if (typeof RNDeviceInfo.appVersion === 'string') {
560
+ version = RNDeviceInfo.appVersion;
561
+ } else if (typeof RNDeviceInfo.getVersion === 'function') {
562
+ try {
563
+ version = RNDeviceInfo.getVersion();
564
+ } catch (e) {}
565
+ }
566
+ }
567
+ if (!build) {
568
+ if (typeof RNDeviceInfo.buildNumber === 'string') {
569
+ build = RNDeviceInfo.buildNumber;
570
+ } else if (typeof RNDeviceInfo.getBuildNumber === 'function') {
571
+ try {
572
+ build = RNDeviceInfo.getBuildNumber();
573
+ } catch (e) {}
574
+ }
575
+ }
576
+ }
577
+
578
+ // 3. Try PlatformConstants / Platform.constants
579
+ const constants =
580
+ (Platform.constants as any) || NativeModules.PlatformConstants;
581
+ if (constants) {
582
+ if (
583
+ !version &&
584
+ constants.Version &&
585
+ typeof constants.Version === 'string' &&
586
+ constants.Version.includes('.')
587
+ ) {
588
+ version = constants.Version;
589
+ }
590
+ if (!version && constants.reactNativeVersion) {
591
+ const rnv = constants.reactNativeVersion;
592
+ if (rnv.major !== undefined) {
593
+ version = `${rnv.major}.${rnv.minor}.${rnv.patch}`;
594
+ }
595
+ }
596
+ }
597
+
598
+ // 4. Try Android AppInfo
599
+ const AppInfo = NativeModules.AppInfo;
600
+ if (AppInfo) {
601
+ if (!version && typeof AppInfo.versionName === 'string') {
602
+ version = AppInfo.versionName;
603
+ }
604
+ if (!build && typeof AppInfo.versionCode === 'string') {
605
+ build = AppInfo.versionCode;
606
+ }
607
+ }
608
+
609
+ // Fallbacks
610
+ if (!version) version = '1.0.0';
611
+ if (!build) build = '1';
612
+
613
+ return {
614
+ version,
615
+ build,
616
+ formatted: `${version} (${build})`,
617
+ };
618
+ };
619
+
412
620
  export const handleOpenExternalLink = (url: string): void => {
413
621
  if (!url) return;
414
622
  const openUrl = formatDisplayUrl(url);
@@ -18,8 +18,9 @@ export function matchNetworkLogQuery(
18
18
  ): boolean {
19
19
  if (!searchQuery || searchQuery.trim().length === 0) return true;
20
20
 
21
+ // Split tokens by spaces or commas while respecting quotes
21
22
  const rawTokens =
22
- searchQuery.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
23
+ searchQuery.trim().match(/(?:[^\s,"']+|"[^"]*"|'[^']*')+/g) || [];
23
24
 
24
25
  if (rawTokens.length === 0) return true;
25
26
 
@@ -32,27 +33,38 @@ export function matchNetworkLogQuery(
32
33
  const statusNum =
33
34
  typeof log.status === 'number'
34
35
  ? log.status
35
- : parseInt(String(log.status || 0), 10);
36
- const statusStr = String(log.status ?? '');
36
+ : log.status != null
37
+ ? parseInt(String(log.status), 10)
38
+ : null;
39
+ const statusStr = log.status != null ? String(log.status) : 'pending';
37
40
  const durationNum = log.duration || 0;
38
41
  const clientRaw = log.client || '';
42
+ const callerRaw = log.caller || '';
39
43
  const reqRaw =
40
44
  typeof log.request === 'string'
41
45
  ? log.request
42
- : JSON.stringify(log.request || '');
46
+ : log.request != null
47
+ ? JSON.stringify(log.request)
48
+ : '';
43
49
  const resRaw =
44
50
  typeof log.response === 'string'
45
51
  ? log.response
46
- : JSON.stringify(log.response || '');
47
- const reqHeadersRaw = JSON.stringify(log.requestHeaders || '');
48
- const resHeadersRaw = JSON.stringify(log.responseHeaders || '');
49
- const pathRaw = routePath || '';
52
+ : log.response != null
53
+ ? JSON.stringify(log.response)
54
+ : '';
55
+ const reqHeadersRaw = log.requestHeaders
56
+ ? JSON.stringify(log.requestHeaders)
57
+ : '';
58
+ const resHeadersRaw = log.responseHeaders
59
+ ? JSON.stringify(log.responseHeaders)
60
+ : '';
61
+ const pathRaw = routePath || (log as any)?.routeInfo?.path || '';
50
62
 
51
63
  // Construct search corpus based on active scope
52
64
  const getScopedCorpus = (caseSens: boolean): string => {
53
65
  let parts: string[] = [];
54
66
  if (scope === 'url') {
55
- parts = [methodRaw, urlRaw, pathRaw];
67
+ parts = [methodRaw, urlRaw, pathRaw, callerRaw];
56
68
  } else if (scope === 'reqBody') {
57
69
  parts = [reqRaw];
58
70
  } else if (scope === 'resBody') {
@@ -65,6 +77,7 @@ export function matchNetworkLogQuery(
65
77
  urlRaw,
66
78
  statusStr,
67
79
  pathRaw,
80
+ callerRaw,
68
81
  clientRaw,
69
82
  reqRaw,
70
83
  resRaw,
@@ -81,6 +94,7 @@ export function matchNetworkLogQuery(
81
94
  const methodStr = methodRaw.toLowerCase();
82
95
  const urlStr = urlRaw.toLowerCase();
83
96
  const clientStr = clientRaw.toLowerCase();
97
+ const callerStr = callerRaw.toLowerCase();
84
98
  const reqStr = reqRaw.toLowerCase();
85
99
  const resStr = resRaw.toLowerCase();
86
100
  const reqHeadersStr = reqHeadersRaw.toLowerCase();
@@ -107,18 +121,20 @@ export function matchNetworkLogQuery(
107
121
  // 2. status:<VAL> or s:<VAL>
108
122
  if (lowerToken.startsWith('status:') || lowerToken.startsWith('s:')) {
109
123
  const val = lowerToken.replace(/^(status:|s:)/, '').trim();
110
- if (val.startsWith('>=')) {
124
+ if (val === 'pending' || val === 'loading') {
125
+ if (log.status != null) return false;
126
+ } else if (val.startsWith('>=')) {
111
127
  const threshold = parseInt(val.slice(2), 10);
112
- if (isNaN(threshold) || statusNum < threshold) return false;
128
+ if (isNaN(threshold) || statusNum === null || statusNum < threshold) return false;
113
129
  } else if (val.startsWith('>')) {
114
130
  const threshold = parseInt(val.slice(1), 10);
115
- if (isNaN(threshold) || statusNum <= threshold) return false;
131
+ if (isNaN(threshold) || statusNum === null || statusNum <= threshold) return false;
116
132
  } else if (val.startsWith('<=')) {
117
133
  const threshold = parseInt(val.slice(2), 10);
118
- if (isNaN(threshold) || statusNum > threshold) return false;
134
+ if (isNaN(threshold) || statusNum === null || statusNum > threshold) return false;
119
135
  } else if (val.startsWith('<')) {
120
136
  const threshold = parseInt(val.slice(1), 10);
121
- if (isNaN(threshold) || statusNum >= threshold) return false;
137
+ if (isNaN(threshold) || statusNum === null || statusNum >= threshold) return false;
122
138
  } else if (val.endsWith('xx') || val.endsWith('x')) {
123
139
  const prefix = val.replace(/x/g, '');
124
140
  if (!statusStr.startsWith(prefix)) return false;
@@ -143,11 +159,13 @@ export function matchNetworkLogQuery(
143
159
  flag === 'fail'
144
160
  ) {
145
161
  const isErr =
146
- log.status === 0 || log.status == null || statusNum >= 400;
162
+ log.status === 0 || (statusNum !== null && statusNum >= 400);
147
163
  if (!isErr) return false;
148
164
  } else if (flag === 'success' || flag === 'ok' || flag === '2xx') {
149
- const isSuccess = statusNum >= 200 && statusNum < 300;
165
+ const isSuccess = statusNum !== null && statusNum >= 200 && statusNum < 300;
150
166
  if (!isSuccess) return false;
167
+ } else if (flag === 'pending' || flag === 'loading') {
168
+ if (log.status != null) return false;
151
169
  } else if (flag === 'slow') {
152
170
  if (durationNum < 1000) return false;
153
171
  } else if (flag === 'fast') {
@@ -169,6 +187,11 @@ export function matchNetworkLogQuery(
169
187
  resHeadersStr.includes('application/json') ||
170
188
  urlStr.includes('.json');
171
189
  if (!isJson) return false;
190
+ } else if (flag === 'image') {
191
+ const isImg =
192
+ resHeadersStr.includes('image/') ||
193
+ /\.(png|jpe?g|gif|webp|svg)/i.test(urlStr);
194
+ if (!isImg) return false;
172
195
  }
173
196
  continue;
174
197
  }
@@ -230,10 +253,20 @@ export function matchNetworkLogQuery(
230
253
  continue;
231
254
  }
232
255
 
233
- // 7. path:<PATH> or p:<PATH>
234
- if (lowerToken.startsWith('path:') || lowerToken.startsWith('p:')) {
235
- const targetPath = lowerToken.replace(/^(path:|p:)/, '').trim();
236
- if (!urlStr.includes(targetPath) && !pathStr.includes(targetPath)) {
256
+ // 7. path:<PATH> or p:<PATH> or page:<PAGE>
257
+ if (
258
+ lowerToken.startsWith('path:') ||
259
+ lowerToken.startsWith('p:') ||
260
+ lowerToken.startsWith('page:')
261
+ ) {
262
+ const targetPath = lowerToken
263
+ .replace(/^(path:|p:|page:)/, '')
264
+ .trim();
265
+ if (
266
+ !urlStr.includes(targetPath) &&
267
+ !pathStr.includes(targetPath) &&
268
+ !callerStr.includes(targetPath)
269
+ ) {
237
270
  return false;
238
271
  }
239
272
  continue;
@@ -274,6 +307,16 @@ export function matchNetworkLogQuery(
274
307
  } else {
275
308
  const queryTerm = isCaseSensitive ? token : lowerToken;
276
309
  if (!targetCorpus.includes(queryTerm)) {
310
+ // Special case: if user typed "error" / "failed", check error status
311
+ if (
312
+ queryTerm === 'error' ||
313
+ queryTerm === 'failed' ||
314
+ queryTerm === 'fail'
315
+ ) {
316
+ const isErr =
317
+ log.status === 0 || (statusNum !== null && statusNum >= 400);
318
+ if (isErr) continue;
319
+ }
277
320
  return false;
278
321
  }
279
322
  }
package/src/i18n/index.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import React from 'react';
2
- import en from './locales/en.json';
2
+ import enLocale from './locales/en.json';
3
3
 
4
4
  type TranslationParams = Record<string, any>;
5
5
 
6
+ const localeRegistry: Record<string, Record<string, any>> = {
7
+ en: enLocale,
8
+ };
9
+
10
+ let currentLanguage = 'en';
11
+
6
12
  /**
7
13
  * Lightweight nested lookup for translation keys (e.g. "network.headers")
8
14
  */
@@ -31,7 +37,12 @@ function interpolate(template: string, params?: TranslationParams): string {
31
37
  * Standalone zero-dependency translation function
32
38
  */
33
39
  export function t(key: string, optionsOrFallback?: TranslationParams | string): string {
34
- const raw = lookupKey(en, key);
40
+ const activeBundle = localeRegistry[currentLanguage] || localeRegistry.en;
41
+ let raw = lookupKey(activeBundle, key);
42
+ if (raw === undefined && currentLanguage !== 'en') {
43
+ raw = lookupKey(localeRegistry.en, key);
44
+ }
45
+
35
46
  if (typeof raw === 'string') {
36
47
  if (typeof optionsOrFallback === 'object' && optionsOrFallback !== null) {
37
48
  return interpolate(raw, optionsOrFallback);
@@ -44,9 +55,33 @@ export function t(key: string, optionsOrFallback?: TranslationParams | string):
44
55
  return key;
45
56
  }
46
57
 
58
+ export function setLanguage(lang: string): void {
59
+ if (typeof lang === 'string' && lang.trim()) {
60
+ currentLanguage = lang.trim();
61
+ i18n.language = currentLanguage;
62
+ }
63
+ }
64
+
65
+ export function addTranslations(lang: string, translations: Record<string, any>): void {
66
+ if (!lang || !translations || typeof translations !== 'object') return;
67
+ localeRegistry[lang] = {
68
+ ...(localeRegistry[lang] || {}),
69
+ ...translations,
70
+ };
71
+ }
72
+
73
+ export function setTranslations(translations: Record<string, any>, lang: string = 'en'): void {
74
+ addTranslations(lang, translations);
75
+ }
76
+
47
77
  export const i18n = {
48
78
  t,
49
- language: 'en',
79
+ language: currentLanguage,
80
+ changeLanguage: setLanguage,
81
+ addResourceBundle: (lng: string, _ns: string, resources: any) => addTranslations(lng, resources),
82
+ setLanguage,
83
+ addTranslations,
84
+ setTranslations,
50
85
  };
51
86
 
52
87
  /**