@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.
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +14 -27
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
- package/ios/Capture/RJCaptureEngine.m +9 -61
- package/ios/Capture/RJViewHierarchyScanner.m +68 -51
- package/ios/Core/RJLifecycleManager.m +0 -14
- package/ios/Core/Rejourney.mm +24 -37
- package/ios/Network/RJDeviceAuthManager.m +0 -2
- package/ios/Network/RJUploadManager.h +8 -0
- package/ios/Network/RJUploadManager.m +45 -0
- package/ios/Privacy/RJPrivacyMask.m +5 -31
- package/ios/Rejourney.h +0 -14
- package/ios/Touch/RJTouchInterceptor.m +21 -15
- package/ios/Utils/RJEventBuffer.m +57 -69
- package/ios/Utils/RJWindowUtils.m +87 -86
- package/lib/commonjs/index.js +44 -31
- package/lib/commonjs/sdk/autoTracking.js +0 -3
- package/lib/commonjs/sdk/constants.js +1 -1
- package/lib/commonjs/sdk/networkInterceptor.js +0 -11
- package/lib/commonjs/sdk/utils.js +73 -14
- package/lib/module/index.js +44 -31
- package/lib/module/sdk/autoTracking.js +0 -3
- package/lib/module/sdk/constants.js +1 -1
- package/lib/module/sdk/networkInterceptor.js +0 -11
- package/lib/module/sdk/utils.js +73 -14
- package/lib/typescript/sdk/constants.d.ts +1 -1
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/package.json +16 -4
- package/src/index.ts +42 -20
- package/src/sdk/autoTracking.ts +0 -2
- package/src/sdk/constants.ts +14 -14
- package/src/sdk/networkInterceptor.ts +0 -9
- 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;
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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)
|
|
156
|
-
|
|
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",
|
|
255
|
-
@"AIRMap",
|
|
256
|
-
@"AIRMapView",
|
|
257
|
-
@"RNMMapView",
|
|
258
|
-
@"GMSMapView",
|
|
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 &&
|
|
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 &&
|
|
502
|
-
|
|
503
|
-
|
|
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 &&
|
|
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 =
|
|
683
|
-
|
|
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
|
-
|
|
766
|
-
[self
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
|
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 =
|
|
1318
|
-
|
|
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
|
-
|
|
1335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1380
|
-
|
|
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
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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
|
}
|