@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.
Files changed (39) 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 +3 -26
  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/RJViewHierarchyScanner.m +68 -51
  15. package/ios/Core/RJLifecycleManager.m +0 -14
  16. package/ios/Core/Rejourney.mm +24 -37
  17. package/ios/Network/RJDeviceAuthManager.m +0 -2
  18. package/ios/Network/RJUploadManager.h +8 -0
  19. package/ios/Network/RJUploadManager.m +45 -0
  20. package/ios/Privacy/RJPrivacyMask.m +5 -31
  21. package/ios/Rejourney.h +0 -14
  22. package/ios/Touch/RJTouchInterceptor.m +21 -15
  23. package/ios/Utils/RJEventBuffer.m +57 -69
  24. package/ios/Utils/RJWindowUtils.m +87 -86
  25. package/lib/commonjs/index.js +42 -30
  26. package/lib/commonjs/sdk/autoTracking.js +0 -3
  27. package/lib/commonjs/sdk/networkInterceptor.js +0 -11
  28. package/lib/commonjs/sdk/utils.js +73 -14
  29. package/lib/module/index.js +42 -30
  30. package/lib/module/sdk/autoTracking.js +0 -3
  31. package/lib/module/sdk/networkInterceptor.js +0 -11
  32. package/lib/module/sdk/utils.js +73 -14
  33. package/lib/typescript/sdk/utils.d.ts +31 -1
  34. package/package.json +16 -4
  35. package/src/index.ts +40 -19
  36. package/src/sdk/autoTracking.ts +0 -2
  37. package/src/sdk/constants.ts +13 -13
  38. package/src/sdk/networkInterceptor.ts +0 -9
  39. 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]; // 0 = TextInput
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]; // 2 = WebView
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]; // 3 = Video
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) { // Camera / Video
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) { // WebView - Use same style as Camera as requested
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
- @"isTrackingEnabled=%d (skipCount=%d)",
58
- interceptor ? @"exists" : @"nil",
59
- interceptor ? interceptor.isTrackingEnabled : -1,
60
- swizzleSkipCount);
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
- @"isTrackingEnabled=%d",
152
- self.isTrackingEnabled);
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
- self.isTrackingEnabled);
167
+ self.isTrackingEnabled);
168
168
  } else {
169
169
  RJLogError(@"Failed to enable touch tracking - sendEvent: not found");
170
170
  }
171
171
  });
172
- RJLogInfo(@"[RJ-TOUCH-SETUP] enableGlobalTracking finished, isTrackingEnabled=%d",
173
- self.isTrackingEnabled);
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
- event ? @"exists" : @"nil", self.isTrackingEnabled);
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
- notRecordingCount);
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 mutableCopy];
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(@"[RJ-TOUCH] Calling delegate touchInterceptorDidRecognizeGesture");
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
- @"delegate=%@",
765
- delegate);
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 = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
40
- NSUserDomainMask, YES)
41
- .firstObject stringByAppendingPathComponent:@"rj_pending"];
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
- NSError *error = nil;
121
- NSString *content = [NSString stringWithContentsOfFile:self.eventsFilePath
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
- NSArray<NSString *> *lines = [content componentsSeparatedByString:@"\n"];
130
- NSInteger count = 0;
131
- NSTimeInterval lastTs = 0;
132
-
133
- for (NSString *line in lines) {
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
- if (@available(iOS 13.0, *)) {
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
- NSError *error = nil;
273
- NSString *content = [NSString stringWithContentsOfFile:self.eventsFilePath
274
- encoding:NSUTF8StringEncoding
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 subarrayWithRange:NSMakeRange(
314
- startIndex,
315
- allEvents.count - startIndex)];
302
+ return [allEvents
303
+ subarrayWithRange:NSMakeRange(startIndex, allEvents.count - startIndex)];
316
304
  }
317
305
 
318
306
  - (void)markEventsUploadedUpToIndex:(NSInteger)eventIndex {