react-native-inapp-inspector 1.1.23 → 1.1.25

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 (79) hide show
  1. package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
  2. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  3. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  4. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  6. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  7. package/dist/commonjs/components/Inspector/BundleTab.js +859 -402
  8. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  9. package/dist/commonjs/components/Inspector/InspectorHeader.js +13 -5
  10. package/dist/commonjs/components/Inspector/LogDetail.js +223 -111
  11. package/dist/commonjs/components/Inspector/MainScreen.js +1 -1
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  13. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  14. package/dist/commonjs/components/NetworkIcons.d.ts +14 -0
  15. package/dist/commonjs/components/NetworkIcons.js +93 -1
  16. package/dist/commonjs/components/SegmentedTabs.js +31 -22
  17. package/dist/commonjs/components/TreeNode.js +13 -12
  18. package/dist/commonjs/constants/version.d.ts +1 -1
  19. package/dist/commonjs/constants/version.js +1 -1
  20. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +6 -2
  21. package/dist/commonjs/customHooks/bundleAnalyzer.js +552 -114
  22. package/dist/commonjs/customHooks/consoleLogger.js +83 -45
  23. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  24. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  25. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  26. package/dist/commonjs/decorators/index.d.ts +49 -0
  27. package/dist/commonjs/decorators/index.js +142 -0
  28. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  29. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  30. package/dist/commonjs/helpers/index.d.ts +7 -4
  31. package/dist/commonjs/helpers/index.js +102 -64
  32. package/dist/commonjs/i18n/locales/en.json +53 -4
  33. package/dist/commonjs/index.d.ts +4 -0
  34. package/dist/commonjs/index.js +216 -8
  35. package/dist/commonjs/styles/index.d.ts +4 -1
  36. package/dist/commonjs/styles/index.js +22 -19
  37. package/dist/commonjs/types/enums.d.ts +115 -9
  38. package/dist/commonjs/types/enums.js +103 -2
  39. package/dist/commonjs/types/interfaces.d.ts +19 -0
  40. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  41. package/dist/esm/components/ConsoleLogCard.js +49 -25
  42. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  43. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  44. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  45. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  46. package/dist/esm/components/Inspector/BundleTab.js +860 -403
  47. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  48. package/dist/esm/components/Inspector/InspectorHeader.js +14 -6
  49. package/dist/esm/components/Inspector/LogDetail.js +223 -111
  50. package/dist/esm/components/Inspector/MainScreen.js +2 -2
  51. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  52. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  53. package/dist/esm/components/NetworkIcons.d.ts +14 -0
  54. package/dist/esm/components/NetworkIcons.js +78 -0
  55. package/dist/esm/components/SegmentedTabs.js +31 -22
  56. package/dist/esm/components/TreeNode.js +13 -12
  57. package/dist/esm/constants/version.d.ts +1 -1
  58. package/dist/esm/constants/version.js +1 -1
  59. package/dist/esm/customHooks/bundleAnalyzer.d.ts +6 -2
  60. package/dist/esm/customHooks/bundleAnalyzer.js +549 -114
  61. package/dist/esm/customHooks/consoleLogger.js +83 -45
  62. package/dist/esm/customHooks/networkLogger.js +10 -1
  63. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  64. package/dist/esm/customHooks/performanceTracker.js +38 -1
  65. package/dist/esm/decorators/index.d.ts +49 -0
  66. package/dist/esm/decorators/index.js +137 -0
  67. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  68. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  69. package/dist/esm/helpers/index.d.ts +7 -4
  70. package/dist/esm/helpers/index.js +100 -63
  71. package/dist/esm/i18n/locales/en.json +53 -4
  72. package/dist/esm/index.d.ts +4 -0
  73. package/dist/esm/index.js +190 -8
  74. package/dist/esm/styles/index.d.ts +4 -1
  75. package/dist/esm/styles/index.js +22 -19
  76. package/dist/esm/types/enums.d.ts +115 -9
  77. package/dist/esm/types/enums.js +102 -3
  78. package/dist/esm/types/interfaces.d.ts +19 -0
  79. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { NativeModules } from 'react-native';
1
2
  import { IGNORED_LOG_PREFIXES } from './logFilters';
