@rejourneyco/react-native 1.0.2 → 1.0.4

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 +38 -363
  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 +14 -27
  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/ios/Capture/RJCaptureEngine.m +9 -61
  15. package/ios/Capture/RJViewHierarchyScanner.m +68 -51
  16. package/ios/Core/RJLifecycleManager.m +0 -14
  17. package/ios/Core/Rejourney.mm +24 -37
  18. package/ios/Network/RJDeviceAuthManager.m +0 -2
  19. package/ios/Network/RJUploadManager.h +8 -0
  20. package/ios/Network/RJUploadManager.m +45 -0
  21. package/ios/Privacy/RJPrivacyMask.m +5 -31
  22. package/ios/Rejourney.h +0 -14
  23. package/ios/Touch/RJTouchInterceptor.m +21 -15
  24. package/ios/Utils/RJEventBuffer.m +57 -69
  25. package/ios/Utils/RJWindowUtils.m +87 -86
  26. package/lib/commonjs/index.js +44 -31
  27. package/lib/commonjs/sdk/autoTracking.js +0 -3
  28. package/lib/commonjs/sdk/constants.js +1 -1
  29. package/lib/commonjs/sdk/networkInterceptor.js +0 -11
  30. package/lib/commonjs/sdk/utils.js +73 -14
  31. package/lib/module/index.js +44 -31
  32. package/lib/module/sdk/autoTracking.js +0 -3
  33. package/lib/module/sdk/constants.js +1 -1
  34. package/lib/module/sdk/networkInterceptor.js +0 -11
  35. package/lib/module/sdk/utils.js +73 -14
  36. package/lib/typescript/sdk/constants.d.ts +1 -1
  37. package/lib/typescript/sdk/utils.d.ts +31 -1
  38. package/package.json +16 -4
  39. package/src/index.ts +42 -20
  40. package/src/sdk/autoTracking.ts +0 -2
  41. package/src/sdk/constants.ts +14 -14
  42. package/src/sdk/networkInterceptor.ts +0 -9
  43. package/src/sdk/utils.ts +76 -14
@@ -203,23 +203,19 @@ typedef struct {
203
203
  screenSize = CGSizeMake(390, 844);
204
204
  }
205
205
 
206
- // Always use main screen scale to avoid UIWindow casting header issues
207
- // and ensure consistent native resolution (@2x or @3x)
208
206
  CGFloat screenScale = [UIScreen mainScreen].scale;
209
207
  if (!isfinite(screenScale) || screenScale <= 0) {
210
208
  screenScale = 1.0;
211
209
  }
212
210
 
213
- CGFloat scaleToUse = self.captureScale; // Configured value (e.g. 0.72)
211
+ CGFloat scaleToUse = self.captureScale;
214
212
  if (!isfinite(scaleToUse) || scaleToUse <= 0) {
215
213
  scaleToUse = RJDefaultCaptureScale;
216
214
  }
217
215
 
218
- // define targetFPS and targetBitrate
219
216
  NSInteger targetFPS = self.videoFPS;
220
217
  NSInteger targetBitrate = self.videoBitrate;
221
218
 
222
- // Performance Level Overrides - only reduce slightly for reduced performance
223
219
  if (self.internalPerformanceLevel >= RJPerformanceLevelReduced) {
224
220
  scaleToUse = MIN(scaleToUse, 0.25);
225
221
  }
@@ -228,24 +224,16 @@ typedef struct {
228
224
  scaleToUse = MIN(scaleToUse, 0.15);
229
225
  }
230
226
 
231
- // Removed aggressive warmup/scroll downscaling multipliers (0.3x) which
232
- // caused fuzziness
233
227
 
234
- // Clamp
235
228
  scaleToUse = MIN(MAX(scaleToUse, 0.05), 1.0);
236
229
 
237
- // Calculate dimensions
238
- // Use native pixel dimensions * scaleToUse
239
230
  size_t width = (size_t)(screenSize.width * screenScale * scaleToUse);
240
231
  size_t height = (size_t)(screenSize.height * screenScale * scaleToUse);
241
232
 
242
- // H.264 Alignment (mod 2)
243
233
  width = (width / 2) * 2;
244
234
  height = (height / 2) * 2;
245
235
 
