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