@rejourneyco/react-native 1.0.1 → 1.0.2
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 +35 -29
- 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/Core/Rejourney.mm +32 -95
- package/ios/Utils/RJPerfTiming.m +0 -5
- package/ios/Utils/RJWindowUtils.m +0 -1
- package/lib/commonjs/components/Mask.js +1 -6
- package/lib/commonjs/index.js +4 -87
- package/lib/commonjs/sdk/autoTracking.js +39 -310
- 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 -49
- package/lib/commonjs/sdk/utils.js +0 -5
- package/lib/module/components/Mask.js +1 -6
- package/lib/module/index.js +3 -91
- package/lib/module/sdk/autoTracking.js +39 -311
- 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 -49
- package/lib/module/sdk/utils.js +0 -5
- package/lib/typescript/NativeRejourney.d.ts +1 -0
- package/lib/typescript/sdk/autoTracking.d.ts +4 -4
- package/lib/typescript/types/index.d.ts +0 -1
- package/package.json +2 -8
- package/src/NativeRejourney.ts +2 -0
- package/src/components/Mask.tsx +0 -3
- package/src/index.ts +3 -73
- package/src/sdk/autoTracking.ts +51 -282
- 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 -33
- package/src/sdk/utils.ts +0 -5
- 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
|
}
|
|
@@ -153,7 +141,7 @@ function flushPendingRequests(): void {
|
|
|
153
141
|
try {
|
|
154
142
|
logCallback(request);
|
|
155
143
|
} catch {
|
|
156
|
-
// Ignore
|
|
144
|
+
// Ignore
|
|
157
145
|
}
|
|
158
146
|
}
|
|
159
147
|
}
|
|
@@ -167,11 +155,9 @@ function parseUrlFast(url: string): { host: string; path: string } {
|
|
|
167
155
|
let hostEnd = -1;
|
|
168
156
|
let pathStart = -1;
|
|
169
157
|
|
|
170
|
-
// Find ://
|
|
171
158
|
const protoEnd = url.indexOf('://');
|
|
172
159
|
if (protoEnd !== -1) {
|
|
173
160
|
const afterProto = protoEnd + 3;
|
|
174
|
-
// Find end of host (first / after ://)
|
|
175
161
|
const slashPos = url.indexOf('/', afterProto);
|
|
176
162
|
if (slashPos !== -1) {
|
|
177
163
|
hostEnd = slashPos;
|
|
@@ -187,7 +173,6 @@ function parseUrlFast(url: string): { host: string; path: string } {
|
|
|
187
173
|
};
|
|
188
174
|
}
|
|
189
175
|
|
|
190
|
-
// Relative URL
|
|
191
176
|
return { host: '', path: url };
|
|
192
177
|
}
|
|
193
178
|
|
|
@@ -204,19 +189,16 @@ function interceptFetch(): void {
|
|
|
204
189
|
input: RequestInfo | URL,
|
|
205
190
|
init?: RequestInit
|
|
206
191
|
): Promise<Response> {
|
|
207
|
-
// Fast path: if disabled or no callback, skip entirely
|
|
208
192
|
if (!config.enabled || !logCallback) {
|
|
209
193
|
return originalFetch!(input, init);
|
|
210
194
|
}
|
|
211
195
|
|
|
212
|
-
// Extract URL string (minimal work)
|
|
213
196
|
const url = typeof input === 'string'
|
|
214
197
|
? input
|
|
215
198
|
: input instanceof URL
|
|
216
199
|
? input.href
|
|
217
200
|
: (input as Request).url;
|
|
218
201
|
|
|
219
|
-
// Fast ignore check
|
|
220
202
|
if (shouldIgnoreUrl(url)) {
|
|
221
203
|
return originalFetch!(input, init);
|
|
222
204
|
}
|
|
@@ -248,7 +230,6 @@ function interceptFetch(): void {
|
|
|
248
230
|
return response;
|
|
249
231
|
},
|
|
250
232
|
(error) => {
|
|
251
|
-
// Error - queue the log asynchronously
|
|
252
233
|
queueRequest({
|
|
253
234
|
requestId: `f${startTime}`,
|
|
254
235
|
method,
|
|
@@ -285,7 +266,6 @@ function interceptXHR(): void {
|
|
|
285
266
|
): void {
|
|
286
267
|
const urlString = typeof url === 'string' ? url : url.toString();
|
|
287
268
|
|
|
288
|
-
// Store minimal info
|
|
289
269
|
(this as any).__rj = {
|
|
290
270
|
m: method.toUpperCase(),
|
|
291
271
|
u: urlString,
|
|
@@ -325,8 +305,6 @@ function interceptXHR(): void {
|
|
|
325
305
|
});
|
|
326
306
|
};
|
|
327
307
|
|
|
328
|
-
// Use load/error events (more efficient than readystatechange)
|
|
329
|
-
// Note: We use basic addEventListener without options for RN compatibility
|
|
330
308
|
this.addEventListener('load', onComplete);
|
|
331
309
|
this.addEventListener('error', onComplete);
|
|
332
310
|
this.addEventListener('abort', onComplete);
|
|
@@ -347,11 +325,9 @@ export function initNetworkInterceptor(
|
|
|
347
325
|
): void {
|
|
348
326
|
logCallback = callback;
|
|
349
327
|
|
|
350
|
-
// Convert patterns to simple strings for fast matching
|
|
351
328
|
if (options?.ignoreUrls) {
|
|
352
329
|
config.ignorePatterns = options.ignoreUrls
|
|
353
330
|
.filter((p): p is string => typeof p === 'string');
|
|
354
|
-
// Note: RegExp patterns are not supported in optimized version for performance
|
|
355
331
|
}
|
|
356
332
|
|
|
357
333
|
if (options?.captureSizes !== undefined) {
|
package/src/sdk/utils.ts
CHANGED
|
@@ -302,11 +302,6 @@ class Logger {
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
306
|
-
// Lifecycle Logs - Industry standard minimal logging
|
|
307
|
-
// These are the only logs integrators will see in debug builds
|
|
308
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
309
|
-
|
|
310
305
|
/**
|
|
311
306
|
* Log SDK initialization success.
|
|
312
307
|
* Only shown in development builds - this is the minimal "SDK started" log.
|
package/src/types/index.ts
CHANGED
|
@@ -104,10 +104,6 @@ export interface RejourneyConfig {
|
|
|
104
104
|
networkCaptureSizes?: boolean;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// ============================================================================
|
|
108
|
-
// Event Types
|
|
109
|
-
// ============================================================================
|
|
110
|
-
|
|
111
107
|
export type GestureType =
|
|
112
108
|
| 'tap'
|
|
113
109
|
| 'double_tap'
|
|
@@ -293,10 +289,6 @@ export interface ErrorEvent {
|
|
|
293
289
|
name?: string;
|
|
294
290
|
}
|
|
295
291
|
|
|
296
|
-
// ============================================================================
|
|
297
|
-
// Network Request Types (API Call Tracking)
|
|
298
|
-
// ============================================================================
|
|
299
|
-
|
|
300
292
|
export interface NetworkRequestEvent {
|
|
301
293
|
type: 'network_request';
|
|
302
294
|
/** Unique request ID for correlating request/response */
|
|
@@ -355,10 +347,6 @@ export type SessionEvent =
|
|
|
355
347
|
| ErrorEvent
|
|
356
348
|
| NetworkRequestEvent;
|
|
357
349
|
|
|
358
|
-
// ============================================================================
|
|
359
|
-
// Session Types
|
|
360
|
-
// ============================================================================
|
|
361
|
-
|
|
362
350
|
export interface GeoLocation {
|
|
363
351
|
/** IP address */
|
|
364
352
|
ip: string;
|
|
@@ -451,14 +439,9 @@ export interface SessionSummary {
|
|
|
451
439
|
videoSegmentCount?: number;
|
|
452
440
|
storageSize: number;
|
|
453
441
|
isComplete: boolean;
|
|
454
|
-
/** Path to session data file */
|
|
455
442
|
filePath: string;
|
|
456
443
|
}
|
|
457
444
|
|
|
458
|
-
// ============================================================================
|
|
459
|
-
// Replay Types
|
|
460
|
-
// ============================================================================
|
|
461
|
-
|
|
462
445
|
export interface ReplayState {
|
|
463
446
|
/** Current playback position in ms from session start */
|
|
464
447
|
currentTime: number;
|
|
@@ -507,10 +490,6 @@ export interface ReplayProps {
|
|
|
507
490
|
style?: object;
|
|
508
491
|
}
|
|
509
492
|
|
|
510
|
-
// ============================================================================
|
|
511
|
-
// Native Module Types
|
|
512
|
-
// ============================================================================
|
|
513
|
-
|
|
514
493
|
export interface RejourneyNativeModule {
|
|
515
494
|
/** Initialize the native SDK */
|
|
516
495
|
initialize(config: RejourneyConfig): Promise<void>;
|
|
@@ -540,10 +519,6 @@ export interface RejourneyNativeModule {
|
|
|
540
519
|
getStorageUsage(): Promise<{ used: number; max: number }>;
|
|
541
520
|
}
|
|
542
521
|
|
|
543
|
-
// ============================================================================
|
|
544
|
-
// API Types
|
|
545
|
-
// ============================================================================
|
|
546
|
-
|
|
547
522
|
export interface RejourneyAPI {
|
|
548
523
|
/** SDK version */
|
|
549
524
|
readonly version: string;
|
|
@@ -704,10 +679,6 @@ export interface NetworkRequestParams {
|
|
|
704
679
|
success?: boolean;
|
|
705
680
|
}
|
|
706
681
|
|
|
707
|
-
// ============================================================================
|
|
708
|
-
// Hook Types
|
|
709
|
-
// ============================================================================
|
|
710
|
-
|
|
711
682
|
export interface UseRejourneyResult {
|
|
712
683
|
/** Whether SDK is initialized */
|
|
713
684
|
isInitialized: boolean;
|