@rejourneyco/react-native 1.0.0 → 1.0.1

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.
@@ -20,7 +20,7 @@ static const NSTimeInterval kRJQuietScrollSeconds = 0.2;
20
20
  static const NSTimeInterval kRJQuietBounceSeconds = 0.2;
21
21
  static const NSTimeInterval kRJQuietRefreshSeconds = 0.22;
22
22
  static const NSTimeInterval kRJQuietMapSeconds = 0.55;
23
- static const NSTimeInterval kRJQuietTransitionSeconds = 0.2;
23
+ static const NSTimeInterval kRJQuietTransitionSeconds = 0.1;
24
24
  static const NSTimeInterval kRJQuietKeyboardSeconds = 0.25;
25
25
  static const NSTimeInterval kRJQuietAnimationSeconds = 0.25;
26
26
 
@@ -204,7 +204,8 @@ typedef struct {
204
204
 
205
205
  - (void)recordTouchEventAtTime:(NSTimeInterval)time {
206
206
  self.lastTouchTime = time;
207
- [self scheduleBonusCaptureAfterDelay:kRJBonusInteractionDelaySeconds now:time];
207
+ [self scheduleBonusCaptureAfterDelay:kRJBonusInteractionDelaySeconds
208
+ now:time];
208
209
  }
209
210
 
210
211
  - (void)recordInteractionEventAtTime:(NSTimeInterval)time {
@@ -221,12 +222,11 @@ typedef struct {
221
222
 
222
223
  - (void)recordNavigationEventAtTime:(NSTimeInterval)time {
223
224
  self.lastTransitionTime = time;
224
- [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds
225
- now:time];
225
+ [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds now:time];
226
226
  }
227
227
 
228
228
  - (void)recordRenderedSignature:(nullable NSString *)signature
229
- atTime:(NSTimeInterval)time {
229
+ atTime:(NSTimeInterval)time {
230
230
  self.lastRenderedSignature = signature.length > 0 ? signature : nil;
231
231
  self.lastRenderedTime = time;
232
232
  if (self.pendingKeyframes > 0) {
@@ -262,8 +262,8 @@ typedef struct {
262
262
  }
263
263
  self.lastObservedSignatureTime = now;
264
264
  self.lastSignatureChurnTime = now;
265
- self.lastObservedSignature = currentSignature.length > 0 ? currentSignature
266
- : nil;
265
+ self.lastObservedSignature =
266
+ currentSignature.length > 0 ? currentSignature : nil;
267
267
  } else if (self.lastSignatureChurnTime > 0 &&
268
268
  (now - self.lastSignatureChurnTime) >
269
269
  kRJSignatureChurnWindowSeconds) {
@@ -296,15 +296,15 @@ typedef struct {
296
296
  }
297
297
 
298
298
  [self updateScrollActiveState:scanResult.scrollActive
299
- refreshActive:scanResult.refreshActive
300
- mapActive:scanResult.mapActive
301
- now:now];
299
+ refreshActive:scanResult.refreshActive
300
+ mapActive:scanResult.mapActive
301
+ now:now];
302
302
 
303
303
  BOOL blockingAnimation = NO;
304
304
  if (scanResult.hasAnyAnimations) {
305
305
  self.lastAnimationAreaRatio = scanResult.animationAreaRatio;
306
- blockingAnimation = (scanResult.animationAreaRatio >=
307
- kRJAnimationSmallAreaAllowed);
306
+ blockingAnimation =
307
+ (scanResult.animationAreaRatio >= kRJAnimationSmallAreaAllowed);
308
308
  } else {
309
309
  self.lastAnimationAreaRatio = 0.0;
310
310
  }
@@ -335,88 +335,113 @@ typedef struct {
335
335
  [self probeAnimatedViewsAtTime:now];
336
336
  }
337
337
 
338
- - (RJCaptureHeuristicsDecision *)decisionForSignature:
339
- (nullable NSString *)signature
340
- now:(NSTimeInterval)now
341
- hasLastFrame:(BOOL)hasLastFrame {
338
+ - (RJCaptureHeuristicsDecision *)
339
+ decisionForSignature:(nullable NSString *)signature
340
+ now:(NSTimeInterval)now
341
+ hasLastFrame:(BOOL)hasLastFrame
342
+ importance:(RJCaptureImportance)importance {
342
343
  RJCaptureHeuristicsDecision *decision =
343
344
  [[RJCaptureHeuristicsDecision alloc] init];
344
345
 
345
346
  NSTimeInterval earliestSafeTime = now;
346
347
  RJCaptureHeuristicsReason blockerReason = RJCaptureHeuristicsReasonRenderNow;
347
348
 
349
+ // Check importance to potentially bypass heuristics
350
+ BOOL isUrgent = (importance == RJCaptureImportanceHigh ||
351
+ importance == RJCaptureImportanceCritical);
352
+
348
353
  // Active touch/gesture input should be avoided to keep input latency smooth.
349
- [self considerBlockerSince:self.lastTouchTime
350
- quietInterval:kRJQuietTouchSeconds
351
- now:now
352
- reason:RJCaptureHeuristicsReasonDeferTouch
353
- earliestTime:&earliestSafeTime
354
- chosenReason:&blockerReason];
354
+ // CRITICAL updates bypass this.
355
+ if (!isUrgent) {
356
+ [self considerBlockerSince:self.lastTouchTime
357
+ quietInterval:kRJQuietTouchSeconds
358
+ now:now
359
+ reason:RJCaptureHeuristicsReasonDeferTouch
360
+ earliestTime:&earliestSafeTime
361
+ chosenReason:&blockerReason];
362
+ }
355
363
 
356
364
  // Scroll motion (dragging or deceleration) is a high-jank period.
357
- [self considerBlockerSince:self.lastScrollTime
358
- quietInterval:kRJQuietScrollSeconds
359
- now:now
360
- reason:RJCaptureHeuristicsReasonDeferScroll
361
- earliestTime:&earliestSafeTime
362
- chosenReason:&blockerReason];
365
+ // Even urgent captures should respect scroll to avoid visible hitching,
366
+ // unless CRITICAL
367
+ if (importance != RJCaptureImportanceCritical) {
368
+ [self considerBlockerSince:self.lastScrollTime
369
+ quietInterval:kRJQuietScrollSeconds
370
+ now:now
371
+ reason:RJCaptureHeuristicsReasonDeferScroll
372
+ earliestTime:&earliestSafeTime
373
+ chosenReason:&blockerReason];
374
+ }
363
375
 
364
376
  // Rubber-band bounce and inset animations are visually sensitive.
365
- [self considerBlockerSince:self.lastBounceTime
366
- quietInterval:kRJQuietBounceSeconds
367
- now:now
368
- reason:RJCaptureHeuristicsReasonDeferBounce
369
- earliestTime:&earliestSafeTime
370
- chosenReason:&blockerReason];
377
+ if (!isUrgent) {
378
+ [self considerBlockerSince:self.lastBounceTime
379
+ quietInterval:kRJQuietBounceSeconds
380
+ now:now
381
+ reason:RJCaptureHeuristicsReasonDeferBounce
382
+ earliestTime:&earliestSafeTime
383
+ chosenReason:&blockerReason];
384
+ }
371
385
 
372
386
  // Pull-to-refresh animations should finish before rendering.
373
- [self considerBlockerSince:self.lastRefreshTime
374
- quietInterval:kRJQuietRefreshSeconds
375
- now:now
376
- reason:RJCaptureHeuristicsReasonDeferRefresh
377
- earliestTime:&earliestSafeTime
378
- chosenReason:&blockerReason];
387
+ if (!isUrgent) {
388
+ [self considerBlockerSince:self.lastRefreshTime
389
+ quietInterval:kRJQuietRefreshSeconds
390
+ now:now
391
+ reason:RJCaptureHeuristicsReasonDeferRefresh
392
+ earliestTime:&earliestSafeTime
393
+ chosenReason:&blockerReason];
394
+ }
379
395
 
380
396
  // Interactive transitions (swipe back, drag-to-dismiss) are hitch-sensitive.
381
- [self considerBlockerSince:self.lastTransitionTime
382
- quietInterval:kRJQuietTransitionSeconds
383
- now:now
384
- reason:RJCaptureHeuristicsReasonDeferTransition
385
- earliestTime:&earliestSafeTime
386
- chosenReason:&blockerReason];
397
+ // KEY FIX: Urgent captures (NAVIGATION) must bypass this!
398
+ if (!isUrgent) {
399
+ [self considerBlockerSince:self.lastTransitionTime
400
+ quietInterval:kRJQuietTransitionSeconds
401
+ now:now
402
+ reason:RJCaptureHeuristicsReasonDeferTransition
403
+ earliestTime:&earliestSafeTime
404
+ chosenReason:&blockerReason];
405
+ }
387
406
 
388
407
  // Keyboard frame animations can stutter; wait for settle.
389
408
  if (self.keyboardAnimating) {
390
409
  self.lastKeyboardTime = now;
391
410
  }
392
- [self considerBlockerSince:self.lastKeyboardTime
393
- quietInterval:kRJQuietKeyboardSeconds
394
- now:now
395
- reason:RJCaptureHeuristicsReasonDeferKeyboard
396
- earliestTime:&earliestSafeTime
397
- chosenReason:&blockerReason];
411
+ if (!isUrgent) {
412
+ [self considerBlockerSince:self.lastKeyboardTime
413
+ quietInterval:kRJQuietKeyboardSeconds
414
+ now:now
415
+ reason:RJCaptureHeuristicsReasonDeferKeyboard
416
+ earliestTime:&earliestSafeTime
417
+ chosenReason:&blockerReason];
418
+ }
398
419
 
399
420
  // Map camera or tile motion is visually obvious; avoid rendering mid-flight.
400
- [self considerBlockerSince:self.lastMapTime
401
- quietInterval:kRJQuietMapSeconds
402
- now:now
403
- reason:RJCaptureHeuristicsReasonDeferMap
404
- earliestTime:&earliestSafeTime
405
- chosenReason:&blockerReason];
421
+ // Maps are special; even CRITICAL captures might want to wait for map settle
422
+ // if possible.
423
+ if (importance != RJCaptureImportanceCritical) {
424
+ [self considerBlockerSince:self.lastMapTime
425
+ quietInterval:kRJQuietMapSeconds
426
+ now:now
427
+ reason:RJCaptureHeuristicsReasonDeferMap
428
+ earliestTime:&earliestSafeTime
429
+ chosenReason:&blockerReason];
406
430
 
407
- if (self.mapSettleUntil > now && self.mapSettleUntil > earliestSafeTime) {
408
- earliestSafeTime = self.mapSettleUntil;
409
- blockerReason = RJCaptureHeuristicsReasonDeferMap;
431
+ if (self.mapSettleUntil > now && self.mapSettleUntil > earliestSafeTime) {
432
+ earliestSafeTime = self.mapSettleUntil;
433
+ blockerReason = RJCaptureHeuristicsReasonDeferMap;
434
+ }
410
435
  }
411
436
 
412
437
  // Large-area animations (Lottie, shimmer, etc.) are very noticeable.
413
- if (self.animationBlocking) {
438
+ if (self.animationBlocking && !isUrgent) {
414
439
  [self considerBlockerSince:self.lastAnimationTime
415
440
  quietInterval:kRJQuietAnimationSeconds
416
441
  now:now
417
- reason:RJCaptureHeuristicsReasonDeferBigAnimation
418
- earliestTime:&earliestSafeTime
419
- chosenReason:&blockerReason];
442
+ reason:RJCaptureHeuristicsReasonDeferBigAnimation
443
+ earliestTime:&earliestSafeTime
444
+ chosenReason:&blockerReason];
420
445
  }
421
446
 
422
447
  if (earliestSafeTime > now) {
@@ -426,26 +451,27 @@ typedef struct {
426
451
  return decision;
427
452
  }
428
453
 
429
- BOOL signatureChanged = (signature.length == 0 ||
430
- ![signature isEqualToString:self.lastRenderedSignature]);
454
+ BOOL signatureChanged =
455
+ (signature.length == 0 ||
456
+ ![signature isEqualToString:self.lastRenderedSignature]);
431
457
  BOOL stale = (self.lastRenderedTime <= 0 ||
432
458
  (now - self.lastRenderedTime) > self.maxStaleSeconds);
433
459
  BOOL bonusDue = (self.bonusCaptureTime > 0 && now >= self.bonusCaptureTime);
434
- BOOL keyframeDue = bonusDue && self.pendingKeyframes > 0 &&
435
- (now - self.lastKeyframeRenderTime) >=
436
- kRJKeyframeSpacingSeconds;
460
+ BOOL keyframeDue =
461
+ bonusDue && self.pendingKeyframes > 0 &&
462
+ (now - self.lastKeyframeRenderTime) >= kRJKeyframeSpacingSeconds;
437
463
  BOOL staleOnly = stale && hasLastFrame && !signatureChanged && !keyframeDue;
438
464
  BOOL suppressStaleRender =
439
465
  staleOnly &&
440
466
  (self.hasVideoSurface || self.hasWebSurface || self.hasCameraSurface);
441
467
 
442
- if (suppressStaleRender) {
468
+ if (suppressStaleRender && !isUrgent) {
443
469
  decision.action = RJCaptureHeuristicsActionReuseLast;
444
470
  decision.reason = RJCaptureHeuristicsReasonReuseSignatureUnchanged;
445
471
  return decision;
446
472
  }
447
473
 
448
- if (!hasLastFrame || signatureChanged || stale || keyframeDue) {
474
+ if (!hasLastFrame || signatureChanged || stale || keyframeDue || isUrgent) {
449
475
  decision.action = RJCaptureHeuristicsActionRenderNow;
450
476
  decision.reason = RJCaptureHeuristicsReasonRenderNow;
451
477
  return decision;
@@ -516,7 +542,8 @@ typedef struct {
516
542
  UIViewController *topVC = [self topViewControllerForWindow:window];
517
543
  if (topVC != self.lastTopVC) {
518
544
  self.lastTransitionTime = now;
519
- [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds now:now];
545
+ [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds
546
+ now:now];
520
547
  self.lastTopVC = topVC;
521
548
  }
522
549
  id<UIViewControllerTransitionCoordinator> coordinator =
@@ -552,7 +579,7 @@ typedef struct {
552
579
  #pragma mark - Scroll Tracking
553
580
 
554
581
  - (void)updateTrackedScrollViews:(NSArray<NSValue *> *)scrollViewPointers
555
- now:(NSTimeInterval)now {
582
+ now:(NSTimeInterval)now {
556
583
  if (!scrollViewPointers) {
557
584
  return;
558
585
  }
@@ -563,18 +590,18 @@ typedef struct {
563
590
  continue;
564
591
  }
565
592
  [self evaluateScrollView:scrollView
566
- now:now
567
- scrollActive:NULL
568
- refreshActive:NULL];
593
+ now:now
594
+ scrollActive:NULL
595
+ refreshActive:NULL];
569
596
  }
570
-
571
597
  }
572
598
 
573
599
  - (void)probeScrollViewsAtTime:(NSTimeInterval)now {
574
600
  BOOL anyScrollActive = NO;
575
601
  BOOL anyRefreshActive = NO;
576
602
 
577
- NSArray<UIScrollView *> *scrollViews = [[self.scrollSamples keyEnumerator] allObjects];
603
+ NSArray<UIScrollView *> *scrollViews =
604
+ [[self.scrollSamples keyEnumerator] allObjects];
578
605
  if (!scrollViews) {
579
606
  scrollViews = @[];
580
607
  }
@@ -583,21 +610,21 @@ typedef struct {
583
610
  continue;
584
611
  }
585
612
  [self evaluateScrollView:scrollView
586
- now:now
587
- scrollActive:&anyScrollActive
588
- refreshActive:&anyRefreshActive];
613
+ now:now
614
+ scrollActive:&anyScrollActive
615
+ refreshActive:&anyRefreshActive];
589
616
  }
590
617
 
591
618
  [self updateScrollActiveState:anyScrollActive
592
- refreshActive:anyRefreshActive
593
- mapActive:self.mapActive
594
- now:now];
619
+ refreshActive:anyRefreshActive
620
+ mapActive:self.mapActive
621
+ now:now];
595
622
  }
596
623
 
597
624
  - (void)evaluateScrollView:(UIScrollView *)scrollView
598
625
  now:(NSTimeInterval)now
599
- scrollActive:(BOOL *)scrollActive
600
- refreshActive:(BOOL *)refreshActive {
626
+ scrollActive:(BOOL *)scrollActive
627
+ refreshActive:(BOOL *)refreshActive {
601
628
  RJScrollViewSample *sample = [self.scrollSamples objectForKey:scrollView];
602
629
  if (!sample) {
603
630
  sample = [[RJScrollViewSample alloc] init];
@@ -609,8 +636,9 @@ typedef struct {
609
636
 
610
637
  BOOL tracking = scrollView.isTracking || scrollView.isDragging ||
611
638
  scrollView.isDecelerating;
612
- BOOL offsetMoved = (fabs(offset.x - sample.contentOffset.x) > kRJScrollEpsilon ||
613
- fabs(offset.y - sample.contentOffset.y) > kRJScrollEpsilon);
639
+ BOOL offsetMoved =
640
+ (fabs(offset.x - sample.contentOffset.x) > kRJScrollEpsilon ||
641
+ fabs(offset.y - sample.contentOffset.y) > kRJScrollEpsilon);
614
642
  BOOL zoomMoved = fabs(zoomScale - sample.zoomScale) > kRJZoomEpsilon;
615
643
  BOOL isScrolling = tracking || offsetMoved || zoomMoved;
616
644
 
@@ -635,8 +663,8 @@ typedef struct {
635
663
  }
636
664
 
637
665
  BOOL refreshVisible = [self isRefreshActiveForScrollView:scrollView
638
- offset:offset
639
- inset:inset];
666
+ offset:offset
667
+ inset:inset];
640
668
  if (refreshVisible) {
641
669
  // Pull-to-refresh is active or settling.
642
670
  self.lastRefreshTime = now;
@@ -660,8 +688,8 @@ typedef struct {
660
688
  }
661
689
  CGFloat topLimit = -inset.top - kRJScrollEpsilon;
662
690
  CGFloat bottomLimit = scrollView.contentSize.height -
663
- scrollView.bounds.size.height +
664
- inset.bottom + kRJScrollEpsilon;
691
+ scrollView.bounds.size.height + inset.bottom +
692
+ kRJScrollEpsilon;
665
693
 
666
694
  if (offset.y < topLimit || offset.y > bottomLimit) {
667
695
  return YES;
@@ -669,8 +697,8 @@ typedef struct {
669
697
 
670
698
  CGFloat leftLimit = -inset.left - kRJScrollEpsilon;
671
699
  CGFloat rightLimit = scrollView.contentSize.width -
672
- scrollView.bounds.size.width +
673
- inset.right + kRJScrollEpsilon;
700
+ scrollView.bounds.size.width + inset.right +
701
+ kRJScrollEpsilon;
674
702
  if (offset.x < leftLimit || offset.x > rightLimit) {
675
703
  return YES;
676
704
  }
@@ -690,8 +718,8 @@ typedef struct {
690
718
  return YES;
691
719
  }
692
720
 
693
- CGFloat triggerOffset = -scrollView.adjustedContentInset.top -
694
- kRJScrollEpsilon;
721
+ CGFloat triggerOffset =
722
+ -scrollView.adjustedContentInset.top - kRJScrollEpsilon;
695
723
  if (offset.y < triggerOffset) {
696
724
  return YES;
697
725
  }
@@ -709,9 +737,9 @@ typedef struct {
709
737
  }
710
738
 
711
739
  - (void)updateScrollActiveState:(BOOL)scrollActive
712
- refreshActive:(BOOL)refreshActive
713
- mapActive:(BOOL)mapActive
714
- now:(NSTimeInterval)now {
740
+ refreshActive:(BOOL)refreshActive
741
+ mapActive:(BOOL)mapActive
742
+ now:(NSTimeInterval)now {
715
743
  if (self.scrollActive && !scrollActive) {
716
744
  [self scheduleBonusCaptureAfterDelay:kRJBonusScrollDelaySeconds now:now];
717
745
  }
@@ -734,7 +762,7 @@ typedef struct {
734
762
  #pragma mark - Map Tracking
735
763
 
736
764
  - (void)updateTrackedMapViews:(NSArray<NSValue *> *)mapViewPointers
737
- now:(NSTimeInterval)now {
765
+ now:(NSTimeInterval)now {
738
766
  if (!mapViewPointers) {
739
767
  return;
740
768
  }
@@ -763,9 +791,9 @@ typedef struct {
763
791
  self.lastMapTime = now;
764
792
  }
765
793
  [self updateScrollActiveState:self.scrollActive
766
- refreshActive:self.refreshActive
767
- mapActive:anyMapActive || self.mapActive
768
- now:now];
794
+ refreshActive:self.refreshActive
795
+ mapActive:anyMapActive || self.mapActive
796
+ now:now];
769
797
  }
770
798
 
771
799
  - (BOOL)updateMapStateForView:(UIView *)view atTime:(NSTimeInterval)now {
@@ -836,8 +864,7 @@ typedef struct {
836
864
 
837
865
  - (void)probeAnimatedViewsAtTime:(NSTimeInterval)now {
838
866
  if (self.churnBlocking) {
839
- if ((now - self.lastSignatureChurnTime) <
840
- kRJSignatureChurnWindowSeconds) {
867
+ if ((now - self.lastSignatureChurnTime) < kRJSignatureChurnWindowSeconds) {
841
868
  self.lastAnimationTime = now;
842
869
  return;
843
870
  }
@@ -863,15 +890,14 @@ typedef struct {
863
890
  self.lastAnimationTime = now;
864
891
  } else {
865
892
  self.animationBlocking = NO;
866
- [self scheduleBonusCaptureAfterDelay:kRJBonusAnimationDelaySeconds
867
- now:now];
893
+ [self scheduleBonusCaptureAfterDelay:kRJBonusAnimationDelaySeconds now:now];
868
894
  }
869
895
  }
870
896
 
871
897
  #pragma mark - Bonus Capture
872
898
 
873
899
  - (void)scheduleBonusCaptureAfterDelay:(NSTimeInterval)delay
874
- now:(NSTimeInterval)now {
900
+ now:(NSTimeInterval)now {
875
901
  if (self.pendingKeyframes < kRJMaxPendingKeyframes) {
876
902
  self.pendingKeyframes += 1;
877
903
  }
@@ -886,9 +912,9 @@ typedef struct {
886
912
  - (void)considerBlockerSince:(NSTimeInterval)timestamp
887
913
  quietInterval:(NSTimeInterval)quietInterval
888
914
  now:(NSTimeInterval)now
889
- reason:(RJCaptureHeuristicsReason)reason
890
- earliestTime:(NSTimeInterval *)earliestTime
891
- chosenReason:(RJCaptureHeuristicsReason *)chosenReason {
915
+ reason:(RJCaptureHeuristicsReason)reason
916
+ earliestTime:(NSTimeInterval *)earliestTime
917
+ chosenReason:(RJCaptureHeuristicsReason *)chosenReason {
892
918
  if (timestamp <= 0) {
893
919
  return;
894
920
  }
@@ -943,33 +943,54 @@ RCT_EXPORT_METHOD(setDebugMode : (BOOL)enabled resolve : (
943
943
 
944
944
  RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
945
945
  RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
946
- dispatch_async(dispatch_get_main_queue(), ^{
946
+ dispatch_async(self.stateQueue, ^{
947
947
  @try {
948
948
  NSString *safeUserId = userId.length > 0 ? userId : @"anonymous";
949
949
 
950
- [self performStateSync:^{
951
- self.userId = safeUserId;
952
- }];
950
+ // KEY CHANGE: Persist directly to NSUserDefaults (Native Storage)
951
+ [[NSUserDefaults standardUserDefaults] setObject:safeUserId
952
+ forKey:@"rj_user_identity"];
953
+ [[NSUserDefaults standardUserDefaults] synchronize];
953
954
 
955
+ self.userId = safeUserId;
954
956
  if (self.uploadManager) {
955
957
  self.uploadManager.userId = safeUserId;
956
958
  }
957
959
 
958
- RJLogDebug(@"User identity updated: %@", safeUserId);
960
+ RJLogDebug(@"User identity updated and persisted: %@", safeUserId);
959
961
 
960
962
  if (self.isRecording) {
961
- [self logEventInternal:@"user_identity_changed"
962
- details:@{@"userId" : safeUserId}];
963
+ // Log event for tracking
964
+ NSMutableDictionary *event = [NSMutableDictionary new];
965
+ event[@"type"] = @"user_identity_changed";
966
+ event[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
967
+ event[@"userId"] = safeUserId;
968
+
969
+ // Helper to log event safely
970
+ if (self.eventBuffer) {
971
+ [self.eventBuffer appendEvent:event];
972
+ }
973
+ if (self.sessionEvents && !self.isShuttingDown) {
974
+ [self.sessionEvents addObject:event];
975
+ }
963
976
  }
964
977
 
965
- resolve(@{@"success" : @YES});
978
+ if (resolve)
979
+ resolve(@{@"success" : @YES});
966
980
  } @catch (NSException *exception) {
967
- RJLogWarning(@"setUserIdentity failed: %@", exception);
968
- resolve(@{@"success" : @NO});
981
+ if (resolve)
982
+ resolve(@{@"success" : @NO});
969
983
  }
970
984
  });
971
985
  }
972
986
 
987
+ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
988
+ resolve reject : (RCTPromiseRejectBlock)reject) {
989
+ NSString *userId =
990
+ [[NSUserDefaults standardUserDefaults] stringForKey:@"rj_user_identity"];
991
+ resolve(userId ?: [NSNull null]);
992
+ }
993
+
973
994
  #pragma mark - RJTouchInterceptorDelegate
974
995
 
975
996
  - (void)touchInterceptorDidDetectInteractionStart {
@@ -2345,6 +2366,10 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2345
2366
  self.uploadManager.totalBackgroundTimeMs = 0;
2346
2367
 
2347
2368
  // Preserve user identity
2369
+ if (!self.userId) {
2370
+ self.userId = [[NSUserDefaults standardUserDefaults]
2371
+ stringForKey:@"rj_user_identity"];
2372
+ }
2348
2373
  self.uploadManager.userId = self.userId ?: @"anonymous";
2349
2374
  }
2350
2375
 
@@ -276,7 +276,6 @@ function getAutoTracking() {
276
276
  }
277
277
 
278
278
  // State
279
- const USER_IDENTITY_KEY = '@rejourney_user_identity';
280
279
  let _isInitialized = false;
281
280
  let _isRecording = false;
282
281
  let _initializationFailed = false;
@@ -291,22 +290,17 @@ let _lastScrollOffset = 0;
291
290
  const SCROLL_THROTTLE_MS = 100;
292
291
 
293
292
  // Helper to save/load user identity
294
- async function persistUserIdentity(identity) {
295
- try {
296
- const AsyncStorage = require('@react-native-async-storage/async-storage').default;
297
- if (identity) {
298
- await AsyncStorage.setItem(USER_IDENTITY_KEY, identity);
299
- } else {
300
- await AsyncStorage.removeItem(USER_IDENTITY_KEY);
301
- }
302
- } catch (e) {
303
- // Ignore storage errors
304
- }
293
+ // NOW HANDLED NATIVELY - No-op on JS side to avoid unnecessary bridge calls
294
+ async function persistUserIdentity(_identity) {
295
+ // Native module handles persistence automatically in setUserIdentity
305
296
  }
306
297
  async function loadPersistedUserIdentity() {
307
298
  try {
308
- const AsyncStorage = require('@react-native-async-storage/async-storage').default;
309
- return await AsyncStorage.getItem(USER_IDENTITY_KEY);
299
+ const nativeModule = getRejourneyNative();
300
+ if (!nativeModule) return null;
301
+
302
+ // NATIVE STORAGE: Read directly from SharedPreferences/NSUserDefaults
303
+ return await nativeModule.getUserIdentity();
310
304
  } catch (e) {
311
305
  return null;
312
306
  }