@rejourneyco/react-native 1.0.0 → 1.0.2

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 (49) hide show
  1. package/README.md +29 -0
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +47 -30
  3. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +25 -1
  4. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +70 -32
  5. package/android/src/main/java/com/rejourney/core/Constants.kt +4 -4
  6. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
  7. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
  8. package/ios/Capture/RJCaptureEngine.m +72 -34
  9. package/ios/Capture/RJCaptureHeuristics.h +7 -5
  10. package/ios/Capture/RJCaptureHeuristics.m +138 -112
  11. package/ios/Capture/RJVideoEncoder.m +0 -26
  12. package/ios/Core/Rejourney.mm +64 -102
  13. package/ios/Utils/RJPerfTiming.m +0 -5
  14. package/ios/Utils/RJWindowUtils.m +0 -1
  15. package/lib/commonjs/components/Mask.js +1 -6
  16. package/lib/commonjs/index.js +12 -101
  17. package/lib/commonjs/sdk/autoTracking.js +55 -353
  18. package/lib/commonjs/sdk/constants.js +2 -13
  19. package/lib/commonjs/sdk/errorTracking.js +1 -29
  20. package/lib/commonjs/sdk/metricsTracking.js +3 -24
  21. package/lib/commonjs/sdk/navigation.js +3 -42
  22. package/lib/commonjs/sdk/networkInterceptor.js +7 -49
  23. package/lib/commonjs/sdk/utils.js +0 -5
  24. package/lib/module/components/Mask.js +1 -6
  25. package/lib/module/index.js +11 -105
  26. package/lib/module/sdk/autoTracking.js +55 -354
  27. package/lib/module/sdk/constants.js +2 -13
  28. package/lib/module/sdk/errorTracking.js +1 -29
  29. package/lib/module/sdk/index.js +0 -2
  30. package/lib/module/sdk/metricsTracking.js +3 -24
  31. package/lib/module/sdk/navigation.js +3 -42
  32. package/lib/module/sdk/networkInterceptor.js +7 -49
  33. package/lib/module/sdk/utils.js +0 -5
  34. package/lib/typescript/NativeRejourney.d.ts +2 -0
  35. package/lib/typescript/sdk/autoTracking.d.ts +5 -6
  36. package/lib/typescript/types/index.d.ts +0 -1
  37. package/package.json +11 -3
  38. package/src/NativeRejourney.ts +4 -0
  39. package/src/components/Mask.tsx +0 -3
  40. package/src/index.ts +11 -88
  41. package/src/sdk/autoTracking.ts +72 -331
  42. package/src/sdk/constants.ts +13 -13
  43. package/src/sdk/errorTracking.ts +1 -17
  44. package/src/sdk/index.ts +0 -2
  45. package/src/sdk/metricsTracking.ts +5 -33
  46. package/src/sdk/navigation.ts +8 -29
  47. package/src/sdk/networkInterceptor.ts +9 -33
  48. package/src/sdk/utils.ts +0 -5
  49. package/src/types/index.ts +0 -29
@@ -46,6 +46,7 @@
46
46
  #import <UIKit/UIKit.h>
47
47
  #import <mach/mach_time.h>
48
48
  #import <sys/sysctl.h>
49
+ #import <sys/utsname.h>
49
50
 
50
51
  static uint64_t _rj_constructorMachTime = 0;
51
52
  static NSTimeInterval _rj_constructorWallTimeMs = 0;
@@ -608,6 +609,34 @@ RCT_EXPORT_METHOD(debugTriggerANR : (double)durationMs) {
608
609
  });
609
610
  }
610
611
 
