@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.
Files changed (43) hide show
  1. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +35 -29
  2. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +7 -0
  3. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
  4. package/ios/Capture/RJCaptureEngine.m +3 -34
  5. package/ios/Capture/RJVideoEncoder.m +0 -26
  6. package/ios/Core/Rejourney.mm +32 -95
  7. package/ios/Utils/RJPerfTiming.m +0 -5
  8. package/ios/Utils/RJWindowUtils.m +0 -1
  9. package/lib/commonjs/components/Mask.js +1 -6
  10. package/lib/commonjs/index.js +4 -87
  11. package/lib/commonjs/sdk/autoTracking.js +39 -310
  12. package/lib/commonjs/sdk/constants.js +2 -13
  13. package/lib/commonjs/sdk/errorTracking.js +1 -29
  14. package/lib/commonjs/sdk/metricsTracking.js +3 -24
  15. package/lib/commonjs/sdk/navigation.js +3 -42
  16. package/lib/commonjs/sdk/networkInterceptor.js +7 -49
  17. package/lib/commonjs/sdk/utils.js +0 -5
  18. package/lib/module/components/Mask.js +1 -6
  19. package/lib/module/index.js +3 -91
  20. package/lib/module/sdk/autoTracking.js +39 -311
  21. package/lib/module/sdk/constants.js +2 -13
  22. package/lib/module/sdk/errorTracking.js +1 -29
  23. package/lib/module/sdk/index.js +0 -2
  24. package/lib/module/sdk/metricsTracking.js +3 -24
  25. package/lib/module/sdk/navigation.js +3 -42
  26. package/lib/module/sdk/networkInterceptor.js +7 -49
  27. package/lib/module/sdk/utils.js +0 -5
  28. package/lib/typescript/NativeRejourney.d.ts +1 -0
  29. package/lib/typescript/sdk/autoTracking.d.ts +4 -4
  30. package/lib/typescript/types/index.d.ts +0 -1
  31. package/package.json +2 -8
  32. package/src/NativeRejourney.ts +2 -0
  33. package/src/components/Mask.tsx +0 -3
  34. package/src/index.ts +3 -73
  35. package/src/sdk/autoTracking.ts +51 -282
  36. package/src/sdk/constants.ts +13 -13
  37. package/src/sdk/errorTracking.ts +1 -17
  38. package/src/sdk/index.ts +0 -2
  39. package/src/sdk/metricsTracking.ts +5 -33
  40. package/src/sdk/navigation.ts +8 -29
  41. package/src/sdk/networkInterceptor.ts +9 -33
  42. package/src/sdk/utils.ts +0 -5
  43. 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,
@@ -607,8 +495,7 @@ function setupNavigationTracking() {
607
495
  _utils.logger.debug('Expo Router setup: SUCCESS on attempt', attempts);
608
496
  }
609
497
  } else if (attempts < maxAttempts) {
610
- // Retry with exponential backoff
611
- const delay = 200 * attempts; // 200, 400, 600, 800ms
498
+ const delay = 200 * attempts;
612
499
  if (__DEV__) {
613
500
  _utils.logger.debug('Expo Router not ready, retrying in', delay, 'ms');
614
501
  }
@@ -620,8 +507,6 @@ function setupNavigationTracking() {
620
507
  }
621
508
  }
622
509
  };
623
-
624
- // Start first attempt after 200ms
625
510
  setTimeout(trySetup, 200);
626
511
  }
627
512
 
@@ -647,14 +532,10 @@ function trySetupExpoRouter() {
647
532
  normalizeScreenName,
648
533
  getScreenNameFromPath
649
534
  } = require('./navigation');
