@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.
Files changed (65) hide show
  1. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +72 -391
  2. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
  3. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
  4. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
  5. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
  6. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
  7. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +3 -26
  8. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
  9. package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
  10. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
  11. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
  12. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
  13. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
  14. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +7 -0
  15. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
  16. package/ios/Capture/RJCaptureEngine.m +3 -34
  17. package/ios/Capture/RJVideoEncoder.m +0 -26
  18. package/ios/Capture/RJViewHierarchyScanner.m +68 -51
  19. package/ios/Core/RJLifecycleManager.m +0 -14
  20. package/ios/Core/Rejourney.mm +53 -129
  21. package/ios/Network/RJDeviceAuthManager.m +0 -2
  22. package/ios/Network/RJUploadManager.h +8 -0
  23. package/ios/Network/RJUploadManager.m +45 -0
  24. package/ios/Privacy/RJPrivacyMask.m +5 -31
  25. package/ios/Rejourney.h +0 -14
  26. package/ios/Touch/RJTouchInterceptor.m +21 -15
  27. package/ios/Utils/RJEventBuffer.m +57 -69
  28. package/ios/Utils/RJPerfTiming.m +0 -5
  29. package/ios/Utils/RJWindowUtils.m +87 -87
  30. package/lib/commonjs/components/Mask.js +1 -6
  31. package/lib/commonjs/index.js +46 -117
  32. package/lib/commonjs/sdk/autoTracking.js +39 -313
  33. package/lib/commonjs/sdk/constants.js +2 -13
  34. package/lib/commonjs/sdk/errorTracking.js +1 -29
  35. package/lib/commonjs/sdk/metricsTracking.js +3 -24
  36. package/lib/commonjs/sdk/navigation.js +3 -42
  37. package/lib/commonjs/sdk/networkInterceptor.js +7 -60
  38. package/lib/commonjs/sdk/utils.js +73 -19
  39. package/lib/module/components/Mask.js +1 -6
  40. package/lib/module/index.js +45 -121
  41. package/lib/module/sdk/autoTracking.js +39 -314
  42. package/lib/module/sdk/constants.js +2 -13
  43. package/lib/module/sdk/errorTracking.js +1 -29
  44. package/lib/module/sdk/index.js +0 -2
  45. package/lib/module/sdk/metricsTracking.js +3 -24
  46. package/lib/module/sdk/navigation.js +3 -42
  47. package/lib/module/sdk/networkInterceptor.js +7 -60
  48. package/lib/module/sdk/utils.js +73 -19
  49. package/lib/typescript/NativeRejourney.d.ts +1 -0
  50. package/lib/typescript/sdk/autoTracking.d.ts +4 -4
  51. package/lib/typescript/sdk/utils.d.ts +31 -1
  52. package/lib/typescript/types/index.d.ts +0 -1
  53. package/package.json +17 -11
  54. package/src/NativeRejourney.ts +2 -0
  55. package/src/components/Mask.tsx +0 -3
  56. package/src/index.ts +43 -92
  57. package/src/sdk/autoTracking.ts +51 -284
  58. package/src/sdk/constants.ts +13 -13
  59. package/src/sdk/errorTracking.ts +1 -17
  60. package/src/sdk/index.ts +0 -2
  61. package/src/sdk/metricsTracking.ts +5 -33
  62. package/src/sdk/navigation.ts +8 -29
  63. package/src/sdk/networkInterceptor.ts +9 -42
  64. package/src/sdk/utils.ts +76 -19
  65. package/src/types/index.ts +0 -29
