@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/autoTracking.ts
CHANGED
|
@@ -42,9 +42,7 @@ function getDimensions() {
|
|
|
42
42
|
return getRN()?.Dimensions;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
return getRN()?.NativeModules;
|
|
47
|
-
}
|
|
45
|
+
|
|
48
46
|
|
|
49
47
|
function getRejourneyNativeModule() {
|
|
50
48
|
const RN = getRN();
|
|
@@ -57,7 +55,7 @@ function getRejourneyNativeModule() {
|
|
|
57
55
|
try {
|
|
58
56
|
nativeModule = TurboModuleRegistry.get('Rejourney');
|
|
59
57
|
} catch {
|
|
60
|
-
// Ignore
|
|
58
|
+
// Ignore
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -68,8 +66,6 @@ function getRejourneyNativeModule() {
|
|
|
68
66
|
return nativeModule;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
// Type declarations for browser globals (only used in hybrid apps where DOM is available)
|
|
72
|
-
// These don't exist in pure React Native but are needed for error tracking in hybrid scenarios
|
|
73
69
|
type OnErrorEventHandler = ((
|
|
74
70
|
event: Event | string,
|
|
75
71
|
source?: string,
|
|
@@ -83,7 +79,6 @@ interface PromiseRejectionEvent {
|
|
|
83
79
|
promise?: Promise<any>;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
// Cast globalThis to work with both RN and hybrid scenarios
|
|
87
82
|
const _globalThis = globalThis as typeof globalThis & {
|
|
88
83
|
onerror?: OnErrorEventHandler;
|
|
89
84
|
addEventListener?: (type: string, handler: (event: any) => void) => void;
|
|
@@ -94,10 +89,6 @@ const _globalThis = globalThis as typeof globalThis & {
|
|
|
94
89
|
};
|
|
95
90
|
};
|
|
96
91
|
|
|
97
|
-
// =============================================================================
|
|
98
|
-
// Types
|
|
99
|
-
// =============================================================================
|
|
100
|
-
|
|
101
92
|
export interface TapEvent {
|
|
102
93
|
x: number;
|
|
103
94
|
y: number;
|
|
@@ -106,93 +97,63 @@ export interface TapEvent {
|
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
export interface SessionMetrics {
|
|
109
|
-
// Core counts
|
|
110
100
|
totalEvents: number;
|
|
111
101
|
touchCount: number;
|
|
112
102
|
scrollCount: number;
|
|
113
103
|
gestureCount: number;
|
|
114
104
|
inputCount: number;
|
|
115
105
|
navigationCount: number;
|
|
116
|
-
|
|
117
|
-
// Issue tracking
|
|
118
106
|
errorCount: number;
|
|
119
107
|
rageTapCount: number;
|
|
120
|
-
|
|
121
|
-
// API metrics
|
|
122
108
|
apiSuccessCount: number;
|
|
123
109
|
apiErrorCount: number;
|
|
124
110
|
apiTotalCount: number;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
netTotalDurationMs: number; // Sum of all API durations
|
|
128
|
-
netTotalBytes: number; // Sum of all response bytes
|
|
129
|
-
|
|
130
|
-
// Screen tracking for funnels
|
|
111
|
+
netTotalDurationMs: number;
|
|
112
|
+
netTotalBytes: number;
|
|
131
113
|
screensVisited: string[];
|
|
132
114
|
uniqueScreensCount: number;
|
|
133
115
|
|
|
134
|
-
// Scores (0-100)
|
|
135
116
|
interactionScore: number;
|
|
136
117
|
explorationScore: number;
|
|
137
118
|
uxScore: number;
|
|
138
119
|
}
|
|
139
120
|
|
|
140
121
|
export interface AutoTrackingConfig {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
trackReactNativeErrors?: boolean; // Track RN ErrorUtils (default: true)
|
|
150
|
-
|
|
151
|
-
// Session settings
|
|
152
|
-
collectDeviceInfo?: boolean; // Collect device info (default: true)
|
|
153
|
-
maxSessionDurationMs?: number; // Clamp to 1–10 minutes; default set from server/config
|
|
122
|
+
rageTapThreshold?: number;
|
|
123
|
+
rageTapTimeWindow?: number;
|
|
124
|
+
rageTapRadius?: number;
|
|
125
|
+
trackJSErrors?: boolean;
|
|
126
|
+
trackPromiseRejections?: boolean;
|
|
127
|
+
trackReactNativeErrors?: boolean;
|
|
128
|
+
collectDeviceInfo?: boolean;
|
|
129
|
+
maxSessionDurationMs?: number;
|
|
154
130
|
}
|
|
155
131
|
|
|
156
|
-
// =============================================================================
|
|
157
|
-
// State
|
|
158
|
-
// =============================================================================
|
|
159
|
-
|
|
160
132
|
let isInitialized = false;
|
|
161
133
|
let config: AutoTrackingConfig = {};
|
|
162
134
|
|
|
163
|
-
// Rage tap tracking
|
|
164
135
|
const recentTaps: TapEvent[] = [];
|
|
165
|
-
let tapHead = 0;
|
|
166
|
-
let tapCount = 0;
|
|
136
|
+
let tapHead = 0;
|
|
137
|
+
let tapCount = 0;
|
|
167
138
|
const MAX_RECENT_TAPS = 10;
|
|
168
139
|
|
|
169
|
-
// Session metrics
|
|
170
140
|
let metrics: SessionMetrics = createEmptyMetrics();
|
|
171
141
|
let sessionStartTime: number = 0;
|
|
172
142
|
let maxSessionDurationMs: number = 10 * 60 * 1000;
|
|
173
|
-
|
|
174
|
-
// Screen tracking
|
|
175
143
|
let currentScreen = '';
|
|
176
144
|
let screensVisited: string[] = [];
|
|
177
145
|
|
|
178
|
-
// Anonymous ID
|
|
179
146
|
let anonymousId: string | null = null;
|
|
180
147
|
let anonymousIdPromise: Promise<string> | null = null;
|
|
181
148
|
|
|
182
|
-
// Callbacks
|
|
183
149
|
let onRageTapDetected: ((count: number, x: number, y: number) => void) | null = null;
|
|
184
150
|
let onErrorCaptured: ((error: ErrorEvent) => void) | null = null;
|
|
185
151
|
let onScreenChange: ((screenName: string, previousScreen?: string) => void) | null = null;
|
|
186
152
|
|
|
187
|
-
// Original error handlers (for restoration)
|
|
188
153
|
let originalErrorHandler: ((error: Error, isFatal: boolean) => void) | undefined;
|
|
189
154
|
let originalOnError: OnErrorEventHandler | null = null;
|
|
190
155
|
let originalOnUnhandledRejection: ((event: PromiseRejectionEvent) => void) | null = null;
|
|
191
156
|
|
|
192
|
-
// =============================================================================
|
|
193
|
-
// Initialization
|
|
194
|
-
// =============================================================================
|
|
195
|
-
|
|
196
157
|
/**
|
|
197
158
|
* Initialize auto tracking features
|
|
198
159
|
* Called automatically by Rejourney.init() - no user action needed
|
|
@@ -219,7 +180,6 @@ export function initAutoTracking(
|
|
|
219
180
|
...trackingConfig,
|
|
220
181
|
};
|
|
221
182
|
|
|
222
|
-
// Session timing
|
|
223
183
|
sessionStartTime = Date.now();
|
|
224
184
|
setMaxSessionDurationMinutes(
|
|
225
185
|
trackingConfig.maxSessionDurationMs
|
|
@@ -227,18 +187,11 @@ export function initAutoTracking(
|
|
|
227
187
|
: undefined
|
|
228
188
|
);
|
|
229
189
|
|
|
230
|
-
// Set callbacks
|
|
231
190
|
onRageTapDetected = callbacks.onRageTap || null;
|
|
232
191
|
onErrorCaptured = callbacks.onError || null;
|
|
233
192
|
onScreenChange = callbacks.onScreen || null;
|
|
234
|
-
|
|
235
|
-
// Setup error tracking
|
|
236
193
|
setupErrorTracking();
|
|
237
|
-
|
|
238
|
-
// Setup React Navigation tracking (if available)
|
|
239
194
|
setupNavigationTracking();
|
|
240
|
-
|
|
241
|
-
// Load anonymous ID from native storage (or generate new one)
|
|
242
195
|
loadAnonymousId().then(id => {
|
|
243
196
|
anonymousId = id;
|
|
244
197
|
});
|
|
@@ -252,10 +205,7 @@ export function initAutoTracking(
|
|
|
252
205
|
export function cleanupAutoTracking(): void {
|
|
253
206
|
if (!isInitialized) return;
|
|
254
207
|
|
|
255
|
-
// Restore original error handlers
|
|
256
208
|
restoreErrorHandlers();
|
|
257
|
-
|
|
258
|
-
// Cleanup navigation tracking
|
|
259
209
|
cleanupNavigationTracking();
|
|
260
210
|
|
|
261
211
|
// Reset state
|
|
@@ -269,10 +219,6 @@ export function cleanupAutoTracking(): void {
|
|
|
269
219
|
isInitialized = false;
|
|
270
220
|
}
|
|
271
221
|
|
|
272
|
-
// =============================================================================
|
|
273
|
-
// Rage Tap Detection
|
|
274
|
-
// =============================================================================
|
|
275
|
-
|
|
276
222
|
/**
|
|
277
223
|
* Track a tap event for rage tap detection
|
|
278
224
|
* Called automatically from touch interceptor
|
|
@@ -282,18 +228,14 @@ export function trackTap(tap: TapEvent): void {
|
|
|
282
228
|
|
|
283
229
|
const now = Date.now();
|
|
284
230
|
|
|
285
|
-
// Add to circular buffer (O(1) instead of shift() which is O(n))
|
|
286
231
|
const insertIndex = (tapHead + tapCount) % MAX_RECENT_TAPS;
|
|
287
232
|
if (tapCount < MAX_RECENT_TAPS) {
|
|
288
233
|
recentTaps[insertIndex] = { ...tap, timestamp: now };
|
|
289
234
|
tapCount++;
|
|
290
235
|
} else {
|
|
291
|
-
// Buffer full, overwrite oldest
|
|
292
236
|
recentTaps[tapHead] = { ...tap, timestamp: now };
|
|
293
237
|
tapHead = (tapHead + 1) % MAX_RECENT_TAPS;
|
|
294
238
|
}
|
|
295
|
-
|
|
296
|
-
// Evict old taps outside time window
|
|
297
239
|
const windowStart = now - (config.rageTapTimeWindow || 500);
|
|
298
240
|
while (tapCount > 0) {
|
|
299
241
|
const oldestTap = recentTaps[tapHead];
|
|
@@ -305,10 +247,7 @@ export function trackTap(tap: TapEvent): void {
|
|
|
305
247
|
}
|
|
306
248
|
}
|
|
307
249
|
|
|
308
|
-
// Check for rage tap
|
|
309
250
|
detectRageTap();
|
|
310
|
-
|
|
311
|
-
// Update metrics
|
|
312
251
|
metrics.touchCount++;
|
|
313
252
|
metrics.totalEvents++;
|
|
314
253
|
}
|
|
@@ -321,14 +260,12 @@ function detectRageTap(): void {
|
|
|
321
260
|
const radius = config.rageTapRadius || 50;
|
|
322
261
|
|
|
323
262
|
if (tapCount < threshold) return;
|
|
324
|
-
// Check last N taps from circular buffer
|
|
325
263
|
const tapsToCheck: TapEvent[] = [];
|
|
326
264
|
for (let i = 0; i < threshold; i++) {
|
|
327
265
|
const idx = (tapHead + tapCount - threshold + i) % MAX_RECENT_TAPS;
|
|
328
266
|
tapsToCheck.push(recentTaps[idx]!);
|
|
329
267
|
}
|
|
330
268
|
|
|
331
|
-
// Calculate center point
|
|
332
269
|
let centerX = 0;
|
|
333
270
|
let centerY = 0;
|
|
334
271
|
for (const tap of tapsToCheck) {
|
|
@@ -338,7 +275,6 @@ function detectRageTap(): void {
|
|
|
338
275
|
centerX /= tapsToCheck.length;
|
|
339
276
|
centerY /= tapsToCheck.length;
|
|
340
277
|
|
|
341
|
-
// Check if all taps are within radius of center
|
|
342
278
|
let allWithinRadius = true;
|
|
343
279
|
for (const tap of tapsToCheck) {
|
|
344
280
|
const dx = tap.x - centerX;
|
|
@@ -351,9 +287,7 @@ function detectRageTap(): void {
|
|
|
351
287
|
}
|
|
352
288
|
|
|
353
289
|
if (allWithinRadius) {
|
|
354
|
-
// Rage tap detected!
|
|
355
290
|
metrics.rageTapCount++;
|
|
356
|
-
// Clear circular buffer to prevent duplicate detection
|
|
357
291
|
tapHead = 0;
|
|
358
292
|
tapCount = 0;
|
|
359
293
|
|
|
@@ -364,10 +298,6 @@ function detectRageTap(): void {
|
|
|
364
298
|
}
|
|
365
299
|
}
|
|
366
300
|
|
|
367
|
-
// =============================================================================
|
|
368
|
-
// State Change Notification
|
|
369
|
-
// =============================================================================
|
|
370
|
-
|
|
371
301
|
/**
|
|
372
302
|
* Notify that a state change occurred (navigation, modal, etc.)
|
|
373
303
|
* Kept for API compatibility
|
|
@@ -376,25 +306,18 @@ export function notifyStateChange(): void {
|
|
|
376
306
|
// No-op - kept for backward compatibility
|
|
377
307
|
}
|
|
378
308
|
|
|
379
|
-
// =============================================================================
|
|
380
|
-
// Error Tracking
|
|
381
|
-
// =============================================================================
|
|
382
|
-
|
|
383
309
|
/**
|
|
384
310
|
* Setup automatic error tracking
|
|
385
311
|
*/
|
|
386
312
|
function setupErrorTracking(): void {
|
|
387
|
-
// Track React Native errors
|
|
388
313
|
if (config.trackReactNativeErrors !== false) {
|
|
389
314
|
setupReactNativeErrorHandler();
|
|
390
315
|
}
|
|
391
316
|
|
|
392
|
-
// Track JavaScript errors (only works in web/debug)
|
|
393
317
|
if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
|
|
394
318
|
setupJSErrorHandler();
|
|
395
319
|
}
|
|
396
320
|
|
|
397
|
-
// Track unhandled promise rejections
|
|
398
321
|
if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
|
|
399
322
|
setupPromiseRejectionHandler();
|
|
400
323
|
}
|
|
@@ -405,16 +328,12 @@ function setupErrorTracking(): void {
|
|
|
405
328
|
*/
|
|
406
329
|
function setupReactNativeErrorHandler(): void {
|
|
407
330
|
try {
|
|
408
|
-
// Access ErrorUtils from global scope
|
|
409
331
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
410
332
|
if (!ErrorUtils) return;
|
|
411
333
|
|
|
412
|
-
// Store original handler
|
|
413
334
|
originalErrorHandler = ErrorUtils.getGlobalHandler();
|
|
414
335
|
|
|
415
|
-
// Set new handler
|
|
416
336
|
ErrorUtils.setGlobalHandler((error: Error, isFatal: boolean) => {
|
|
417
|
-
// Track the error
|
|
418
337
|
trackError({
|
|
419
338
|
type: 'error',
|
|
420
339
|
timestamp: Date.now(),
|
|
@@ -423,13 +342,12 @@ function setupReactNativeErrorHandler(): void {
|
|
|
423
342
|
name: error.name || 'Error',
|
|
424
343
|
});
|
|
425
344
|
|
|
426
|
-
// Call original handler
|
|
427
345
|
if (originalErrorHandler) {
|
|
428
346
|
originalErrorHandler(error, isFatal);
|
|
429
347
|
}
|
|
430
348
|
});
|
|
431
349
|
} catch {
|
|
432
|
-
//
|
|
350
|
+
// Ignore
|
|
433
351
|
}
|
|
434
352
|
}
|
|
435
353
|
|
|
@@ -438,8 +356,6 @@ function setupReactNativeErrorHandler(): void {
|
|
|
438
356
|
*/
|
|
439
357
|
function setupJSErrorHandler(): void {
|
|
440
358
|
if (typeof _globalThis.onerror !== 'undefined') {
|
|
441
|
-
// Note: In React Native, this typically doesn't fire
|
|
442
|
-
// But we set it up anyway for hybrid apps
|
|
443
359
|
originalOnError = _globalThis.onerror;
|
|
444
360
|
|
|
445
361
|
_globalThis.onerror = (
|
|
@@ -457,7 +373,6 @@ function setupJSErrorHandler(): void {
|
|
|
457
373
|
name: error?.name || 'Error',
|
|
458
374
|
});
|
|
459
375
|
|
|
460
|
-
// Call original handler
|
|
461
376
|
if (originalOnError) {
|
|
462
377
|
return originalOnError(message, source, lineno, colno, error);
|
|
463
378
|
}
|
|
@@ -491,7 +406,6 @@ function setupPromiseRejectionHandler(): void {
|
|
|
491
406
|
* Restore original error handlers
|
|
492
407
|
*/
|
|
493
408
|
function restoreErrorHandlers(): void {
|
|
494
|
-
// Restore React Native handler
|
|
495
409
|
if (originalErrorHandler) {
|
|
496
410
|
try {
|
|
497
411
|
const ErrorUtils = _globalThis.ErrorUtils;
|
|
@@ -504,13 +418,11 @@ function restoreErrorHandlers(): void {
|
|
|
504
418
|
originalErrorHandler = undefined;
|
|
505
419
|
}
|
|
506
420
|
|
|
507
|
-
// Restore global onerror
|
|
508
421
|
if (originalOnError !== null) {
|
|
509
422
|
_globalThis.onerror = originalOnError;
|
|
510
423
|
originalOnError = null;
|
|
511
424
|
}
|
|
512
425
|
|
|
513
|
-
// Remove promise rejection handler
|
|
514
426
|
if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
|
|
515
427
|
_globalThis.removeEventListener!('unhandledrejection', originalOnUnhandledRejection);
|
|
516
428
|
originalOnUnhandledRejection = null;
|
|
@@ -546,15 +458,10 @@ export function captureError(
|
|
|
546
458
|
});
|
|
547
459
|
}
|
|
548
460
|
|
|
549
|
-
// =============================================================================
|
|
550
|
-
// Screen/Funnel Tracking - Automatic Navigation Detection
|
|
551
|
-
// =============================================================================
|
|
552
|
-
|
|
553
|
-
// Navigation detection state
|
|
554
461
|
let navigationPollingInterval: ReturnType<typeof setInterval> | null = null;
|
|
555
462
|
let lastDetectedScreen = '';
|
|
556
463
|
let navigationSetupDone = false;
|
|
557
|
-
let navigationPollingErrors = 0;
|
|
464
|
+
let navigationPollingErrors = 0;
|
|
558
465
|
const MAX_POLLING_ERRORS = 10; // Stop polling after 10 consecutive errors
|
|
559
466
|
|
|
560
467
|
/**
|
|
@@ -578,7 +485,6 @@ export function trackNavigationState(state: any): void {
|
|
|
578
485
|
try {
|
|
579
486
|
const { normalizeScreenName } = require('./navigation');
|
|
580
487
|
|
|
581
|
-
// Find the active screen recursively
|
|
582
488
|
const findActiveScreen = (navState: any): string | null => {
|
|
583
489
|
if (!navState?.routes) return null;
|
|
584
490
|
const index = navState.index ?? navState.routes.length - 1;
|
|
@@ -594,7 +500,7 @@ export function trackNavigationState(state: any): void {
|
|
|
594
500
|
trackScreen(screenName);
|
|
595
501
|
}
|
|
596
502
|
} catch {
|
|
597
|
-
//
|
|
503
|
+
// Ignore
|
|
598
504
|
}
|
|
599
505
|
}
|
|
600
506
|
|
|
@@ -624,14 +530,11 @@ export function trackNavigationState(state: any): void {
|
|
|
624
530
|
* ```
|
|
625
531
|
*/
|
|
626
532
|
export function useNavigationTracking() {
|
|
627
|
-
// Use React's useRef and useCallback to create stable references
|
|
628
533
|
const React = require('react');
|
|
629
534
|
const { createNavigationContainerRef } = require('@react-navigation/native');
|
|
630
535
|
|
|
631
|
-
// Create a stable navigation ref
|
|
632
536
|
const navigationRef = React.useRef(createNavigationContainerRef());
|
|
633
537
|
|
|
634
|
-
// Track initial screen when navigation is ready
|
|
635
538
|
const onReady = React.useCallback(() => {
|
|
636
539
|
try {
|
|
637
540
|
const currentRoute = navigationRef.current?.getCurrentRoute?.();
|
|
@@ -644,11 +547,10 @@ export function useNavigationTracking() {
|
|
|
644
547
|
}
|
|
645
548
|
}
|
|
646
549
|
} catch {
|
|
647
|
-
//
|
|
550
|
+
// Ignore
|
|
648
551
|
}
|
|
649
552
|
}, []);
|
|
650
553
|
|
|
651
|
-
// Return props to spread on NavigationContainer
|
|
652
554
|
return {
|
|
653
555
|
ref: navigationRef.current,
|
|
654
556
|
onReady,
|
|
@@ -670,8 +572,6 @@ function setupNavigationTracking(): void {
|
|
|
670
572
|
logger.debug('Setting up navigation tracking...');
|
|
671
573
|
}
|
|
672
574
|
|
|
673
|
-
// Delay to ensure navigation is initialized - Expo Router needs more time
|
|
674
|
-
// We retry a few times with increasing delays
|
|
675
575
|
let attempts = 0;
|
|
676
576
|
const maxAttempts = 5;
|
|
677
577
|
|
|
@@ -688,8 +588,7 @@ function setupNavigationTracking(): void {
|
|
|
688
588
|
logger.debug('Expo Router setup: SUCCESS on attempt', attempts);
|
|
689
589
|
}
|
|
690
590
|
} else if (attempts < maxAttempts) {
|
|
691
|
-
|
|
692
|
-
const delay = 200 * attempts; // 200, 400, 600, 800ms
|
|
591
|
+
const delay = 200 * attempts;
|
|
693
592
|
if (__DEV__) {
|
|
694
593
|
logger.debug('Expo Router not ready, retrying in', delay, 'ms');
|
|
695
594
|
}
|
|
@@ -702,7 +601,6 @@ function setupNavigationTracking(): void {
|
|
|
702
601
|
}
|
|
703
602
|
};
|
|
704
603
|
|
|
705
|
-
// Start first attempt after 200ms
|
|
706
604
|
setTimeout(trySetup, 200);
|
|
707
605
|
}
|
|
708
606
|
|
|
@@ -729,13 +627,10 @@ function trySetupExpoRouter(): boolean {
|
|
|
729
627
|
|
|
730
628
|
const { normalizeScreenName, getScreenNameFromPath } = require('./navigation');
|
|
731
629
|
|
|
732
|
-
// Poll for route changes (expo-router doesn't expose a listener API outside hooks)
|
|
733
630
|
navigationPollingInterval = setInterval(() => {
|
|
734
631
|
try {
|
|
735
632
|
let state = null;
|
|
736
633
|
let stateSource = '';
|
|
737
|
-
|
|
738
|
-
// Method 1: Public accessors on router object
|
|
739
634
|
if (typeof router.getState === 'function') {
|
|
740
635
|
state = router.getState();
|
|
741
636
|
stateSource = 'router.getState()';
|
|
@@ -744,33 +639,28 @@ function trySetupExpoRouter(): boolean {
|
|
|
744
639
|
stateSource = 'router.rootState';
|
|
745
640
|
}
|
|
746
641
|
|
|
747
|
-
// Method 2: Internal store access (works for both v3 and v6+)
|
|
748
642
|
if (!state) {
|
|
749
643
|
try {
|
|
750
644
|
const storeModule = require('expo-router/build/global-state/router-store');
|
|
751
645
|
if (storeModule?.store) {
|
|
752
|
-
// v6+: store.state or store.navigationRef
|
|
753
646
|
state = storeModule.store.state;
|
|
754
647
|
if (state) stateSource = 'store.state';
|
|
755
648
|
|
|
756
|
-
// v6+: Try navigationRef if state is undefined
|
|
757
649
|
if (!state && storeModule.store.navigationRef?.current) {
|
|
758
650
|
state = storeModule.store.navigationRef.current.getRootState?.();
|
|
759
651
|
if (state) stateSource = 'navigationRef.getRootState()';
|
|
760
652
|
}
|
|
761
653
|
|
|
762
|
-
// v3: store.rootState or store.initialState
|
|
763
654
|
if (!state) {
|
|
764
655
|
state = storeModule.store.rootState || storeModule.store.initialState;
|
|
765
656
|
if (state) stateSource = 'store.rootState/initialState';
|
|
766
657
|
}
|
|
767
658
|
}
|
|
768
659
|
} catch {
|
|
769
|
-
//
|
|
660
|
+
// Ignore
|
|
770
661
|
}
|
|
771
662
|
}
|
|
772
663
|
|
|
773
|
-
// Method 3: Try accessing via a different export path for v6
|
|
774
664
|
if (!state) {
|
|
775
665
|
try {
|
|
776
666
|
const imperative = require('expo-router/build/imperative-api');
|
|
@@ -779,12 +669,12 @@ function trySetupExpoRouter(): boolean {
|
|
|
779
669
|
if (state) stateSource = 'imperative-api';
|
|
780
670
|
}
|
|
781
671
|
} catch {
|
|
782
|
-
//
|
|
672
|
+
// Ignore
|
|
783
673
|
}
|
|
784
674
|
}
|
|
785
675
|
|
|
786
676
|
if (state) {
|
|
787
|
-
|
|
677
|
+
navigationPollingErrors = 0;
|
|
788
678
|
navigationPollingErrors = 0;
|
|
789
679
|
const screenName = extractScreenNameFromRouterState(state, getScreenNameFromPath, normalizeScreenName);
|
|
790
680
|
if (screenName && screenName !== lastDetectedScreen) {
|
|
@@ -795,21 +685,15 @@ function trySetupExpoRouter(): boolean {
|
|
|
795
685
|
trackScreen(screenName);
|
|
796
686
|
}
|
|
797
687
|
} else {
|
|
798
|
-
// Track consecutive failures to get state
|
|
799
688
|
navigationPollingErrors++;
|
|
800
689
|
if (__DEV__ && navigationPollingErrors === 1) {
|
|
801
690
|
logger.debug('Expo Router: Could not get navigation state');
|
|
802
691
|
}
|
|
803
692
|
if (navigationPollingErrors >= MAX_POLLING_ERRORS) {
|
|
804
|
-
// Stop polling after too many errors to save CPU
|
|
805
|
-
if (__DEV__) {
|
|
806
|
-
logger.debug('Expo Router: Stopped polling after', MAX_POLLING_ERRORS, 'errors');
|
|
807
|
-
}
|
|
808
693
|
cleanupNavigationTracking();
|
|
809
694
|
}
|
|
810
695
|
}
|
|
811
696
|
} catch (e) {
|
|
812
|
-
// Error - track and potentially stop
|
|
813
697
|
navigationPollingErrors++;
|
|
814
698
|
if (__DEV__ && navigationPollingErrors === 1) {
|
|
815
699
|
logger.debug('Expo Router polling error:', e);
|
|
@@ -818,14 +702,13 @@ function trySetupExpoRouter(): boolean {
|
|
|
818
702
|
cleanupNavigationTracking();
|
|
819
703
|
}
|
|
820
704
|
}
|
|
821
|
-
}, 500);
|
|
705
|
+
}, 500);
|
|
822
706
|
|
|
823
707
|
return true;
|
|
824
708
|
} catch (e) {
|
|
825
709
|
if (__DEV__) {
|
|
826
710
|
logger.debug('Expo Router not available:', e);
|
|
827
711
|
}
|
|
828
|
-
// expo-router not installed
|
|
829
712
|
return false;
|
|
830
713
|
}
|
|
831
714
|
}
|
|
@@ -847,10 +730,8 @@ function extractScreenNameFromRouterState(
|
|
|
847
730
|
const route = state.routes[state.index ?? state.routes.length - 1];
|
|
848
731
|
if (!route) return null;
|
|
849
732
|
|
|
850
|
-
// Add current route name to accumulated segments
|
|
851
733
|
const newSegments = [...accumulatedSegments, route.name];
|
|
852
734
|
|
|
853
|
-
// If this route has nested state, recurse deeper
|
|
854
735
|
if (route.state) {
|
|
855
736
|
return extractScreenNameFromRouterState(
|
|
856
737
|
route.state,
|
|
@@ -860,13 +741,9 @@ function extractScreenNameFromRouterState(
|
|
|
860
741
|
);
|
|
861
742
|
}
|
|
862
743
|
|
|
863
|
-
// We've reached the deepest level - build the screen name
|
|
864
|
-
// Filter out group markers like (tabs), (main), (auth)
|
|
865
744
|
const cleanSegments = newSegments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
|
|
866
745
|
|
|
867
|
-
// If after filtering we have no segments, use the last meaningful name
|
|
868
746
|
if (cleanSegments.length === 0) {
|
|
869
|
-
// Find the last non-group segment
|
|
870
747
|
for (let i = newSegments.length - 1; i >= 0; i--) {
|
|
871
748
|
const seg = newSegments[i];
|
|
872
749
|
if (seg && !seg.startsWith('(') && !seg.endsWith(')')) {
|
|
@@ -907,14 +784,10 @@ export function trackScreen(screenName: string): void {
|
|
|
907
784
|
|
|
908
785
|
const previousScreen = currentScreen;
|
|
909
786
|
currentScreen = screenName;
|
|
910
|
-
// Add to screens visited (only track for unique set, avoid large array copies)
|
|
911
787
|
screensVisited.push(screenName);
|
|
912
788
|
|
|
913
|
-
// Update unique screens count
|
|
914
789
|
const uniqueScreens = new Set(screensVisited);
|
|
915
790
|
metrics.uniqueScreensCount = uniqueScreens.size;
|
|
916
|
-
|
|
917
|
-
// Update navigation count
|
|
918
791
|
metrics.navigationCount++;
|
|
919
792
|
metrics.totalEvents++;
|
|
920
793
|
|
|
@@ -922,13 +795,10 @@ export function trackScreen(screenName: string): void {
|
|
|
922
795
|
logger.debug('trackScreen:', screenName, '(total screens:', metrics.uniqueScreensCount, ')');
|
|
923
796
|
}
|
|
924
797
|
|
|
925
|
-
// Notify callback
|
|
926
798
|
if (onScreenChange) {
|
|
927
799
|
onScreenChange(screenName, previousScreen);
|
|
928
800
|
}
|
|
929
801
|
|
|
930
|
-
// IMPORTANT: Also notify native module to send to backend
|
|
931
|
-
// This is the key fix - without this, screens don't get recorded!
|
|
932
802
|
try {
|
|
933
803
|
const RejourneyNative = getRejourneyNativeModule();
|
|
934
804
|
if (RejourneyNative?.screenChanged) {
|
|
@@ -950,10 +820,6 @@ export function trackScreen(screenName: string): void {
|
|
|
950
820
|
}
|
|
951
821
|
}
|
|
952
822
|
|
|
953
|
-
// =============================================================================
|
|
954
|
-
// API Metrics Tracking
|
|
955
|
-
// =============================================================================
|
|
956
|
-
|
|
957
823
|
/**
|
|
958
824
|
* Track an API request with timing data
|
|
959
825
|
*/
|
|
@@ -967,7 +833,6 @@ export function trackAPIRequest(
|
|
|
967
833
|
|
|
968
834
|
metrics.apiTotalCount++;
|
|
969
835
|
|
|
970
|
-
// Accumulate timing and size for avg calculation
|
|
971
836
|
if (durationMs > 0) {
|
|
972
837
|
metrics.netTotalDurationMs += durationMs;
|
|
973
838
|
}
|
|
@@ -979,16 +844,10 @@ export function trackAPIRequest(
|
|
|
979
844
|
metrics.apiSuccessCount++;
|
|
980
845
|
} else {
|
|
981
846
|
metrics.apiErrorCount++;
|
|
982
|
-
|
|
983
|
-
// API errors also count toward error count for UX score
|
|
984
847
|
metrics.errorCount++;
|
|
985
848
|
}
|
|
986
849
|
}
|
|
987
850
|
|
|
988
|
-
// =============================================================================
|
|
989
|
-
// Session Metrics
|
|
990
|
-
// =============================================================================
|
|
991
|
-
|
|
992
851
|
/**
|
|
993
852
|
* Create empty metrics object
|
|
994
853
|
*/
|
|
@@ -1046,19 +905,15 @@ export function trackInput(): void {
|
|
|
1046
905
|
* Get current session metrics
|
|
1047
906
|
*/
|
|
1048
907
|
export function getSessionMetrics(): SessionMetrics & { netAvgDurationMs: number } {
|
|
1049
|
-
// Calculate scores before returning
|
|
1050
908
|
calculateScores();
|
|
1051
909
|
|
|
1052
|
-
// Compute average API response time
|
|
1053
910
|
const netAvgDurationMs = metrics.apiTotalCount > 0
|
|
1054
911
|
? Math.round(metrics.netTotalDurationMs / metrics.apiTotalCount)
|
|
1055
912
|
: 0;
|
|
1056
913
|
|
|
1057
|
-
// Lazily populate screensVisited only when metrics are retrieved
|
|
1058
|
-
// This avoids expensive array copies on every screen change
|
|
1059
914
|
return {
|
|
1060
915
|
...metrics,
|
|
1061
|
-
screensVisited: [...screensVisited],
|
|
916
|
+
screensVisited: [...screensVisited],
|
|
1062
917
|
netAvgDurationMs,
|
|
1063
918
|
};
|
|
1064
919
|
}
|
|
@@ -1067,38 +922,26 @@ export function getSessionMetrics(): SessionMetrics & { netAvgDurationMs: number
|
|
|
1067
922
|
* Calculate session scores
|
|
1068
923
|
*/
|
|
1069
924
|
function calculateScores(): void {
|
|
1070
|
-
// Interaction Score (0-100)
|
|
1071
|
-
// Based on total interactions normalized to a baseline
|
|
1072
925
|
const totalInteractions =
|
|
1073
926
|
metrics.touchCount +
|
|
1074
927
|
metrics.scrollCount +
|
|
1075
928
|
metrics.gestureCount +
|
|
1076
929
|
metrics.inputCount;
|
|
1077
930
|
|
|
1078
|
-
// Assume 50 interactions is "average" for a session
|
|
1079
931
|
const avgInteractions = 50;
|
|
1080
932
|
metrics.interactionScore = Math.min(100, Math.round((totalInteractions / avgInteractions) * 100));
|
|
1081
933
|
|
|
1082
|
-
// Exploration Score (0-100)
|
|
1083
|
-
// Based on unique screens visited
|
|
1084
|
-
// Assume 5 screens is "average" exploration
|
|
1085
934
|
const avgScreens = 5;
|
|
1086
935
|
metrics.explorationScore = Math.min(100, Math.round((metrics.uniqueScreensCount / avgScreens) * 100));
|
|
1087
936
|
|
|
1088
|
-
// UX Score (0-100)
|
|
1089
|
-
// Starts at 100, deducts for issues
|
|
1090
937
|
let uxScore = 100;
|
|
1091
938
|
|
|
1092
|
-
|
|
1093
|
-
uxScore -= Math.min(30, metrics.errorCount * 15); // Max 30 point deduction
|
|
939
|
+
uxScore -= Math.min(30, metrics.errorCount * 15);
|
|
1094
940
|
|
|
1095
|
-
|
|
1096
|
-
uxScore -= Math.min(24, metrics.rageTapCount * 8); // Max 24 point deduction
|
|
941
|
+
uxScore -= Math.min(24, metrics.rageTapCount * 8);
|
|
1097
942
|
|
|
1098
|
-
|
|
1099
|
-
uxScore -= Math.min(20, metrics.apiErrorCount * 10); // Max 20 point deduction
|
|
943
|
+
uxScore -= Math.min(20, metrics.apiErrorCount * 10);
|
|
1100
944
|
|
|
1101
|
-
// Bonus for completing funnel (if screens > 3)
|
|
1102
945
|
if (metrics.uniqueScreensCount >= 3) {
|
|
1103
946
|
uxScore += 5;
|
|
1104
947
|
}
|
|
@@ -1118,42 +961,31 @@ export function resetMetrics(): void {
|
|
|
1118
961
|
sessionStartTime = Date.now();
|
|
1119
962
|
}
|
|
1120
963
|
|
|
1121
|
-
// =============================================================================
|
|
1122
|
-
// Session duration helpers
|
|
1123
|
-
// =============================================================================
|
|
1124
|
-
|
|
1125
|
-
/** Clamp and set max session duration in minutes (1–10). Defaults to 10. */
|
|
1126
964
|
export function setMaxSessionDurationMinutes(minutes?: number): void {
|
|
1127
965
|
const clampedMinutes = Math.min(10, Math.max(1, minutes ?? 10));
|
|
1128
966
|
maxSessionDurationMs = clampedMinutes * 60 * 1000;
|
|
1129
967
|
}
|
|
1130
|
-
|
|
1131
|
-
/** Returns true if the current session exceeded the configured max duration. */
|
|
1132
968
|
export function hasExceededMaxSessionDuration(): boolean {
|
|
1133
969
|
if (!sessionStartTime) return false;
|
|
1134
970
|
return Date.now() - sessionStartTime >= maxSessionDurationMs;
|
|
1135
971
|
}
|
|
1136
972
|
|
|
1137
|
-
/** Returns remaining milliseconds until the session should stop. */
|
|
1138
973
|
export function getRemainingSessionDurationMs(): number {
|
|
1139
974
|
if (!sessionStartTime) return maxSessionDurationMs;
|
|
1140
975
|
const remaining = maxSessionDurationMs - (Date.now() - sessionStartTime);
|
|
1141
976
|
return Math.max(0, remaining);
|
|
1142
977
|
}
|
|
1143
978
|
|
|
1144
|
-
// =============================================================================
|
|
1145
|
-
// Device Info Collection
|
|
1146
|
-
// =============================================================================
|
|
1147
|
-
|
|
1148
979
|
/**
|
|
1149
980
|
* Collect device information
|
|
1150
981
|
*/
|
|
1151
|
-
|
|
982
|
+
/**
|
|
983
|
+
* Collect device information
|
|
984
|
+
*/
|
|
985
|
+
export async function collectDeviceInfo(): Promise<DeviceInfo> {
|
|
1152
986
|
const Dimensions = getDimensions();
|
|
1153
987
|
const Platform = getPlatform();
|
|
1154
|
-
const NativeModules = getNativeModules();
|
|
1155
988
|
|
|
1156
|
-
// Default values if react-native isn't available
|
|
1157
989
|
let width = 0, height = 0, scale = 1;
|
|
1158
990
|
|
|
1159
991
|
if (Dimensions) {
|
|
@@ -1164,105 +996,46 @@ export function collectDeviceInfo(): DeviceInfo {
|
|
|
1164
996
|
scale = screenDims?.scale || 1;
|
|
1165
997
|
}
|
|
1166
998
|
|
|
1167
|
-
//
|
|
1168
|
-
let model = 'Unknown';
|
|
1169
|
-
let manufacturer: string | undefined;
|
|
1170
|
-
let osVersion = 'Unknown';
|
|
1171
|
-
let appVersion: string | undefined;
|
|
1172
|
-
let appId: string | undefined;
|
|
999
|
+
// Basic JS-side info
|
|
1173
1000
|
let locale: string | undefined;
|
|
1174
1001
|
let timezone: string | undefined;
|
|
1175
1002
|
|
|
1176
1003
|
try {
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
const DeviceInfo = require('react-native-device-info');
|
|
1180
|
-
model = DeviceInfo.getModel?.() || model;
|
|
1181
|
-
manufacturer = DeviceInfo.getBrand?.() || undefined;
|
|
1182
|
-
osVersion = DeviceInfo.getSystemVersion?.() || osVersion;
|
|
1183
|
-
appVersion = DeviceInfo.getVersion?.() || undefined;
|
|
1184
|
-
appId = DeviceInfo.getBundleId?.() || undefined;
|
|
1185
|
-
locale = DeviceInfo.getDeviceLocale?.() || undefined;
|
|
1186
|
-
timezone = DeviceInfo.getTimezone?.() || undefined;
|
|
1004
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1005
|
+
locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
1187
1006
|
} catch {
|
|
1188
|
-
//
|
|
1189
|
-
try {
|
|
1190
|
-
// Try expo-application for app version/id
|
|
1191
|
-
const Application = require('expo-application');
|
|
1192
|
-
appVersion = Application.nativeApplicationVersion || Application.applicationVersion || undefined;
|
|
1193
|
-
appId = Application.applicationId || undefined;
|
|
1194
|
-
} catch {
|
|
1195
|
-
// expo-application not available
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
try {
|
|
1199
|
-
// Try expo-constants for additional info
|
|
1200
|
-
const Constants = require('expo-constants');
|
|
1201
|
-
const expoConfig = Constants.expoConfig || Constants.manifest2?.extra?.expoClient || Constants.manifest;
|
|
1202
|
-
if (!appVersion && expoConfig?.version) {
|
|
1203
|
-
appVersion = expoConfig.version;
|
|
1204
|
-
}
|
|
1205
|
-
if (!appId && (expoConfig?.ios?.bundleIdentifier || expoConfig?.android?.package)) {
|
|
1206
|
-
appId = Platform?.OS === 'ios'
|
|
1207
|
-
? expoConfig?.ios?.bundleIdentifier
|
|
1208
|
-
: expoConfig?.android?.package;
|
|
1209
|
-
}
|
|
1210
|
-
} catch {
|
|
1211
|
-
// expo-constants not available
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// Fall back to basic platform info
|
|
1215
|
-
if (Platform?.OS === 'ios') {
|
|
1216
|
-
// Get basic info from constants
|
|
1217
|
-
const PlatformConstants = NativeModules?.PlatformConstants;
|
|
1218
|
-
osVersion = Platform.Version?.toString() || osVersion;
|
|
1219
|
-
model = PlatformConstants?.interfaceIdiom === 'pad' ? 'iPad' : 'iPhone';
|
|
1220
|
-
} else if (Platform?.OS === 'android') {
|
|
1221
|
-
osVersion = Platform.Version?.toString() || osVersion;
|
|
1222
|
-
model = 'Android Device';
|
|
1223
|
-
}
|
|
1007
|
+
// Ignore
|
|
1224
1008
|
}
|
|
1225
1009
|
|
|
1226
|
-
// Get
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1230
|
-
} catch {
|
|
1231
|
-
timezone = undefined;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1010
|
+
// Get native info
|
|
1011
|
+
const nativeModule = getRejourneyNativeModule();
|
|
1012
|
+
let nativeInfo: any = {};
|
|
1234
1013
|
|
|
1235
|
-
|
|
1236
|
-
if (!locale) {
|
|
1014
|
+
if (nativeModule && nativeModule.getDeviceInfo) {
|
|
1237
1015
|
try {
|
|
1238
|
-
|
|
1239
|
-
} catch {
|
|
1240
|
-
|
|
1016
|
+
nativeInfo = await nativeModule.getDeviceInfo();
|
|
1017
|
+
} catch (e) {
|
|
1018
|
+
if (__DEV__) {
|
|
1019
|
+
console.warn('[Rejourney] Failed to get native device info:', e);
|
|
1020
|
+
}
|
|
1241
1021
|
}
|
|
1242
1022
|
}
|
|
1243
1023
|
|
|
1244
1024
|
return {
|
|
1245
|
-
model,
|
|
1246
|
-
manufacturer,
|
|
1025
|
+
model: nativeInfo.model || 'Unknown',
|
|
1026
|
+
manufacturer: nativeInfo.brand,
|
|
1247
1027
|
os: (Platform?.OS || 'ios') as 'ios' | 'android',
|
|
1248
|
-
osVersion,
|
|
1028
|
+
osVersion: nativeInfo.systemVersion || Platform?.Version?.toString() || 'Unknown',
|
|
1249
1029
|
screenWidth: Math.round(width),
|
|
1250
1030
|
screenHeight: Math.round(height),
|
|
1251
1031
|
pixelRatio: scale,
|
|
1252
|
-
appVersion,
|
|
1253
|
-
appId,
|
|
1254
|
-
locale,
|
|
1255
|
-
timezone,
|
|
1032
|
+
appVersion: nativeInfo.appVersion,
|
|
1033
|
+
appId: nativeInfo.bundleId,
|
|
1034
|
+
locale: locale,
|
|
1035
|
+
timezone: timezone,
|
|
1256
1036
|
};
|
|
1257
1037
|
}
|
|
1258
1038
|
|
|
1259
|
-
// =============================================================================
|
|
1260
|
-
// Anonymous ID Generation
|
|
1261
|
-
// =============================================================================
|
|
1262
|
-
|
|
1263
|
-
// Storage key for anonymous ID
|
|
1264
|
-
// const ANONYMOUS_ID_KEY = '@rejourney_anonymous_id';
|
|
1265
|
-
|
|
1266
1039
|
/**
|
|
1267
1040
|
* Generate a persistent anonymous ID
|
|
1268
1041
|
*/
|
|
@@ -1290,7 +1063,6 @@ export async function ensurePersistentAnonymousId(): Promise<string> {
|
|
|
1290
1063
|
if (anonymousId) return anonymousId;
|
|
1291
1064
|
if (!anonymousIdPromise) {
|
|
1292
1065
|
anonymousIdPromise = (async () => {
|
|
1293
|
-
// Just use the load logic which now delegates to native or memory
|
|
1294
1066
|
const id = await loadAnonymousId();
|
|
1295
1067
|
anonymousId = id;
|
|
1296
1068
|
return id;
|
|
@@ -1320,13 +1092,8 @@ export async function loadAnonymousId(): Promise<string> {
|
|
|
1320
1092
|
*/
|
|
1321
1093
|
export function setAnonymousId(id: string): void {
|
|
1322
1094
|
anonymousId = id;
|
|
1323
|
-
// No-op for async persistence as we moved to native-only or memory-only
|
|
1324
1095
|
}
|
|
1325
1096
|
|
|
1326
|
-
// =============================================================================
|
|
1327
|
-
// Exports
|
|
1328
|
-
// =============================================================================
|
|
1329
|
-
|
|
1330
1097
|
export default {
|
|
1331
1098
|
init: initAutoTracking,
|
|
1332
1099
|
cleanup: cleanupAutoTracking,
|