650
-
651
- // Poll for route changes (expo-router doesn't expose a listener API outside hooks)
652
535
  navigationPollingInterval = setInterval(() => {
653
536
  try {
654
537
  let state = null;
655
538
  let stateSource = '';
656
-
657
- // Method 1: Public accessors on router object
658
539
  if (typeof router.getState === 'function') {
659
540
  state = router.getState();
660
541
  stateSource = 'router.getState()';
@@ -662,34 +543,25 @@ function trySetupExpoRouter() {
662
543
  state = router.rootState;
663
544
  stateSource = 'router.rootState';
664
545
  }
665
-
666
- // Method 2: Internal store access (works for both v3 and v6+)
667
546
  if (!state) {
668
547
  try {
669
548
  const storeModule = require('expo-router/build/global-state/router-store');
670
549
  if (storeModule?.store) {
671
- // v6+: store.state or store.navigationRef
672
550
  state = storeModule.store.state;
673
551
  if (state) stateSource = 'store.state';
674
-
675
- // v6+: Try navigationRef if state is undefined
676
552
  if (!state && storeModule.store.navigationRef?.current) {
677
553
  state = storeModule.store.navigationRef.current.getRootState?.();
678
554
  if (state) stateSource = 'navigationRef.getRootState()';
679
555
  }
680
-
681
- // v3: store.rootState or store.initialState
682
556
  if (!state) {
683
557
  state = storeModule.store.rootState || storeModule.store.initialState;
684
558
  if (state) stateSource = 'store.rootState/initialState';
685
559
  }
686
560
  }
687
561
  } catch {
688
- // Store not available
562
+ // Ignore
689
563
  }
690
564
  }
691
-
692
- // Method 3: Try accessing via a different export path for v6
693
565
  if (!state) {
694
566
  try {
695
567
  const imperative = require('expo-router/build/imperative-api');
@@ -698,11 +570,11 @@ function trySetupExpoRouter() {
698
570
  if (state) stateSource = 'imperative-api';
699
571
  }
700
572
  } catch {
701
- // Imperative API not available
573
+ // Ignore
702
574
  }
703
575
  }
704
576
  if (state) {
705
- // Reset error count on success
577
+ navigationPollingErrors = 0;
706
578
  navigationPollingErrors = 0;
707
579
  const screenName = extractScreenNameFromRouterState(state, getScreenNameFromPath, normalizeScreenName);
708
580
  if (screenName && screenName !== lastDetectedScreen) {
@@ -713,21 +585,15 @@ function trySetupExpoRouter() {
713
585
  trackScreen(screenName);
714
586
  }
715
587
  } else {
716
- // Track consecutive failures to get state
717
588
  navigationPollingErrors++;
718
589
  if (__DEV__ && navigationPollingErrors === 1) {
719
590
  _utils.logger.debug('Expo Router: Could not get navigation state');
720
591
  }
721
592
  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
593
  cleanupNavigationTracking();
727
594
  }
728
595
  }
729
596
  } catch (e) {
730
- // Error - track and potentially stop
731
597
  navigationPollingErrors++;
732
598
  if (__DEV__ && navigationPollingErrors === 1) {
733
599
  _utils.logger.debug('Expo Router polling error:', e);
@@ -736,14 +602,12 @@ function trySetupExpoRouter() {
736
602
  cleanupNavigationTracking();
737
603
  }
738
604
  }
739
- }, 500); // 500ms polling (reduced from 300ms for lower CPU usage)
740
-
605
+ }, 500);
741
606
  return true;
742
607
  } catch (e) {
743
608
  if (__DEV__) {
744
609
  _utils.logger.debug('Expo Router not available:', e);
745
610
  }
746
- // expo-router not installed
747
611
  return false;
748
612
  }
749
613
  }