@@ -61,9 +61,6 @@ function getPlatform() {
61
61
  function getDimensions() {
62
62
  return getRN()?.Dimensions;
63
63
  }
64
- function getNativeModules() {
65
- return getRN()?.NativeModules;
66
- }
67
64
  function getRejourneyNativeModule() {
68
65
  const RN = getRN();
69
66
  if (!RN) return null;
@@ -76,7 +73,7 @@ function getRejourneyNativeModule() {
76
73
  try {
77
74
  nativeModule = TurboModuleRegistry.get('Rejourney');
78
75
  } catch {
79
- // Ignore and fall back to NativeModules
76
+ // Ignore
80
77
  }
81
78
  }
82
79
  if (!nativeModule && NativeModules) {
@@ -84,57 +81,27 @@ function getRejourneyNativeModule() {
84
81
  }
85
82
  return nativeModule;
86
83
  }
87
-
88
- // Type declarations for browser globals (only used in hybrid apps where DOM is available)
89
- // These don't exist in pure React Native but are needed for error tracking in hybrid scenarios
90
-
91
- // Cast globalThis to work with both RN and hybrid scenarios
92
84
  const _globalThis = globalThis;
93
-
94
- // =============================================================================
95
- // Types
96
- // =============================================================================
97
-
98
- // =============================================================================
99
- // State
100
- // =============================================================================
101
-
102
85
  let isInitialized = false;
103
86
  let config = {};
104
-
105
- // Rage tap tracking
106
87
  const recentTaps = [];
107
- let tapHead = 0; // Circular buffer head pointer
108
- let tapCount = 0; // Actual count of taps in buffer
88
+ let tapHead = 0;
89
+ let tapCount = 0;
109
90
  const MAX_RECENT_TAPS = 10;
110
-
111
- // Session metrics
112
91
  let metrics = createEmptyMetrics();
113
92
  let sessionStartTime = 0;
114
93
  let maxSessionDurationMs = 10 * 60 * 1000;
115
-
116
- // Screen tracking
117
94
  let currentScreen = '';
118
95
  let screensVisited = [];
119
-
120
- // Anonymous ID
121
96
  let anonymousId = null;
122
97
  let anonymousIdPromise = null;
123
-
124
- // Callbacks
125
98
  let onRageTapDetected = null;
126
99
  let onErrorCaptured = null;
127
100
  let onScreenChange = null;
128
-
129
- // Original error handlers (for restoration)
130
101
  let originalErrorHandler;
131
102
  let originalOnError = null;
132
103
  let originalOnUnhandledRejection = null;
133
104
 
134
- // =============================================================================
135
- // Initialization
136
- // =============================================================================
137
-
138
105
  /**
139
106
  * Initialize auto tracking features
140
107
  * Called automatically by Rejourney.init() - no user action needed
@@ -152,23 +119,13 @@ function initAutoTracking(trackingConfig, callbacks = {}) {
152
119
  maxSessionDurationMs: trackingConfig.maxSessionDurationMs,
153
120
  ...trackingConfig
154
121
  };
155
-
156
- // Session timing
157
122
  sessionStartTime = Date.now();
158
123
  setMaxSessionDurationMinutes(trackingConfig.maxSessionDurationMs ? trackingConfig.maxSessionDurationMs / 60000 : undefined);
159
-
160
- // Set callbacks
161
124
  onRageTapDetected = callbacks.onRageTap || null;
162
125
  onErrorCaptured = callbacks.onError || null;
163
126
  onScreenChange = callbacks.onScreen || null;
164
-
165
- // Setup error tracking
166
127
  setupErrorTracking();
167
-
168
- // Setup React Navigation tracking (if available)
169
128
  setupNavigationTracking();
170
-
171
- // Load anonymous ID from native storage (or generate new one)
172
129
  loadAnonymousId().then(id => {
173
130
  anonymousId = id;
174
131
  });
@@ -180,11 +137,7 @@ function initAutoTracking(trackingConfig, callbacks = {}) {
180
137
  */
181
138
  function cleanupAutoTracking() {
182
139
  if (!isInitialized) return;
183
-
184
- // Restore original error handlers
185
140
  restoreErrorHandlers();
186
-
187
- // Cleanup navigation tracking
188
141
  cleanupNavigationTracking();
189
142
 
190
143
  // Reset state
@@ -198,10 +151,6 @@ function cleanupAutoTracking() {
198
151
  isInitialized = false;
199
152
  }
200
153
 
201
- // =============================================================================
202
- // Rage Tap Detection
203
- // =============================================================================
204
-
205
154
  /**
206
155
  * Track a tap event for rage tap detection
207
156
  * Called automatically from touch interceptor
@@ -209,8 +158,6 @@ function cleanupAutoTracking() {
209
158
  function trackTap(tap) {
210
159
  if (!isInitialized) return;
211
160
  const now = Date.now();
212
-
213
- // Add to circular buffer (O(1) instead of shift() which is O(n))
214
161
  const insertIndex = (tapHead + tapCount) % MAX_RECENT_TAPS;
215
162
  if (tapCount < MAX_RECENT_TAPS) {
216
163
  recentTaps[insertIndex] = {
@@ -219,15 +166,12 @@ function trackTap(tap) {
219
166
  };
220
167
  tapCount++;
221
168
  } else {
222
- // Buffer full, overwrite oldest
223
169
  recentTaps[tapHead] = {
224
170
  ...tap,
225
171
  timestamp: now
226
172
  };
227
173
  tapHead = (tapHead + 1) % MAX_RECENT_TAPS;
228
174
  }
229
-
230
- // Evict old taps outside time window
231
175
  const windowStart = now - (config.rageTapTimeWindow || 500);
232
176
  while (tapCount > 0) {
233
177
  const oldestTap = recentTaps[tapHead];
@@ -238,11 +182,7 @@ function trackTap(tap) {
238
182
  break;
239
183
  }
240
184
  }
241
-
242
- // Check for rage tap
243
185
  detectRageTap();
244
-
245
- // Update metrics
246
186
  metrics.touchCount++;
247
187
  metrics.totalEvents++;
248
188
  }
@@ -254,14 +194,11 @@ function detectRageTap() {
254
194
  const threshold = config.rageTapThreshold || 3;
255
195
  const radius = config.rageTapRadius || 50;
256
196
  if (tapCount < threshold) return;
257
- // Check last N taps from circular buffer
258
197
  const tapsToCheck = [];
259
198
  for (let i = 0; i < threshold; i++) {
260
199
  const idx = (tapHead + tapCount - threshold + i) % MAX_RECENT_TAPS;
261
200
  tapsToCheck.push(recentTaps[idx]);
262
201
  }
263
-
264
- // Calculate center point
265
202
  let centerX = 0;
266
203
  let centerY = 0;
267
204
  for (const tap of tapsToCheck) {
@@ -270,8 +207,6 @@ function detectRageTap() {
270
207
  }
271
208
  centerX /= tapsToCheck.length;
272
209
  centerY /= tapsToCheck.length;
273
-
274
- // Check if all taps are within radius of center
275
210
  let allWithinRadius = true;
276
211
  for (const tap of tapsToCheck) {
277
212
  const dx = tap.x - centerX;
@@ -283,9 +218,7 @@ function detectRageTap() {
283
218
  }
284
219
  }
285
220
  if (allWithinRadius) {
286
- // Rage tap detected!
287
221
  metrics.rageTapCount++;
288
- // Clear circular buffer to prevent duplicate detection
289
222
  tapHead = 0;
290
223
  tapCount = 0;
291
224
 
@@ -296,10 +229,6 @@ function detectRageTap() {
296
229
  }
297
230
  }
298
231
 
299
- // =============================================================================
300
- // State Change Notification
301
- // =============================================================================
302
-
303
232
  /**
304
233
  * Notify that a state change occurred (navigation, modal, etc.)
305
234
  * Kept for API compatibility
@@ -308,25 +237,16 @@ function notifyStateChange() {
308
237
  // No-op - kept for backward compatibility
309
238
  }
310
239
 
311
- // =============================================================================
312
- // Error Tracking
313
- // =============================================================================
314
-
315
240
  /**
316
241
  * Setup automatic error tracking
317
242
  */
318
243
  function setupErrorTracking() {
319
- // Track React Native errors
320
244
  if (config.trackReactNativeErrors !== false) {
321
245
  setupReactNativeErrorHandler();
322
246
  }
323
-
324
- // Track JavaScript errors (only works in web/debug)
325
247
  if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
326
248
  setupJSErrorHandler();
327
249
  }
328
-
329
- // Track unhandled promise rejections
330
250
  if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
331
251
  setupPromiseRejectionHandler();
332
252
  }
@@ -337,16 +257,10 @@ function setupErrorTracking() {
337
257
  */
338
258
  function setupReactNativeErrorHandler() {
339
259
  try {
340
- // Access ErrorUtils from global scope
341
260
  const ErrorUtils = _globalThis.ErrorUtils;
342
261
  if (!ErrorUtils) return;
343
-
344
- // Store original handler
345
262
  originalErrorHandler = ErrorUtils.getGlobalHandler();
346
-
347
- // Set new handler
348
263
  ErrorUtils.setGlobalHandler((error, isFatal) => {
349
- // Track the error
350
264
  trackError({
351
265
  type: 'error',
352
266
  timestamp: Date.now(),
@@ -354,14 +268,12 @@ function setupReactNativeErrorHandler() {
354
268
  stack: error.stack,
355
269
  name: error.name || 'Error'
356
270
  });
357
-
358
- // Call original handler
359
271
  if (originalErrorHandler) {
360
272
  originalErrorHandler(error, isFatal);
361
273
  }
362
274
  });
363
275
  } catch {
364
- // ErrorUtils not available
276
+ // Ignore
365
277
  }
366
278
  }
367
279
 
@@ -370,8 +282,6 @@ function setupReactNativeErrorHandler() {
370
282
  */
371
283
  function setupJSErrorHandler() {
372
284
  if (typeof _globalThis.onerror !== 'undefined') {
373
- // Note: In React Native, this typically doesn't fire
374
- // But we set it up anyway for hybrid apps
375
285
  originalOnError = _globalThis.onerror;
376
286
  _globalThis.onerror = (message, source, lineno, colno, error) => {
377
287
  trackError({
@@ -381,8 +291,6 @@ function setupJSErrorHandler() {
381
291
  stack: error?.stack || `${source}:${lineno}:${colno}`,
382
292
  name: error?.name || 'Error'
383
293
  });
384
-
385
- // Call original handler
386
294
  if (originalOnError) {
387
295
  return originalOnError(message, source, lineno, colno, error);
388
296
  }
@@ -415,7 +323,6 @@ function setupPromiseRejectionHandler() {
415
323
  * Restore original error handlers
416
324
  */
417
325
  function restoreErrorHandlers() {
418
- // Restore React Native handler
419
326
  if (originalErrorHandler) {
420
327
  try {
421
328
  const ErrorUtils = _globalThis.ErrorUtils;
@@ -427,14 +334,10 @@ function restoreErrorHandlers() {
427
334
  }
428
335
  originalErrorHandler = undefined;
429
336
  }
430
-
431
- // Restore global onerror
432
337
  if (originalOnError !== null) {
433
338
  _globalThis.onerror = originalOnError;
434
339
  originalOnError = null;
435
340
  }
436
-
437
- // Remove promise rejection handler
438
341
  if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
439
342
  _globalThis.removeEventListener('unhandledrejection', originalOnUnhandledRejection);
440
343
  originalOnUnhandledRejection = null;
@@ -464,16 +367,10 @@ function captureError(message, stack, name) {
464
367
  name: name || 'Error'
465
368
  });
466
369
  }
467
-
468
- // =============================================================================
469
- // Screen/Funnel Tracking - Automatic Navigation Detection
470
- // =============================================================================
471
-
472
- // Navigation detection state
473
370
  let navigationPollingInterval = null;
474
371
  let lastDetectedScreen = '';
475
372
  let navigationSetupDone = false;
476
- let navigationPollingErrors = 0; // Track consecutive errors
373
+ let navigationPollingErrors = 0;
477
374
  const MAX_POLLING_ERRORS = 10; // Stop polling after 10 consecutive errors
478
375
 
479
376
  /**
@@ -497,8 +394,6 @@ function trackNavigationState(state) {
497
394
  const {
498
395
  normalizeScreenName
499
396
  } = require('./navigation');
500
-
501
- // Find the active screen recursively
502
397
  const findActiveScreen = navState => {
503
398
  if (!navState?.routes) return null;
504
399
  const index = navState.index ?? navState.routes.length - 1;
@@ -513,7 +408,7 @@ function trackNavigationState(state) {
513
408
  trackScreen(screenName);
514
409
  }
515
410
  } catch {
516
- // Navigation tracking error
411
+ // Ignore
517
412
  }
518
413
  }
519
414
 
@@ -543,16 +438,11 @@ function trackNavigationState(state) {
543
438
  * ```
544
439
  */
545
440
  function useNavigationTracking() {
546
- // Use React's useRef and useCallback to create stable references
547
441
  const React = require('react');
548
442
  const {
549
443
  createNavigationContainerRef
550
444
  } = require('@react-navigation/native');
551
-
552
- // Create a stable navigation ref
553
445
  const navigationRef = React.useRef(createNavigationContainerRef());
554
-
555
- // Track initial screen when navigation is ready
556
446
  const onReady = React.useCallback(() => {
557
447
  try {
558
448
  const currentRoute = navigationRef.current?.getCurrentRoute?.();
@@ -567,11 +457,9 @@ function useNavigationTracking() {
567
457
  }
568
458
  }
569
459
  } catch {
570
- // Navigation not ready yet
460
+ // Ignore
571
461
  }
572
462
  }, []);
573
-
574
- // Return props to spread on NavigationContainer
575
463
  return {
576
464
  ref: navigationRef.current,
577
465
  onReady,
@@ -591,9 +479,6 @@ function setupNavigationTracking() {
591
479
  if (__DEV__) {
592
480
  _utils.logger.debug('Setting up navigation tracking...');
593
481
  }
594
-
595
- // Delay to ensure navigation is initialized - Expo Router needs more time
596
- // We retry a few times with increasing delays
597
482
  let attempts = 0;
598
483
  const maxAttempts = 5;
599
484
  const trySetup = () => {
@@ -607,8 +492,7 @@ function setupNavigationTracking() {
607
492
  _utils.logger.debug('Expo Router setup: SUCCESS on attempt', attempts);
608
493
  }
609
494
  } else if (attempts < maxAttempts) {
610
- // Retry with exponential backoff
611
- const delay = 200 * attempts; // 200, 400, 600, 800ms
495
+ const delay = 200 * attempts;
612
496
  if (__DEV__) {
613
497
  _utils.logger.debug('Expo Router not ready, retrying in', delay, 'ms');
614
498
  }
@@ -620,8 +504,6 @@ function setupNavigationTracking() {
620
504
  }
621
505
  }
622
506
  };
623
-
624
- // Start first attempt after 200ms
625
507
  setTimeout(trySetup, 200);
626
508
  }
627
509
 
@@ -647,14 +529,10 @@ function trySetupExpoRouter() {
647
529
  normalizeScreenName,
648
530
  getScreenNameFromPath
649
531
  } = require('./navigation');
650
-
651
- // Poll for route changes (expo-router doesn't expose a listener API outside hooks)
652
532
  navigationPollingInterval = setInterval(() => {
653
533
  try {
654
534
  let state = null;
655
535
  let stateSource = '';
656
-
657
- // Method 1: Public accessors on router object
658
536
  if (typeof router.getState === 'function') {
659
537
  state = router.getState();
660
538
  stateSource = 'router.getState()';
@@ -662,34 +540,25 @@ function trySetupExpoRouter() {
662
540
  state = router.rootState;
663
541
  stateSource = 'router.rootState';
664
542
  }
665
-
666
- // Method 2: Internal store access (works for both v3 and v6+)
667
543
  if (!state) {
668
544
  try {
669
545
  const storeModule = require('expo-router/build/global-state/router-store');
670
546
  if (storeModule?.store) {
671
- // v6+: store.state or store.navigationRef
672
547
  state = storeModule.store.state;
673
548
  if (state) stateSource = 'store.state';
674
-
675
- // v6+: Try navigationRef if state is undefined
676
549
  if (!state && storeModule.store.navigationRef?.current) {
677
550
  state = storeModule.store.navigationRef.current.getRootState?.();
678
551
  if (state) stateSource = 'navigationRef.getRootState()';
679
552
  }
680
-
681
- // v3: store.rootState or store.initialState
682
553
  if (!state) {
683
554
  state = storeModule.store.rootState || storeModule.store.initialState;
684
555
  if (state) stateSource = 'store.rootState/initialState';
685
556
  }
686
557
  }
687
558
  } catch {
688
- // Store not available
559
+ // Ignore
689
560
  }
690
561
  }
691
-
692
- // Method 3: Try accessing via a different export path for v6
693
562
  if (!state) {
694
563
  try {
695
564
  const imperative = require('expo-router/build/imperative-api');
@@ -698,11 +567,11 @@ function trySetupExpoRouter() {
698
567
  if (state) stateSource = 'imperative-api';
699
568
  }
700
569
  } catch {
701
- // Imperative API not available
570
+ // Ignore
702
571
  }
703
572
  }
704
573
  if (state) {
705
- // Reset error count on success
574
+ navigationPollingErrors = 0;
706
575
  navigationPollingErrors = 0;
707
576
  const screenName = extractScreenNameFromRouterState(state, getScreenNameFromPath, normalizeScreenName);
708
577
  if (screenName && screenName !== lastDetectedScreen) {
@@ -713,21 +582,15 @@ function trySetupExpoRouter() {
713
582
  trackScreen(screenName);
714
583
  }
715
584
  } else {
716
- // Track consecutive failures to get state
717
585
  navigationPollingErrors++;
718
586
  if (__DEV__ && navigationPollingErrors === 1) {
719
587
  _utils.logger.debug('Expo Router: Could not get navigation state');
720
588
  }
721
589
  if (navigationPollingErrors >= MAX_POLLING_ERRORS) {
722
- // Stop polling after too many errors to save CPU
723
- if (__DEV__) {
724
- _utils.logger.debug('Expo Router: Stopped polling after', MAX_POLLING_ERRORS, 'errors');
725
- }
726
590
  cleanupNavigationTracking();
727
591
  }
728
592
  }
729
593
  } catch (e) {
730
- // Error - track and potentially stop
731
594
  navigationPollingErrors++;
732
595
  if (__DEV__ && navigationPollingErrors === 1) {
733
596
  _utils.logger.debug('Expo Router polling error:', e);
@@ -736,14 +599,12 @@ function trySetupExpoRouter() {
736
599
  cleanupNavigationTracking();
737
600
  }
738
601
  }
739
- }, 500); // 500ms polling (reduced from 300ms for lower CPU usage)
740
-
602
+ }, 500);
741
603
  return true;
742
604
  } catch (e) {
743
605
  if (__DEV__) {
744
606
  _utils.logger.debug('Expo Router not available:', e);
745
607
  }
746
- // expo-router not installed
747
608
  return false;
748
609
  }
749
610
  }
@@ -758,22 +619,12 @@ function extractScreenNameFromRouterState(state, getScreenNameFromPath, normaliz
758
619
  if (!state?.routes) return null;
759
620
  const route = state.routes[state.index ?? state.routes.length - 1];
760
621
  if (!route) return null;
761
-
762
- // Add current route name to accumulated segments
763
622
  const newSegments = [...accumulatedSegments, route.name];
764
-
765
- // If this route has nested state, recurse deeper
766
623
  if (route.state) {
767
624
  return extractScreenNameFromRouterState(route.state, getScreenNameFromPath, normalizeScreenName, newSegments);
768
625
  }
769
-
770
- // We've reached the deepest level - build the screen name
771
- // Filter out group markers like (tabs), (main), (auth)
772
626
  const cleanSegments = newSegments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
773
-
774
- // If after filtering we have no segments, use the last meaningful name
775
627
  if (cleanSegments.length === 0) {
776
- // Find the last non-group segment
777
628
  for (let i = newSegments.length - 1; i >= 0; i--) {
778
629
  const seg = newSegments[i];
779
630
  if (seg && !seg.startsWith('(') && !seg.endsWith(')')) {
@@ -812,27 +663,17 @@ function trackScreen(screenName) {
812
663
  }
813
664
  const previousScreen = currentScreen;
814
665
  currentScreen = screenName;
815
- // Add to screens visited (only track for unique set, avoid large array copies)
816
666
  screensVisited.push(screenName);
817
-
818
- // Update unique screens count
819
667
  const uniqueScreens = new Set(screensVisited);
820
668
  metrics.uniqueScreensCount = uniqueScreens.size;
821
-
822
- // Update navigation count
823
669
  metrics.navigationCount++;
824
670
  metrics.totalEvents++;
825
671
  if (__DEV__) {
826
672
  _utils.logger.debug('trackScreen:', screenName, '(total screens:', metrics.uniqueScreensCount, ')');
827
673
  }
828
-
829
- // Notify callback
830
674
  if (onScreenChange) {
831
675
  onScreenChange(screenName, previousScreen);
832
676
  }
833
-
834
- // IMPORTANT: Also notify native module to send to backend
835
- // This is the key fix - without this, screens don't get recorded!
836
677
  try {
837
678
  const RejourneyNative = getRejourneyNativeModule();
838
679
  if (RejourneyNative?.screenChanged) {
@@ -854,18 +695,12 @@ function trackScreen(screenName) {
854
695
  }
855
696
  }
856
697
 
857
- // =============================================================================
858
- // API Metrics Tracking
859
- // =============================================================================
860
-
861
698
  /**
862
699
  * Track an API request with timing data
863
700
  */
864
701
  function trackAPIRequest(success, _statusCode, durationMs = 0, responseBytes = 0) {
865
702
  if (!isInitialized) return;
866
703
  metrics.apiTotalCount++;
867
-
868
- // Accumulate timing and size for avg calculation
869
704
  if (durationMs > 0) {
870
705
  metrics.netTotalDurationMs += durationMs;
871
706
  }
@@ -876,16 +711,10 @@ function trackAPIRequest(success, _statusCode, durationMs = 0, responseBytes = 0
876
711
  metrics.apiSuccessCount++;
877
712
  } else {
878
713
  metrics.apiErrorCount++;
879
-
880
- // API errors also count toward error count for UX score
881
714
  metrics.errorCount++;
882
715
  }
883
716
  }
884
717
 
885
- // =============================================================================
886
- // Session Metrics
887
- // =============================================================================
888
-
889
718
  /**
890
719
  * Create empty metrics object
891
720
  */
@@ -943,18 +772,11 @@ function trackInput() {
943
772
  * Get current session metrics
944
773
  */
945
774
  function getSessionMetrics() {
946
- // Calculate scores before returning
947
775
  calculateScores();
948
-
949
- // Compute average API response time
950
776
  const netAvgDurationMs = metrics.apiTotalCount > 0 ? Math.round(metrics.netTotalDurationMs / metrics.apiTotalCount) : 0;
951
-
952
- // Lazily populate screensVisited only when metrics are retrieved
953
- // This avoids expensive array copies on every screen change
954
777
  return {
955
778
  ...metrics,
956
779
  screensVisited: [...screensVisited],
957
- // Only copy here when needed
958
780
  netAvgDurationMs
959
781
  };
960
782
  }
@@ -963,34 +785,15 @@ function getSessionMetrics() {
963
785
  * Calculate session scores
964
786
  */
965
787
  function calculateScores() {
966
- // Interaction Score (0-100)
967
- // Based on total interactions normalized to a baseline
968
788
  const totalInteractions = metrics.touchCount + metrics.scrollCount + metrics.gestureCount + metrics.inputCount;
969
-
970
- // Assume 50 interactions is "average" for a session
971
789
  const avgInteractions = 50;
972
790
  metrics.interactionScore = Math.min(100, Math.round(totalInteractions / avgInteractions * 100));
973
-
974
- // Exploration Score (0-100)
975
- // Based on unique screens visited
976
- // Assume 5 screens is "average" exploration
977
791
  const avgScreens = 5;
978
792
  metrics.explorationScore = Math.min(100, Math.round(metrics.uniqueScreensCount / avgScreens * 100));
979
-
980
- // UX Score (0-100)
981
- // Starts at 100, deducts for issues
982
793
  let uxScore = 100;
983
-
984
- // Deduct for errors
985
- uxScore -= Math.min(30, metrics.errorCount * 15); // Max 30 point deduction
986
-
987
- // Deduct for rage taps
988
- uxScore -= Math.min(24, metrics.rageTapCount * 8); // Max 24 point deduction
989
-
990
- // Deduct for API errors
991
- uxScore -= Math.min(20, metrics.apiErrorCount * 10); // Max 20 point deduction
992
-
993
- // Bonus for completing funnel (if screens > 3)
794
+ uxScore -= Math.min(30, metrics.errorCount * 15);
795
+ uxScore -= Math.min(24, metrics.rageTapCount * 8);
796
+ uxScore -= Math.min(20, metrics.apiErrorCount * 10);
994
797
  if (metrics.uniqueScreensCount >= 3) {
995
798
  uxScore += 5;
996
799
  }
@@ -1008,43 +811,29 @@ function resetMetrics() {
1008
811
  tapCount = 0;
1009
812
  sessionStartTime = Date.now();
1010
813
  }
1011
-
1012
- // =============================================================================
1013
- // Session duration helpers
1014
- // =============================================================================
1015
-
1016
- /** Clamp and set max session duration in minutes (1–10). Defaults to 10. */
1017
814
  function setMaxSessionDurationMinutes(minutes) {
1018
815
  const clampedMinutes = Math.min(10, Math.max(1, minutes ?? 10));
1019
816
  maxSessionDurationMs = clampedMinutes * 60 * 1000;
1020
817
  }
1021
-
1022
- /** Returns true if the current session exceeded the configured max duration. */
1023
818
  function hasExceededMaxSessionDuration() {
1024
819
  if (!sessionStartTime) return false;
1025
820
  return Date.now() - sessionStartTime >= maxSessionDurationMs;
1026
821
  }
1027
-
1028
- /** Returns remaining milliseconds until the session should stop. */
1029
822
  function getRemainingSessionDurationMs() {
1030
823
  if (!sessionStartTime) return maxSessionDurationMs;
1031
824
  const remaining = maxSessionDurationMs - (Date.now() - sessionStartTime);
1032
825
  return Math.max(0, remaining);
1033
826
  }
1034
827
 
1035
- // =============================================================================
1036
- // Device Info Collection
1037
- // =============================================================================
1038
-
1039
828
  /**
1040
829
  * Collect device information
1041
830
  */
1042
- function collectDeviceInfo() {
831
+ /**
832
+ * Collect device information
833
+ */
834
+ async function collectDeviceInfo() {
1043
835
  const Dimensions = getDimensions();
1044
836
  const Platform = getPlatform();
1045
- const NativeModules = getNativeModules();
1046
-
1047
- // Default values if react-native isn't available
1048
837
  let width = 0,
1049
838
  height = 0,
1050
839
  scale = 1;
@@ -1056,100 +845,43 @@ function collectDeviceInfo() {
1056
845
  scale = screenDims?.scale || 1;
1057
846
  }
1058
847
 
1059
- // Get device model - this varies by platform
1060
- let model = 'Unknown';
1061
- let manufacturer;
1062
- let osVersion = 'Unknown';
1063
- let appVersion;
1064
- let appId;
848
+ // Basic JS-side info
1065
849
  let locale;
1066
850
  let timezone;
1067
851
  try {
1068
- // Try to get from react-native-device-info if available
1069
- // This is optional - falls back to basic info if not installed
1070
- const DeviceInfo = require('react-native-device-info');
1071
- model = DeviceInfo.getModel?.() || model;
1072
- manufacturer = DeviceInfo.getBrand?.() || undefined;
1073
- osVersion = DeviceInfo.getSystemVersion?.() || osVersion;
1074
- appVersion = DeviceInfo.getVersion?.() || undefined;
1075
- appId = DeviceInfo.getBundleId?.() || undefined;
1076
- locale = DeviceInfo.getDeviceLocale?.() || undefined;
1077
- timezone = DeviceInfo.getTimezone?.() || undefined;
852
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
853
+ locale = Intl.DateTimeFormat().resolvedOptions().locale;
1078
854
  } catch {
1079
- // react-native-device-info not installed, try Expo packages
1080
- try {
1081
- // Try expo-application for app version/id
1082
- const Application = require('expo-application');
1083
- appVersion = Application.nativeApplicationVersion || Application.applicationVersion || undefined;
1084
- appId = Application.applicationId || undefined;
1085
- } catch {
1086
- // expo-application not available
1087
- }
1088
- try {
1089
- // Try expo-constants for additional info
1090
- const Constants = require('expo-constants');
1091
- const expoConfig = Constants.expoConfig || Constants.manifest2?.extra?.expoClient || Constants.manifest;
1092
- if (!appVersion && expoConfig?.version) {
1093
- appVersion = expoConfig.version;
1094
- }
1095
- if (!appId && (expoConfig?.ios?.bundleIdentifier || expoConfig?.android?.package)) {
1096
- appId = Platform?.OS === 'ios' ? expoConfig?.ios?.bundleIdentifier : expoConfig?.android?.package;
1097
- }
1098
- } catch {
1099
- // expo-constants not available
1100
- }
1101
-
1102
- // Fall back to basic platform info
1103
- if (Platform?.OS === 'ios') {
1104
- // Get basic info from constants
1105
- const PlatformConstants = NativeModules?.PlatformConstants;
1106
- osVersion = Platform.Version?.toString() || osVersion;
1107
- model = PlatformConstants?.interfaceIdiom === 'pad' ? 'iPad' : 'iPhone';
1108
- } else if (Platform?.OS === 'android') {
1109
- osVersion = Platform.Version?.toString() || osVersion;
1110
- model = 'Android Device';
1111
- }
855
+ // Ignore
1112
856
  }
1113
857
 
1114
- // Get timezone
1115
- if (!timezone) {
1116
- try {
1117
- timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1118
- } catch {
1119
- timezone = undefined;
1120
- }
1121
- }
1122
-
1123
- // Get locale
1124
- if (!locale) {
858
+ // Get native info
859
+ const nativeModule = getRejourneyNativeModule();
860
+ let nativeInfo = {};
861
+ if (nativeModule && nativeModule.getDeviceInfo) {
1125
862
  try {
1126
- locale = Intl.DateTimeFormat().resolvedOptions().locale;
1127
- } catch {
1128
- locale = undefined;
863
+ nativeInfo = await nativeModule.getDeviceInfo();
864
+ } catch (e) {
865
+ if (__DEV__) {
866
+ console.warn('[Rejourney] Failed to get native device info:', e);
867
+ }
1129
868
  }
1130
869
  }
1131
870
  return {
1132
- model,
1133
- manufacturer,
871
+ model: nativeInfo.model || 'Unknown',
872
+ manufacturer: nativeInfo.brand,
1134
873
  os: Platform?.OS || 'ios',
1135
- osVersion,
874
+ osVersion: nativeInfo.systemVersion || Platform?.Version?.toString() || 'Unknown',
1136
875
  screenWidth: Math.round(width),
1137
876
  screenHeight: Math.round(height),
1138
877
  pixelRatio: scale,
1139
- appVersion,
1140
- appId,
1141
- locale,
1142
- timezone
878
+ appVersion: nativeInfo.appVersion,
879
+ appId: nativeInfo.bundleId,
880
+ locale: locale,
881
+ timezone: timezone
1143
882
  };
1144
883
  }
1145
884
 
1146
- // =============================================================================
1147
- // Anonymous ID Generation
1148
- // =============================================================================
1149
-
1150
- // Storage key for anonymous ID
1151
- // const ANONYMOUS_ID_KEY = '@rejourney_anonymous_id';
1152
-
1153
885
  /**
1154
886
  * Generate a persistent anonymous ID
1155
887
  */
@@ -1177,7 +909,6 @@ async function ensurePersistentAnonymousId() {
1177
909
  if (anonymousId) return anonymousId;
1178
910
  if (!anonymousIdPromise) {
1179
911
  anonymousIdPromise = (async () => {
1180
- // Just use the load logic which now delegates to native or memory
1181
912
  const id = await loadAnonymousId();
1182
913
  anonymousId = id;
1183
914
  return id;
@@ -1207,12 +938,7 @@ async function loadAnonymousId() {
1207
938
  */
1208
939
  function setAnonymousId(id) {
1209
940
  anonymousId = id;
1210
- // No-op for async persistence as we moved to native-only or memory-only
1211
941
  }
1212
-
1213
- // =============================================================================
1214
- // Exports
1215
- // =============================================================================
1216
942
  var _default = exports.default = {
1217
943
  init: initAutoTracking,
1218
944
  cleanup: cleanupAutoTracking,