612
+ RCT_EXPORT_METHOD(getDeviceInfo : (RCTPromiseResolveBlock)
613
+ resolve reject : (RCTPromiseRejectBlock)reject) {
614
+ NSMutableDictionary *info = [NSMutableDictionary new];
615
+
616
+ // Model
617
+ struct utsname systemInfo;
618
+ uname(&systemInfo);
619
+ NSString *modelCode = [NSString stringWithCString:systemInfo.machine
620
+ encoding:NSUTF8StringEncoding];
621
+ info[@"model"] = modelCode ?: [[UIDevice currentDevice] model];
622
+
623
+ info[@"brand"] = @"Apple";
624
+ info[@"systemName"] = [[UIDevice currentDevice] systemName];
625
+ info[@"systemVersion"] = [[UIDevice currentDevice] systemVersion];
626
+ info[@"bundleId"] = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
627
+ info[@"appVersion"] =
628
+ [[NSBundle mainBundle]
629
+ objectForInfoDictionaryKey:@"CFBundleShortVersionString"]
630
+ ?: @"";
631
+ info[@"buildNumber"] =
632
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
633
+ ?: @"";
634
+ info[@"isTablet"] = @([[UIDevice currentDevice] userInterfaceIdiom] ==
635
+ UIUserInterfaceIdiomPad);
636
+
637
+ resolve(info);
638
+ }
639
+
611
640
  RCT_EXPORT_METHOD(getSessionId : (RCTPromiseResolveBlock)
612
641
  resolve reject : (RCTPromiseRejectBlock)reject) {
613
642
  NSString *sessionId = self.currentSessionId;
@@ -943,33 +972,51 @@ RCT_EXPORT_METHOD(setDebugMode : (BOOL)enabled resolve : (
943
972
 
944
973
  RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
945
974
  RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
946
- dispatch_async(dispatch_get_main_queue(), ^{
975
+ dispatch_async(self.stateQueue, ^{
947
976
  @try {
948
977
  NSString *safeUserId = userId.length > 0 ? userId : @"anonymous";
949
978
 
950
- [self performStateSync:^{
951
- self.userId = safeUserId;
952
- }];
979
+ [[NSUserDefaults standardUserDefaults] setObject:safeUserId
980
+ forKey:@"rj_user_identity"];
981
+ [[NSUserDefaults standardUserDefaults] synchronize];
953
982
 
983
+ self.userId = safeUserId;
954
984
  if (self.uploadManager) {
955
985
  self.uploadManager.userId = safeUserId;
956
986
  }
957
987
 
958
- RJLogDebug(@"User identity updated: %@", safeUserId);
988
+ RJLogDebug(@"User identity updated and persisted: %@", safeUserId);
959
989
 
960
990
  if (self.isRecording) {
961
- [self logEventInternal:@"user_identity_changed"
962
- details:@{@"userId" : safeUserId}];
991
+ NSMutableDictionary *event = [NSMutableDictionary new];
992
+ event[@"type"] = @"user_identity_changed";
993
+ event[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
994
+ event[@"userId"] = safeUserId;
995
+
996
+ if (self.eventBuffer) {
997
+ [self.eventBuffer appendEvent:event];
998
+ }
999
+ if (self.sessionEvents && !self.isShuttingDown) {
1000
+ [self.sessionEvents addObject:event];
1001
+ }
963
1002
  }
964
1003
 
965
- resolve(@{@"success" : @YES});
1004
+ if (resolve)
1005
+ resolve(@{@"success" : @YES});
966
1006
  } @catch (NSException *exception) {
967
- RJLogWarning(@"setUserIdentity failed: %@", exception);
968
- resolve(@{@"success" : @NO});
1007
+ if (resolve)
1008
+ resolve(@{@"success" : @NO});
969
1009
  }
970
1010
  });
971
1011
  }
972
1012
 
1013
+ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
1014
+ resolve reject : (RCTPromiseRejectBlock)reject) {
1015
+ NSString *userId =
1016
+ [[NSUserDefaults standardUserDefaults] stringForKey:@"rj_user_identity"];
1017
+ resolve(userId ?: [NSNull null]);
1018
+ }
1019
+
973
1020
  #pragma mark - RJTouchInterceptorDelegate
974
1021
 
975
1022
  - (void)touchInterceptorDidDetectInteractionStart {
@@ -987,17 +1034,15 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
987
1034
  return;
988
1035
  }
989
1036
 
990
- // Throttle scroll events to avoid spamming the main thread/logs
991
1037
  if ([gestureType hasPrefix:@"scroll"]) {
992
1038
  static NSTimeInterval lastScrollLogTime = 0;
993
1039
  NSTimeInterval now = CACurrentMediaTime();
994
- if (now - lastScrollLogTime < 0.5) { // 500ms throttle
1040
+ if (now - lastScrollLogTime < 0.5) {
995
1041
  return;
996
1042
  }
997
1043
  lastScrollLogTime = now;
998
1044
  }
999
1045
 
1000
- // Move ALL processing to the background state queue to unblock Main Thread
1001
1046
  dispatch_async(self.stateQueue, ^{
1002
1047
  @try {
1003
1048
  NSMutableDictionary *details =
@@ -1009,18 +1054,8 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1009
1054
  @"targetLabel" : targetLabel ?: [NSNull null]
1010
1055
  }];
1011
1056
 
1012
- // Log internal event (already on stateQueue, so safe)
1013
- // We call a simpler version that assumes we are already on background or
1014
- // handles it Actually logEventInternal dispatches TO stateQueue. Since we
1015
- // are ON stateQueue, we can call a direct helper or just logEventInternal
1016
- // (it will just dispatch_async again which is fine) But to be cleaner,
1017
- // let's just use logEventInternal but ensuring we don't do main thread
1018
- // dictionary work above. We moved the dictionary creation HERE
1019
- // (background).
1020
-
1021
1057
  [self logEventInternal:RJEventTypeGesture details:[details copy]];
1022
1058
 
1023
- // Notify engine on main thread (it's lightweight)
1024
1059
  dispatch_async(dispatch_get_main_queue(), ^{
1025
1060
  if (self.captureEngine) {
1026
1061
  [self.captureEngine notifyGesture:gestureType];
@@ -1102,11 +1137,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1102
1137
  RJLogError(@"Device registration failed: %@",
1103
1138
  error);
1104
1139
 
1105
- // SECURITY: Handle specific error codes
1106
- // 403 = Bundle ID mismatch or forbidden - PERMANENT
1107
- // failure 404 = Project not found - could be
1108
- // temporary, retry Other = Network/transient -
1109
- // retry
1110
1140
  if (error.code == 403) {
1111
1141
  RJLogError(@"SECURITY: Bundle ID mismatch or "
1112
1142
  @"access forbidden. "
@@ -1115,9 +1145,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1115
1145
  strongSelf.authPermanentlyFailed = YES;
1116
1146
  [strongSelf handleAuthenticationFailure:error];
1117
1147
  } else {
1118
- // For 404 and other errors, schedule retry with
1119
- // exponential backoff Recording continues locally
1120
- // - events queued for later upload
1121
1148
  [strongSelf scheduleAuthRetryWithError:error
1122
1149
  publicKey:publicKey
1123
1150
  apiUrl:apiUrl];
@@ -1125,7 +1152,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1125
1152
  } else {
1126
1153
  RJLogDebug(@"Device registered: %@", credId);
1127
1154
 
1128
- // Auth succeeded - reset retry state
1129
1155
  [strongSelf resetAuthRetryState];
1130
1156
 
1131
1157
  [strongSelf
@@ -1140,21 +1166,16 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1140
1166
  - (void)handleAuthenticationFailure:(NSError *)error {
1141
1167
  RJLogError(@"Authentication failure - stopping recording. Error: %@", error);
1142
1168
 
1143
- // Stop recording to prevent data accumulation that can't be uploaded
1144
1169
  dispatch_async(dispatch_get_main_queue(), ^{
1145
1170
  if (self.isRecording) {
1146
1171
  self.isRecording = NO;
1147
1172
 
1148
- // Stop capture engine
1149
1173
  if (self.captureEngine) {
1150
1174
  [self.captureEngine stopSession];
1151
1175
  }
1152
1176
 
1153
- // Clear auth data so next attempt starts fresh
1154
1177
  [[RJDeviceAuthManager sharedManager] clearAllAuthData];
1155
1178
 
1156
- // Notify JS layer about the failure (if bridge is available)
1157
- // This allows the app to handle the error (e.g., show user message)
1158
1179
  @try {
1159
1180
  if (self.bridge) {
1160
1181
  [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter"
@@ -1181,22 +1202,18 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1181
1202
  - (void)scheduleAuthRetryWithError:(NSError *)error
1182
1203
  publicKey:(NSString *)publicKey
1183
1204
  apiUrl:(NSString *)apiUrl {
1184
- // Check if already permanently failed (403 security error)
1185
1205
  if (self.authPermanentlyFailed) {
1186
1206
  RJLogWarning(@"Auth permanently failed - not scheduling retry");
1187
1207
  return;
1188
1208
  }
1189
1209
 
1190
- // Increment retry count
1191
1210
  self.authRetryCount++;
1192
1211
 
1193
- // Check max retries
1194
1212
  if (self.authRetryCount > RJ_MAX_AUTH_RETRIES) {
1195
1213
  RJLogError(@"Auth failed after %ld retries. Recording continues locally, "
1196
1214
  @"events will be uploaded when auth succeeds.",
1197
1215
  (long)RJ_MAX_AUTH_RETRIES);
1198
1216
 
1199
- // Notify JS but DON'T stop recording - events queue locally
1200
1217
  dispatch_async(dispatch_get_main_queue(), ^{
1201
1218
  @try {
1202
1219
  if (self.bridge) {
@@ -1213,18 +1230,15 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1213
1230
  completion:nil];
1214
1231
  }
1215
1232
  } @catch (NSException *exception) {
1216
- // Ignore JS notification failures
1217
1233
  }
1218
1234
  });
1219
1235
 
1220
- // Schedule a much longer retry (5 minutes) to try again later
1221
1236
  [self scheduleBackgroundAuthRetryAfter:300.0
1222
1237
  publicKey:publicKey
1223
1238
  apiUrl:apiUrl];
1224
1239
  return;
1225
1240
  }
1226
1241
 
1227
- // Calculate exponential backoff delay: 2, 4, 8, 16, 32... capped at 60s
1228
1242
  NSTimeInterval delay =
1229
1243
  MIN(RJ_AUTH_RETRY_BASE_DELAY * pow(2, self.authRetryCount - 1),
1230
1244
  RJ_AUTH_RETRY_MAX_DELAY);
@@ -1242,7 +1256,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1242
1256
  - (void)scheduleBackgroundAuthRetryAfter:(NSTimeInterval)delay
1243
1257
  publicKey:(NSString *)publicKey
1244
1258
  apiUrl:(NSString *)apiUrl {
1245
- // Cancel any existing retry timer
1246
1259
  if (self.authRetryTimer) {
1247
1260
  [self.authRetryTimer invalidate];
1248
1261
  self.authRetryTimer = nil;
@@ -1275,14 +1288,11 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1275
1288
 
1276
1289
  RJLogInfo(@"Retrying auth (attempt %ld)...", (long)(self.authRetryCount + 1));
1277
1290
 
1278
- // After 2 failed attempts, clear cached auth data and re-register fresh
1279
- // This handles expired/corrupted tokens or server-side revocations
1280
1291
  if (self.authRetryCount >= 2) {
1281
1292
  RJLogInfo(@"Clearing cached auth data and re-registering fresh...");
1282
1293
  [[RJDeviceAuthManager sharedManager] clearAllAuthData];
1283
1294
  }
1284
1295
 
1285
- // Re-attempt device auth setup
1286
1296
  [self setupDeviceAuthWithPublicKey:publicKey apiUrl:apiUrl];
1287
1297
  }
1288
1298
 
@@ -1351,8 +1361,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1351
1361
  apiUrl
1352
1362
  isRetry:YES];
1353
1363
  } else {
1354
- // Re-registration also failed - this is a
1355
- // security error
1356
1364
  RJLogError(@"Re-registration failed: %@",
1357
1365
  retryError);
1358
1366
  if (retryError.code == 403 ||
@@ -1363,12 +1371,10 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1363
1371
  }
1364
1372
  }];
1365
1373
  } else {
1366
- // Already retried - this is a persistent security error
1367
1374
  RJLogError(@"Token fetch failed after retry: %@", error);
1368
1375
  [strongSelf handleAuthenticationFailure:error];
1369
1376
  }
1370
1377
  } else {
1371
- // Network or other transient errors
1372
1378
  RJLogWarning(@"Failed to get upload token (transient): %@", error);
1373
1379
  }
1374
1380
  }];
@@ -1536,7 +1542,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1536
1542
  if (self.isShuttingDown || !eventType)
1537
1543
  return;
1538
1544
 
1539
- // Log gesture events specifically for debugging
1540
1545
  if ([eventType isEqualToString:@"gesture"]) {
1541
1546
  NSString *gestureType = details[@"gestureType"] ?: @"unknown";
1542
1547
  RJLogInfo(
@@ -1753,9 +1758,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1753
1758
  });
1754
1759
  }
1755
1760
 
1756
- /// Flush data with option to end session or keep it alive for resumption.
1757
- /// @param isFinal If YES, ends the session on backend. If NO, just uploads
1758
- /// pending data.
1759
1761
  - (void)flushDataWithCompletion:(RJCompletionHandler)completion
1760
1762
  isFinal:(BOOL)isFinal {
1761
1763
 
@@ -1813,7 +1815,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1813
1815
  return;
1814
1816
  }
1815
1817
 
1816
- // For non-final flushes, skip promotion evaluation and just upload
1817
1818
  if (!isFinal) {
1818
1819
  RJLogInfo(
1819
1820
  @"[RJ-FLUSH] Non-final background flush - uploading %lu "
@@ -1894,13 +1895,10 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1894
1895
  }
1895
1896
  }
1896
1897
 
1897
- /// Convenience method for final flush (ends session).
1898
1898
  - (void)flushAllDataWithCompletion:(RJCompletionHandler)completion {
1899
1899
  [self flushDataWithCompletion:completion isFinal:YES];
1900
1900
  }
1901
1901
 
1902
- /// Flush pending data for background transition without ending the session.
1903
- /// Used when app goes to background but may return quickly.
1904
1902
  - (void)flushDataForBackgroundWithCompletion:(RJCompletionHandler)completion {
1905
1903
  [self flushDataWithCompletion:completion isFinal:NO];
1906
1904
  }
@@ -1939,7 +1937,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1939
1937
  if (self.isRecording) {
1940
1938
  RJLogDebug(@"[KEYBOARD] Keyboard shown (height=%.0f)",
1941
1939
  keyboardFrame.size.height);
1942
- // Include keyboard height so web UI can render overlay
1943
1940
  [self logEventInternal:RJEventTypeKeyboardShow
1944
1941
  details:@{
1945
1942
  @"keyboardHeight" : @(keyboardFrame.size.height),
@@ -1999,9 +1996,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
1999
1996
  @try {
2000
1997
  [self stopBatchUploadTimer];
2001
1998
 
2002
- // CRITICAL: Sync accumulated background time to uploadManager BEFORE any
2003
- // flush This ensures if the session ends during background, the correct
2004
- // time is included
2005
1999
  if (self.uploadManager && self.lifecycleManager) {
2006
2000
  NSTimeInterval currentBgTime =
2007
2001
  self.lifecycleManager.totalBackgroundTimeMs;
@@ -2011,14 +2005,10 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2011
2005
  currentBgTime);
2012
2006
  }
2013
2007
 
2014
- // Log background event FIRST and SYNCHRONOUSLY so it's included in the
2015
- // flush This must happen before we pause capture or start the flush
2016
2008
  NSMutableDictionary *bgEvent =
2017
2009
  [NSMutableDictionary dictionaryWithCapacity:3];
2018
2010
  bgEvent[@"type"] = RJEventTypeAppBackground;
2019
2011
  bgEvent[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
2020
-
2021
- // Add directly to session events synchronously
2022
2012
  [self performStateSync:^{
2023
2013
  if (self.sessionEvents) {
2024
2014
  [self.sessionEvents addObject:bgEvent];
@@ -2028,17 +2018,12 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2028
2018
  }
2029
2019
  }];
2030
2020
 
2031
- // Also add to event buffer for persistence
2032
2021
  if (self.eventBuffer) {
2033
2022
  [self.eventBuffer appendEvent:bgEvent];
2034
2023
  }
2035
2024
 
2036
2025
  if (self.captureEngine) {
2037
2026
  @try {
2038
- // Use ASYNCHRONOUS pause for backgrounding.
2039
- // The encoder now implements internal UIBackgroundTask protection, so
2040
- // it will finish finalizing the MP4 on a background queue without
2041
- // locking the UI.
2042
2027
  RJLogInfo(@"[RJ-VIDEO] Pausing video capture for background (ASYNC)");
2043
2028
  [self.captureEngine pauseVideoCapture];
2044
2029
  RJLogInfo(@"[RJ-VIDEO] Video capture pause initiated");
@@ -2053,9 +2038,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2053
2038
  beginBackgroundTaskWithName:@"RejourneySessionFlush"];
2054
2039
  }
2055
2040
 
2056
- // Use non-final flush for background - session may resume if user returns
2057
- // quickly This avoids calling session/end which would prevent frame capture
2058
- // after returning
2059
2041
  RJLogInfo(@"[RJ-FLUSH] Starting non-final background flush (session will "
2060
2042
  @"resume if user returns)");
2061
2043
  [self flushDataForBackgroundWithCompletion:^(BOOL success) {
@@ -2108,13 +2090,9 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2108
2090
  @try {
2109
2091
  [self stopBatchUploadTimer];
2110
2092
 
2111
- // CRITICAL: Calculate and sync background time before ending session
2112
- // If we're being terminated while in background, we need to include
2113
- // the current background duration in the session's total background time
2114
2093
  if (self.uploadManager && self.lifecycleManager) {
2115
2094
  NSTimeInterval totalBgTime = self.lifecycleManager.totalBackgroundTimeMs;
2116
2095
 
2117
- // If we're currently in background, add the ongoing duration
2118
2096
  if (self.lifecycleManager.isInBackground &&
2119
2097
  self.lifecycleManager.backgroundEntryTime > 0) {
2120
2098
  NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
@@ -2134,15 +2112,12 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2134
2112
  }
2135
2113
 
2136
2114
  if (self.captureEngine) {
2137
- // Use SYNCHRONOUS stop to ensure segment is finished and upload is
2138
- // triggered before we try to upload events. This prevents video segments
2139
- // from being lost when app is terminated.
2115
+
2140
2116
  RJLogInfo(@"[RJ-TERMINATE] Stopping capture engine synchronously");
2141
2117
  [self.captureEngine stopSessionSync];
2142
2118
  RJLogInfo(@"[RJ-TERMINATE] Capture engine stopped");
2143
2119
  }
2144
2120
 
2145
- // Add terminated event SYNCHRONOUSLY before we copy events
2146
2121
  NSMutableDictionary *terminateEvent =
2147
2122
  [NSMutableDictionary dictionaryWithCapacity:3];
2148
2123
  terminateEvent[@"type"] = RJEventTypeAppTerminated;
@@ -2198,7 +2173,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2198
2173
  @try {
2199
2174
  NSTimeInterval bgTimeMs = self.lifecycleManager.totalBackgroundTimeMs;
2200
2175
 
2201
- // Keep local state in sync so stopSession/flush paths can rely on it.
2202
2176
  self.totalBackgroundTimeMs = bgTimeMs;
2203
2177
 
2204
2178
  if (self.uploadManager) {
@@ -2238,10 +2212,7 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2238
2212
  [self handleSessionTimeout:backgroundDuration currentTime:currentTime];
2239
2213
  }
2240
2214
 
2241
- /// Handle session timeout after extended background period.
2242
- /// This cleanly ends the old session and starts a fresh one.
2243
- ///
2244
- /// Flow:
2215
+
2245
2216
  /// 1. Capture final background time from lifecycle manager
2246
2217
  /// 2. Stop timers and capture engine for old session
2247
2218
  /// 3. Synchronously end the old session with correct background time
@@ -2305,7 +2276,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2305
2276
  if (finalEvents.count > 0) {
2306
2277
  [self.uploadManager synchronousUploadWithEvents:finalEvents];
2307
2278
  } else {
2308
- // Even with no events, end the session to record background time
2309
2279
  [self.uploadManager endSessionSync];
2310
2280
  }
2311
2281
 
@@ -2322,8 +2292,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2322
2292
  self.sessionStartTime = currentTime;
2323
2293
  self.totalBackgroundTimeMs = 0;
2324
2294
  [self.sessionEvents removeAllObjects];
2325
-
2326
- // Persist for crash recovery
2327
2295
  [[NSUserDefaults standardUserDefaults]
2328
2296
  setObject:newSessionId
2329
2297
  forKey:@"rj_current_session_id"];
@@ -2332,7 +2300,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2332
2300
 
2333
2301
  RJLogInfo(@"[RJ-SESSION-TIMEOUT] New session ID: %@", newSessionId);
2334
2302
 
2335
- // Reset upload manager for new session
2336
2303
  if (self.uploadManager) {
2337
2304
  @try {
2338
2305
  [self.uploadManager resetForNewSession];
@@ -2344,21 +2311,21 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2344
2311
  self.uploadManager.sessionStartTime = currentTime;
2345
2312
  self.uploadManager.totalBackgroundTimeMs = 0;
2346
2313
 
2347
- // Preserve user identity
2314
+ if (!self.userId) {
2315
+ self.userId = [[NSUserDefaults standardUserDefaults]
2316
+ stringForKey:@"rj_user_identity"];
2317
+ }
2348
2318
  self.uploadManager.userId = self.userId ?: @"anonymous";
2349
2319
  }
2350
2320
 
2351
- // Reset lifecycle manager background tracking
2352
2321
  if (self.lifecycleManager) {
2353
2322
  [self.lifecycleManager resetBackgroundTime];
2354
2323
  self.lifecycleManager.isRecording = YES;
2355
2324
  }
2356
2325
 
2357
- // Reset event buffer for new session - recreate since sessionId is readonly
2358
2326
  if (self.eventBuffer) {
2359
2327
  [self.eventBuffer clearAllEvents];
2360
2328
  }
2361
- // Create new event buffer for new session (sessionId is readonly)
2362
2329
  NSString *pendingPath = self.eventBuffer.pendingRootPath;
2363
2330
  if (pendingPath.length == 0) {
2364
2331
  pendingPath =
@@ -2392,15 +2359,11 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2392
2359
  }
2393
2360
 
2394
2361
  self.isRecording = YES;
2395
-
2396
- // Verify touch tracking
2397
2362
  RJTouchInterceptor *touchInterceptor = [RJTouchInterceptor sharedInstance];
2398
2363
  if (touchInterceptor && !touchInterceptor.isTrackingEnabled) {
2399
2364
  RJLogInfo(@"[RJ-SESSION-TIMEOUT] Re-enabling touch tracking");
2400
2365
  [self setupTouchTracking];
2401
2366
  }
2402
-
2403
- // Start timers for new session
2404
2367
  [self startBatchUploadTimer];
2405
2368
  [self startDurationLimitTimer];
2406
2369
 
@@ -2422,7 +2385,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
2422
2385
  [self.eventBuffer appendEvent:sessionStartEvent];
2423
2386
  }
2424
2387
 
2425
- // Immediate upload to register session with backend
2426
2388
  dispatch_after(
2427
2389
  dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
2428
2390
  dispatch_get_main_queue(), ^{
@@ -68,14 +68,9 @@ void rj_perf_record(RJPerfMetric metric, uint64_t start, uint64_t end) {
68
68
 
69
69
  double ms = rj_ms(start, end);
70
70
 
71
- // IMMEDIATE LOGGING (Request by user)
72
- // Pinpoints main thread blockers
73
71
  BOOL isMain = [NSThread isMainThread];
74
72
  const char *threadName = isMain ? "MAIN" : "BG";
75
73
 
76
- // Log critical main thread hitches (> 4ms) visibly
77
- // Or just log everything as requested ("intense logging")
78
- // Using specific emoji to highlight potential issues
79
74
  if (isMain && ms > 4.0) {
80
75
  RJLogInfo(@"[RJ-PERF] ⚠️ [%s] %s: %.2fms", threadName, rj_metric_names[metric],
81
76
  ms);
@@ -73,7 +73,6 @@
73
73
  CGFloat level = window.windowLevel;
74
74
 
75
75
  if (window.isKeyWindow) {
76
- // Keep as a last-ditch fallback (even if it's a system window).
77
76
  if (!anyKey) {
78
77
  anyKey = window;
79
78
  }
@@ -30,7 +30,6 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
30
30
  * </Mask>
31
31
  * ```
32
32
  */
33
- // Lazy-loaded React Native modules
34
33
  let _RN = null;
35
34
  function getRN() {
36
35
  if (_RN) return _RN;
@@ -54,8 +53,6 @@ const Mask = ({
54
53
  ...props
55
54
  }) => {
56
55
  const RN = getRN();
57
-
58
- // If RN isn't loaded yet (shouldn't happen in practice), render children directly
59
56
  if (!RN) {
60
57
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
61
58
  }
@@ -64,9 +61,7 @@ const Mask = ({
64
61
  StyleSheet
65
62
  } = RN;
66
63
  const styles = StyleSheet.create({
67
- container: {
68
- // Minimal container style - doesn't affect layout
69
- }
64
+ container: {}
70
65
  });
71
66
  return /*#__PURE__*/_react.default.createElement(View, _extends({}, props, {
72
67
  style: [styles.container, style],