@rejourneyco/react-native 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +3 -26
- 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/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 +42 -30
- package/lib/commonjs/sdk/autoTracking.js +0 -3
- package/lib/commonjs/sdk/networkInterceptor.js +0 -11
- package/lib/commonjs/sdk/utils.js +73 -14
- package/lib/module/index.js +42 -30
- package/lib/module/sdk/autoTracking.js +0 -3
- package/lib/module/sdk/networkInterceptor.js +0 -11
- package/lib/module/sdk/utils.js +73 -14
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/package.json +16 -4
- package/src/index.ts +40 -19
- package/src/sdk/autoTracking.ts +0 -2
- package/src/sdk/constants.ts +13 -13
- package/src/sdk/networkInterceptor.ts +0 -9
- package/src/sdk/utils.ts +76 -14
|
@@ -280,28 +280,19 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
if (context) {
|
|
283
|
-
// PUSH context so high-level UIKit drawing (NSString drawAtPoint) works
|
|
284
283
|
UIGraphicsPushContext(context);
|
|
285
284
|
|
|
286
|
-
// CRITICAL FIX: Flip coordinate system to match UIKit (Top-Left origin)
|
|
287
|
-
// CoreGraphics defaults to Bottom-Left origin, so without this flip,
|
|
288
|
-
// masking rects are drawn vertically mirrored (wrong place).
|
|
289
285
|
CGContextTranslateCTM(context, 0, height);
|
|
290
286
|
CGContextScaleCTM(context, 1.0, -1.0);
|
|
291
|
-
|
|
292
|
-
// Apply correct scale transform (Points -> Pixels)
|
|
293
287
|
CGContextScaleCTM(context, safeScale, safeScale);
|
|
294
288
|
|
|
295
|
-
// If in background or privacy scan is unavailable, draw full overlay
|
|
296
289
|
if (self.isInBackground || shouldMaskAll) {
|
|
297
|
-
// Create bounds in points (since we scaled the CTM)
|
|
298
290
|
CGRect boundsPoints =
|
|
299
291
|
CGRectMake(0, 0, width / safeScale, height / safeScale);
|
|
300
292
|
[self drawBackgroundOverlayInContext:context
|
|
301
293
|
bounds:boundsPoints
|
|
302
294
|
scale:1.0];
|
|
303
295
|
} else {
|
|
304
|
-
// Draw standard masks
|
|
305
296
|
[self drawMasksWithScanResult:scanResult
|
|
306
297
|
context:context
|
|
307
298
|
bounds:CGRectMake(0, 0, width / safeScale,
|
|
@@ -332,9 +323,7 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
332
323
|
self.lastFrameHadWebView = NO;
|
|
333
324
|
self.lastFrameHadVideo = NO;
|
|
334
325
|
|
|
335
|
-
// Handle background state
|
|
336
326
|
if (self.isInBackground) {
|
|
337
|
-
// Only draw background overlay if we have a method for it
|
|
338
327
|
if ([self respondsToSelector:@selector
|
|
339
328
|
(drawBackgroundOverlayInContext:bounds:scale:)]) {
|
|
340
329
|
[self drawBackgroundOverlayInContext:context bounds:bounds scale:scale];
|
|
@@ -342,7 +331,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
342
331
|
return;
|
|
343
332
|
}
|
|
344
333
|
|
|
345
|
-
// Early exit if nothing to mask - saves ~1-2ms per frame
|
|
346
334
|
if (scanResult.textInputFrames.count == 0 &&
|
|
347
335
|
scanResult.cameraFrames.count == 0 &&
|
|
348
336
|
scanResult.webViewFrames.count == 0 &&
|
|
@@ -363,7 +351,7 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
363
351
|
}
|
|
364
352
|
[self drawBlurRectInContext:context
|
|
365
353
|
frame:frame
|
|
366
|
-
maskType:0];
|
|
354
|
+
maskType:0];
|
|
367
355
|
self.lastFrameHadTextInput = YES;
|
|
368
356
|
}
|
|
369
357
|
|
|
@@ -396,7 +384,7 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
396
384
|
}
|
|
397
385
|
[self drawBlurRectInContext:context
|
|
398
386
|
frame:frame
|
|
399
|
-
maskType:2];
|
|
387
|
+
maskType:2];
|
|
400
388
|
self.lastFrameHadWebView = YES;
|
|
401
389
|
}
|
|
402
390
|
}
|
|
@@ -415,7 +403,7 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
415
403
|
}
|
|
416
404
|
[self drawBlurRectInContext:context
|
|
417
405
|
frame:frame
|
|
418
|
-
maskType:3];
|
|
406
|
+
maskType:3];
|
|
419
407
|
self.lastFrameHadVideo = YES;
|
|
420
408
|
}
|
|
421
409
|
}
|
|
@@ -427,17 +415,13 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
427
415
|
- (void)drawBackgroundOverlayInContext:(CGContextRef)context
|
|
428
416
|
bounds:(CGRect)bounds
|
|
429
417
|
scale:(CGFloat)scale {
|
|
430
|
-
// Simple black overlay for backgrounding
|
|
431
418
|
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
|
|
432
419
|
CGContextFillRect(context, bounds);
|
|
433
|
-
|
|
434
|
-
// Optional: Draw text or logo? Keeping it simple/fast.
|
|
435
420
|
}
|
|
436
421
|
|
|
437
422
|
- (void)drawBlurRectInContext:(CGContextRef)context
|
|
438
423
|
frame:(CGRect)frame
|
|
439
424
|
maskType:(NSInteger)maskType {
|
|
440
|
-
// Validate frame to prevent CoreGraphics NaN errors
|
|
441
425
|
if (isnan(frame.origin.x) || isnan(frame.origin.y) ||
|
|
442
426
|
isnan(frame.size.width) || isnan(frame.size.height) ||
|
|
443
427
|
isinf(frame.origin.x) || isinf(frame.origin.y) ||
|
|
@@ -446,7 +430,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
446
430
|
return;
|
|
447
431
|
}
|
|
448
432
|
|
|
449
|
-
// Also skip zero or negative sized frames
|
|
450
433
|
if (frame.size.width <= 0 || frame.size.height <= 0) {
|
|
451
434
|
return;
|
|
452
435
|
}
|
|
@@ -458,12 +441,11 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
458
441
|
cornerRadius:self.blurCornerRadius];
|
|
459
442
|
|
|
460
443
|
UIColor *blurColor;
|
|
461
|
-
if (maskType == 1 || maskType == 3) {
|
|
444
|
+
if (maskType == 1 || maskType == 3) {
|
|
462
445
|
blurColor = [UIColor colorWithRed:0.12 green:0.12 blue:0.15 alpha:1.0];
|
|
463
|
-
} else if (maskType == 2) {
|
|
446
|
+
} else if (maskType == 2) {
|
|
464
447
|
blurColor = [UIColor colorWithRed:0.12 green:0.12 blue:0.15 alpha:1.0];
|
|
465
448
|
} else {
|
|
466
|
-
// TextInput and others
|
|
467
449
|
blurColor = [UIColor blackColor];
|
|
468
450
|
}
|
|
469
451
|
|
|
@@ -480,7 +462,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
480
462
|
if (maskType == 1) {
|
|
481
463
|
[self drawCameraLabelInContext:context frame:frame];
|
|
482
464
|
} else if (maskType == 2) {
|
|
483
|
-
// Reuse camera label style but with different text
|
|
484
465
|
[self drawWebViewLabelInContext:context frame:frame];
|
|
485
466
|
} else if (maskType == 3) {
|
|
486
467
|
[self drawVideoLabelInContext:context frame:frame];
|
|
@@ -492,7 +473,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
492
473
|
}
|
|
493
474
|
|
|
494
475
|
- (void)drawCameraLabelInContext:(CGContextRef)context frame:(CGRect)frame {
|
|
495
|
-
// Skip if frame has invalid values
|
|
496
476
|
if (isnan(frame.size.width) || isnan(frame.size.height) ||
|
|
497
477
|
frame.size.width <= 0 || frame.size.height <= 0) {
|
|
498
478
|
return;
|
|
@@ -501,7 +481,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
501
481
|
CGFloat centerX = CGRectGetMidX(frame);
|
|
502
482
|
CGFloat centerY = CGRectGetMidY(frame);
|
|
503
483
|
|
|
504
|
-
// Validate centers aren't NaN
|
|
505
484
|
if (isnan(centerX) || isnan(centerY)) {
|
|
506
485
|
return;
|
|
507
486
|
}
|
|
@@ -524,7 +503,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
524
503
|
}
|
|
525
504
|
|
|
526
505
|
- (void)drawWebViewLabelInContext:(CGContextRef)context frame:(CGRect)frame {
|
|
527
|
-
// Skip if frame has invalid values
|
|
528
506
|
if (isnan(frame.size.width) || isnan(frame.size.height) ||
|
|
529
507
|
frame.size.width <= 0 || frame.size.height <= 0) {
|
|
530
508
|
return;
|
|
@@ -533,7 +511,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
533
511
|
CGFloat centerX = CGRectGetMidX(frame);
|
|
534
512
|
CGFloat centerY = CGRectGetMidY(frame);
|
|
535
513
|
|
|
536
|
-
// Validate centers aren't NaN
|
|
537
514
|
if (isnan(centerX) || isnan(centerY)) {
|
|
538
515
|
return;
|
|
539
516
|
}
|
|
@@ -586,7 +563,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
586
563
|
}
|
|
587
564
|
|
|
588
565
|
- (void)drawTextInputLabelInContext:(CGContextRef)context frame:(CGRect)frame {
|
|
589
|
-
// Skip if frame has invalid values
|
|
590
566
|
if (isnan(frame.size.width) || isnan(frame.size.height) ||
|
|
591
567
|
frame.size.width <= 0 || frame.size.height <= 0) {
|
|
592
568
|
return;
|
|
@@ -595,7 +571,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
595
571
|
CGFloat centerX = CGRectGetMidX(frame);
|
|
596
572
|
CGFloat centerY = CGRectGetMidY(frame);
|
|
597
573
|
|
|
598
|
-
// Validate centers aren't NaN
|
|
599
574
|
if (isnan(centerX) || isnan(centerY)) {
|
|
600
575
|
return;
|
|
601
576
|
}
|
|
@@ -664,7 +639,6 @@ static inline BOOL RJIsValidMaskFrame(CGRect frame) {
|
|
|
664
639
|
if (isTextInput || isCamera || isWebView || isVideo || isManuallyMasked) {
|
|
665
640
|
CGRect frameInWindow = [view convertRect:view.bounds toView:window];
|
|
666
641
|
|
|
667
|
-
// Sanitize NaN/Inf values from convertRect:toView:
|
|
668
642
|
CGFloat x = (isnan(frameInWindow.origin.x) || isinf(frameInWindow.origin.x))
|
|
669
643
|
? 0
|
|
670
644
|
: frameInWindow.origin.x;
|
package/ios/Rejourney.h
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
-
// you may not use this file except in compliance with the License.
|
|
3
|
-
// You may obtain a copy of the License at
|
|
4
|
-
//
|
|
5
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
//
|
|
7
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
8
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
-
// See the License for the specific language governing permissions and
|
|
11
|
-
// limitations under the License.
|
|
12
|
-
//
|
|
13
|
-
// Copyright (c) 2026 Rejourney
|
|
14
|
-
|
|
15
1
|
//
|
|
16
2
|
//
|
|
17
3
|
// Rejourney.h
|
|
@@ -54,10 +54,10 @@ static void RJ_swizzled_sendEvent(id self, SEL _cmd, UIEvent *event) {
|
|
|
54
54
|
static int swizzleSkipCount = 0;
|
|
55
55
|
if (++swizzleSkipCount % 500 == 1) {
|
|
56
56
|
RJLogInfo(@"[RJ-TOUCH-SWIZZLE] Touch not handled: interceptor=%@, "
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
@"isTrackingEnabled=%d (skipCount=%d)",
|
|
58
|
+
interceptor ? @"exists" : @"nil",
|
|
59
|
+
interceptor ? interceptor.isTrackingEnabled : -1,
|
|
60
|
+
swizzleSkipCount);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -148,8 +148,8 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
148
148
|
|
|
149
149
|
- (void)enableGlobalTracking {
|
|
150
150
|
RJLogInfo(@"[RJ-TOUCH-SETUP] enableGlobalTracking called, current "
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
@"isTrackingEnabled=%d",
|
|
152
|
+
self.isTrackingEnabled);
|
|
153
153
|
static dispatch_once_t onceToken;
|
|
154
154
|
dispatch_once(&onceToken, ^{
|
|
155
155
|
RJLogInfo(@"[RJ-TOUCH-SETUP] First-time setup - performing swizzle");
|
|
@@ -164,13 +164,14 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
164
164
|
_swizzlingPerformed = YES;
|
|
165
165
|
self.isTrackingEnabled = YES;
|
|
166
166
|
RJLogInfo(@"[RJ-TOUCH-SETUP] Swizzle complete, isTrackingEnabled=%d",
|
|
167
|
-
|
|
167
|
+
self.isTrackingEnabled);
|
|
168
168
|
} else {
|
|
169
169
|
RJLogError(@"Failed to enable touch tracking - sendEvent: not found");
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
|
-
RJLogInfo(
|
|
173
|
-
|
|
172
|
+
RJLogInfo(
|
|
173
|
+
@"[RJ-TOUCH-SETUP] enableGlobalTracking finished, isTrackingEnabled=%d",
|
|
174
|
+
self.isTrackingEnabled);
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
#pragma mark - Touch Event Handling
|
|
@@ -181,7 +182,7 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
181
182
|
static int skipCount = 0;
|
|
182
183
|
if (++skipCount % 100 == 1) {
|
|
183
184
|
RJLogInfo(@"[RJ-TOUCH] Skipping touch: event=%@, isTrackingEnabled=%d",
|
|
184
|
-
|
|
185
|
+
event ? @"exists" : @"nil", self.isTrackingEnabled);
|
|
185
186
|
}
|
|
186
187
|
return;
|
|
187
188
|
}
|
|
@@ -205,7 +206,7 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
205
206
|
static int notRecordingCount = 0;
|
|
206
207
|
if (++notRecordingCount % 100 == 1) {
|
|
207
208
|
RJLogInfo(@"[RJ-TOUCH] Not recording, skipping touch (count=%d)",
|
|
208
|
-
|
|
209
|
+
notRecordingCount);
|
|
209
210
|
}
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
@@ -465,7 +466,11 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
465
466
|
@try {
|
|
466
467
|
|
|
467
468
|
NSMutableDictionary<NSNumber *, NSArray *> *touchPathsCopy =
|
|
468
|
-
[self.touchPaths
|
|
469
|
+
[NSMutableDictionary dictionaryWithCapacity:self.touchPaths.count];
|
|
470
|
+
[self.touchPaths enumerateKeysAndObjectsUsingBlock:^(
|
|
471
|
+
NSNumber *key, NSMutableArray *obj, BOOL *stop) {
|
|
472
|
+
touchPathsCopy[key] = [obj copy];
|
|
473
|
+
}];
|
|
469
474
|
NSTimeInterval duration = MAX(0, timestamp - self.touchStartTime);
|
|
470
475
|
NSInteger touchCount = self.touchPaths.count;
|
|
471
476
|
CGPoint startPoint = self.touchStartPoint;
|
|
@@ -753,7 +758,8 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
753
758
|
respondsToSelector:@selector
|
|
754
759
|
(touchInterceptorDidRecognizeGesture:
|
|
755
760
|
touches:duration:targetLabel:)]) {
|
|
756
|
-
RJLogInfo(
|
|
761
|
+
RJLogInfo(
|
|
762
|
+
@"[RJ-TOUCH] Calling delegate touchInterceptorDidRecognizeGesture");
|
|
757
763
|
[delegate
|
|
758
764
|
touchInterceptorDidRecognizeGesture:gestureType ?: RJGestureTypeTap
|
|
759
765
|
touches:touches ?: @[]
|
|
@@ -761,8 +767,8 @@ static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
|
761
767
|
targetLabel:targetLabel];
|
|
762
768
|
} else {
|
|
763
769
|
RJLogInfo(@"[RJ-TOUCH] Delegate missing or doesn't respond to selector: "
|
|
764
|
-
|
|
765
|
-
|
|
770
|
+
@"delegate=%@",
|
|
771
|
+
delegate);
|
|
766
772
|
}
|
|
767
773
|
} @catch (NSException *exception) {
|
|
768
774
|
RJLogWarning(@"Gesture notification failed: %@", exception);
|
|
@@ -36,9 +36,10 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
36
36
|
if (self) {
|
|
37
37
|
_sessionId = [sessionId copy];
|
|
38
38
|
if (pendingRootPath.length == 0) {
|
|
39
|
-
NSString *defaultPath =
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
NSString *defaultPath =
|
|
40
|
+
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
|
|
41
|
+
NSUserDomainMask, YES)
|
|
42
|
+
.firstObject stringByAppendingPathComponent:@"rj_pending"];
|
|
42
43
|
_pendingRootPath = [defaultPath copy];
|
|
43
44
|
} else {
|
|
44
45
|
_pendingRootPath = [pendingRootPath copy];
|
|
@@ -68,7 +69,6 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
68
69
|
NSString *sessionDir =
|
|
69
70
|
[self.pendingRootPath stringByAppendingPathComponent:self.sessionId];
|
|
70
71
|
|
|
71
|
-
|
|
72
72
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
73
73
|
if (![fm fileExistsAtPath:sessionDir]) {
|
|
74
74
|
NSError *error = nil;
|
|
@@ -87,12 +87,11 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
87
87
|
|
|
88
88
|
if (![fm fileExistsAtPath:self.eventsFilePath]) {
|
|
89
89
|
NSDictionary *attrs = @{
|
|
90
|
-
NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication
|
|
90
|
+
NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication
|
|
91
91
|
};
|
|
92
92
|
[fm createFileAtPath:self.eventsFilePath contents:nil attributes:attrs];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
95
|
NSError *error = nil;
|
|
97
96
|
self.fileHandle =
|
|
98
97
|
[NSFileHandle fileHandleForWritingAtPath:self.eventsFilePath];
|
|
@@ -102,14 +101,12 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
102
101
|
return;
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
|
|
106
104
|
if (@available(iOS 13.0, *)) {
|
|
107
105
|
[self.fileHandle seekToEndReturningOffset:nil error:&error];
|
|
108
106
|
} else {
|
|
109
107
|
[self.fileHandle seekToEndOfFile];
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
|
|
113
110
|
[self countExistingEvents];
|
|
114
111
|
|
|
115
112
|
RJLogDebug(@"Event buffer ready: %@ (%ld existing events)",
|
|
@@ -117,40 +114,57 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
- (void)countExistingEvents {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
encoding:NSUTF8StringEncoding
|
|
123
|
-
error:&error];
|
|
124
|
-
if (error || !content) {
|
|
125
|
-
self.eventCount = 0;
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
117
|
+
__block NSInteger count = 0;
|
|
118
|
+
__block NSTimeInterval lastTs = 0;
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (line.length == 0)
|
|
135
|
-
continue;
|
|
136
|
-
|
|
137
|
-
NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
|
|
138
|
-
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
139
|
-
options:0
|
|
140
|
-
error:nil];
|
|
141
|
-
if (event) {
|
|
142
|
-
count++;
|
|
143
|
-
NSNumber *ts = event[@"timestamp"];
|
|
144
|
-
if (ts && [ts doubleValue] > lastTs) {
|
|
145
|
-
lastTs = [ts doubleValue];
|
|
146
|
-
}
|
|
120
|
+
[self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
|
|
121
|
+
count++;
|
|
122
|
+
NSNumber *ts = event[@"timestamp"];
|
|
123
|
+
if (ts && [ts doubleValue] > lastTs) {
|
|
124
|
+
lastTs = [ts doubleValue];
|
|
147
125
|
}
|
|
148
|
-
}
|
|
126
|
+
}];
|
|
149
127
|
|
|
150
128
|
self.eventCount = count;
|
|
151
129
|
self.lastEventTimestamp = lastTs;
|
|
152
130
|
}
|
|
153
131
|
|
|
132
|
+
- (void)enumerateEventsWithBlock:(void (^)(NSDictionary *event,
|
|
133
|
+
BOOL *stop))block {
|
|
134
|
+
if (!block || !self.eventsFilePath)
|
|
135
|
+
return;
|
|
136
|
+
|
|
137
|
+
FILE *file = fopen([self.eventsFilePath UTF8String], "r");
|
|
138
|
+
if (!file)
|
|
139
|
+
return;
|
|
140
|
+
|
|
141
|
+
char *line = NULL;
|
|
142
|
+
size_t linecap = 0;
|
|
143
|
+
ssize_t linelen;
|
|
144
|
+
BOOL stop = NO;
|
|
145
|
+
|
|
146
|
+
while (!stop && (linelen = getline(&line, &linecap, file)) > 0) {
|
|
147
|
+
@autoreleasepool {
|
|
148
|
+
if (linelen <= 1)
|
|
149
|
+
continue;
|
|
150
|
+
|
|
151
|
+
NSData *data = [NSData dataWithBytesNoCopy:line
|
|
152
|
+
length:linelen
|
|
153
|
+
freeWhenDone:NO];
|
|
154
|
+
NSError *error = nil;
|
|
155
|
+
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
156
|
+
options:0
|
|
157
|
+
error:&error];
|
|
158
|
+
if (event) {
|
|
159
|
+
block(event, &stop);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (line)
|
|
164
|
+
free(line);
|
|
165
|
+
fclose(file);
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
- (void)closeFileHandle {
|
|
155
169
|
if (self.fileHandle) {
|
|
156
170
|
if (@available(iOS 13.0, *)) {
|
|
@@ -222,11 +236,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
222
236
|
return NO;
|
|
223
237
|
}
|
|
224
238
|
|
|
225
|
-
|
|
226
239
|
NSMutableData *lineData = [jsonData mutableCopy];
|
|
227
240
|
[lineData appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
228
241
|
|
|
229
|
-
|
|
230
242
|
if (@available(iOS 13.4, *)) {
|
|
231
243
|
NSError *writeError = nil;
|
|
232
244
|
[self.fileHandle writeData:lineData error:&writeError];
|
|
@@ -243,14 +255,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
[self.fileHandle synchronizeAndReturnError:nil];
|
|
249
|
-
} else {
|
|
250
|
-
[self.fileHandle synchronizeFile];
|
|
251
|
-
}
|
|
258
|
+
// Removed redundant synchronizeFile (fsync) calls to improve performance.
|
|
259
|
+
// The serial writeQueue ensures order, and the OS will manage buffering.
|
|
252
260
|
|
|
253
|
-
|
|
254
261
|
self.eventCount++;
|
|
255
262
|
NSNumber *ts = event[@"timestamp"];
|
|
256
263
|
if (ts) {
|
|
@@ -269,27 +276,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
269
276
|
__block NSMutableArray<NSDictionary *> *events = [NSMutableArray new];
|
|
270
277
|
|
|
271
278
|
[self performWriteSync:^{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
error:&error];
|
|
276
|
-
if (error || !content) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
NSArray<NSString *> *lines = [content componentsSeparatedByString:@"\n"];
|
|
281
|
-
for (NSString *line in lines) {
|
|
282
|
-
if (line.length == 0)
|
|
283
|
-
continue;
|
|
284
|
-
|
|
285
|
-
NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
|
|
286
|
-
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
287
|
-
options:0
|
|
288
|
-
error:nil];
|
|
289
|
-
if (event) {
|
|
290
|
-
[events addObject:event];
|
|
291
|
-
}
|
|
292
|
-
}
|
|
279
|
+
[self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
|
|
280
|
+
[events addObject:event];
|
|
281
|
+
}];
|
|
293
282
|
}];
|
|
294
283
|
|
|
295
284
|
return events;
|
|
@@ -297,7 +286,7 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
297
286
|
|
|
298
287
|
- (NSArray<NSDictionary *> *)readEventsAfterBatchNumber:
|
|
299
288
|
(NSInteger)afterBatchNumber {
|
|
300
|
-
|
|
289
|
+
|
|
301
290
|
NSArray<NSDictionary *> *allEvents = [self readAllEvents];
|
|
302
291
|
|
|
303
292
|
__block NSInteger uploadedCount = 0;
|
|
@@ -310,9 +299,8 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
310
299
|
return @[];
|
|
311
300
|
}
|
|
312
301
|
|
|
313
|
-
return [allEvents
|
|
314
|
-
|
|
315
|
-
allEvents.count - startIndex)];
|
|
302
|
+
return [allEvents
|
|
303
|
+
subarrayWithRange:NSMakeRange(startIndex, allEvents.count - startIndex)];
|
|
316
304
|
}
|
|
317
305
|
|
|
318
306
|
- (void)markEventsUploadedUpToIndex:(NSInteger)eventIndex {
|