@@ -758,22 +622,12 @@ function extractScreenNameFromRouterState(state, getScreenNameFromPath, normaliz
758
622
  if (!state?.routes) return null;
759
623
  const route = state.routes[state.index ?? state.routes.length - 1];
760
624
  if (!route) return null;
761
-
762
- // Add current route name to accumulated segments
763
625
  const newSegments = [...accumulatedSegments, route.name];
764
-
765
- // If this route has nested state, recurse deeper
766
626
  if (route.state) {
767
627
  return extractScreenNameFromRouterState(route.state, getScreenNameFromPath, normalizeScreenName, newSegments);
768
628
  }
769
-
770
- // We've reached the deepest level - build the screen name
771
- // Filter out group markers like (tabs), (main), (auth)
772
629
  const cleanSegments = newSegments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
773
-
774
- // If after filtering we have no segments, use the last meaningful name
775
630
  if (cleanSegments.length === 0) {
776
- // Find the last non-group segment
777
631
  for (let i = newSegments.length - 1; i >= 0; i--) {
778
632
  const seg = newSegments[i];
779
633
  if (seg && !seg.startsWith('(') && !seg.endsWith(')')) {
@@ -812,27 +666,17 @@ function trackScreen(screenName) {
812
666
  }
813
667
  const previousScreen = currentScreen;
814
668
  currentScreen = screenName;
815
- // Add to screens visited (only track for unique set, avoid large array copies)
816
669
  screensVisited.push(screenName);
817
-
818
- // Update unique screens count
819
670
  const uniqueScreens = new Set(screensVisited);
820
671
  metrics.uniqueScreensCount = uniqueScreens.size;
821
-
822
- // Update navigation count
823
672
  metrics.navigationCount++;
824
673
  metrics.totalEvents++;
825
674
  if (__DEV__) {
826
675
  _utils.logger.debug('trackScreen:', screenName, '(total screens:', metrics.uniqueScreensCount, ')');
827
676
  }
828
-
829
- // Notify callback
830
677
  if (onScreenChange) {
831
678
  onScreenChange(screenName, previousScreen);
832
679
  }
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
680
  try {
837
681
  const RejourneyNative = getRejourneyNativeModule();
838
682
  if (RejourneyNative?.screenChanged) {
@@ -854,18 +698,12 @@ function trackScreen(screenName) {
854
698
  }
855
699
  }
856
700
 
857
- // =============================================================================
858
- // API Metrics Tracking
859
- // =============================================================================
860
-
861
701
  /**
862
702
  * Track an API request with timing data
863
703
  */
864
704
  function trackAPIRequest(success, _statusCode, durationMs = 0, responseBytes = 0) {
865
705
  if (!isInitialized) return;
866
706
  metrics.apiTotalCount++;
867
-
868
- // Accumulate timing and size for avg calculation
869
707
  if (durationMs > 0) {
870
708
  metrics.netTotalDurationMs += durationMs;
871
709
  }
@@ -876,16 +714,10 @@ function trackAPIRequest(success, _statusCode, durationMs = 0, responseBytes = 0
876
714
  metrics.apiSuccessCount++;
877
715
  } else {
878
716
  metrics.apiErrorCount++;
879
-
880
- // API errors also count toward error count for UX score
881
717
  metrics.errorCount++;
882
718
  }
883
719
  }
884
720
 
885
- // =============================================================================
886
- // Session Metrics
887
- // =============================================================================
888
-
889
721
  /**
890
722
  * Create empty metrics object
891
723
  */
@@ -943,18 +775,11 @@ function trackInput() {
943
775
  * Get current session metrics
944
776
  */
945
777
  function getSessionMetrics() {
946
- // Calculate scores before returning
947
778
  calculateScores();
948
-
949
- // Compute average API response time
950
779
  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
780
  return {
955
781
  ...metrics,
956
782
  screensVisited: [...screensVisited],
957
- // Only copy here when needed
958
783
  netAvgDurationMs
959
784
  };
960
785
  }
@@ -963,34 +788,15 @@ function getSessionMetrics() {
963
788
  * Calculate session scores
964
789
  */
965
790
  function calculateScores() {
966
- // Interaction Score (0-100)
967
- // Based on total interactions normalized to a baseline
968
791
  const totalInteractions = metrics.touchCount + metrics.scrollCount + metrics.gestureCount + metrics.inputCount;
969
-
970
- // Assume 50 interactions is "average" for a session
971
792
  const avgInteractions = 50;
972
793
  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
794
  const avgScreens = 5;
978
795
  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
796
  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)
797
+ uxScore -= Math.min(30, metrics.errorCount * 15);
798
+ uxScore -= Math.min(24, metrics.rageTapCount * 8);
799
+ uxScore -= Math.min(20, metrics.apiErrorCount * 10);
994
800
  if (metrics.uniqueScreensCount >= 3) {
995
801
  uxScore += 5;
996
802
  }
@@ -1008,43 +814,29 @@ function resetMetrics() {
1008
814
  tapCount = 0;
1009
815
  sessionStartTime = Date.now();
1010
816
  }
1011
-
1012
- // =============================================================================
1013
- // Session duration helpers
1014
- // =============================================================================
1015
-
1016
- /** Clamp and set max session duration in minutes (1–10). Defaults to 10. */
1017
817
  function setMaxSessionDurationMinutes(minutes) {
1018
818
  const clampedMinutes = Math.min(10, Math.max(1, minutes ?? 10));
1019
819
  maxSessionDurationMs = clampedMinutes * 60 * 1000;
1020
820
  }
1021
-
1022
- /** Returns true if the current session exceeded the configured max duration. */
1023
821
  function hasExceededMaxSessionDuration() {
1024
822
  if (!sessionStartTime) return false;
1025
823
  return Date.now() - sessionStartTime >= maxSessionDurationMs;
1026
824
  }
1027
-
1028
- /** Returns remaining milliseconds until the session should stop. */
1029
825
  function getRemainingSessionDurationMs() {
1030
826
  if (!sessionStartTime) return maxSessionDurationMs;
1031
827
  const remaining = maxSessionDurationMs - (Date.now() - sessionStartTime);
1032
828
  return Math.max(0, remaining);
1033
829
  }
1034
830
 
1035
- // =============================================================================
1036
- // Device Info Collection
1037
- // =============================================================================
1038
-
1039
831
  /**
1040
832
  * Collect device information
1041
833
  */
1042
- function collectDeviceInfo() {
834
+ /**
835
+ * Collect device information
836
+ */
837
+ async function collectDeviceInfo() {
1043
838
  const Dimensions = getDimensions();
1044
839
  const Platform = getPlatform();
1045
- const NativeModules = getNativeModules();
1046
-
1047
- // Default values if react-native isn't available
1048
840
  let width = 0,
1049
841
  height = 0,
1050
842
  scale = 1;
@@ -1056,100 +848,43 @@ function collectDeviceInfo() {
1056
848
  scale = screenDims?.scale || 1;
1057
849
  }
1058
850
 
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;
851
+ // Basic JS-side info
1065
852
  let locale;
1066
853
  let timezone;
1067
854
  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;
855
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
856
+ locale = Intl.DateTimeFormat().resolvedOptions().locale;
1078
857
  } 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
- }
858
+ // Ignore
1112
859
  }
1113
860
 
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) {
861
+ // Get native info
862
+ const nativeModule = getRejourneyNativeModule();
863
+ let nativeInfo = {};
864
+ if (nativeModule && nativeModule.getDeviceInfo) {
1125
865
  try {
1126
- locale = Intl.DateTimeFormat().resolvedOptions().locale;
1127
- } catch {
1128
- locale = undefined;
866
+ nativeInfo = await nativeModule.getDeviceInfo();
867
+ } catch (e) {
868
+ if (__DEV__) {
869
+ console.warn('[Rejourney] Failed to get native device info:', e);
870
+ }
1129
871
  }
1130
872
  }
1131
873
  return {
1132
- model,
1133
- manufacturer,
874
+ model: nativeInfo.model || 'Unknown',
875
+ manufacturer: nativeInfo.brand,
1134
876
  os: Platform?.OS || 'ios',
1135
- osVersion,
877
+ osVersion: nativeInfo.systemVersion || Platform?.Version?.toString() || 'Unknown',
1136
878
  screenWidth: Math.round(width),
1137
879
  screenHeight: Math.round(height),
1138
880
  pixelRatio: scale,
1139
- appVersion,
1140
- appId,
1141
- locale,
1142
- timezone
881
+ appVersion: nativeInfo.appVersion,
882
+ appId: nativeInfo.bundleId,
883
+ locale: locale,
884
+ timezone: timezone
1143
885
  };
1144
886
  }
1145
887
 
1146
- // =============================================================================
1147
- // Anonymous ID Generation
1148
- // =============================================================================
1149
-
1150
- // Storage key for anonymous ID
1151
- // const ANONYMOUS_ID_KEY = '@rejourney_anonymous_id';
1152
-
1153
888
  /**
1154
889
  * Generate a persistent anonymous ID
1155
890
  */
@@ -1177,7 +912,6 @@ async function ensurePersistentAnonymousId() {
1177
912
  if (anonymousId) return anonymousId;
1178
913
  if (!anonymousIdPromise) {
1179
914
  anonymousIdPromise = (async () => {
1180
- // Just use the load logic which now delegates to native or memory
1181
915
  const id = await loadAnonymousId();
1182
916
  anonymousId = id;
1183
917
  return id;
@@ -1207,12 +941,7 @@ async function loadAnonymousId() {
1207
941
  */
1208
942
  function setAnonymousId(id) {
1209
943
  anonymousId = id;
1210
- // No-op for async persistence as we moved to native-only or memory-only
1211
944
  }
1212
-
1213
- // =============================================================================
1214
- // Exports
1215
- // =============================================================================
1216
945
  var _default = exports.default = {
1217
946
  init: initAutoTracking,
1218
947
  cleanup: cleanupAutoTracking,