2
3
  let logs = [];
3
4
  let listeners = [];
@@ -24,12 +25,25 @@ const formatArgs = (args) => {
24
25
  })
25
26
  .join(' ');
26
27
  };
27
- // ─── Symbolication Helper for React Native Metro ────────────────────────────
28
+ // ─── Dynamic Symbolication Helper for React Native Metro ────────────────────
29
+ const getMetroSymbolicateUrl = () => {
30
+ try {
31
+ const scriptURL = NativeModules.SourceCode?.scriptURL;
32
+ if (typeof scriptURL === 'string') {
33
+ const match = scriptURL.match(/^(https?:\/\/[^\/]+)/);
34
+ if (match) {
35
+ return `${match[1]}/symbolicate`;
36
+ }
37
+ }
38
+ }
39
+ catch { }
40
+ return 'http://localhost:8081/symbolicate';
41
+ };
28
42
  const symbolicateStack = async (stackString) => {
29
- if (typeof __DEV__ === 'undefined' || !__DEV__)
43
+ if (typeof __DEV__ === 'undefined' || !__DEV__ || !stackString)
30
44
  return null;
45
+ // 1. Try React Native built-in symbolicateStackTrace if available
31
46
  try {
32
- // 1. Try React Native's built-in symbolicateStackTrace if available
33
47
  let symModule = null;
34
48
  try {
35
49
  // @ts-ignore
@@ -39,24 +53,61 @@ const symbolicateStack = async (stackString) => {
39
53
  const symFn = symModule?.default || symModule;
40
54
  if (typeof symFn === 'function') {
41
55
  const res = await symFn(stackString);
42
- if (res && Array.isArray(res.stack)) {
56
+ if (res && Array.isArray(res.stack) && res.stack.length > 0) {
43
57
  return res.stack;
44
58
  }
45
59
  }
46
60
  }
47
61
  catch { }
62
+ // 2. Direct HTTP symbolication to dynamic Metro packager endpoint
48
63
  try {
49
- // 2. Direct HTTP symbolication to Metro packager endpoint
50
- const metroUrl = 'http://localhost:8081/symbolicate';
51
- const response = await fetch(metroUrl, {
52
- method: 'POST',
53
- headers: { 'Content-Type': 'application/json' },
54
- body: JSON.stringify({ stack: stackString }),
55
- });
56
- if (response.ok) {
57
- const json = await response.json();
58
- if (json && Array.isArray(json.stack)) {
59
- return json.stack;
64
+ const metroUrl = getMetroSymbolicateUrl();
65
+ const scriptURL = NativeModules.SourceCode?.scriptURL || 'http://localhost:8081/index.bundle?platform=ios&dev=true';
66
+ // Parse Hermes stack string into frame objects for Metro symbolicate endpoint
67
+ const lines = stackString.split('\n');
68
+ const inputFrames = [];
69
+ for (const rawLine of lines) {
70
+ const line = rawLine.trim().replace(/^at /, '');
71
+ if (!line)
72
+ continue;
73
+ let methodName = '<anonymous>';
74
+ let locationPart = line;
75
+ const parenMatch = line.match(/^(.*?)\s*\((.*?)\)$/);
76
+ if (parenMatch) {
77
+ methodName = parenMatch[1].trim() || '<anonymous>';
78
+ locationPart = parenMatch[2].trim();
79
+ }
80
+ else if (line.includes('@')) {
81
+ const atIdx = line.indexOf('@');
82
+ methodName = line.substring(0, atIdx).trim() || '<anonymous>';
83
+ locationPart = line.substring(atIdx + 1).trim();
84
+ }
85
+ locationPart = locationPart.replace(/^address at /, '');
86
+ const locMatch = locationPart.match(/^(.*?):(\d+):(\d+)$/);
87
+ if (locMatch) {
88
+ let file = locMatch[1];
89
+ if (!file.startsWith('http') && file !== 'native' && !file.includes('InternalBytecode')) {
90
+ file = scriptURL;
91
+ }
92
+ inputFrames.push({
93
+ file,
94
+ lineNumber: parseInt(locMatch[2], 10),
95
+ column: parseInt(locMatch[3], 10),
96
+ methodName,
97
+ });
98
+ }
99
+ }
100
+ if (inputFrames.length > 0) {
101
+ const response = await fetch(metroUrl, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({ stack: inputFrames }),
105
+ });
106
+ if (response.ok) {
107
+ const json = await response.json();
108
+ if (json && Array.isArray(json.stack) && json.stack.length > 0) {
109
+ return json.stack;
110
+ }
60
111
  }
61
112
  }
62
113
  }
@@ -163,34 +214,10 @@ const addLog = (type, args, sourceMethod) => {
163
214
  logs = logs.slice(0, MAX_LOGS);
164
215
  notify();
165
216
  // Asynchronously symbolicate stack trace via Metro in development
166
- if (newLog.stack && typeof __DEV__ !== 'undefined' && __DEV__) {
167
- symbolicateStack(newLog.stack)
168
- .then(symbolicatedFrames => {
169
- if (symbolicatedFrames && symbolicatedFrames.length > 0) {
170
- const validFrames = symbolicatedFrames.filter(f => f.file &&
171
- !f.file.includes('react-native/Libraries') &&
172
- !f.file.includes('node_modules') &&
173
- !isLoggerInternal(f.file) &&
174
- !isLoggerInternal(f.methodName || ''));
175
- const topFrame = validFrames[0] || symbolicatedFrames[0];
176
- if (topFrame) {
177
- const cleanMethod = topFrame.methodName || 'anonymous';
178
- const cleanFile = topFrame.file.split('/').pop() || topFrame.file;
179
- newLog.caller = `${cleanMethod} (${cleanFile}:${topFrame.lineNumber || 1}:${topFrame.column || 1})`;
180
- }
181
- newLog.stack = symbolicatedFrames
182
- .filter(f => !isLoggerInternal(f.file || '') && !isLoggerInternal(f.methodName || ''))
183
- .map(f => ` at ${f.methodName || 'anonymous'} (${f.file}:${f.lineNumber || 1}:${f.column || 1})`)
184
- .join('\n');
185
- notify();
186
- }
187
- })
188
- .catch(() => { });
189
- }
190
- // Asynchronously symbolicate stack trace using Metro in DEV mode
191
- if (stackToUse && typeof __DEV__ !== 'undefined' && __DEV__) {
217
+ const stackToSymbolicate = errorStack || stackToUse || stack;
218
+ if (stackToSymbolicate && typeof __DEV__ !== 'undefined' && __DEV__) {
192
219
  const currentLogId = newLog.id;
193
- symbolicateStack(stackToUse)
220
+ symbolicateStack(stackToSymbolicate)
194
221
  .then(symFrames => {
195
222
  if (symFrames && Array.isArray(symFrames) && symFrames.length > 0) {
196
223
  const symLines = [];
@@ -200,19 +227,30 @@ const addLog = (type, args, sourceMethod) => {
200
227
  const method = frame.methodName || '<anonymous>';
201
228
  const line = frame.lineNumber != null ? `:${frame.lineNumber}` : '';
202
229
  const col = frame.column != null ? `:${frame.column}` : '';
230
+ // Skip logger internal frames
231
+ const isInternal = file.includes('customHooks/consoleLogger') ||
232
+ file.includes('setupConsoleLogger') ||
233
+ file.includes('getStackDetails') ||
234
+ file.includes('react-native-inapp-inspector/dist') ||
235
+ file.includes('react-native-inapp-inspector/src') ||
236
+ method === 'getStackDetails' ||
237
+ method === 'addLog' ||
238
+ (method === 'log' && file.includes('consoleLogger'));
239
+ if (isInternal) {
240
+ continue;
241
+ }
203
242
  const lineStr = `at ${method} (${file}${line}${col})`;
204
243
  symLines.push(lineStr);
205
244
  const isUserFile = file &&
206
245
  !file.includes('node_modules') &&
207
246
  !file.includes('react-native/') &&
208
- !file.includes('consoleLogger') &&
209
- !file.includes('setupConsoleLogger') &&
210
247
  (file.endsWith('.tsx') ||
211
248
  file.endsWith('.jsx') ||
212
249
  file.endsWith('.ts') ||
213
250
  file.endsWith('.js'));
214
251
  if (!symUserCaller && isUserFile) {
215
- symUserCaller = `${method} (${file}${line}${col})`;
252
+ const cleanFile = file.split('/').pop() || file;
253
+ symUserCaller = `${method} (${cleanFile}${line}${col})`;
216
254
  }
217
255
  }
218
256
  const targetLog = logs.find(l => l.id === currentLogId);
@@ -3,7 +3,16 @@ let logs = [];
3
3
  let listeners = [];
4
4
  let counter = 0;
5
5
  const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
6
- const IGNORED_URL_PATTERNS = [/\/symbolicate(?:[/?#]|$)/];
6
+ const IGNORED_URL_PATTERNS = [
7
+ /\/symbolicate(?:[/?#]|$)/i,
8
+ /\/index\.bundle(?:\?|$)/i,
9
+ /\/hot(?:\?|$)/i,
10
+ /\/message(?:\?|$)/i,
11
+ /\/open-debugger(?:\?|$)/i,
12
+ /\/status(?:\?|$)/i,
13
+ /127\.0\.0\.1:(?:8081|8082|8083|19000|19001|19002|3000|8000)/i,
14
+ /localhost:(?:8081|8082|8083|19000|19001|19002|3000|8000)\/index\.bundle/i,
15
+ ];
7
16
  function shouldIgnoreUrl(url) {
8
17
  if (!url)
9
18
  return false;
@@ -54,6 +54,7 @@ export interface ComponentRenderProfile {
54
54
  fixKeys: PerformanceFixKey[];
55
55
  severity: 'optimal' | 'warning' | 'critical';
56
56
  }
57
+ export declare const INITIAL_EVENTS: PerformanceEvent[];
57
58
  export declare const usePerformanceTracker: () => {
58
59
  isRecording: boolean;
59
60
  setIsRecording: import("react").Dispatch<import("react").SetStateAction<boolean>>;
@@ -82,3 +83,10 @@ export declare const usePerformanceTracker: () => {
82
83
  clearEvents: () => void;
83
84
  triggerGc: () => void;
84
85
  };
86
+ export declare const logPerformanceEvent: (event: Omit<PerformanceEvent, "id" | "timestamp"> & {
87
+ id?: string;
88
+ timestamp?: number;
89
+ }) => PerformanceEvent;
90
+ export declare const clearPerformanceEvents: () => void;
91
+ export declare const subscribePerformanceEvents: (listener: (events: PerformanceEvent[]) => void) => () => void;
92
+ export declare const getPerformanceEvents: () => PerformanceEvent[];
@@ -170,7 +170,7 @@ const INITIAL_RENDER_PROFILES = [
170
170
  severity: 'optimal',
171
171
  },
172
172
  ];
173
- const INITIAL_EVENTS = [
173
+ export const INITIAL_EVENTS = [
174
174
  {
175
175
  id: 'perf-1',
176
176
  timestamp: Date.now() - 48000,
@@ -535,3 +535,40 @@ export const usePerformanceTracker = () => {
535
535
  triggerGc,
536
536
  };
537
537
  };
538
+ const globalPerformanceEvents = [...INITIAL_EVENTS];
539
+ const performanceListeners = new Set();
540
+ export const logPerformanceEvent = (event) => {
541
+ const fullEvent = {
542
+ id: event.id || `perf-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
543
+ timestamp: event.timestamp || Date.now(),
544
+ ...event,
545
+ };
546
+ globalPerformanceEvents.unshift(fullEvent);
547
+ if (globalPerformanceEvents.length > 100) {
548
+ globalPerformanceEvents.pop();
549
+ }
550
+ performanceListeners.forEach(listener => {
551
+ try {
552
+ listener([...globalPerformanceEvents]);
553
+ }
554
+ catch { }
555
+ });
556
+ return fullEvent;
557
+ };
558
+ export const clearPerformanceEvents = () => {
559
+ globalPerformanceEvents.length = 0;
560
+ performanceListeners.forEach(listener => {
561
+ try {
562
+ listener([]);
563
+ }
564
+ catch { }
565
+ });
566
+ };
567
+ export const subscribePerformanceEvents = (listener) => {
568
+ performanceListeners.add(listener);
569
+ listener([...globalPerformanceEvents]);
570
+ return () => {
571
+ performanceListeners.delete(listener);
572
+ };
573
+ };
574
+ export const getPerformanceEvents = () => [...globalPerformanceEvents];
@@ -0,0 +1,49 @@
1
+ export interface InspectLogOptions {
2
+ tag?: string;
3
+ logArgs?: boolean;
4
+ logResult?: boolean;
5
+ logDuration?: boolean;
6
+ }
7
+ /**
8
+ * `@InspectLog`: Automatically logs method execution, arguments, duration, and return values
9
+ * to the In-App Inspector Console.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * class UserService {
14
+ * @InspectLog({ tag: 'API', logArgs: true, logDuration: true })
15
+ * async fetchUser(id: string) {
16
+ * return api.get(`/users/${id}`);
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ export declare function InspectLog(options?: InspectLogOptions): (_target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
22
+ /**
23
+ * `@InspectTrackTime`: Measures and logs execution duration for critical functions.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * class FeedManager {
28
+ * @InspectTrackTime('Feed Data Processing')
29
+ * processFeedItems(items: FeedItem[]) { ... }
30
+ * }
31
+ * ```
32
+ */
33
+ export declare function InspectTrackTime(label?: string): (_target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
34
+ /**
35
+ * `@InspectCatch`: Safely catches unhandled exceptions thrown inside the method,
36
+ * logs them to the In-App Inspector, and returns a fallback value.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * class AuthService {
41
+ * @InspectCatch({ fallback: null, tag: 'AUTH' })
42
+ * async getCachedToken() { ... }
43
+ * }
44
+ * ```
45
+ */
46
+ export declare function InspectCatch(options?: {
47
+ fallback?: any;
48
+ tag?: string;
49
+ }): (_target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
@@ -0,0 +1,137 @@
1
+ // ─── Method & Class Decorators for React Native In-App Inspector ─────────────
2
+ //
3
+ // Clean TypeScript decorators to seamlessly annotate methods, asynchronous actions,
4
+ // and API services with automatic inspection, logging, timing, and error boundaries.
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ /**
7
+ * `@InspectLog`: Automatically logs method execution, arguments, duration, and return values
8
+ * to the In-App Inspector Console.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * class UserService {
13
+ * @InspectLog({ tag: 'API', logArgs: true, logDuration: true })
14
+ * async fetchUser(id: string) {
15
+ * return api.get(`/users/${id}`);
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ export function InspectLog(options = {}) {
21
+ const { tag = 'Inspect', logArgs = true, logResult = true, logDuration = true, } = options;
22
+ return function (_target, propertyKey, descriptor) {
23
+ const originalMethod = descriptor.value;
24
+ descriptor.value = function (...args) {
25
+ const start = Date.now();
26
+ const prefix = `[${tag}] ${propertyKey}`;
27
+ if (logArgs && args.length > 0) {
28
+ console.log(`${prefix} called with arguments:`, ...args);
29
+ }
30
+ else {
31
+ console.log(`${prefix} invoked`);
32
+ }
33
+ try {
34
+ const result = originalMethod.apply(this, args);
35
+ if (result && typeof result.then === 'function') {
36
+ return result
37
+ .then((asyncResult) => {
38
+ const elapsed = Date.now() - start;
39
+ if (logResult) {
40
+ console.log(`${prefix} resolved${logDuration ? ` in ${elapsed}ms` : ''}:`, asyncResult);
41
+ }
42
+ return asyncResult;
43
+ })
44
+ .catch((err) => {
45
+ const elapsed = Date.now() - start;
46
+ console.error(`${prefix} rejected${logDuration ? ` in ${elapsed}ms` : ''}:`, err);
47
+ throw err;
48
+ });
49
+ }
50
+ const elapsed = Date.now() - start;
51
+ if (logResult) {
52
+ console.log(`${prefix} returned${logDuration ? ` in ${elapsed}ms` : ''}:`, result);
53
+ }
54
+ return result;
55
+ }
56
+ catch (error) {
57
+ const elapsed = Date.now() - start;
58
+ console.error(`${prefix} failed${logDuration ? ` in ${elapsed}ms` : ''}:`, error);
59
+ throw error;
60
+ }
61
+ };
62
+ return descriptor;
63
+ };
64
+ }
65
+ /**
66
+ * `@InspectTrackTime`: Measures and logs execution duration for critical functions.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * class FeedManager {
71
+ * @InspectTrackTime('Feed Data Processing')
72
+ * processFeedItems(items: FeedItem[]) { ... }
73
+ * }
74
+ * ```
75
+ */
76
+ export function InspectTrackTime(label) {
77
+ return function (_target, propertyKey, descriptor) {
78
+ const originalMethod = descriptor.value;
79
+ const metricLabel = label || propertyKey;
80
+ descriptor.value = function (...args) {
81
+ const start = Date.now();
82
+ try {
83
+ const result = originalMethod.apply(this, args);
84
+ if (result && typeof result.then === 'function') {
85
+ return result.finally(() => {
86
+ const duration = Date.now() - start;
87
+ console.log(`[PERF] ⚡ ${metricLabel} took ${duration}ms`);
88
+ });
89
+ }
90
+ const duration = Date.now() - start;
91
+ console.log(`[PERF] ⚡ ${metricLabel} took ${duration}ms`);
92
+ return result;
93
+ }
94
+ catch (err) {
95
+ const duration = Date.now() - start;
96
+ console.error(`[PERF] ⚡ ${metricLabel} failed after ${duration}ms:`, err);
97
+ throw err;
98
+ }
99
+ };
100
+ return descriptor;
101
+ };
102
+ }
103
+ /**
104
+ * `@InspectCatch`: Safely catches unhandled exceptions thrown inside the method,
105
+ * logs them to the In-App Inspector, and returns a fallback value.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * class AuthService {
110
+ * @InspectCatch({ fallback: null, tag: 'AUTH' })
111
+ * async getCachedToken() { ... }
112
+ * }
113
+ * ```
114
+ */
115
+ export function InspectCatch(options = {}) {
116
+ const { fallback = undefined, tag = 'Catch' } = options;
117
+ return function (_target, propertyKey, descriptor) {
118
+ const originalMethod = descriptor.value;
119
+ descriptor.value = function (...args) {
120
+ try {
121
+ const result = originalMethod.apply(this, args);
122
+ if (result && typeof result.then === 'function') {
123
+ return result.catch((err) => {
124
+ console.error(`[${tag}] Error caught in ${propertyKey}:`, err);
125
+ return fallback;
126
+ });
127
+ }
128
+ return result;
129
+ }
130
+ catch (err) {
131
+ console.error(`[${tag}] Error caught in ${propertyKey}:`, err);
132
+ return fallback;
133
+ }
134
+ };
135
+ return descriptor;
136
+ };
137
+ }
@@ -0,0 +1,18 @@
1
+ import { GAEventCategory } from '../types/enums';
2
+ export { GAEventCategory };
3
+ export interface GAPlugin {
4
+ name: string;
5
+ resolveCategory?: (eventName: string, params?: Record<string, any>) => GAEventCategory | string | null | undefined;
6
+ }
7
+ /**
8
+ * Register a dynamic Google Analytics / Firebase plugin or custom resolver.
9
+ */
10
+ export declare const registerGAPlugin: (plugin: GAPlugin) => void;
11
+ /**
12
+ * Dynamically resolves the GA4 event category using:
13
+ * 1. Registered dynamic GA plugins
14
+ * 2. Runtime Firebase Analytics inspection
15
+ * 3. GA4 standard event taxonomies (O(1) Set lookup)
16
+ * 4. Semantic pattern & prefix analysis
17
+ */
18
+ export declare const getEventCategory: (name: string, params?: Record<string, any>) => GAEventCategory;
@@ -0,0 +1,169 @@
1
+ // ─── Google Analytics (GA4) & Firebase Analytics Dynamic Registry ─────────────
2
+ //
3
+ // Dynamically inspects GA4 / Firebase Analytics runtime schemas, auto-detects
4
+ // standard GA4 event taxonomies, and supports dynamic GA plugins.
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ import { GAEventCategory } from '../types/enums';
7
+ export { GAEventCategory };
8
+ // ─── Official GA4 & Firebase Standard Event Sets ─────────────────────────────
9
+ const GA4_PAGE_VIEW_EVENTS = new Set([
10
+ 'screen_view',
11
+ 'page_view',
12
+ 'screen_active',
13
+ 'screen_inactive',
14
+ 'view_search_results',
15
+ 'search',
16
+ ]);
17
+ const GA4_ECOMMERCE_EVENTS = new Set([
18
+ 'purchase',
19
+ 'refund',
20
+ 'add_to_cart',
21
+ 'remove_from_cart',
22
+ 'view_cart',
23
+ 'begin_checkout',
24
+ 'add_shipping_info',
25
+ 'add_payment_info',
26
+ 'view_item',
27
+ 'view_item_list',
28
+ 'select_item',
29
+ 'select_promotion',
30
+ 'view_promotion',
31
+ 'add_to_wishlist',
32
+ 'generate_lead',
33
+ 'in_app_purchase',
34
+ 'earn_virtual_currency',
35
+ 'spend_virtual_currency',
36
+ 'join_group',
37
+ 'level_up',
38
+ 'level_start',
39
+ 'level_end',
40
+ 'post_score',
41
+ 'select_content',
42
+ 'tutorial_begin',
43
+ 'tutorial_complete',
44
+ 'unlock_achievement',
45
+ ]);
46
+ const GA4_SYSTEM_EVENTS = new Set([
47
+ 'first_open',
48
+ 'first_visit',
49
+ 'session_start',
50
+ 'user_engagement',
51
+ 'app_clear_data',
52
+ 'app_exception',
53
+ 'app_remove',
54
+ 'app_store_refund',
55
+ 'app_store_subscription_cancel',
56
+ 'app_store_subscription_convert',
57
+ 'app_store_subscription_renew',
58
+ 'app_update',
59
+ 'os_update',
60
+ 'notification_receive',
61
+ 'notification_open',
62
+ 'notification_dismiss',
63
+ 'notification_foreground',
64
+ 'dynamic_link_first_open',
65
+ 'dynamic_link_app_open',
66
+ 'dynamic_link_app_update',
67
+ 'ad_impression',
68
+ 'ad_click',
69
+ 'ad_reward',
70
+ 'ad_query',
71
+ 'ad_exposure',
72
+ ]);
73
+ // ─── Registered Dynamic Plugins ──────────────────────────────────────────────
74
+ const registeredPlugins = [];
75
+ /**
76
+ * Register a dynamic Google Analytics / Firebase plugin or custom resolver.
77
+ */
78
+ export const registerGAPlugin = (plugin) => {
79
+ if (!registeredPlugins.some(p => p.name === plugin.name)) {
80
+ registeredPlugins.push(plugin);
81
+ }
82
+ };
83
+ /**
84
+ * Attempt dynamic inspection of host runtime Firebase / Google Analytics modules.
85
+ */
86
+ const tryInspectRuntimeFirebaseAnalytics = () => {
87
+ try {
88
+ const firebaseAnalytics = require('@react-native-firebase/analytics');
89
+ const mod = firebaseAnalytics?.default || firebaseAnalytics;
90
+ if (mod?.EventNames) {
91
+ Object.keys(mod.EventNames).forEach(k => {
92
+ const val = mod.EventNames[k]?.toLowerCase();
93
+ if (val) {
94
+ if (val.includes('cart') ||
95
+ val.includes('checkout') ||
96
+ val.includes('purchase') ||
97
+ val.includes('item')) {
98
+ GA4_ECOMMERCE_EVENTS.add(val);
99
+ }
100
+ else if (val.includes('screen') || val.includes('page')) {
101
+ GA4_PAGE_VIEW_EVENTS.add(val);
102
+ }
103
+ }
104
+ });
105
+ }
106
+ }
107
+ catch {
108
+ // Optional dependency - safe fallback to built-in GA4 taxonomy
109
+ }
110
+ };
111
+ // Initialize dynamic runtime inspection once
112
+ tryInspectRuntimeFirebaseAnalytics();
113
+ /**
114
+ * Dynamically resolves the GA4 event category using:
115
+ * 1. Registered dynamic GA plugins
116
+ * 2. Runtime Firebase Analytics inspection
117
+ * 3. GA4 standard event taxonomies (O(1) Set lookup)
118
+ * 4. Semantic pattern & prefix analysis
119
+ */
120
+ export const getEventCategory = (name, params) => {
121
+ if (!name)
122
+ return 'custom';
123
+ const cleanName = name.trim().toLowerCase();
124
+ // 1. Run through registered dynamic plugins
125
+ for (const plugin of registeredPlugins) {
126
+ if (plugin.resolveCategory) {
127
+ const category = plugin.resolveCategory(cleanName, params);
128
+ if (category)
129
+ return category;
130
+ }
131
+ }
132
+ // 2. Exact GA4 taxonomy matching (O(1))
133
+ if (GA4_PAGE_VIEW_EVENTS.has(cleanName)) {
134
+ return 'page_view';
135
+ }
136
+ if (GA4_ECOMMERCE_EVENTS.has(cleanName)) {
137
+ return 'ecommerce';
138
+ }
139
+ if (GA4_SYSTEM_EVENTS.has(cleanName)) {
140
+ return 'system';
141
+ }
142
+ // 3. Prefix & namespace detection
143
+ if (cleanName.startsWith('firebase_') ||
144
+ cleanName.startsWith('ga_') ||
145
+ cleanName.startsWith('gtag_') ||
146
+ cleanName.startsWith('_') ||
147
+ cleanName.startsWith('google_')) {
148
+ return 'system';
149
+ }
150
+ // 4. Semantic pattern matching
151
+ if (cleanName.endsWith('_view') ||
152
+ cleanName.endsWith('_screen') ||
153
+ cleanName.startsWith('screen_') ||
154
+ cleanName.startsWith('page_') ||
155
+ cleanName.includes('screen_view') ||
156
+ cleanName.includes('page_view')) {
157
+ return 'page_view';
158
+ }
159
+ if (cleanName.includes('cart') ||
160
+ cleanName.includes('checkout') ||
161
+ cleanName.includes('purchase') ||
162
+ cleanName.includes('payment') ||
163
+ cleanName.includes('order') ||
164
+ cleanName.includes('refund') ||
165
+ cleanName.includes('shipping')) {
166
+ return 'ecommerce';
167
+ }
168
+ return 'custom';
169
+ };
@@ -1,4 +1,4 @@
1
- import { NetworkLog, RouteInfo, DiffResult, JsonContent } from '../types';
1
+ import { NetworkLog, RouteInfo, DiffResult, JsonContent, StackFrameType } from '../types';
2
2
  export declare const getDomainColor: (domain: string) => string;
3
3
  export declare const formatDateTime: (timestamp: number) => string;
4
4
  export declare const getStatusColor: (status: number | null) => string;
@@ -40,17 +40,20 @@ export interface ParsedStackFrame {
40
40
  functionName: string;
41
41
  fileName: string;
42
42
  fullPath: string;
43
- fileExt?: 'tsx' | 'jsx' | 'ts' | 'js' | 'other';
44
- isUserCode?: boolean;
43
+ fileExt: string;
44
+ frameType: StackFrameType | 'app' | 'dependency' | 'runtime' | 'native';
45
+ isUserCode: boolean;
46
+ isRuntimeNoise: boolean;
45
47
  lineNumber?: string;
46
48
  columnNumber?: string;
47
49
  isOrigin?: boolean;
50
+ copyableLocation: string;
48
51
  }
49
52
  /** Parses a stack trace line to extract function name, file name, extension (.tsx/.jsx/.ts), line, and column numbers */
50
53
  export declare const parseStackLine: (rawLine: string, isOrigin?: boolean) => ParsedStackFrame;
51
54
  export declare const ANALYTICS_EVENT_PALETTE: string[];
52
55
  export declare const getEventColor: (name: string) => string;
53
- export declare const getEventCategory: (name: string) => "page_view" | "ecommerce" | "system" | "custom";
56
+ export { getEventCategory, registerGAPlugin, type GAEventCategory, type GAPlugin, } from './gaAnalyticsRegistry';
54
57
  export declare const getCategoryColors: (category: string) => {
55
58
  bg: string;
56
59
  border: string;