@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
|
@@ -8,35 +8,25 @@ export const SDK_VERSION = '1.0.0';
|
|
|
8
8
|
export const DEFAULT_CONFIG = {
|
|
9
9
|
enabled: true,
|
|
10
10
|
captureFPS: 0.5,
|
|
11
|
-
// Every 2 seconds (only used in timer mode)
|
|
12
11
|
captureOnEvents: true,
|
|
13
|
-
// Event-driven capture (not time-based)
|
|
14
12
|
maxSessionDuration: 10 * 60 * 1000,
|
|
15
|
-
// 10 minutes (project-level configurable, clamped 1–10)
|
|
16
13
|
maxStorageSize: 50 * 1024 * 1024,
|
|
17
|
-
// 50MB
|
|
18
14
|
autoScreenTracking: true,
|
|
19
15
|
autoGestureTracking: true,
|
|
20
16
|
privacyOcclusion: true,
|
|
21
17
|
enableCompression: true,
|
|
22
18
|
inactivityThreshold: 5000,
|
|
23
|
-
// 5 seconds
|
|
24
19
|
disableInDev: false,
|
|
25
20
|
detectRageTaps: true,
|
|
26
21
|
rageTapThreshold: 3,
|
|
27
22
|
rageTapTimeWindow: 1000,
|
|
28
|
-
// 1 second
|
|
29
23
|
debug: false,
|
|
30
24
|
autoStartRecording: true,
|
|
31
25
|
collectDeviceInfo: true,
|
|
32
|
-
// Collect detailed device information
|
|
33
26
|
collectGeoLocation: true,
|
|
34
|
-
// Collect IP address and geolocation
|
|
35
27
|
postNavigationDelay: 300,
|
|
36
|
-
// 300ms - allow navigation animations to complete
|
|
37
28
|
postGestureDelay: 200,
|
|
38
|
-
|
|
39
|
-
postModalDelay: 400 // 400ms - ensure modals/alerts are fully rendered
|
|
29
|
+
postModalDelay: 400
|
|
40
30
|
};
|
|
41
31
|
|
|
42
32
|
/** Event type constants */
|
|
@@ -72,8 +62,7 @@ export const CAPTURE_SETTINGS = {
|
|
|
72
62
|
MIN_FPS: 0.1,
|
|
73
63
|
MAX_FPS: 2,
|
|
74
64
|
CAPTURE_SCALE: 0.25,
|
|
75
|
-
|
|
76
|
-
MIN_CAPTURE_DELTA_TIME: 0.5 // Minimum 0.5s between captures (rate limiting)
|
|
65
|
+
MIN_CAPTURE_DELTA_TIME: 0.5
|
|
77
66
|
};
|
|
78
67
|
|
|
79
68
|
/** Memory management settings */
|
|
@@ -5,20 +5,11 @@
|
|
|
5
5
|
* Split from autoTracking.ts for better code organization.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// Type declarations for browser globals (only used in hybrid apps where DOM is available)
|
|
9
|
-
|
|
10
|
-
// Cast globalThis to work with both RN and hybrid scenarios
|
|
11
8
|
const _globalThis = globalThis;
|
|
12
|
-
|
|
13
|
-
// Original error handlers (for restoration)
|
|
14
9
|
let originalErrorHandler;
|
|
15
10
|
let originalOnError = null;
|
|
16
11
|
let originalOnUnhandledRejection = null;
|
|
17
|
-
|
|
18
|
-
// Callbacks
|
|
19
12
|
let onErrorCallback = null;
|
|
20
|
-
|
|
21
|
-
// Metrics
|
|
22
13
|
let errorCount = 0;
|
|
23
14
|
|
|
24
15
|
/**
|
|
@@ -27,18 +18,12 @@ let errorCount = 0;
|
|
|
27
18
|
export function setupErrorTracking(config, onError) {
|
|
28
19
|
onErrorCallback = onError;
|
|
29
20
|
errorCount = 0;
|
|
30
|
-
|
|
31
|
-
// Track React Native errors
|
|
32
21
|
if (config.trackReactNativeErrors !== false) {
|
|
33
22
|
setupReactNativeErrorHandler();
|
|
34
23
|
}
|
|
35
|
-
|
|
36
|
-
// Track JavaScript errors (only works in web/debug)
|
|
37
24
|
if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
|
|
38
25
|
setupJSErrorHandler();
|
|
39
26
|
}
|
|
40
|
-
|
|
41
|
-
// Track unhandled promise rejections
|
|
42
27
|
if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
|
|
43
28
|
setupPromiseRejectionHandler();
|
|
44
29
|
}
|
|
@@ -48,7 +33,6 @@ export function setupErrorTracking(config, onError) {
|
|
|
48
33
|
* Cleanup error tracking and restore original handlers
|
|
49
34
|
*/
|
|
50
35
|
export function cleanupErrorTracking() {
|
|
51
|
-
// Restore React Native handler
|
|
52
36
|
if (originalErrorHandler) {
|
|
53
37
|
try {
|
|
54
38
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
@@ -60,14 +44,10 @@ export function cleanupErrorTracking() {
|
|
|
60
44
|
}
|
|
61
45
|
originalErrorHandler = undefined;
|
|
62
46
|
}
|
|
63
|
-
|
|
64
|
-
// Restore global onerror
|
|
65
47
|
if (originalOnError !== null) {
|
|
66
48
|
_globalThis.onerror = originalOnError;
|
|
67
49
|
originalOnError = null;
|
|
68
50
|
}
|
|
69
|
-
|
|
70
|
-
// Remove promise rejection handler
|
|
71
51
|
if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
|
|
72
52
|
_globalThis.removeEventListener('unhandledrejection', originalOnUnhandledRejection);
|
|
73
53
|
originalOnUnhandledRejection = null;
|
|
@@ -119,11 +99,7 @@ function setupReactNativeErrorHandler() {
|
|
|
119
99
|
try {
|
|
120
100
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
121
101
|
if (!ErrorUtils) return;
|
|
122
|
-
|
|
123
|
-
// Store original handler
|
|
124
102
|
originalErrorHandler = ErrorUtils.getGlobalHandler();
|
|
125
|
-
|
|
126
|
-
// Set new handler
|
|
127
103
|
ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
128
104
|
trackError({
|
|
129
105
|
type: 'error',
|
|
@@ -132,14 +108,12 @@ function setupReactNativeErrorHandler() {
|
|
|
132
108
|
stack: error.stack,
|
|
133
109
|
name: error.name || 'Error'
|
|
134
110
|
});
|
|
135
|
-
|
|
136
|
-
// Call original handler
|
|
137
111
|
if (originalErrorHandler) {
|
|
138
112
|
originalErrorHandler(error, isFatal);
|
|
139
113
|
}
|
|
140
114
|
});
|
|
141
115
|
} catch {
|
|
142
|
-
//
|
|
116
|
+
// Ignore
|
|
143
117
|
}
|
|
144
118
|
}
|
|
145
119
|
|
|
@@ -157,8 +131,6 @@ function setupJSErrorHandler() {
|
|
|
157
131
|
stack: error?.stack || `${source}:${lineno}:${colno}`,
|
|
158
132
|
name: error?.name || 'Error'
|
|
159
133
|
});
|
|
160
|
-
|
|
161
|
-
// Call original handler
|
|
162
134
|
if (originalOnError) {
|
|
163
135
|
return originalOnError(message, source, lineno, colno, error);
|
|
164
136
|
}
|
package/lib/module/sdk/index.js
CHANGED
|
@@ -7,6 +7,4 @@ 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
|
|
12
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
* Session metrics structure
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
// Metrics state
|
|
14
13
|
let metrics = createEmptyMetrics();
|
|
15
14
|
let sessionStartTime = 0;
|
|
16
|
-
let maxSessionDurationMs = 10 * 60 * 1000;
|
|
15
|
+
let maxSessionDurationMs = 10 * 60 * 1000;
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Create empty metrics object
|
|
@@ -61,11 +60,8 @@ export function initMetrics() {
|
|
|
61
60
|
* Get current session metrics with calculated scores
|
|
62
61
|
*/
|
|
63
62
|
export function getSessionMetrics() {
|
|
64
|
-
// Calculate duration, clamped to max session duration
|
|
65
63
|
const rawDuration = Date.now() - sessionStartTime;
|
|
66
64
|
const durationMs = Math.min(rawDuration, maxSessionDurationMs);
|
|
67
|
-
|
|
68
|
-
// Calculate scores
|
|
69
65
|
const interactionScore = calculateInteractionScore(durationMs);
|
|
70
66
|
const explorationScore = calculateExplorationScore();
|
|
71
67
|
const uxScore = calculateUXScore();
|
|
@@ -87,9 +83,6 @@ export function setMaxSessionDurationMinutes(minutes) {
|
|
|
87
83
|
maxSessionDurationMs = clampedMinutes * 60 * 1000;
|
|
88
84
|
}
|
|
89
85
|
}
|
|
90
|
-
|
|
91
|
-
// ==================== Metric Increment Methods ====================
|
|
92
|
-
|
|
93
86
|
export function incrementTouchCount() {
|
|
94
87
|
metrics.touchCount++;
|
|
95
88
|
metrics.totalEvents++;
|
|
@@ -129,8 +122,6 @@ export function trackAPIMetrics(success, durationMs = 0, responseBytes = 0) {
|
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
124
|
|
|
132
|
-
// ==================== Score Calculations ====================
|
|
133
|
-
|
|
134
125
|
/**
|
|
135
126
|
* Calculate interaction score based on engagement with app
|
|
136
127
|
* Higher = more engaged (more interactions per minute)
|
|
@@ -139,16 +130,12 @@ function calculateInteractionScore(durationMs) {
|
|
|
139
130
|
if (durationMs <= 0) return 100;
|
|
140
131
|
const durationMinutes = durationMs / 60000;
|
|
141
132
|
const interactionsPerMinute = metrics.touchCount / Math.max(0.5, durationMinutes);
|
|
142
|
-
|
|
143
|
-
// Ideal: 10-30 interactions per minute
|
|
144
|
-
// Low (< 5): user passive/confused
|
|
145
|
-
// Very high (> 60): rage tapping
|
|
146
133
|
if (interactionsPerMinute < 2) return 20;
|
|
147
134
|
if (interactionsPerMinute < 5) return 50;
|
|
148
135
|
if (interactionsPerMinute < 10) return 70;
|
|
149
136
|
if (interactionsPerMinute <= 30) return 100;
|
|
150
137
|
if (interactionsPerMinute <= 60) return 80;
|
|
151
|
-
return 50;
|
|
138
|
+
return 50;
|
|
152
139
|
}
|
|
153
140
|
|
|
154
141
|
/**
|
|
@@ -157,14 +144,12 @@ function calculateInteractionScore(durationMs) {
|
|
|
157
144
|
*/
|
|
158
145
|
function calculateExplorationScore() {
|
|
159
146
|
const uniqueScreens = metrics.uniqueScreensCount;
|
|
160
|
-
|
|
161
|
-
// More unique screens = better exploration
|
|
162
147
|
if (uniqueScreens >= 10) return 100;
|
|
163
148
|
if (uniqueScreens >= 7) return 90;
|
|
164
149
|
if (uniqueScreens >= 5) return 80;
|
|
165
150
|
if (uniqueScreens >= 3) return 60;
|
|
166
151
|
if (uniqueScreens >= 2) return 40;
|
|
167
|
-
return 20;
|
|
152
|
+
return 20;
|
|
168
153
|
}
|
|
169
154
|
|
|
170
155
|
/**
|
|
@@ -173,14 +158,8 @@ function calculateExplorationScore() {
|
|
|
173
158
|
*/
|
|
174
159
|
function calculateUXScore() {
|
|
175
160
|
let score = 100;
|
|
176
|
-
|
|
177
|
-
// Deduct for errors
|
|
178
161
|
score -= metrics.errorCount * 10;
|
|
179
|
-
|
|
180
|
-
// Deduct heavily for rage taps
|
|
181
162
|
score -= metrics.rageTapCount * 20;
|
|
182
|
-
|
|
183
|
-
// Deduct for API errors (less severe)
|
|
184
163
|
score -= metrics.apiErrorCount * 5;
|
|
185
164
|
return Math.max(0, Math.min(100, score));
|
|
186
165
|
}
|
|
@@ -18,46 +18,28 @@
|
|
|
18
18
|
export function normalizeScreenName(raw) {
|
|
19
19
|
if (!raw) return 'Unknown';
|
|
20
20
|
let name = raw;
|
|
21
|
-
|
|
22
|
-
// 1. Remove non-printable characters and weird symbols (often from icons)
|
|
23
21
|
name = name.replace(/[^\x20-\x7E\s]/g, '');
|
|
24
|
-
|
|
25
|
-
// 2. Handle hyphens: my-recipes -> My Recipes
|
|
26
22
|
name = name.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
|
|
27
|
-
|
|
28
|
-
// 3. Remove common suffixes
|
|
29
23
|
const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
|
|
30
24
|
for (const suffix of suffixes) {
|
|
31
25
|
if (name.endsWith(suffix) && name.length > suffix.length) {
|
|
32
26
|
name = name.slice(0, -suffix.length).trim();
|
|
33
27
|
}
|
|
34
28
|
}
|
|
35
|
-
|
|
36
|
-
// 4. Remove common prefixes (React Native internals)
|
|
37
29
|
const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
|
|
38
30
|
for (const prefix of prefixes) {
|
|
39
31
|
if (name.startsWith(prefix) && name.length > prefix.length + 2) {
|
|
40
32
|
name = name.slice(prefix.length).trim();
|
|
41
33
|
}
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
// 5. Handle dynamic route params: [id] -> (omit), [userId] -> "User"
|
|
45
35
|
name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
|
|
46
36
|
const clean = param.replace(/Id$/i, '');
|
|
47
37
|
if (clean.length < 2) return '';
|
|
48
38
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
49
39
|
});
|
|
50
|
-
|
|
51
|
-
// 6. Remove leftover brackets
|
|
52
40
|
name = name.replace(/\[\]/g, '');
|
|
53
|
-
|
|
54
|
-
// 7. Convert camelCase/PascalCase to Title Case with spaces
|
|
55
41
|
name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
56
|
-
|
|
57
|
-
// 8. Clean up multiple spaces and trim
|
|
58
42
|
name = name.replace(/\s+/g, ' ').trim();
|
|
59
|
-
|
|
60
|
-
// 9. Capitalize first letter
|
|
61
43
|
if (name.length > 0) {
|
|
62
44
|
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
63
45
|
}
|
|
@@ -72,23 +54,17 @@ export function normalizeScreenName(raw) {
|
|
|
72
54
|
* @returns Human-readable screen name
|
|
73
55
|
*/
|
|
74
56
|
export function getScreenNameFromPath(pathname, segments) {
|
|
75
|
-
// Use segments for cleaner names if available
|
|
76
57
|
if (segments.length > 0) {
|
|
77
|
-
// Filter out group markers like (tabs), (auth), etc.
|
|
78
58
|
const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
|
|
79
59
|
if (cleanSegments.length > 0) {
|
|
80
|
-
// Process each segment
|
|
81
60
|
const processedSegments = cleanSegments.map(s => {
|
|
82
|
-
// Handle dynamic params like [id]
|
|
83
61
|
if (s.startsWith('[') && s.endsWith(']')) {
|
|
84
62
|
const param = s.slice(1, -1);
|
|
85
|
-
// Skip pure ID params, keep meaningful ones
|
|
86
63
|
if (param === 'id' || param === 'slug') return null;
|
|
87
|
-
|
|
64
|
+
if (param === 'id' || param === 'slug') return null;
|
|
88
65
|
const clean = param.replace(/Id$/i, '');
|
|
89
66
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
90
67
|
}
|
|
91
|
-
// Regular segment - capitalize
|
|
92
68
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
93
69
|
}).filter(Boolean);
|
|
94
70
|
if (processedSegments.length > 0) {
|
|
@@ -96,30 +72,17 @@ export function getScreenNameFromPath(pathname, segments) {
|
|
|
96
72
|
}
|
|
97
73
|
}
|
|
98
74
|
}
|
|
99
|
-
|
|
100
|
-
// Fall back to pathname
|
|
101
75
|
if (!pathname || pathname === '/') {
|
|
102
76
|
return 'Home';
|
|
103
77
|
}
|
|
104
|
-
|
|
105
|
-
// Clean up the path
|
|
106
|
-
let cleanPath = pathname.replace(/^\/(tabs)?/, '') // Remove leading slash and (tabs)
|
|
107
|
-
.replace(/\([^)]+\)/g, '') // Remove all group markers like (settings)
|
|
108
|
-
.replace(/\[([^\]]+)\]/g, (_, param) => {
|
|
109
|
-
// Handle dynamic params in path
|
|
78
|
+
const cleanPath = pathname.replace(/^\/(tabs)?/, '').replace(/\([^)]+\)/g, '').replace(/\[([^\]]+)\]/g, (_, param) => {
|
|
110
79
|
if (param === 'id' || param === 'slug') return '';
|
|
111
80
|
const clean = param.replace(/Id$/i, '');
|
|
112
81
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
113
|
-
}).replace(/\/+/g, '/')
|
|
114
|
-
.replace(/^\//, '') // Remove leading slash
|
|
115
|
-
.replace(/\/$/, '') // Remove trailing slash
|
|
116
|
-
.replace(/\//g, ' > ') // Replace slashes with arrows
|
|
117
|
-
.trim();
|
|
82
|
+
}).replace(/\/+/g, '/').replace(/^\//, '').replace(/\/$/, '').replace(/\//g, ' > ').trim();
|
|
118
83
|
if (!cleanPath) {
|
|
119
84
|
return 'Home';
|
|
120
85
|
}
|
|
121
|
-
|
|
122
|
-
// Capitalize first letter of each word
|
|
123
86
|
return cleanPath.split(' > ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).filter(s => s.length > 0).join(' > ') || 'Home';
|
|
124
87
|
}
|
|
125
88
|
|
|
@@ -133,8 +96,6 @@ export function getCurrentRouteFromState(state) {
|
|
|
133
96
|
if (!state || !state.routes) return null;
|
|
134
97
|
const route = state.routes[state.index ?? state.routes.length - 1];
|
|
135
98
|
if (!route) return null;
|
|
136
|
-
|
|
137
|
-
// If nested state, recurse
|
|
138
99
|
if (route.state) {
|
|
139
100
|
return getCurrentRouteFromState(route.state);
|
|
140
101
|
}
|
|
@@ -13,41 +13,26 @@
|
|
|
13
13
|
* - PII Scrubbing for query parameters
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
// Store original implementations
|
|
17
16
|
let originalFetch = null;
|
|
18
17
|
let originalXHROpen = null;
|
|
19
18
|
let originalXHRSend = null;
|
|
20
|
-
|
|
21
|
-
// Callback to log network requests (called asynchronously)
|
|
22
19
|
let logCallback = null;
|
|
23
|
-
|
|
24
|
-
// Pending requests buffer (circular buffer for memory efficiency)
|
|
25
20
|
const MAX_PENDING_REQUESTS = 100;
|
|
26
21
|
const pendingRequests = new Array(MAX_PENDING_REQUESTS).fill(null);
|
|
27
22
|
let pendingHead = 0;
|
|
28
23
|
let pendingTail = 0;
|
|
29
24
|
let pendingCount = 0;
|
|
30
|
-
|
|
31
|
-
// Flush timer
|
|
32
25
|
let flushTimer = null;
|
|
33
|
-
const FLUSH_INTERVAL = 500;
|
|
34
|
-
|
|
35
|
-
// Sampling for high-frequency endpoints
|
|
26
|
+
const FLUSH_INTERVAL = 500;
|
|
36
27
|
const endpointCounts = new Map();
|
|
37
|
-
const SAMPLE_WINDOW = 10000;
|
|
38
|
-
const MAX_PER_ENDPOINT = 20;
|
|
39
|
-
|
|
40
|
-
// Configuration
|
|
28
|
+
const SAMPLE_WINDOW = 10000;
|
|
29
|
+
const MAX_PER_ENDPOINT = 20;
|
|
41
30
|
const config = {
|
|
42
31
|
enabled: true,
|
|
43
32
|
ignorePatterns: [],
|
|
44
|
-
// Simple string patterns for fast matching
|
|
45
33
|
maxUrlLength: 300,
|
|
46
|
-
|
|
47
|
-
captureSizes: false // Disabled by default for performance
|
|
34
|
+
captureSizes: false
|
|
48
35
|
};
|
|
49
|
-
|
|
50
|
-
// PII Scrubbing - Sensitive keys to look for in query params
|
|
51
36
|
const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_token', 'api_key'];
|
|
52
37
|
|
|
53
38
|
/**
|
|
@@ -55,7 +40,6 @@ const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_to
|
|
|
55
40
|
*/
|
|
56
41
|
function scrubUrl(url) {
|
|
57
42
|
try {
|
|
58
|
-
// Fast check if URL might contain params
|
|
59
43
|
if (url.indexOf('?') === -1) return url;
|
|
60
44
|
const urlObj = new URL(url);
|
|
61
45
|
let modified = false;
|
|
@@ -65,13 +49,10 @@ function scrubUrl(url) {
|
|
|
65
49
|
modified = true;
|
|
66
50
|
}
|
|
67
51
|
});
|
|
68
|
-
|
|
69
|
-
// Also scan for partial matches (case-insensitive) if strict scrubbing needed
|
|
70
|
-
// But for performance, we stick to exact keys or common variations
|
|
71
|
-
|
|
72
52
|
return modified ? urlObj.toString() : url;
|
|
73
53
|
} catch {
|
|
74
|
-
//
|
|
54
|
+
// Ignore error, fallback to primitive scrubbing
|
|
55
|
+
|
|
75
56
|
let scrubbed = url;
|
|
76
57
|
SENSITIVE_KEYS.forEach(key => {
|
|
77
58
|
const regex = new RegExp(`([?&])${key}=[^&]*`, 'gi');
|
|
@@ -119,14 +100,10 @@ function queueRequest(request) {
|
|
|
119
100
|
pendingHead = (pendingHead + 1) % MAX_PENDING_REQUESTS;
|
|
120
101
|
pendingCount--;
|
|
121
102
|
}
|
|
122
|
-
|
|
123
|
-
// Scrub URL before queuing
|
|
124
103
|
request.url = scrubUrl(request.url);
|
|
125
104
|
pendingRequests[pendingTail] = request;
|
|
126
105
|
pendingTail = (pendingTail + 1) % MAX_PENDING_REQUESTS;
|
|
127
106
|
pendingCount++;
|
|
128
|
-
|
|
129
|
-
// Schedule flush if not already scheduled
|
|
130
107
|
if (!flushTimer) {
|
|
131
108
|
flushTimer = setTimeout(flushPendingRequests, FLUSH_INTERVAL);
|
|
132
109
|
}
|
|
@@ -138,8 +115,6 @@ function queueRequest(request) {
|
|
|
138
115
|
function flushPendingRequests() {
|
|
139
116
|
flushTimer = null;
|
|
140
117
|
if (!logCallback || pendingCount === 0) return;
|
|
141
|
-
|
|
142
|
-
// Process all pending requests
|
|
143
118
|
while (pendingCount > 0) {
|
|
144
119
|
const request = pendingRequests[pendingHead];
|
|
145
120
|
pendingRequests[pendingHead] = null; // Allow GC
|
|
@@ -149,7 +124,7 @@ function flushPendingRequests() {
|
|
|
149
124
|
try {
|
|
150
125
|
logCallback(request);
|
|
151
126
|
} catch {
|
|
152
|
-
// Ignore
|
|
127
|
+
// Ignore
|
|
153
128
|
}
|
|
154
129
|
}
|
|
155
130
|
}
|
|
@@ -162,12 +137,9 @@ function parseUrlFast(url) {
|
|
|
162
137
|
// Fast path for common patterns
|
|
163
138
|
let hostEnd = -1;
|
|
164
139
|
let pathStart = -1;
|
|
165
|
-
|
|
166
|
-
// Find ://
|
|
167
140
|
const protoEnd = url.indexOf('://');
|
|
168
141
|
if (protoEnd !== -1) {
|
|
169
142
|
const afterProto = protoEnd + 3;
|
|
170
|
-
// Find end of host (first / after ://)
|
|
171
143
|
const slashPos = url.indexOf('/', afterProto);
|
|
172
144
|
if (slashPos !== -1) {
|
|
173
145
|
hostEnd = slashPos;
|
|
@@ -181,8 +153,6 @@ function parseUrlFast(url) {
|
|
|
181
153
|
path: pathStart < url.length ? url.substring(pathStart) : '/'
|
|
182
154
|
};
|
|
183
155
|
}
|
|
184
|
-
|
|
185
|
-
// Relative URL
|
|
186
156
|
return {
|
|
187
157
|
host: '',
|
|
188
158
|
path: url
|
|
@@ -197,15 +167,10 @@ function interceptFetch() {
|
|
|
197
167
|
if (originalFetch) return;
|
|
198
168
|
originalFetch = globalThis.fetch;
|
|
199
169
|
globalThis.fetch = function optimizedFetch(input, init) {
|
|
200
|
-
// Fast path: if disabled or no callback, skip entirely
|
|
201
170
|
if (!config.enabled || !logCallback) {
|
|
202
171
|
return originalFetch(input, init);
|
|
203
172
|
}
|
|
204
|
-
|
|
205
|
-
// Extract URL string (minimal work)
|
|
206
173
|
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
207
|
-
|
|
208
|
-
// Fast ignore check
|
|
209
174
|
if (shouldIgnoreUrl(url)) {
|
|
210
175
|
return originalFetch(input, init);
|
|
211
176
|
}
|
|
@@ -217,14 +182,9 @@ function interceptFetch() {
|
|
|
217
182
|
if (!shouldSampleRequest(path)) {
|
|
218
183
|
return originalFetch(input, init);
|
|
219
184
|
}
|
|
220
|
-
|
|
221
|
-
// Capture start time (only synchronous work)
|
|
222
185
|
const startTime = Date.now();
|
|
223
186
|
const method = (init?.method || 'GET').toUpperCase();
|
|
224
|
-
|
|
225
|
-
// Call original fetch
|
|
226
187
|
return originalFetch(input, init).then(response => {
|
|
227
|
-
// Success - queue the log asynchronously
|
|
228
188
|
queueRequest({
|
|
229
189
|
requestId: `f${startTime}`,
|
|
230
190
|
method,
|
|
@@ -237,7 +197,6 @@ function interceptFetch() {
|
|
|
237
197
|
});
|
|
238
198
|
return response;
|
|
239
199
|
}, error => {
|
|
240
|
-
// Error - queue the log asynchronously
|
|
241
200
|
queueRequest({
|
|
242
201
|
requestId: `f${startTime}`,
|
|
243
202
|
method,
|
|
@@ -264,8 +223,6 @@ function interceptXHR() {
|
|
|
264
223
|
originalXHRSend = XMLHttpRequest.prototype.send;
|
|
265
224
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
266
225
|
const urlString = typeof url === 'string' ? url : url.toString();
|
|
267
|
-
|
|
268
|
-
// Store minimal info
|
|
269
226
|
this.__rj = {
|
|
270
227
|
m: method.toUpperCase(),
|
|
271
228
|
u: urlString,
|
|
@@ -278,8 +235,6 @@ function interceptXHR() {
|
|
|
278
235
|
if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
|
|
279
236
|
return originalXHRSend.call(this, body);
|
|
280
237
|
}
|
|
281
|
-
|
|
282
|
-
// Check sampling
|
|
283
238
|
const {
|
|
284
239
|
path
|
|
285
240
|
} = parseUrlFast(data.u);
|
|
@@ -301,9 +256,6 @@ function interceptXHR() {
|
|
|
301
256
|
errorMessage: this.status === 0 ? 'Network error' : undefined
|
|
302
257
|
});
|
|
303
258
|
};
|
|
304
|
-
|
|
305
|
-
// Use load/error events (more efficient than readystatechange)
|
|
306
|
-
// Note: We use basic addEventListener without options for RN compatibility
|
|
307
259
|
this.addEventListener('load', onComplete);
|
|
308
260
|
this.addEventListener('error', onComplete);
|
|
309
261
|
this.addEventListener('abort', onComplete);
|
|
@@ -316,11 +268,8 @@ function interceptXHR() {
|
|
|
316
268
|
*/
|
|
317
269
|
export function initNetworkInterceptor(callback, options) {
|
|
318
270
|
logCallback = callback;
|
|
319
|
-
|
|
320
|
-
// Convert patterns to simple strings for fast matching
|
|
321
271
|
if (options?.ignoreUrls) {
|
|
322
272
|
config.ignorePatterns = options.ignoreUrls.filter(p => typeof p === 'string');
|
|
323
|
-
// Note: RegExp patterns are not supported in optimized version for performance
|
|
324
273
|
}
|
|
325
274
|
if (options?.captureSizes !== undefined) {
|
|
326
275
|
config.captureSizes = options.captureSizes;
|
|
@@ -334,8 +283,6 @@ export function initNetworkInterceptor(callback, options) {
|
|
|
334
283
|
*/
|
|
335
284
|
export function disableNetworkInterceptor() {
|
|
336
285
|
config.enabled = false;
|
|
337
|
-
|
|
338
|
-
// Flush any pending requests
|
|
339
286
|
if (flushTimer) {
|
|
340
287
|
clearTimeout(flushTimer);
|
|
341
288
|
flushTimer = null;
|