@rejourneyco/react-native 1.0.1 → 1.0.3
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/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +72 -391
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +3 -26
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +7 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
- package/ios/Capture/RJCaptureEngine.m +3 -34
- package/ios/Capture/RJVideoEncoder.m +0 -26
- package/ios/Capture/RJViewHierarchyScanner.m +68 -51
- package/ios/Core/RJLifecycleManager.m +0 -14
- package/ios/Core/Rejourney.mm +53 -129
- package/ios/Network/RJDeviceAuthManager.m +0 -2
- package/ios/Network/RJUploadManager.h +8 -0
- package/ios/Network/RJUploadManager.m +45 -0
- package/ios/Privacy/RJPrivacyMask.m +5 -31
- package/ios/Rejourney.h +0 -14
- package/ios/Touch/RJTouchInterceptor.m +21 -15
- package/ios/Utils/RJEventBuffer.m +57 -69
- package/ios/Utils/RJPerfTiming.m +0 -5
- package/ios/Utils/RJWindowUtils.m +87 -87
- package/lib/commonjs/components/Mask.js +1 -6
- package/lib/commonjs/index.js +46 -117
- package/lib/commonjs/sdk/autoTracking.js +39 -313
- package/lib/commonjs/sdk/constants.js +2 -13
- package/lib/commonjs/sdk/errorTracking.js +1 -29
- package/lib/commonjs/sdk/metricsTracking.js +3 -24
- package/lib/commonjs/sdk/navigation.js +3 -42
- package/lib/commonjs/sdk/networkInterceptor.js +7 -60
- package/lib/commonjs/sdk/utils.js +73 -19
- package/lib/module/components/Mask.js +1 -6
- package/lib/module/index.js +45 -121
- package/lib/module/sdk/autoTracking.js +39 -314
- package/lib/module/sdk/constants.js +2 -13
- package/lib/module/sdk/errorTracking.js +1 -29
- package/lib/module/sdk/index.js +0 -2
- package/lib/module/sdk/metricsTracking.js +3 -24
- package/lib/module/sdk/navigation.js +3 -42
- package/lib/module/sdk/networkInterceptor.js +7 -60
- package/lib/module/sdk/utils.js +73 -19
- package/lib/typescript/NativeRejourney.d.ts +1 -0
- package/lib/typescript/sdk/autoTracking.d.ts +4 -4
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/lib/typescript/types/index.d.ts +0 -1
- package/package.json +17 -11
- package/src/NativeRejourney.ts +2 -0
- package/src/components/Mask.tsx +0 -3
- package/src/index.ts +43 -92
- package/src/sdk/autoTracking.ts +51 -284
- package/src/sdk/constants.ts +13 -13
- package/src/sdk/errorTracking.ts +1 -17
- package/src/sdk/index.ts +0 -2
- package/src/sdk/metricsTracking.ts +5 -33
- package/src/sdk/navigation.ts +8 -29
- package/src/sdk/networkInterceptor.ts +9 -42
- package/src/sdk/utils.ts +76 -19
- package/src/types/index.ts +0 -29
package/src/sdk/constants.ts
CHANGED
|
@@ -7,26 +7,26 @@ export const SDK_VERSION = '1.0.0';
|
|
|
7
7
|
/** Default configuration values */
|
|
8
8
|
export const DEFAULT_CONFIG = {
|
|
9
9
|
enabled: true,
|
|
10
|
-
captureFPS: 0.5,
|
|
11
|
-
captureOnEvents: true,
|
|
12
|
-
maxSessionDuration: 10 * 60 * 1000,
|
|
13
|
-
maxStorageSize: 50 * 1024 * 1024,
|
|
10
|
+
captureFPS: 0.5,
|
|
11
|
+
captureOnEvents: true,
|
|
12
|
+
maxSessionDuration: 10 * 60 * 1000,
|
|
13
|
+
maxStorageSize: 50 * 1024 * 1024,
|
|
14
14
|
autoScreenTracking: true,
|
|
15
15
|
autoGestureTracking: true,
|
|
16
16
|
privacyOcclusion: true,
|
|
17
17
|
enableCompression: true,
|
|
18
|
-
inactivityThreshold: 5000,
|
|
18
|
+
inactivityThreshold: 5000,
|
|
19
19
|
disableInDev: false,
|
|
20
20
|
detectRageTaps: true,
|
|
21
21
|
rageTapThreshold: 3,
|
|
22
|
-
rageTapTimeWindow: 1000,
|
|
22
|
+
rageTapTimeWindow: 1000,
|
|
23
23
|
debug: false,
|
|
24
24
|
autoStartRecording: true,
|
|
25
|
-
collectDeviceInfo: true,
|
|
26
|
-
collectGeoLocation: true,
|
|
27
|
-
postNavigationDelay: 300,
|
|
28
|
-
postGestureDelay: 200,
|
|
29
|
-
postModalDelay: 400,
|
|
25
|
+
collectDeviceInfo: true,
|
|
26
|
+
collectGeoLocation: true,
|
|
27
|
+
postNavigationDelay: 300,
|
|
28
|
+
postGestureDelay: 200,
|
|
29
|
+
postModalDelay: 400,
|
|
30
30
|
} as const;
|
|
31
31
|
|
|
32
32
|
/** Event type constants */
|
|
@@ -61,8 +61,8 @@ export const CAPTURE_SETTINGS = {
|
|
|
61
61
|
DEFAULT_FPS: 0.5,
|
|
62
62
|
MIN_FPS: 0.1,
|
|
63
63
|
MAX_FPS: 2,
|
|
64
|
-
CAPTURE_SCALE: 0.25,
|
|
65
|
-
MIN_CAPTURE_DELTA_TIME: 0.5,
|
|
64
|
+
CAPTURE_SCALE: 0.25,
|
|
65
|
+
MIN_CAPTURE_DELTA_TIME: 0.5,
|
|
66
66
|
} as const;
|
|
67
67
|
|
|
68
68
|
/** Memory management settings */
|
package/src/sdk/errorTracking.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ErrorEvent } from '../types';
|
|
9
9
|
|
|
10
|
-
// Type declarations for browser globals (only used in hybrid apps where DOM is available)
|
|
11
10
|
type OnErrorEventHandler = ((
|
|
12
11
|
event: Event | string,
|
|
13
12
|
source?: string,
|
|
@@ -21,7 +20,6 @@ interface PromiseRejectionEvent {
|
|
|
21
20
|
promise?: Promise<any>;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
// Cast globalThis to work with both RN and hybrid scenarios
|
|
25
23
|
const _globalThis = globalThis as typeof globalThis & {
|
|
26
24
|
onerror?: OnErrorEventHandler;
|
|
27
25
|
addEventListener?: (type: string, handler: (event: any) => void) => void;
|
|
@@ -32,15 +30,11 @@ const _globalThis = globalThis as typeof globalThis & {
|
|
|
32
30
|
};
|
|
33
31
|
};
|
|
34
32
|
|
|
35
|
-
// Original error handlers (for restoration)
|
|
36
33
|
let originalErrorHandler: ((error: Error, isFatal: boolean) => void) | undefined;
|
|
37
34
|
let originalOnError: OnErrorEventHandler | null = null;
|
|
38
35
|
let originalOnUnhandledRejection: ((event: PromiseRejectionEvent) => void) | null = null;
|
|
39
|
-
|
|
40
|
-
// Callbacks
|
|
41
36
|
let onErrorCallback: ((error: ErrorEvent) => void) | null = null;
|
|
42
37
|
|
|
43
|
-
// Metrics
|
|
44
38
|
let errorCount = 0;
|
|
45
39
|
|
|
46
40
|
/**
|
|
@@ -57,17 +51,14 @@ export function setupErrorTracking(
|
|
|
57
51
|
onErrorCallback = onError;
|
|
58
52
|
errorCount = 0;
|
|
59
53
|
|
|
60
|
-
// Track React Native errors
|
|
61
54
|
if (config.trackReactNativeErrors !== false) {
|
|
62
55
|
setupReactNativeErrorHandler();
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
// Track JavaScript errors (only works in web/debug)
|
|
66
58
|
if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
|
|
67
59
|
setupJSErrorHandler();
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
// Track unhandled promise rejections
|
|
71
62
|
if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
|
|
72
63
|
setupPromiseRejectionHandler();
|
|
73
64
|
}
|
|
@@ -77,7 +68,6 @@ export function setupErrorTracking(
|
|
|
77
68
|
* Cleanup error tracking and restore original handlers
|
|
78
69
|
*/
|
|
79
70
|
export function cleanupErrorTracking(): void {
|
|
80
|
-
// Restore React Native handler
|
|
81
71
|
if (originalErrorHandler) {
|
|
82
72
|
try {
|
|
83
73
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
@@ -90,13 +80,11 @@ export function cleanupErrorTracking(): void {
|
|
|
90
80
|
originalErrorHandler = undefined;
|
|
91
81
|
}
|
|
92
82
|
|
|
93
|
-
// Restore global onerror
|
|
94
83
|
if (originalOnError !== null) {
|
|
95
84
|
_globalThis.onerror = originalOnError;
|
|
96
85
|
originalOnError = null;
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
// Remove promise rejection handler
|
|
100
88
|
if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
|
|
101
89
|
_globalThis.removeEventListener!('unhandledrejection', originalOnUnhandledRejection);
|
|
102
90
|
originalOnUnhandledRejection = null;
|
|
@@ -155,10 +143,8 @@ function setupReactNativeErrorHandler(): void {
|
|
|
155
143
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
156
144
|
if (!ErrorUtils) return;
|
|
157
145
|
|
|
158
|
-
// Store original handler
|
|
159
146
|
originalErrorHandler = ErrorUtils.getGlobalHandler();
|
|
160
147
|
|
|
161
|
-
// Set new handler
|
|
162
148
|
ErrorUtils.setGlobalHandler((error: Error, isFatal: boolean) => {
|
|
163
149
|
trackError({
|
|
164
150
|
type: 'error',
|
|
@@ -168,13 +154,12 @@ function setupReactNativeErrorHandler(): void {
|
|
|
168
154
|
name: error.name || 'Error',
|
|
169
155
|
});
|
|
170
156
|
|
|
171
|
-
// Call original handler
|
|
172
157
|
if (originalErrorHandler) {
|
|
173
158
|
originalErrorHandler(error, isFatal);
|
|
174
159
|
}
|
|
175
160
|
});
|
|
176
161
|
} catch {
|
|
177
|
-
//
|
|
162
|
+
// Ignore
|
|
178
163
|
}
|
|
179
164
|
}
|
|
180
165
|
|
|
@@ -200,7 +185,6 @@ function setupJSErrorHandler(): void {
|
|
|
200
185
|
name: error?.name || 'Error',
|
|
201
186
|
});
|
|
202
187
|
|
|
203
|
-
// Call original handler
|
|
204
188
|
if (originalOnError) {
|
|
205
189
|
return originalOnError(message, source, lineno, colno, error);
|
|
206
190
|
}
|
package/src/sdk/index.ts
CHANGED
|
@@ -7,5 +7,3 @@ export * from './constants';
|
|
|
7
7
|
export * from './utils';
|
|
8
8
|
export * from './autoTracking';
|
|
9
9
|
export * from './networkInterceptor';
|
|
10
|
-
// Note: errorTracking and metricsTracking are internal modules
|
|
11
|
-
// Their exports are re-exported through autoTracking for backward compatibility
|
|
@@ -10,41 +10,29 @@
|
|
|
10
10
|
* Session metrics structure
|
|
11
11
|
*/
|
|
12
12
|
export interface SessionMetrics {
|
|
13
|
-
// Core counts
|
|
14
13
|
totalEvents: number;
|
|
15
14
|
touchCount: number;
|
|
16
15
|
scrollCount: number;
|
|
17
16
|
gestureCount: number;
|
|
18
17
|
inputCount: number;
|
|
19
18
|
navigationCount: number;
|
|
20
|
-
|
|
21
|
-
// Issue tracking
|
|
22
19
|
errorCount: number;
|
|
23
20
|
rageTapCount: number;
|
|
24
|
-
|
|
25
|
-
// API metrics
|
|
26
21
|
apiSuccessCount: number;
|
|
27
22
|
apiErrorCount: number;
|
|
28
23
|
apiTotalCount: number;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
netTotalDurationMs: number; // Sum of all API durations
|
|
32
|
-
netTotalBytes: number; // Sum of all response bytes
|
|
33
|
-
|
|
34
|
-
// Screen tracking for funnels
|
|
24
|
+
netTotalDurationMs: number;
|
|
25
|
+
netTotalBytes: number;
|
|
35
26
|
screensVisited: string[];
|
|
36
27
|
uniqueScreensCount: number;
|
|
37
|
-
|
|
38
|
-
// Scores (0-100)
|
|
39
28
|
interactionScore: number;
|
|
40
29
|
explorationScore: number;
|
|
41
30
|
uxScore: number;
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
// Metrics state
|
|
45
33
|
let metrics: SessionMetrics = createEmptyMetrics();
|
|
46
34
|
let sessionStartTime = 0;
|
|
47
|
-
let maxSessionDurationMs = 10 * 60 * 1000;
|
|
35
|
+
let maxSessionDurationMs = 10 * 60 * 1000;
|
|
48
36
|
|
|
49
37
|
/**
|
|
50
38
|
* Create empty metrics object
|
|
@@ -92,11 +80,9 @@ export function initMetrics(): void {
|
|
|
92
80
|
* Get current session metrics with calculated scores
|
|
93
81
|
*/
|
|
94
82
|
export function getSessionMetrics(): SessionMetrics {
|
|
95
|
-
// Calculate duration, clamped to max session duration
|
|
96
83
|
const rawDuration = Date.now() - sessionStartTime;
|
|
97
84
|
const durationMs = Math.min(rawDuration, maxSessionDurationMs);
|
|
98
85
|
|
|
99
|
-
// Calculate scores
|
|
100
86
|
const interactionScore = calculateInteractionScore(durationMs);
|
|
101
87
|
const explorationScore = calculateExplorationScore();
|
|
102
88
|
const uxScore = calculateUXScore();
|
|
@@ -120,8 +106,6 @@ export function setMaxSessionDurationMinutes(minutes?: number): void {
|
|
|
120
106
|
}
|
|
121
107
|
}
|
|
122
108
|
|
|
123
|
-
// ==================== Metric Increment Methods ====================
|
|
124
|
-
|
|
125
109
|
export function incrementTouchCount(): void {
|
|
126
110
|
metrics.touchCount++;
|
|
127
111
|
metrics.totalEvents++;
|
|
@@ -173,8 +157,6 @@ export function trackAPIMetrics(
|
|
|
173
157
|
}
|
|
174
158
|
}
|
|
175
159
|
|
|
176
|
-
// ==================== Score Calculations ====================
|
|
177
|
-
|
|
178
160
|
/**
|
|
179
161
|
* Calculate interaction score based on engagement with app
|
|
180
162
|
* Higher = more engaged (more interactions per minute)
|
|
@@ -185,15 +167,12 @@ function calculateInteractionScore(durationMs: number): number {
|
|
|
185
167
|
const durationMinutes = durationMs / 60000;
|
|
186
168
|
const interactionsPerMinute = metrics.touchCount / Math.max(0.5, durationMinutes);
|
|
187
169
|
|
|
188
|
-
// Ideal: 10-30 interactions per minute
|
|
189
|
-
// Low (< 5): user passive/confused
|
|
190
|
-
// Very high (> 60): rage tapping
|
|
191
170
|
if (interactionsPerMinute < 2) return 20;
|
|
192
171
|
if (interactionsPerMinute < 5) return 50;
|
|
193
172
|
if (interactionsPerMinute < 10) return 70;
|
|
194
173
|
if (interactionsPerMinute <= 30) return 100;
|
|
195
174
|
if (interactionsPerMinute <= 60) return 80;
|
|
196
|
-
return 50;
|
|
175
|
+
return 50;
|
|
197
176
|
}
|
|
198
177
|
|
|
199
178
|
/**
|
|
@@ -203,13 +182,12 @@ function calculateInteractionScore(durationMs: number): number {
|
|
|
203
182
|
function calculateExplorationScore(): number {
|
|
204
183
|
const uniqueScreens = metrics.uniqueScreensCount;
|
|
205
184
|
|
|
206
|
-
// More unique screens = better exploration
|
|
207
185
|
if (uniqueScreens >= 10) return 100;
|
|
208
186
|
if (uniqueScreens >= 7) return 90;
|
|
209
187
|
if (uniqueScreens >= 5) return 80;
|
|
210
188
|
if (uniqueScreens >= 3) return 60;
|
|
211
189
|
if (uniqueScreens >= 2) return 40;
|
|
212
|
-
return 20;
|
|
190
|
+
return 20;
|
|
213
191
|
}
|
|
214
192
|
|
|
215
193
|
/**
|
|
@@ -219,14 +197,8 @@ function calculateExplorationScore(): number {
|
|
|
219
197
|
function calculateUXScore(): number {
|
|
220
198
|
let score = 100;
|
|
221
199
|
|
|
222
|
-
// Deduct for errors
|
|
223
200
|
score -= metrics.errorCount * 10;
|
|
224
|
-
|
|
225
|
-
// Deduct heavily for rage taps
|
|
226
201
|
score -= metrics.rageTapCount * 20;
|
|
227
|
-
|
|
228
|
-
// Deduct for API errors (less severe)
|
|
229
202
|
score -= metrics.apiErrorCount * 5;
|
|
230
|
-
|
|
231
203
|
return Math.max(0, Math.min(100, score));
|
|
232
204
|
}
|
package/src/sdk/navigation.ts
CHANGED
|
@@ -20,15 +20,12 @@ export function normalizeScreenName(raw: string): string {
|
|
|
20
20
|
|
|
21
21
|
let name = raw;
|
|
22
22
|
|
|
23
|
-
// 1. Remove non-printable characters and weird symbols (often from icons)
|
|
24
23
|
name = name.replace(/[^\x20-\x7E\s]/g, '');
|
|
25
24
|
|
|
26
|
-
// 2. Handle hyphens: my-recipes -> My Recipes
|
|
27
25
|
name = name.split(/[-_]/).map(word =>
|
|
28
26
|
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
29
27
|
).join(' ');
|
|
30
28
|
|
|
31
|
-
// 3. Remove common suffixes
|
|
32
29
|
const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
|
|
33
30
|
for (const suffix of suffixes) {
|
|
34
31
|
if (name.endsWith(suffix) && name.length > suffix.length) {
|
|
@@ -36,7 +33,6 @@ export function normalizeScreenName(raw: string): string {
|
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
// 4. Remove common prefixes (React Native internals)
|
|
40
36
|
const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
|
|
41
37
|
for (const prefix of prefixes) {
|
|
42
38
|
if (name.startsWith(prefix) && name.length > prefix.length + 2) {
|
|
@@ -44,23 +40,18 @@ export function normalizeScreenName(raw: string): string {
|
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
// 5. Handle dynamic route params: [id] -> (omit), [userId] -> "User"
|
|
48
43
|
name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
|
|
49
44
|
const clean = param.replace(/Id$/i, '');
|
|
50
45
|
if (clean.length < 2) return '';
|
|
51
46
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
52
47
|
});
|
|
53
48
|
|
|
54
|
-
// 6. Remove leftover brackets
|
|
55
49
|
name = name.replace(/\[\]/g, '');
|
|
56
50
|
|
|
57
|
-
// 7. Convert camelCase/PascalCase to Title Case with spaces
|
|
58
51
|
name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
59
52
|
|
|
60
|
-
// 8. Clean up multiple spaces and trim
|
|
61
53
|
name = name.replace(/\s+/g, ' ').trim();
|
|
62
54
|
|
|
63
|
-
// 9. Capitalize first letter
|
|
64
55
|
if (name.length > 0) {
|
|
65
56
|
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
66
57
|
}
|
|
@@ -76,24 +67,18 @@ export function normalizeScreenName(raw: string): string {
|
|
|
76
67
|
* @returns Human-readable screen name
|
|
77
68
|
*/
|
|
78
69
|
export function getScreenNameFromPath(pathname: string, segments: string[]): string {
|
|
79
|
-
// Use segments for cleaner names if available
|
|
80
70
|
if (segments.length > 0) {
|
|
81
|
-
// Filter out group markers like (tabs), (auth), etc.
|
|
82
71
|
const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
|
|
83
72
|
|
|
84
73
|
if (cleanSegments.length > 0) {
|
|
85
|
-
// Process each segment
|
|
86
74
|
const processedSegments = cleanSegments.map(s => {
|
|
87
|
-
// Handle dynamic params like [id]
|
|
88
75
|
if (s.startsWith('[') && s.endsWith(']')) {
|
|
89
76
|
const param = s.slice(1, -1);
|
|
90
|
-
// Skip pure ID params, keep meaningful ones
|
|
91
77
|
if (param === 'id' || param === 'slug') return null;
|
|
92
|
-
|
|
78
|
+
if (param === 'id' || param === 'slug') return null;
|
|
93
79
|
const clean = param.replace(/Id$/i, '');
|
|
94
80
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
95
81
|
}
|
|
96
|
-
// Regular segment - capitalize
|
|
97
82
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
98
83
|
}).filter(Boolean);
|
|
99
84
|
|
|
@@ -103,32 +88,27 @@ export function getScreenNameFromPath(pathname: string, segments: string[]): str
|
|
|
103
88
|
}
|
|
104
89
|
}
|
|
105
90
|
|
|
106
|
-
// Fall back to pathname
|
|
107
91
|
if (!pathname || pathname === '/') {
|
|
108
92
|
return 'Home';
|
|
109
93
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.replace(/^\/(tabs)?/, '') // Remove leading slash and (tabs)
|
|
114
|
-
.replace(/\([^)]+\)/g, '') // Remove all group markers like (settings)
|
|
94
|
+
const cleanPath = pathname
|
|
95
|
+
.replace(/^\/(tabs)?/, '')
|
|
96
|
+
.replace(/\([^)]+\)/g, '')
|
|
115
97
|
.replace(/\[([^\]]+)\]/g, (_, param) => {
|
|
116
|
-
// Handle dynamic params in path
|
|
117
98
|
if (param === 'id' || param === 'slug') return '';
|
|
118
99
|
const clean = param.replace(/Id$/i, '');
|
|
119
100
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
120
101
|
})
|
|
121
|
-
.replace(/\/+/g, '/')
|
|
122
|
-
.replace(/^\//, '')
|
|
123
|
-
.replace(/\/$/, '')
|
|
124
|
-
.replace(/\//g, ' > ')
|
|
102
|
+
.replace(/\/+/g, '/')
|
|
103
|
+
.replace(/^\//, '')
|
|
104
|
+
.replace(/\/$/, '')
|
|
105
|
+
.replace(/\//g, ' > ')
|
|
125
106
|
.trim();
|
|
126
107
|
|
|
127
108
|
if (!cleanPath) {
|
|
128
109
|
return 'Home';
|
|
129
110
|
}
|
|
130
111
|
|
|
131
|
-
// Capitalize first letter of each word
|
|
132
112
|
return cleanPath
|
|
133
113
|
.split(' > ')
|
|
134
114
|
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
@@ -148,7 +128,6 @@ export function getCurrentRouteFromState(state: any): string | null {
|
|
|
148
128
|
const route = state.routes[state.index ?? state.routes.length - 1];
|
|
149
129
|
if (!route) return null;
|
|
150
130
|
|
|
151
|
-
// If nested state, recurse
|
|
152
131
|
if (route.state) {
|
|
153
132
|
return getCurrentRouteFromState(route.state);
|
|
154
133
|
}
|
|
@@ -15,39 +15,32 @@
|
|
|
15
15
|
|
|
16
16
|
import type { NetworkRequestParams } from '../types';
|
|
17
17
|
|
|
18
|
-
// Store original implementations
|
|
19
18
|
let originalFetch: typeof fetch | null = null;
|
|
20
19
|
let originalXHROpen: typeof XMLHttpRequest.prototype.open | null = null;
|
|
21
20
|
let originalXHRSend: typeof XMLHttpRequest.prototype.send | null = null;
|
|
22
21
|
|
|
23
|
-
// Callback to log network requests (called asynchronously)
|
|
24
22
|
let logCallback: ((request: NetworkRequestParams) => void) | null = null;
|
|
25
23
|
|
|
26
|
-
// Pending requests buffer (circular buffer for memory efficiency)
|
|
27
24
|
const MAX_PENDING_REQUESTS = 100;
|
|
28
25
|
const pendingRequests: (NetworkRequestParams | null)[] = new Array(MAX_PENDING_REQUESTS).fill(null);
|
|
29
26
|
let pendingHead = 0;
|
|
30
27
|
let pendingTail = 0;
|
|
31
28
|
let pendingCount = 0;
|
|
32
29
|
|
|
33
|
-
// Flush timer
|
|
34
30
|
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
35
|
-
const FLUSH_INTERVAL = 500;
|
|
31
|
+
const FLUSH_INTERVAL = 500;
|
|
36
32
|
|
|
37
|
-
// Sampling for high-frequency endpoints
|
|
38
33
|
const endpointCounts = new Map<string, { count: number; lastReset: number }>();
|
|
39
|
-
const SAMPLE_WINDOW = 10000;
|
|
40
|
-
const MAX_PER_ENDPOINT = 20;
|
|
34
|
+
const SAMPLE_WINDOW = 10000;
|
|
35
|
+
const MAX_PER_ENDPOINT = 20;
|
|
41
36
|
|
|
42
|
-
// Configuration
|
|
43
37
|
const config = {
|
|
44
38
|
enabled: true,
|
|
45
|
-
ignorePatterns: [] as string[],
|
|
46
|
-
maxUrlLength: 300,
|
|
47
|
-
captureSizes: false,
|
|
39
|
+
ignorePatterns: [] as string[],
|
|
40
|
+
maxUrlLength: 300,
|
|
41
|
+
captureSizes: false,
|
|
48
42
|
};
|
|
49
43
|
|
|
50
|
-
// PII Scrubbing - Sensitive keys to look for in query params
|
|
51
44
|
const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_token', 'api_key'];
|
|
52
45
|
|
|
53
46
|
/**
|
|
@@ -55,7 +48,6 @@ const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_to
|
|
|
55
48
|
*/
|
|
56
49
|
function scrubUrl(url: string): string {
|
|
57
50
|
try {
|
|
58
|
-
// Fast check if URL might contain params
|
|
59
51
|
if (url.indexOf('?') === -1) return url;
|
|
60
52
|
|
|
61
53
|
const urlObj = new URL(url);
|
|
@@ -68,12 +60,10 @@ function scrubUrl(url: string): string {
|
|
|
68
60
|
}
|
|
69
61
|
});
|
|
70
62
|
|
|
71
|
-
// Also scan for partial matches (case-insensitive) if strict scrubbing needed
|
|
72
|
-
// But for performance, we stick to exact keys or common variations
|
|
73
|
-
|
|
74
63
|
return modified ? urlObj.toString() : url;
|
|
75
64
|
} catch {
|
|
76
|
-
//
|
|
65
|
+
// Ignore error, fallback to primitive scrubbing
|
|
66
|
+
|
|
77
67
|
let scrubbed = url;
|
|
78
68
|
SENSITIVE_KEYS.forEach(key => {
|
|
79
69
|
const regex = new RegExp(`([?&])${key}=[^&]*`, 'gi');
|
|
@@ -121,14 +111,12 @@ function queueRequest(request: NetworkRequestParams): void {
|
|
|
121
111
|
pendingCount--;
|
|
122
112
|
}
|
|
123
113
|
|
|
124
|
-
// Scrub URL before queuing
|
|
125
114
|
request.url = scrubUrl(request.url);
|
|
126
115
|
|
|
127
116
|
pendingRequests[pendingTail] = request;
|
|
128
117
|
pendingTail = (pendingTail + 1) % MAX_PENDING_REQUESTS;
|
|
129
118
|
pendingCount++;
|
|
130
119
|
|
|
131
|
-
// Schedule flush if not already scheduled
|
|
132
120
|
if (!flushTimer) {
|
|
133
121
|
flushTimer = setTimeout(flushPendingRequests, FLUSH_INTERVAL);
|
|
134
122
|
}
|
|
@@ -141,8 +129,6 @@ function flushPendingRequests(): void {
|
|
|
141
129
|
flushTimer = null;
|
|
142
130
|
|
|
143
131
|
if (!logCallback || pendingCount === 0) return;
|
|
144
|
-
|
|
145
|
-
// Process all pending requests
|
|
146
132
|
while (pendingCount > 0) {
|
|
147
133
|
const request = pendingRequests[pendingHead];
|
|
148
134
|
pendingRequests[pendingHead] = null; // Allow GC
|
|
@@ -153,7 +139,7 @@ function flushPendingRequests(): void {
|
|
|
153
139
|
try {
|
|
154
140
|
logCallback(request);
|
|
155
141
|
} catch {
|
|
156
|
-
// Ignore
|
|
142
|
+
// Ignore
|
|
157
143
|
}
|
|
158
144
|
}
|
|
159
145
|
}
|
|
@@ -167,11 +153,9 @@ function parseUrlFast(url: string): { host: string; path: string } {
|
|
|
167
153
|
let hostEnd = -1;
|
|
168
154
|
let pathStart = -1;
|
|
169
155
|
|
|
170
|
-
// Find ://
|
|
171
156
|
const protoEnd = url.indexOf('://');
|
|
172
157
|
if (protoEnd !== -1) {
|
|
173
158
|
const afterProto = protoEnd + 3;
|
|
174
|
-
// Find end of host (first / after ://)
|
|
175
159
|
const slashPos = url.indexOf('/', afterProto);
|
|
176
160
|
if (slashPos !== -1) {
|
|
177
161
|
hostEnd = slashPos;
|
|
@@ -187,7 +171,6 @@ function parseUrlFast(url: string): { host: string; path: string } {
|
|
|
187
171
|
};
|
|
188
172
|
}
|
|
189
173
|
|
|
190
|
-
// Relative URL
|
|
191
174
|
return { host: '', path: url };
|
|
192
175
|
}
|
|
193
176
|
|
|
@@ -204,19 +187,16 @@ function interceptFetch(): void {
|
|
|
204
187
|
input: RequestInfo | URL,
|
|
205
188
|
init?: RequestInit
|
|
206
189
|
): Promise<Response> {
|
|
207
|
-
// Fast path: if disabled or no callback, skip entirely
|
|
208
190
|
if (!config.enabled || !logCallback) {
|
|
209
191
|
return originalFetch!(input, init);
|
|
210
192
|
}
|
|
211
193
|
|
|
212
|
-
// Extract URL string (minimal work)
|
|
213
194
|
const url = typeof input === 'string'
|
|
214
195
|
? input
|
|
215
196
|
: input instanceof URL
|
|
216
197
|
? input.href
|
|
217
198
|
: (input as Request).url;
|
|
218
199
|
|
|
219
|
-
// Fast ignore check
|
|
220
200
|
if (shouldIgnoreUrl(url)) {
|
|
221
201
|
return originalFetch!(input, init);
|
|
222
202
|
}
|
|
@@ -227,14 +207,10 @@ function interceptFetch(): void {
|
|
|
227
207
|
return originalFetch!(input, init);
|
|
228
208
|
}
|
|
229
209
|
|
|
230
|
-
// Capture start time (only synchronous work)
|
|
231
210
|
const startTime = Date.now();
|
|
232
211
|
const method = ((init?.method || 'GET').toUpperCase()) as NetworkRequestParams['method'];
|
|
233
|
-
|
|
234
|
-
// Call original fetch
|
|
235
212
|
return originalFetch!(input, init).then(
|
|
236
213
|
(response) => {
|
|
237
|
-
// Success - queue the log asynchronously
|
|
238
214
|
queueRequest({
|
|
239
215
|
requestId: `f${startTime}`,
|
|
240
216
|
method,
|
|
@@ -248,7 +224,6 @@ function interceptFetch(): void {
|
|
|
248
224
|
return response;
|
|
249
225
|
},
|
|
250
226
|
(error) => {
|
|
251
|
-
// Error - queue the log asynchronously
|
|
252
227
|
queueRequest({
|
|
253
228
|
requestId: `f${startTime}`,
|
|
254
229
|
method,
|
|
@@ -285,7 +260,6 @@ function interceptXHR(): void {
|
|
|
285
260
|
): void {
|
|
286
261
|
const urlString = typeof url === 'string' ? url : url.toString();
|
|
287
262
|
|
|
288
|
-
// Store minimal info
|
|
289
263
|
(this as any).__rj = {
|
|
290
264
|
m: method.toUpperCase(),
|
|
291
265
|
u: urlString,
|
|
@@ -301,8 +275,6 @@ function interceptXHR(): void {
|
|
|
301
275
|
if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
|
|
302
276
|
return originalXHRSend!.call(this, body);
|
|
303
277
|
}
|
|
304
|
-
|
|
305
|
-
// Check sampling
|
|
306
278
|
const { path } = parseUrlFast(data.u);
|
|
307
279
|
if (!shouldSampleRequest(path)) {
|
|
308
280
|
return originalXHRSend!.call(this, body);
|
|
@@ -325,8 +297,6 @@ function interceptXHR(): void {
|
|
|
325
297
|
});
|
|
326
298
|
};
|
|
327
299
|
|
|
328
|
-
// Use load/error events (more efficient than readystatechange)
|
|
329
|
-
// Note: We use basic addEventListener without options for RN compatibility
|
|
330
300
|
this.addEventListener('load', onComplete);
|
|
331
301
|
this.addEventListener('error', onComplete);
|
|
332
302
|
this.addEventListener('abort', onComplete);
|
|
@@ -347,11 +317,9 @@ export function initNetworkInterceptor(
|
|
|
347
317
|
): void {
|
|
348
318
|
logCallback = callback;
|
|
349
319
|
|
|
350
|
-
// Convert patterns to simple strings for fast matching
|
|
351
320
|
if (options?.ignoreUrls) {
|
|
352
321
|
config.ignorePatterns = options.ignoreUrls
|
|
353
322
|
.filter((p): p is string => typeof p === 'string');
|
|
354
|
-
// Note: RegExp patterns are not supported in optimized version for performance
|
|
355
323
|
}
|
|
356
324
|
|
|
357
325
|
if (options?.captureSizes !== undefined) {
|
|
@@ -368,7 +336,6 @@ export function initNetworkInterceptor(
|
|
|
368
336
|
export function disableNetworkInterceptor(): void {
|
|
369
337
|
config.enabled = false;
|
|
370
338
|
|
|
371
|
-
// Flush any pending requests
|
|
372
339
|
if (flushTimer) {
|
|
373
340
|
clearTimeout(flushTimer);
|
|
374
341
|
flushTimer = null;
|