246
- // Max dimension cap (higher cap for better quality)
247
- CGFloat maxDimension =
248
- 1920.0; // Increased from 800.0 to support high-res capture
236
+ CGFloat maxDimension = 1920.0;
249
237
  if (width > maxDimension || height > maxDimension) {
250
238
  CGFloat ratio = MIN(maxDimension / width, maxDimension / height);
251
239
  width = (size_t)(width * ratio);
@@ -482,8 +470,7 @@ typedef struct {
482
470
  }
483
471
 
484
472
  - (void)appWillEnterBackground:(NSNotification *)notification {
485
- // Set background flag BEFORE iOS takes app switcher snapshot
486
- // Avoid rendering during app switch transitions.
473
+
487
474
  self.isInBackground = YES;
488
475
  RJLogDebug(@"CaptureEngine: App will enter background - suspending capture");
489
476
  }
@@ -499,14 +486,8 @@ typedef struct {
499
486
  - (void)appDidBecomeActive:(NSNotification *)notification {
500
487
  self.isInBackground = NO;
501
488
 
502
- // DEFENSIVE FIX: Warmup period
503
- // When returning from background, the view hierarchy and layout may not be
504
- // stable immediately. This can cause privacy masks to be drawn in the wrong
505
- // position relative to the content (race condition). We impose a short
506
- // "warmup" period where we skip capture to allow AutoLayout to settle.
507
489
  self.isWarmingUp = YES;
508
490
 
509
- // Clear stale caches to force fresh scan
510
491
  if (self.lastCapturedPixelBuffer) {
511
492
  CVPixelBufferRelease(self.lastCapturedPixelBuffer);
512
493
  self.lastCapturedPixelBuffer = NULL;
@@ -600,11 +581,9 @@ typedef struct {
600
581
 
601
582
  [self createPixelBufferPoolWithWidth:width height:height];
602
583
 
603
- // Also pre-warm native pool
604
584
  CGFloat screenScale = [UIScreen mainScreen].scale;
605
585
  size_t nativeW = (size_t)(width / layout.unifiedScale * screenScale);
606
586
  size_t nativeH = (size_t)(height / layout.unifiedScale * screenScale);
607
- // Align
608
587
  nativeW = (nativeW / 2) * 2;
609
588
  nativeH = (nativeH / 2) * 2;
610
589
 
@@ -618,7 +597,7 @@ typedef struct {
618
597
  - (void)createPixelBufferPoolWithWidth:(size_t)width height:(size_t)height {
619
598
  if (_pixelBufferPool) {
620
599
  if (_poolWidth == width && _poolHeight == height) {
621
- return; // Already matched
600
+ return;
622
601
  }
623
602
  CVPixelBufferPoolRelease(_pixelBufferPool);
624
603
  _pixelBufferPool = NULL;
@@ -648,20 +627,14 @@ typedef struct {
648
627
  }
649
628
 
650
629
  - (void)prewarmRenderServer {
651
- // Perform a dummy 100x100 render on the main thread to wake up the render
652
- // server. CRITICAL: We must use the EXACT same pipeline (PixelPool ->
653
- // BitmapContext) as the real capture to ensure the specific internal
654
- // toggles for CGBitmapContext and CVPixelBuffer interactions are warmed up.
655
- // Using UIGraphicsBeginImageContext here is useless because we don't use it
656
- // anymore.
630
+
657
631
 
658
632
  dispatch_async(dispatch_get_main_queue(), ^{
659
633
  @try {
660
634
  RJLogDebug(@"CaptureEngine: Pre-warming Render Server (Direct-Buffer "
661
635
  @"Path)...");
662
636
 
663
- // 1. Get a dummy buffer from the NATIVE pool
664
- // Using 100x100 is fine, it will trigger pool creation if needed
637
+
665
638
  CVPixelBufferRef pixelBuffer =
666
639
  [self createNativePixelBufferFromPoolWithWidth:100 height:100];
667
640
  if (pixelBuffer) {
@@ -676,8 +649,7 @@ typedef struct {
676
649
  CGColorSpaceRelease(colorSpace);
677
650
 
678
651
  if (context) {
679
- // 2. Perform the render
680
- // We need a valid view. Pushing context is what UIKit needs.
652
+
681
653
  UIGraphicsPushContext(context);
682
654
 
683
655
  UIWindow *window = self.windowProvider ? self.windowProvider() : nil;
@@ -685,14 +657,10 @@ typedef struct {
685
657
  window = [[UIApplication sharedApplication] windows].firstObject;
686
658
 
687
659
  if (window) {
688
- // Force the heavy lifting
689
660
  [window drawViewHierarchyInRect:CGRectMake(0, 0, 100, 100)
690
661
  afterScreenUpdates:NO];
691
662
 
692
- // 3. Pre-warm the Encoder with the REAL expected size
693
- // This is the most critical fix for the 650ms first-frame spike.
694
- // We calculate what the first frame size WILL be and initialize
695
- // the AVAssetWriter now.
663
+
696
664
  RJCaptureLayout layout =
697
665
  [self currentCaptureLayoutForWindow:window];
698
666
  CGSize expectedSize =
@@ -808,7 +776,6 @@ typedef struct {
808
776
  UIWindowScene *windowScene = (UIWindowScene *)scene;
809
777
  if (windowScene.activationState !=
810
778
  (NSInteger)0) { // Check active state if possible, or just skip
811
- // simplified check to avoid enum mismatch issues if any
812
779
  }
813
780
 
814
781
  for (UIWindow *window in windowScene.windows) {
@@ -832,7 +799,6 @@ typedef struct {
832
799
  [windowsToScan addObject:primaryWindow];
833
800
  }
834
801
 
835
- // Fallback if empty
836
802
  if (windowsToScan.count == 0 && primaryWindow) {
837
803
  [windowsToScan addObject:primaryWindow];
838
804
  }
@@ -910,7 +876,7 @@ typedef struct {
910
876
  @"CaptureEngine: Finishing segment synchronously (session stop)");
911
877
  [self.internalVideoEncoder finishSegmentSync];
912
878
  self.internalVideoEncoder =
913
- nil; // Force full re-creation on next session
879
+ nil;
914
880
  };
915
881
  if (dispatch_get_specific(kRJEncodingQueueKey)) {
916
882
  finishSync();
@@ -978,7 +944,6 @@ typedef struct {
978
944
  self.lastSafePixelBuffer = NULL;
979
945
  }
980
946
 
981
- // Pre-warm view scanner class caches
982
947
  if (!self.didPrewarmScanner) {
983
948
  [self.viewScanner prewarmClassCaches];
984
949
  self.didPrewarmScanner = YES;
@@ -999,21 +964,18 @@ typedef struct {
999
964
 
1000
965
  __weak typeof(self) weakSelf = self;
1001
966
 
1002
- // OPTIMIZATION: Capture first frame after 300ms delay
1003
967
  dispatch_after(
1004
968
  dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),
1005
969
  dispatch_get_main_queue(), ^{
1006
970
  __strong typeof(weakSelf) strongSelf = weakSelf;
1007
971
  if (strongSelf && strongSelf.internalIsRecording &&
1008
972
  !strongSelf.isShuttingDown) {
1009
- // Auto-enable capture readiness if JS hasn't called notifyUIReady yet
1010
973
  strongSelf.uiReadyForCapture = YES;
1011
974
  RJLogDebug(@"Capturing initial frame after session start");
1012
975
  [strongSelf captureVideoFrame];
1013
976
  }
1014
977
  });
1015
978
 
1016
- // Use CADisplayLink for frame-synchronized capture
1017
979
  [self setupDisplayLink];
1018
980
 
1019
981
  RJLogInfo(@"Video capture started: %ld FPS, %ld frames/segment "
@@ -1026,23 +988,18 @@ typedef struct {
1026
988
 
1027
989
  __weak typeof(self) weakSelf = self;
1028
990
 
1029
- // CADisplayLink synchronized with display refresh
1030
991
  _displayLink =
1031
992
  [CADisplayLink displayLinkWithTarget:self
1032
993
  selector:@selector(displayLinkCallback:)];
1033
994
 
1034
- // Set preferred frame rate (iOS 15+)
1035
995
  if (@available(iOS 15.0, *)) {
1036
- // Hard cap at target FPS to avoid 60Hz tick capability
1037
996
  _displayLink.preferredFrameRateRange =
1038
997
  CAFrameRateRangeMake(self.videoFPS, self.videoFPS, self.videoFPS);
1039
998
  } else {
1040
- // For older iOS, use frameInterval
1041
999
  NSInteger interval = (NSInteger)(60.0 / self.videoFPS);
1042
1000
  _displayLink.frameInterval = MAX(1, interval);
1043
1001
  }
1044
1002
 
1045
- // Add to RunLoop in CommonModes to capture during scroll
1046
1003
  [_displayLink addToRunLoop:[NSRunLoop mainRunLoop]
1047
1004
  forMode:NSRunLoopCommonModes];
1048
1005
 
@@ -1109,14 +1066,11 @@ typedef struct {
1109
1066
  NSTimeInterval now = CACurrentMediaTime();
1110
1067
  NSTimeInterval interval = 1.0 / (CGFloat)self.videoFPS;
1111
1068
 
1112
- // Manual throttle check
1113
1069
  if (now - self.lastIntentTime < interval)
1114
1070
  return;
1115
1071
 
1116
1072
  self.lastIntentTime = now;
1117
1073
 
1118
- // Move capture to the next run loop idle cycle to avoid
1119
- // blocking the VSYNC callback with heavy work.
1120
1074
  self.runLoopCapturePending = YES;
1121
1075
  [self setupRunLoopObserver];
1122
1076
  }
@@ -1403,8 +1357,6 @@ typedef struct {
1403
1357
  }
1404
1358
  self.pendingDefensiveCaptureTime = 0;
1405
1359
  self.lastIntentTime = CACurrentMediaTime();
1406
- // Defensive capture triggered by heuristics (e.g.
1407
- // navigation) is High importance
1408
1360
  [self captureVideoFrameWithImportance:RJCaptureImportanceHigh
1409
1361
  reason:reason];
1410
1362
  });
@@ -1449,7 +1401,6 @@ typedef struct {
1449
1401
  ![currentSignature isEqualToString:self.lastSerializedSignature]);
1450
1402
  self.lastSerializedSignature = currentSignature;
1451
1403
 
1452
- // ===== CAPTURE LAYOUTS =====
1453
1404
  RJCaptureLayout targetLayout = [self currentCaptureLayoutForWindow:window];
1454
1405
  CGFloat targetScale = targetLayout.unifiedScale;
1455
1406
 
@@ -1693,9 +1644,7 @@ typedef struct {
1693
1644
  return NULL;
1694
1645
  }
1695
1646
 
1696
- // ===== PIXEL BUFFER ALLOCATION (NATIVE) =====
1697
1647
  RJ_TIME_START_NAMED(buffer);
1698
- // Use NATIVE pool
1699
1648
  CVPixelBufferRef pixelBuffer =
1700
1649
  [self createNativePixelBufferFromPoolWithWidth:width height:height];
1701
1650
  RJ_TIME_END_NAMED(buffer, RJPerfMetricBufferAlloc);
@@ -1709,7 +1658,6 @@ typedef struct {
1709
1658
  void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
1710
1659
  size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
1711
1660
 
1712
- // Optimization #10: Use cached color space
1713
1661
  if (!self.commonColorSpace) {
1714
1662
  self.commonColorSpace = CGColorSpaceCreateDeviceRGB();
1715
1663
  }
@@ -152,8 +152,10 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
152
152
  @property(nonatomic, strong) NSMutableArray<NSValue *> *mutableVideoFrames;
153
153
  @property(nonatomic, strong) NSMutableArray<NSValue *> *mutableMapViewFrames;
154
154
  @property(nonatomic, strong) NSMutableArray<NSValue *> *mutableMapViewPointers;
155
- @property(nonatomic, strong) NSMutableArray<NSValue *> *mutableScrollViewPointers;
156
- @property(nonatomic, strong) NSMutableArray<NSValue *> *mutableAnimatedViewPointers;
155
+ @property(nonatomic, strong)
156
+ NSMutableArray<NSValue *> *mutableScrollViewPointers;
157
+ @property(nonatomic, strong)
158
+ NSMutableArray<NSValue *> *mutableAnimatedViewPointers;
157
159
 
158
160
  @property(nonatomic, strong) NSMapTable<Class, NSString *> *classNameCache;
159
161
 
@@ -251,11 +253,14 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
251
253
  // because map tiles load asynchronously and layout signature doesn't
252
254
  // capture them
253
255
  _mapViewClasses = [NSSet setWithArray:@[
254
- @"MKMapView", // Apple Maps
255
- @"AIRMap", // react-native-maps (iOS)
256
- @"AIRMapView", // react-native-maps alternate
257
- @"RNMMapView", // react-native-maps newer versions
258
- @"GMSMapView", // Google Maps SDK
256
+ @"MKMapView", // Apple Maps
257
+ @"AIRMap", // react-native-maps (iOS)
258
+ @"AIRMapView", // react-native-maps alternate
259
+ @"RNMMapView", // react-native-maps newer versions
260
+ @"GMSMapView", // Google Maps SDK
261
+ @"MGLMapView", // Mapbox GL Native (< v10)
262
+ @"RCTMGLMapView", // React Native Mapbox wrapper
263
+ @"MapboxMapView", // Mapbox Maps SDK (v10+)
259
264
  ]];
260
265
 
261
266
  _layoutSignatureHash = 14695981039346656037ULL;
@@ -331,7 +336,8 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
331
336
  BOOL needsPrivacyFallback =
332
337
  (self.config.detectTextInputs &&
333
338
  self.mutableTextInputFrames.count == 0) ||
334
- (self.config.detectCameraViews && self.mutableCameraFrames.count == 0) ||
339
+ (self.config.detectCameraViews &&
340
+ self.mutableCameraFrames.count == 0) ||
335
341
  (self.config.detectWebViews && self.mutableWebViewFrames.count == 0) ||
336
342
  (self.config.detectVideoLayers && self.mutableVideoFrames.count == 0);
337
343
  if (needsPrivacyFallback && (hitViewLimit || self.didBailOutEarly)) {
@@ -358,9 +364,7 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
358
364
  result.hasAnyAnimations = self.scanHasAnimations;
359
365
  CGFloat screenArea = window.bounds.size.width * window.bounds.size.height;
360
366
  result.animationAreaRatio =
361
- (screenArea > 0)
362
- ? MIN(self.scanAnimatedArea / screenArea, 1.0)
363
- : 0.0;
367
+ (screenArea > 0) ? MIN(self.scanAnimatedArea / screenArea, 1.0) : 0.0;
364
368
  result.didBailOutEarly = self.didBailOutEarly;
365
369
 
366
370
  if (self.layoutSignatureHash != 14695981039346656037ULL) {
@@ -498,9 +502,9 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
498
502
  self.mutableTextInputFrames.count == 0) ||
499
503
  (self.config.detectCameraViews &&
500
504
  self.mutableCameraFrames.count == 0) ||
501
- (self.config.detectWebViews && self.mutableWebViewFrames.count == 0) ||
502
- (self.config.detectVideoLayers &&
503
- self.mutableVideoFrames.count == 0);
505
+ (self.config.detectWebViews &&
506
+ self.mutableWebViewFrames.count == 0) ||
507
+ (self.config.detectVideoLayers && self.mutableVideoFrames.count == 0);
504
508
  if (needsPrivacyFallback && hitViewLimit) {
505
509
  [self scanSensitiveViewsOnlyInWindow:window];
506
510
  }
@@ -511,7 +515,8 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
511
515
  BOOL needsPrivacyFallback =
512
516
  (self.config.detectTextInputs &&
513
517
  self.mutableTextInputFrames.count == 0) ||
514
- (self.config.detectCameraViews && self.mutableCameraFrames.count == 0) ||
518
+ (self.config.detectCameraViews &&
519
+ self.mutableCameraFrames.count == 0) ||
515
520
  (self.config.detectWebViews && self.mutableWebViewFrames.count == 0) ||
516
521
  (self.config.detectVideoLayers && self.mutableVideoFrames.count == 0);
517
522
  if (needsPrivacyFallback && self.didBailOutEarly) {
@@ -542,9 +547,7 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
542
547
  CGFloat screenArea =
543
548
  primaryWindow.bounds.size.width * primaryWindow.bounds.size.height;
544
549
  result.animationAreaRatio =
545
- (screenArea > 0)
546
- ? MIN(self.scanAnimatedArea / screenArea, 1.0)
547
- : 0.0;
550
+ (screenArea > 0) ? MIN(self.scanAnimatedArea / screenArea, 1.0) : 0.0;
548
551
  result.didBailOutEarly = self.didBailOutEarly;
549
552
 
550
553
  if (self.layoutSignatureHash != 14695981039346656037ULL) {
@@ -679,8 +682,10 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
679
682
 
680
683
  @try {
681
684
  BOOL isWebView = self.config.detectWebViews && [self isWebView:view];
682
- BOOL isCamera = self.config.detectCameraViews && [self isCameraPreview:view];
683
- BOOL isVideo = self.config.detectVideoLayers && [self isVideoLayerView:view];
685
+ BOOL isCamera =
686
+ self.config.detectCameraViews && [self isCameraPreview:view];
687
+ BOOL isVideo =
688
+ self.config.detectVideoLayers && [self isVideoLayerView:view];
684
689
  BOOL isBlockedSurface = isWebView || isCamera || isVideo;
685
690
 
686
691
  [self checkSensitiveView:view inWindow:window];
@@ -762,10 +767,11 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
762
767
  UIEdgeInsets inset = ((UIScrollView *)view).contentInset;
763
768
  [self mixInt:(int32_t)lrintf(isfinite(inset.top) ? inset.top * 100 : 0)];
764
769
  [self mixInt:(int32_t)lrintf(isfinite(inset.bottom) ? inset.bottom * 100
765
- : 0)];
766
- [self mixInt:(int32_t)lrintf(isfinite(inset.left) ? inset.left * 100 : 0)];
770
+ : 0)];
771
+ [self
772
+ mixInt:(int32_t)lrintf(isfinite(inset.left) ? inset.left * 100 : 0)];
767
773
  [self mixInt:(int32_t)lrintf(isfinite(inset.right) ? inset.right * 100
768
- : 0)];
774
+ : 0)];
769
775
  }
770
776
 
771
777
  // 5. Mix Text Content (avoid input content; use length only)
@@ -840,7 +846,8 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
840
846
  }
841
847
  }
842
848
 
843
- - (void)appendBlockedSurfaceInfoToSignature:(UIView *)view depth:(NSInteger)depth {
849
+ - (void)appendBlockedSurfaceInfoToSignature:(UIView *)view
850
+ depth:(NSInteger)depth {
844
851
  if (!view) {
845
852
  return;
846
853
  }
@@ -998,18 +1005,18 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
998
1005
  }
999
1006
 
1000
1007
  #ifdef DEBUG
1001
- RJLogDebug(@"ViewHierarchyScanner: Found %@ at (%.0f,%.0f,%.0f,%.0f) - "
1002
- @"view.window=%@ targetWindow=%@",
1003
- isTextInput
1004
- ? @"TextInput"
1005
- : (isCamera
1006
- ? @"Camera"
1007
- : (isWebView ? @"WebView"
1008
- : (isVideo ? @"Video" : @"MaskedView"))),
1009
- sanitizedFrame.origin.x, sanitizedFrame.origin.y,
1010
- sanitizedFrame.size.width, sanitizedFrame.size.height,
1011
- NSStringFromClass([view.window class]),
1012
- NSStringFromClass([targetWindow class]));
1008
+ RJLogDebug(
1009
+ @"ViewHierarchyScanner: Found %@ at (%.0f,%.0f,%.0f,%.0f) - "
1010
+ @"view.window=%@ targetWindow=%@",
1011
+ isTextInput
1012
+ ? @"TextInput"
1013
+ : (isCamera ? @"Camera"
1014
+ : (isWebView ? @"WebView"
1015
+ : (isVideo ? @"Video" : @"MaskedView"))),
1016
+ sanitizedFrame.origin.x, sanitizedFrame.origin.y,
1017
+ sanitizedFrame.size.width, sanitizedFrame.size.height,
1018
+ NSStringFromClass([view.window class]),
1019
+ NSStringFromClass([targetWindow class]));
1013
1020
  #endif
1014
1021
  }
1015
1022
  }
@@ -1212,7 +1219,7 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1212
1219
 
1213
1220
  @try {
1214
1221
  if ([view respondsToSelector:@selector(isLoading)]) {
1215
- BOOL (*loadingMsg)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
1222
+ BOOL (*loadingMsg)(id, SEL) = (BOOL(*)(id, SEL))objc_msgSend;
1216
1223
  return loadingMsg(view, @selector(isLoading));
1217
1224
  }
1218
1225
  id loadingValue = [view valueForKey:@"loading"];
@@ -1314,8 +1321,9 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1314
1321
 
1315
1322
  BOOL tracking = scrollView.isTracking || scrollView.isDragging ||
1316
1323
  scrollView.isDecelerating;
1317
- BOOL offsetMoved = (fabs(offset.x - state.contentOffset.x) > kRJScrollEpsilon ||
1318
- fabs(offset.y - state.contentOffset.y) > kRJScrollEpsilon);
1324
+ BOOL offsetMoved =
1325
+ (fabs(offset.x - state.contentOffset.x) > kRJScrollEpsilon ||
1326
+ fabs(offset.y - state.contentOffset.y) > kRJScrollEpsilon);
1319
1327
  BOOL zoomMoved = fabs(zoomScale - state.zoomScale) > kRJZoomEpsilon;
1320
1328
  if (tracking || offsetMoved || zoomMoved) {
1321
1329
  self.scanScrollActive = YES;
@@ -1331,8 +1339,8 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1331
1339
  }
1332
1340
 
1333
1341
  if ([self isRefreshActiveForScrollView:scrollView
1334
- offset:offset
1335
- inset:inset]) {
1342
+ offset:offset
1343
+ inset:inset]) {
1336
1344
  self.scanRefreshActive = YES;
1337
1345
  }
1338
1346
 
@@ -1351,16 +1359,16 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1351
1359
  }
1352
1360
  CGFloat topLimit = -inset.top - kRJScrollEpsilon;
1353
1361
  CGFloat bottomLimit = scrollView.contentSize.height -
1354
- scrollView.bounds.size.height +
1355
- inset.bottom + kRJScrollEpsilon;
1362
+ scrollView.bounds.size.height + inset.bottom +
1363
+ kRJScrollEpsilon;
1356
1364
  if (offset.y < topLimit || offset.y > bottomLimit) {
1357
1365
  return YES;
1358
1366
  }
1359
1367
 
1360
1368
  CGFloat leftLimit = -inset.left - kRJScrollEpsilon;
1361
1369
  CGFloat rightLimit = scrollView.contentSize.width -
1362
- scrollView.bounds.size.width +
1363
- inset.right + kRJScrollEpsilon;
1370
+ scrollView.bounds.size.width + inset.right +
1371
+ kRJScrollEpsilon;
1364
1372
  return (offset.x < leftLimit || offset.x > rightLimit);
1365
1373
  }
1366
1374
 
@@ -1376,8 +1384,8 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1376
1384
  return YES;
1377
1385
  }
1378
1386
 
1379
- CGFloat triggerOffset = -scrollView.adjustedContentInset.top -
1380
- kRJScrollEpsilon;
1387
+ CGFloat triggerOffset =
1388
+ -scrollView.adjustedContentInset.top - kRJScrollEpsilon;
1381
1389
  if (offset.y < triggerOffset) {
1382
1390
  return YES;
1383
1391
  }
@@ -1448,14 +1456,23 @@ static inline uint64_t fnv1a_u64(uint64_t h, const void *data, size_t len) {
1448
1456
  NSNumber *heading = [view valueForKeyPath:@"camera.heading"];
1449
1457
  NSNumber *pitch = [view valueForKeyPath:@"camera.pitch"];
1450
1458
 
1459
+ // Mapbox uses zoomLevel instead of span
1460
+ NSNumber *zoomLevel = nil;
1461
+ @try {
1462
+ zoomLevel = [view valueForKey:@"zoomLevel"];
1463
+ } @catch (NSException *e) {
1464
+ }
1465
+
1451
1466
  double altitudeValue = altitude ? altitude.doubleValue : 0;
1452
1467
  double headingValue = heading ? heading.doubleValue : 0;
1453
1468
  double pitchValue = pitch ? pitch.doubleValue : 0;
1469
+ double zoomValue = zoomLevel ? zoomLevel.doubleValue : 0;
1454
1470
 
1455
- return [NSString stringWithFormat:@"%.5f:%.5f:%.5f:%.5f:%.1f:%.1f:%.1f",
1456
- center.latitude, center.longitude,
1457
- span.latitudeDelta, span.longitudeDelta,
1458
- altitudeValue, headingValue, pitchValue];
1471
+ return [NSString
1472
+ stringWithFormat:@"%.5f:%.5f:%.5f:%.5f:%.1f:%.1f:%.1f:%.2f",
1473
+ center.latitude, center.longitude, span.latitudeDelta,
1474
+ span.longitudeDelta, altitudeValue, headingValue,
1475
+ pitchValue, zoomValue];
1459
1476
  } @catch (NSException *exception) {
1460
1477
  return @"";
1461
1478
  }
@@ -121,7 +121,6 @@
121
121
  name:UIApplicationDidBecomeActiveNotification
122
122
  object:nil];
123
123
 
124
- // Text change notifications
125
124
  [center addObserver:self
126
125
  selector:@selector(textDidChange:)
127
126
  name:UITextFieldTextDidChangeNotification
@@ -220,8 +219,6 @@
220
219
  RJLogInfo(@"[RJ-LIFECYCLE] appDidEnterBackground (isRecording=%@)",
221
220
  self.isRecording ? @"YES" : @"NO");
222
221
 
223
- // Always track background entry time, even if not recording
224
- // This allows us to detect timeout when session was ended while in background
225
222
  self.inBackground = YES;
226
223
  self.backgroundEntryTime = [[NSDate date] timeIntervalSince1970];
227
224
 
@@ -257,13 +254,10 @@
257
254
  RJLogInfo(@"[RJ-LIFECYCLE] appDidBecomeActive - was NOT in background");
258
255
  }
259
256
 
260
- // Reset background tracking state
261
257
  self.inBackground = NO;
262
258
  self.backgroundEntryTime = 0;
263
259
 
264
- // Handle the case where we weren't recording (session already ended)
265
260
  if (!self.isRecording) {
266
- // If we were in background long enough, signal that a new session should start
267
261
  if (wasInBackground && backgroundDurationSec >= self.backgroundTimeoutThreshold) {
268
262
  RJLogInfo(@"[RJ-LIFECYCLE] Was not recording, background >= %.0fs - signaling for new session start",
269
263
  self.backgroundTimeoutThreshold);
@@ -274,14 +268,10 @@
274
268
  return;
275
269
  }
276
270
 
277
- // We ARE recording - handle background return
278
271
  if (wasInBackground) {
279
272
  NSTimeInterval bgDurationMs = backgroundDurationSec * 1000;
280
273
 
281
274
  if (backgroundDurationSec >= self.backgroundTimeoutThreshold) {
282
- // TIMEOUT CASE: End old session, start new one
283
- // Add this background duration to accumulated time BEFORE signaling timeout
284
- // so the old session gets the correct total background time
285
275
  self.accumulatedBackgroundTimeMs += bgDurationMs;
286
276
  RJLogInfo(@"[RJ-LIFECYCLE] TIMEOUT: Added %.0fms, total background=%.0fms - signaling session restart",
287
277
  bgDurationMs, self.accumulatedBackgroundTimeMs);
@@ -289,17 +279,13 @@
289
279
  if ([self.delegate respondsToSelector:@selector(lifecycleManagerSessionDidTimeout:)]) {
290
280
  [self.delegate lifecycleManagerSessionDidTimeout:backgroundDurationSec];
291
281
  }
292
- // Note: The delegate's handleSessionTimeout will read totalBackgroundTimeMs
293
- // and then call resetBackgroundTime for the new session
294
282
  } else {
295
- // SHORT BACKGROUND: Just accumulate and resume
296
283
  self.accumulatedBackgroundTimeMs += bgDurationMs;
297
284
  RJLogInfo(@"[RJ-LIFECYCLE] Short background: Added %.0fms, total=%.0fms - resuming session",
298
285
  bgDurationMs, self.accumulatedBackgroundTimeMs);
299
286
  }
300
287
  }
301
288
 
302
- // Call didBecomeActive for normal resume handling (video capture, etc.)
303
289
  if ([self.delegate respondsToSelector:@selector(lifecycleManagerDidBecomeActive)]) {
304
290
  [self.delegate lifecycleManagerDidBecomeActive];
305
291
  }