@rejourneyco/react-native 1.0.1 → 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 +72 -391
- 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/android/src/newarch/java/com/rejourney/RejourneyModule.kt +7 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
- package/ios/Capture/RJCaptureEngine.m +3 -34
- package/ios/Capture/RJVideoEncoder.m +0 -26
- package/ios/Capture/RJViewHierarchyScanner.m +68 -51
- package/ios/Core/RJLifecycleManager.m +0 -14
- package/ios/Core/Rejourney.mm +53 -129
- 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/RJPerfTiming.m +0 -5
- package/ios/Utils/RJWindowUtils.m +87 -87
- package/lib/commonjs/components/Mask.js +1 -6
- package/lib/commonjs/index.js +46 -117
- package/lib/commonjs/sdk/autoTracking.js +39 -313
- package/lib/commonjs/sdk/constants.js +2 -13
- package/lib/commonjs/sdk/errorTracking.js +1 -29
- package/lib/commonjs/sdk/metricsTracking.js +3 -24
- package/lib/commonjs/sdk/navigation.js +3 -42
- package/lib/commonjs/sdk/networkInterceptor.js +7 -60
- package/lib/commonjs/sdk/utils.js +73 -19
- package/lib/module/components/Mask.js +1 -6
- package/lib/module/index.js +45 -121
- package/lib/module/sdk/autoTracking.js +39 -314
- package/lib/module/sdk/constants.js +2 -13
- package/lib/module/sdk/errorTracking.js +1 -29
- package/lib/module/sdk/index.js +0 -2
- package/lib/module/sdk/metricsTracking.js +3 -24
- package/lib/module/sdk/navigation.js +3 -42
- package/lib/module/sdk/networkInterceptor.js +7 -60
- package/lib/module/sdk/utils.js +73 -19
- package/lib/typescript/NativeRejourney.d.ts +1 -0
- package/lib/typescript/sdk/autoTracking.d.ts +4 -4
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/lib/typescript/types/index.d.ts +0 -1
- package/package.json +17 -11
- package/src/NativeRejourney.ts +2 -0
- package/src/components/Mask.tsx +0 -3
- package/src/index.ts +43 -92
- package/src/sdk/autoTracking.ts +51 -284
- package/src/sdk/constants.ts +13 -13
- package/src/sdk/errorTracking.ts +1 -17
- package/src/sdk/index.ts +0 -2
- package/src/sdk/metricsTracking.ts +5 -33
- package/src/sdk/navigation.ts +8 -29
- package/src/sdk/networkInterceptor.ts +9 -42
- package/src/sdk/utils.ts +76 -19
- package/src/types/index.ts +0 -29
package/ios/Core/Rejourney.mm
CHANGED
|
@@ -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;
|
|
@@ -248,22 +249,33 @@ RCT_EXPORT_MODULE()
|
|
|
248
249
|
didDecideSample = YES;
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
// Decouple video recording from session active state.
|
|
253
|
+
// We want to record video ONLY if:
|
|
254
|
+
// 1. Config says recording is enabled (remote toggles)
|
|
255
|
+
// 2. Sample rate allows it
|
|
256
|
+
//
|
|
257
|
+
// NOTE: Even if shouldRecordVideo is NO, the session remains active
|
|
258
|
+
// (isRecording=YES) and events are still observed and uploaded ("Data-Only
|
|
259
|
+
// Mode").
|
|
260
|
+
BOOL shouldRecordVideo = self.recordingEnabledByConfig && self.sessionSampled;
|
|
261
|
+
self.remoteRecordingEnabled = shouldRecordVideo;
|
|
253
262
|
|
|
254
263
|
if (self.captureEngine) {
|
|
255
|
-
self.captureEngine.uploadsEnabled =
|
|
256
|
-
if (!
|
|
264
|
+
self.captureEngine.uploadsEnabled = shouldRecordVideo;
|
|
265
|
+
if (!shouldRecordVideo && self.captureEngine.isRecording) {
|
|
266
|
+
// Stop video capture pipeline to save resources, but keep session alive
|
|
257
267
|
[self.captureEngine stopSession];
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
270
|
|
|
261
271
|
if (didDecideSample && self.recordingEnabledByConfig &&
|
|
262
272
|
!self.sessionSampled) {
|
|
263
|
-
|
|
273
|
+
RJLogInfo(@"Session sampled out for video (%ld%%) - entering Data-Only "
|
|
274
|
+
@"Mode (Events enabled, Video disabled)",
|
|
275
|
+
(long)clampedRate);
|
|
264
276
|
}
|
|
265
277
|
|
|
266
|
-
return
|
|
278
|
+
return shouldRecordVideo;
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
- (void)ensureFullyInitialized {
|
|
@@ -608,6 +620,34 @@ RCT_EXPORT_METHOD(debugTriggerANR : (double)durationMs) {
|
|
|
608
620
|
});
|
|
609
621
|
}
|
|
610
622
|
|
|
623
|
+
RCT_EXPORT_METHOD(getDeviceInfo : (RCTPromiseResolveBlock)
|
|
624
|
+
resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
625
|
+
NSMutableDictionary *info = [NSMutableDictionary new];
|
|
626
|
+
|
|
627
|
+
// Model
|
|
628
|
+
struct utsname systemInfo;
|
|
629
|
+
uname(&systemInfo);
|
|
630
|
+
NSString *modelCode = [NSString stringWithCString:systemInfo.machine
|
|
631
|
+
encoding:NSUTF8StringEncoding];
|
|
632
|
+
info[@"model"] = modelCode ?: [[UIDevice currentDevice] model];
|
|
633
|
+
|
|
634
|
+
info[@"brand"] = @"Apple";
|
|
635
|
+
info[@"systemName"] = [[UIDevice currentDevice] systemName];
|
|
636
|
+
info[@"systemVersion"] = [[UIDevice currentDevice] systemVersion];
|
|
637
|
+
info[@"bundleId"] = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
|
|
638
|
+
info[@"appVersion"] =
|
|
639
|
+
[[NSBundle mainBundle]
|
|
640
|
+
objectForInfoDictionaryKey:@"CFBundleShortVersionString"]
|
|
641
|
+
?: @"";
|
|
642
|
+
info[@"buildNumber"] =
|
|
643
|
+
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
|
|
644
|
+
?: @"";
|
|
645
|
+
info[@"isTablet"] = @([[UIDevice currentDevice] userInterfaceIdiom] ==
|
|
646
|
+
UIUserInterfaceIdiomPad);
|
|
647
|
+
|
|
648
|
+
resolve(info);
|
|
649
|
+
}
|
|
650
|
+
|
|
611
651
|
RCT_EXPORT_METHOD(getSessionId : (RCTPromiseResolveBlock)
|
|
612
652
|
resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
613
653
|
NSString *sessionId = self.currentSessionId;
|
|
@@ -647,10 +687,7 @@ RCT_EXPORT_METHOD(stopSession : (RCTPromiseResolveBlock)
|
|
|
647
687
|
|
|
648
688
|
NSString *sessionId = self.currentSessionId ?: @"";
|
|
649
689
|
|
|
650
|
-
|
|
651
|
-
// When a session ends while the app is in background (e.g. max duration),
|
|
652
|
-
// we must include the ongoing background duration, otherwise the backend
|
|
653
|
-
// will think the entire wall-clock session duration is playable.
|
|
690
|
+
|
|
654
691
|
NSTimeInterval totalBgTimeMs = 0;
|
|
655
692
|
if (self.lifecycleManager) {
|
|
656
693
|
totalBgTimeMs = self.lifecycleManager.totalBackgroundTimeMs;
|
|
@@ -947,7 +984,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
|
|
|
947
984
|
@try {
|
|
948
985
|
NSString *safeUserId = userId.length > 0 ? userId : @"anonymous";
|
|
949
986
|
|
|
950
|
-
// KEY CHANGE: Persist directly to NSUserDefaults (Native Storage)
|
|
951
987
|
[[NSUserDefaults standardUserDefaults] setObject:safeUserId
|
|
952
988
|
forKey:@"rj_user_identity"];
|
|
953
989
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
@@ -960,13 +996,11 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
|
|
|
960
996
|
RJLogDebug(@"User identity updated and persisted: %@", safeUserId);
|
|
961
997
|
|
|
962
998
|
if (self.isRecording) {
|
|
963
|
-
// Log event for tracking
|
|
964
999
|
NSMutableDictionary *event = [NSMutableDictionary new];
|
|
965
1000
|
event[@"type"] = @"user_identity_changed";
|
|
966
1001
|
event[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
|
|
967
1002
|
event[@"userId"] = safeUserId;
|
|
968
1003
|
|
|
969
|
-
// Helper to log event safely
|
|
970
1004
|
if (self.eventBuffer) {
|
|
971
1005
|
[self.eventBuffer appendEvent:event];
|
|
972
1006
|
}
|
|
@@ -1008,17 +1042,15 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1008
1042
|
return;
|
|
1009
1043
|
}
|
|
1010
1044
|
|
|
1011
|
-
// Throttle scroll events to avoid spamming the main thread/logs
|
|
1012
1045
|
if ([gestureType hasPrefix:@"scroll"]) {
|
|
1013
1046
|
static NSTimeInterval lastScrollLogTime = 0;
|
|
1014
1047
|
NSTimeInterval now = CACurrentMediaTime();
|
|
1015
|
-
if (now - lastScrollLogTime < 0.5) {
|
|
1048
|
+
if (now - lastScrollLogTime < 0.5) {
|
|
1016
1049
|
return;
|
|
1017
1050
|
}
|
|
1018
1051
|
lastScrollLogTime = now;
|
|
1019
1052
|
}
|
|
1020
1053
|
|
|
1021
|
-
// Move ALL processing to the background state queue to unblock Main Thread
|
|
1022
1054
|
dispatch_async(self.stateQueue, ^{
|
|
1023
1055
|
@try {
|
|
1024
1056
|
NSMutableDictionary *details =
|
|
@@ -1030,18 +1062,8 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1030
1062
|
@"targetLabel" : targetLabel ?: [NSNull null]
|
|
1031
1063
|
}];
|
|
1032
1064
|
|
|
1033
|
-
// Log internal event (already on stateQueue, so safe)
|
|
1034
|
-
// We call a simpler version that assumes we are already on background or
|
|
1035
|
-
// handles it Actually logEventInternal dispatches TO stateQueue. Since we
|
|
1036
|
-
// are ON stateQueue, we can call a direct helper or just logEventInternal
|
|
1037
|
-
// (it will just dispatch_async again which is fine) But to be cleaner,
|
|
1038
|
-
// let's just use logEventInternal but ensuring we don't do main thread
|
|
1039
|
-
// dictionary work above. We moved the dictionary creation HERE
|
|
1040
|
-
// (background).
|
|
1041
|
-
|
|
1042
1065
|
[self logEventInternal:RJEventTypeGesture details:[details copy]];
|
|
1043
1066
|
|
|
1044
|
-
// Notify engine on main thread (it's lightweight)
|
|
1045
1067
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1046
1068
|
if (self.captureEngine) {
|
|
1047
1069
|
[self.captureEngine notifyGesture:gestureType];
|
|
@@ -1111,7 +1133,7 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1111
1133
|
registerDeviceWithProjectKey:publicKey
|
|
1112
1134
|
bundleId:bundleId
|
|
1113
1135
|
platform:@"ios"
|
|
1114
|
-
sdkVersion
|
|
1136
|
+
sdkVersion:RJSDKVersion
|
|
1115
1137
|
apiUrl:apiUrl
|
|
1116
1138
|
completion:^(BOOL success, NSString *credId,
|
|
1117
1139
|
NSError *error) {
|
|
@@ -1123,11 +1145,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1123
1145
|
RJLogError(@"Device registration failed: %@",
|
|
1124
1146
|
error);
|
|
1125
1147
|
|
|
1126
|
-
// SECURITY: Handle specific error codes
|
|
1127
|
-
// 403 = Bundle ID mismatch or forbidden - PERMANENT
|
|
1128
|
-
// failure 404 = Project not found - could be
|
|
1129
|
-
// temporary, retry Other = Network/transient -
|
|
1130
|
-
// retry
|
|
1131
1148
|
if (error.code == 403) {
|
|
1132
1149
|
RJLogError(@"SECURITY: Bundle ID mismatch or "
|
|
1133
1150
|
@"access forbidden. "
|
|
@@ -1136,9 +1153,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1136
1153
|
strongSelf.authPermanentlyFailed = YES;
|
|
1137
1154
|
[strongSelf handleAuthenticationFailure:error];
|
|
1138
1155
|
} else {
|
|
1139
|
-
// For 404 and other errors, schedule retry with
|
|
1140
|
-
// exponential backoff Recording continues locally
|
|
1141
|
-
// - events queued for later upload
|
|
1142
1156
|
[strongSelf scheduleAuthRetryWithError:error
|
|
1143
1157
|
publicKey:publicKey
|
|
1144
1158
|
apiUrl:apiUrl];
|
|
@@ -1146,7 +1160,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1146
1160
|
} else {
|
|
1147
1161
|
RJLogDebug(@"Device registered: %@", credId);
|
|
1148
1162
|
|
|
1149
|
-
// Auth succeeded - reset retry state
|
|
1150
1163
|
[strongSelf resetAuthRetryState];
|
|
1151
1164
|
|
|
1152
1165
|
[strongSelf
|
|
@@ -1161,21 +1174,16 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1161
1174
|
- (void)handleAuthenticationFailure:(NSError *)error {
|
|
1162
1175
|
RJLogError(@"Authentication failure - stopping recording. Error: %@", error);
|
|
1163
1176
|
|
|
1164
|
-
// Stop recording to prevent data accumulation that can't be uploaded
|
|
1165
1177
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1166
1178
|
if (self.isRecording) {
|
|
1167
1179
|
self.isRecording = NO;
|
|
1168
1180
|
|
|
1169
|
-
// Stop capture engine
|
|
1170
1181
|
if (self.captureEngine) {
|
|
1171
1182
|
[self.captureEngine stopSession];
|
|
1172
1183
|
}
|
|
1173
1184
|
|
|
1174
|
-
// Clear auth data so next attempt starts fresh
|
|
1175
1185
|
[[RJDeviceAuthManager sharedManager] clearAllAuthData];
|
|
1176
1186
|
|
|
1177
|
-
// Notify JS layer about the failure (if bridge is available)
|
|
1178
|
-
// This allows the app to handle the error (e.g., show user message)
|
|
1179
1187
|
@try {
|
|
1180
1188
|
if (self.bridge) {
|
|
1181
1189
|
[self.bridge enqueueJSCall:@"RCTDeviceEventEmitter"
|
|
@@ -1202,22 +1210,18 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1202
1210
|
- (void)scheduleAuthRetryWithError:(NSError *)error
|
|
1203
1211
|
publicKey:(NSString *)publicKey
|
|
1204
1212
|
apiUrl:(NSString *)apiUrl {
|
|
1205
|
-
// Check if already permanently failed (403 security error)
|
|
1206
1213
|
if (self.authPermanentlyFailed) {
|
|
1207
1214
|
RJLogWarning(@"Auth permanently failed - not scheduling retry");
|
|
1208
1215
|
return;
|
|
1209
1216
|
}
|
|
1210
1217
|
|
|
1211
|
-
// Increment retry count
|
|
1212
1218
|
self.authRetryCount++;
|
|
1213
1219
|
|
|
1214
|
-
// Check max retries
|
|
1215
1220
|
if (self.authRetryCount > RJ_MAX_AUTH_RETRIES) {
|
|
1216
1221
|
RJLogError(@"Auth failed after %ld retries. Recording continues locally, "
|
|
1217
1222
|
@"events will be uploaded when auth succeeds.",
|
|
1218
1223
|
(long)RJ_MAX_AUTH_RETRIES);
|
|
1219
1224
|
|
|
1220
|
-
// Notify JS but DON'T stop recording - events queue locally
|
|
1221
1225
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1222
1226
|
@try {
|
|
1223
1227
|
if (self.bridge) {
|
|
@@ -1234,18 +1238,15 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1234
1238
|
completion:nil];
|
|
1235
1239
|
}
|
|
1236
1240
|
} @catch (NSException *exception) {
|
|
1237
|
-
// Ignore JS notification failures
|
|
1238
1241
|
}
|
|
1239
1242
|
});
|
|
1240
1243
|
|
|
1241
|
-
// Schedule a much longer retry (5 minutes) to try again later
|
|
1242
1244
|
[self scheduleBackgroundAuthRetryAfter:300.0
|
|
1243
1245
|
publicKey:publicKey
|
|
1244
1246
|
apiUrl:apiUrl];
|
|
1245
1247
|
return;
|
|
1246
1248
|
}
|
|
1247
1249
|
|
|
1248
|
-
// Calculate exponential backoff delay: 2, 4, 8, 16, 32... capped at 60s
|
|
1249
1250
|
NSTimeInterval delay =
|
|
1250
1251
|
MIN(RJ_AUTH_RETRY_BASE_DELAY * pow(2, self.authRetryCount - 1),
|
|
1251
1252
|
RJ_AUTH_RETRY_MAX_DELAY);
|
|
@@ -1263,7 +1264,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1263
1264
|
- (void)scheduleBackgroundAuthRetryAfter:(NSTimeInterval)delay
|
|
1264
1265
|
publicKey:(NSString *)publicKey
|
|
1265
1266
|
apiUrl:(NSString *)apiUrl {
|
|
1266
|
-
// Cancel any existing retry timer
|
|
1267
1267
|
if (self.authRetryTimer) {
|
|
1268
1268
|
[self.authRetryTimer invalidate];
|
|
1269
1269
|
self.authRetryTimer = nil;
|
|
@@ -1296,14 +1296,11 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1296
1296
|
|
|
1297
1297
|
RJLogInfo(@"Retrying auth (attempt %ld)...", (long)(self.authRetryCount + 1));
|
|
1298
1298
|
|
|
1299
|
-
// After 2 failed attempts, clear cached auth data and re-register fresh
|
|
1300
|
-
// This handles expired/corrupted tokens or server-side revocations
|
|
1301
1299
|
if (self.authRetryCount >= 2) {
|
|
1302
1300
|
RJLogInfo(@"Clearing cached auth data and re-registering fresh...");
|
|
1303
1301
|
[[RJDeviceAuthManager sharedManager] clearAllAuthData];
|
|
1304
1302
|
}
|
|
1305
1303
|
|
|
1306
|
-
// Re-attempt device auth setup
|
|
1307
1304
|
[self setupDeviceAuthWithPublicKey:publicKey apiUrl:apiUrl];
|
|
1308
1305
|
}
|
|
1309
1306
|
|
|
@@ -1340,12 +1337,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1340
1337
|
return;
|
|
1341
1338
|
}
|
|
1342
1339
|
|
|
1343
|
-
// Handle security errors (403 = forbidden/mismatch, 404 = not found)
|
|
1344
1340
|
if ((error.code == 403 || error.code == 404) &&
|
|
1345
1341
|
[error.domain isEqualToString:@"RJDeviceAuth"]) {
|
|
1346
1342
|
|
|
1347
1343
|
if (!isRetry) {
|
|
1348
|
-
// First failure - try re-registration
|
|
1349
1344
|
RJLogDebug(@"Device auth invalid (%ld), attempting re-registration...",
|
|
1350
1345
|
(long)error.code);
|
|
1351
1346
|
NSString *bundleId =
|
|
@@ -1356,7 +1351,7 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1356
1351
|
registerDeviceWithProjectKey:publicKey
|
|
1357
1352
|
bundleId:bundleId
|
|
1358
1353
|
platform:@"ios"
|
|
1359
|
-
sdkVersion
|
|
1354
|
+
sdkVersion:RJSDKVersion
|
|
1360
1355
|
apiUrl:apiUrl
|
|
1361
1356
|
completion:^(BOOL retrySuccess, NSString *credId,
|
|
1362
1357
|
NSError *retryError) {
|
|
@@ -1372,8 +1367,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1372
1367
|
apiUrl
|
|
1373
1368
|
isRetry:YES];
|
|
1374
1369
|
} else {
|
|
1375
|
-
// Re-registration also failed - this is a
|
|
1376
|
-
// security error
|
|
1377
1370
|
RJLogError(@"Re-registration failed: %@",
|
|
1378
1371
|
retryError);
|
|
1379
1372
|
if (retryError.code == 403 ||
|
|
@@ -1384,12 +1377,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1384
1377
|
}
|
|
1385
1378
|
}];
|
|
1386
1379
|
} else {
|
|
1387
|
-
// Already retried - this is a persistent security error
|
|
1388
1380
|
RJLogError(@"Token fetch failed after retry: %@", error);
|
|
1389
1381
|
[strongSelf handleAuthenticationFailure:error];
|
|
1390
1382
|
}
|
|
1391
1383
|
} else {
|
|
1392
|
-
// Network or other transient errors
|
|
1393
1384
|
RJLogWarning(@"Failed to get upload token (transient): %@", error);
|
|
1394
1385
|
}
|
|
1395
1386
|
}];
|
|
@@ -1557,7 +1548,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1557
1548
|
if (self.isShuttingDown || !eventType)
|
|
1558
1549
|
return;
|
|
1559
1550
|
|
|
1560
|
-
// Log gesture events specifically for debugging
|
|
1561
1551
|
if ([eventType isEqualToString:@"gesture"]) {
|
|
1562
1552
|
NSString *gestureType = details[@"gestureType"] ?: @"unknown";
|
|
1563
1553
|
RJLogInfo(
|
|
@@ -1774,9 +1764,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1774
1764
|
});
|
|
1775
1765
|
}
|
|
1776
1766
|
|
|
1777
|
-
/// Flush data with option to end session or keep it alive for resumption.
|
|
1778
|
-
/// @param isFinal If YES, ends the session on backend. If NO, just uploads
|
|
1779
|
-
/// pending data.
|
|
1780
1767
|
- (void)flushDataWithCompletion:(RJCompletionHandler)completion
|
|
1781
1768
|
isFinal:(BOOL)isFinal {
|
|
1782
1769
|
|
|
@@ -1834,7 +1821,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1834
1821
|
return;
|
|
1835
1822
|
}
|
|
1836
1823
|
|
|
1837
|
-
// For non-final flushes, skip promotion evaluation and just upload
|
|
1838
1824
|
if (!isFinal) {
|
|
1839
1825
|
RJLogInfo(
|
|
1840
1826
|
@"[RJ-FLUSH] Non-final background flush - uploading %lu "
|
|
@@ -1915,13 +1901,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1915
1901
|
}
|
|
1916
1902
|
}
|
|
1917
1903
|
|
|
1918
|
-
/// Convenience method for final flush (ends session).
|
|
1919
1904
|
- (void)flushAllDataWithCompletion:(RJCompletionHandler)completion {
|
|
1920
1905
|
[self flushDataWithCompletion:completion isFinal:YES];
|
|
1921
1906
|
}
|
|
1922
1907
|
|
|
1923
|
-
/// Flush pending data for background transition without ending the session.
|
|
1924
|
-
/// Used when app goes to background but may return quickly.
|
|
1925
1908
|
- (void)flushDataForBackgroundWithCompletion:(RJCompletionHandler)completion {
|
|
1926
1909
|
[self flushDataWithCompletion:completion isFinal:NO];
|
|
1927
1910
|
}
|
|
@@ -1960,7 +1943,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1960
1943
|
if (self.isRecording) {
|
|
1961
1944
|
RJLogDebug(@"[KEYBOARD] Keyboard shown (height=%.0f)",
|
|
1962
1945
|
keyboardFrame.size.height);
|
|
1963
|
-
// Include keyboard height so web UI can render overlay
|
|
1964
1946
|
[self logEventInternal:RJEventTypeKeyboardShow
|
|
1965
1947
|
details:@{
|
|
1966
1948
|
@"keyboardHeight" : @(keyboardFrame.size.height),
|
|
@@ -2020,9 +2002,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2020
2002
|
@try {
|
|
2021
2003
|
[self stopBatchUploadTimer];
|
|
2022
2004
|
|
|
2023
|
-
// CRITICAL: Sync accumulated background time to uploadManager BEFORE any
|
|
2024
|
-
// flush This ensures if the session ends during background, the correct
|
|
2025
|
-
// time is included
|
|
2026
2005
|
if (self.uploadManager && self.lifecycleManager) {
|
|
2027
2006
|
NSTimeInterval currentBgTime =
|
|
2028
2007
|
self.lifecycleManager.totalBackgroundTimeMs;
|
|
@@ -2032,14 +2011,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2032
2011
|
currentBgTime);
|
|
2033
2012
|
}
|
|
2034
2013
|
|
|
2035
|
-
// Log background event FIRST and SYNCHRONOUSLY so it's included in the
|
|
2036
|
-
// flush This must happen before we pause capture or start the flush
|
|
2037
2014
|
NSMutableDictionary *bgEvent =
|
|
2038
2015
|
[NSMutableDictionary dictionaryWithCapacity:3];
|
|
2039
2016
|
bgEvent[@"type"] = RJEventTypeAppBackground;
|
|
2040
2017
|
bgEvent[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
|
|
2041
|
-
|
|
2042
|
-
// Add directly to session events synchronously
|
|
2043
2018
|
[self performStateSync:^{
|
|
2044
2019
|
if (self.sessionEvents) {
|
|
2045
2020
|
[self.sessionEvents addObject:bgEvent];
|
|
@@ -2049,17 +2024,12 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2049
2024
|
}
|
|
2050
2025
|
}];
|
|
2051
2026
|
|
|
2052
|
-
// Also add to event buffer for persistence
|
|
2053
2027
|
if (self.eventBuffer) {
|
|
2054
2028
|
[self.eventBuffer appendEvent:bgEvent];
|
|
2055
2029
|
}
|
|
2056
2030
|
|
|
2057
2031
|
if (self.captureEngine) {
|
|
2058
2032
|
@try {
|
|
2059
|
-
// Use ASYNCHRONOUS pause for backgrounding.
|
|
2060
|
-
// The encoder now implements internal UIBackgroundTask protection, so
|
|
2061
|
-
// it will finish finalizing the MP4 on a background queue without
|
|
2062
|
-
// locking the UI.
|
|
2063
2033
|
RJLogInfo(@"[RJ-VIDEO] Pausing video capture for background (ASYNC)");
|
|
2064
2034
|
[self.captureEngine pauseVideoCapture];
|
|
2065
2035
|
RJLogInfo(@"[RJ-VIDEO] Video capture pause initiated");
|
|
@@ -2074,9 +2044,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2074
2044
|
beginBackgroundTaskWithName:@"RejourneySessionFlush"];
|
|
2075
2045
|
}
|
|
2076
2046
|
|
|
2077
|
-
// Use non-final flush for background - session may resume if user returns
|
|
2078
|
-
// quickly This avoids calling session/end which would prevent frame capture
|
|
2079
|
-
// after returning
|
|
2080
2047
|
RJLogInfo(@"[RJ-FLUSH] Starting non-final background flush (session will "
|
|
2081
2048
|
@"resume if user returns)");
|
|
2082
2049
|
[self flushDataForBackgroundWithCompletion:^(BOOL success) {
|
|
@@ -2129,13 +2096,9 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2129
2096
|
@try {
|
|
2130
2097
|
[self stopBatchUploadTimer];
|
|
2131
2098
|
|
|
2132
|
-
// CRITICAL: Calculate and sync background time before ending session
|
|
2133
|
-
// If we're being terminated while in background, we need to include
|
|
2134
|
-
// the current background duration in the session's total background time
|
|
2135
2099
|
if (self.uploadManager && self.lifecycleManager) {
|
|
2136
2100
|
NSTimeInterval totalBgTime = self.lifecycleManager.totalBackgroundTimeMs;
|
|
2137
2101
|
|
|
2138
|
-
// If we're currently in background, add the ongoing duration
|
|
2139
2102
|
if (self.lifecycleManager.isInBackground &&
|
|
2140
2103
|
self.lifecycleManager.backgroundEntryTime > 0) {
|
|
2141
2104
|
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
|
|
@@ -2155,15 +2118,12 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2155
2118
|
}
|
|
2156
2119
|
|
|
2157
2120
|
if (self.captureEngine) {
|
|
2158
|
-
|
|
2159
|
-
// triggered before we try to upload events. This prevents video segments
|
|
2160
|
-
// from being lost when app is terminated.
|
|
2121
|
+
|
|
2161
2122
|
RJLogInfo(@"[RJ-TERMINATE] Stopping capture engine synchronously");
|
|
2162
2123
|
[self.captureEngine stopSessionSync];
|
|
2163
2124
|
RJLogInfo(@"[RJ-TERMINATE] Capture engine stopped");
|
|
2164
2125
|
}
|
|
2165
2126
|
|
|
2166
|
-
// Add terminated event SYNCHRONOUSLY before we copy events
|
|
2167
2127
|
NSMutableDictionary *terminateEvent =
|
|
2168
2128
|
[NSMutableDictionary dictionaryWithCapacity:3];
|
|
2169
2129
|
terminateEvent[@"type"] = RJEventTypeAppTerminated;
|
|
@@ -2192,9 +2152,9 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2192
2152
|
|
|
2193
2153
|
if (self.uploadManager && events.count > 0) {
|
|
2194
2154
|
@try {
|
|
2195
|
-
[self.uploadManager
|
|
2155
|
+
[self.uploadManager persistTerminationEvents:events ?: @[]];
|
|
2196
2156
|
} @catch (NSException *e) {
|
|
2197
|
-
RJLogWarning(@"Terminate
|
|
2157
|
+
RJLogWarning(@"Terminate persistence failed: %@", e);
|
|
2198
2158
|
}
|
|
2199
2159
|
}
|
|
2200
2160
|
} @catch (NSException *exception) {
|
|
@@ -2219,7 +2179,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2219
2179
|
@try {
|
|
2220
2180
|
NSTimeInterval bgTimeMs = self.lifecycleManager.totalBackgroundTimeMs;
|
|
2221
2181
|
|
|
2222
|
-
// Keep local state in sync so stopSession/flush paths can rely on it.
|
|
2223
2182
|
self.totalBackgroundTimeMs = bgTimeMs;
|
|
2224
2183
|
|
|
2225
2184
|
if (self.uploadManager) {
|
|
@@ -2259,16 +2218,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2259
2218
|
[self handleSessionTimeout:backgroundDuration currentTime:currentTime];
|
|
2260
2219
|
}
|
|
2261
2220
|
|
|
2262
|
-
/// Handle session timeout after extended background period.
|
|
2263
|
-
/// This cleanly ends the old session and starts a fresh one.
|
|
2264
|
-
///
|
|
2265
|
-
/// Flow:
|
|
2266
|
-
/// 1. Capture final background time from lifecycle manager
|
|
2267
|
-
/// 2. Stop timers and capture engine for old session
|
|
2268
|
-
/// 3. Synchronously end the old session with correct background time
|
|
2269
|
-
/// 4. Create new session ID and reset all state
|
|
2270
|
-
/// 5. Start capture for new session
|
|
2271
|
-
/// 6. Trigger immediate upload to register new session
|
|
2272
2221
|
- (void)handleSessionTimeout:(NSTimeInterval)backgroundDuration
|
|
2273
2222
|
currentTime:(NSTimeInterval)currentTime {
|
|
2274
2223
|
RJLogInfo(
|
|
@@ -2282,8 +2231,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2282
2231
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] === ENDING OLD SESSION: %@ ===",
|
|
2283
2232
|
oldSessionId);
|
|
2284
2233
|
|
|
2285
|
-
// ========== STEP 1: Capture background time BEFORE any state changes
|
|
2286
|
-
// ==========
|
|
2287
2234
|
NSTimeInterval totalBackgroundMs = 0;
|
|
2288
2235
|
if (self.lifecycleManager) {
|
|
2289
2236
|
totalBackgroundMs = self.lifecycleManager.totalBackgroundTimeMs;
|
|
@@ -2292,7 +2239,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2292
2239
|
totalBackgroundMs);
|
|
2293
2240
|
}
|
|
2294
2241
|
|
|
2295
|
-
// ========== STEP 2: Stop all capture/timers for old session ==========
|
|
2296
2242
|
[self stopBatchUploadTimer];
|
|
2297
2243
|
[self stopDurationLimitTimer];
|
|
2298
2244
|
|
|
@@ -2305,14 +2251,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2305
2251
|
}
|
|
2306
2252
|
}
|
|
2307
2253
|
|
|
2308
|
-
// ========== STEP 3: End old session SYNCHRONOUSLY with correct background
|
|
2309
|
-
// time ==========
|
|
2310
2254
|
if (wasRecording && self.uploadManager && oldSessionId.length > 0 &&
|
|
2311
2255
|
![oldSessionId isEqualToString:@"none"]) {
|
|
2312
|
-
// Set background time on upload manager
|
|
2313
2256
|
self.uploadManager.totalBackgroundTimeMs = totalBackgroundMs;
|
|
2314
2257
|
|
|
2315
|
-
// Get current events for final upload
|
|
2316
2258
|
__block NSArray<NSDictionary *> *finalEvents = nil;
|
|
2317
2259
|
[self performStateSync:^{
|
|
2318
2260
|
finalEvents = [self.sessionEvents copy];
|
|
@@ -2322,18 +2264,15 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2322
2264
|
@"bgTime=%.0fms",
|
|
2323
2265
|
(unsigned long)finalEvents.count, totalBackgroundMs);
|
|
2324
2266
|
|
|
2325
|
-
// Synchronous upload and session end
|
|
2326
2267
|
if (finalEvents.count > 0) {
|
|
2327
2268
|
[self.uploadManager synchronousUploadWithEvents:finalEvents];
|
|
2328
2269
|
} else {
|
|
2329
|
-
// Even with no events, end the session to record background time
|
|
2330
2270
|
[self.uploadManager endSessionSync];
|
|
2331
2271
|
}
|
|
2332
2272
|
|
|
2333
2273
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] Old session %@ ended", oldSessionId);
|
|
2334
2274
|
}
|
|
2335
2275
|
|
|
2336
|
-
// ========== STEP 4: Reset ALL state for new session ==========
|
|
2337
2276
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] === STARTING NEW SESSION ===");
|
|
2338
2277
|
|
|
2339
2278
|
__block NSString *newSessionId = nil;
|
|
@@ -2343,8 +2282,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2343
2282
|
self.sessionStartTime = currentTime;
|
|
2344
2283
|
self.totalBackgroundTimeMs = 0;
|
|
2345
2284
|
[self.sessionEvents removeAllObjects];
|
|
2346
|
-
|
|
2347
|
-
// Persist for crash recovery
|
|
2348
2285
|
[[NSUserDefaults standardUserDefaults]
|
|
2349
2286
|
setObject:newSessionId
|
|
2350
2287
|
forKey:@"rj_current_session_id"];
|
|
@@ -2353,7 +2290,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2353
2290
|
|
|
2354
2291
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] New session ID: %@", newSessionId);
|
|
2355
2292
|
|
|
2356
|
-
// Reset upload manager for new session
|
|
2357
2293
|
if (self.uploadManager) {
|
|
2358
2294
|
@try {
|
|
2359
2295
|
[self.uploadManager resetForNewSession];
|
|
@@ -2365,7 +2301,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2365
2301
|
self.uploadManager.sessionStartTime = currentTime;
|
|
2366
2302
|
self.uploadManager.totalBackgroundTimeMs = 0;
|
|
2367
2303
|
|
|
2368
|
-
// Preserve user identity
|
|
2369
2304
|
if (!self.userId) {
|
|
2370
2305
|
self.userId = [[NSUserDefaults standardUserDefaults]
|
|
2371
2306
|
stringForKey:@"rj_user_identity"];
|
|
@@ -2373,17 +2308,14 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2373
2308
|
self.uploadManager.userId = self.userId ?: @"anonymous";
|
|
2374
2309
|
}
|
|
2375
2310
|
|
|
2376
|
-
// Reset lifecycle manager background tracking
|
|
2377
2311
|
if (self.lifecycleManager) {
|
|
2378
2312
|
[self.lifecycleManager resetBackgroundTime];
|
|
2379
2313
|
self.lifecycleManager.isRecording = YES;
|
|
2380
2314
|
}
|
|
2381
2315
|
|
|
2382
|
-
// Reset event buffer for new session - recreate since sessionId is readonly
|
|
2383
2316
|
if (self.eventBuffer) {
|
|
2384
2317
|
[self.eventBuffer clearAllEvents];
|
|
2385
2318
|
}
|
|
2386
|
-
// Create new event buffer for new session (sessionId is readonly)
|
|
2387
2319
|
NSString *pendingPath = self.eventBuffer.pendingRootPath;
|
|
2388
2320
|
if (pendingPath.length == 0) {
|
|
2389
2321
|
pendingPath =
|
|
@@ -2394,7 +2326,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2394
2326
|
self.eventBuffer = [[RJEventBuffer alloc] initWithSessionId:newSessionId
|
|
2395
2327
|
pendingRootPath:pendingPath];
|
|
2396
2328
|
|
|
2397
|
-
// ========== STEP 5: Start capture for new session ==========
|
|
2398
2329
|
[self resetSamplingDecision];
|
|
2399
2330
|
self.remoteRecordingEnabled = self.recordingEnabledByConfig;
|
|
2400
2331
|
if (self.captureEngine) {
|
|
@@ -2417,20 +2348,14 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2417
2348
|
}
|
|
2418
2349
|
|
|
2419
2350
|
self.isRecording = YES;
|
|
2420
|
-
|
|
2421
|
-
// Verify touch tracking
|
|
2422
2351
|
RJTouchInterceptor *touchInterceptor = [RJTouchInterceptor sharedInstance];
|
|
2423
2352
|
if (touchInterceptor && !touchInterceptor.isTrackingEnabled) {
|
|
2424
2353
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] Re-enabling touch tracking");
|
|
2425
2354
|
[self setupTouchTracking];
|
|
2426
2355
|
}
|
|
2427
|
-
|
|
2428
|
-
// Start timers for new session
|
|
2429
2356
|
[self startBatchUploadTimer];
|
|
2430
2357
|
[self startDurationLimitTimer];
|
|
2431
2358
|
|
|
2432
|
-
// ========== STEP 6: Log session_start and trigger immediate upload
|
|
2433
|
-
// ==========
|
|
2434
2359
|
NSMutableDictionary *sessionStartEvent = [NSMutableDictionary dictionary];
|
|
2435
2360
|
sessionStartEvent[@"type"] = RJEventTypeSessionStart;
|
|
2436
2361
|
sessionStartEvent[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
|
|
@@ -2447,7 +2372,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2447
2372
|
[self.eventBuffer appendEvent:sessionStartEvent];
|
|
2448
2373
|
}
|
|
2449
2374
|
|
|
2450
|
-
// Immediate upload to register session with backend
|
|
2451
2375
|
dispatch_after(
|
|
2452
2376
|
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
|
|
2453
2377
|
dispatch_get_main_queue(), ^{
|
|
@@ -857,10 +857,8 @@ static const NSInteger RJ_AUTH_MAX_CONSECUTIVE_FAILURES = 10;
|
|
|
857
857
|
return;
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
-
// Check cooldown after consecutive failures to prevent flooding
|
|
861
860
|
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
|
862
861
|
if (self.consecutiveFailures > 0 && self.lastFailedRegistrationTime > 0) {
|
|
863
|
-
// Exponential backoff: 5s, 10s, 20s, 40s... up to 5 minutes
|
|
864
862
|
NSTimeInterval cooldown = MIN(
|
|
865
863
|
RJ_AUTH_COOLDOWN_BASE_SECONDS * pow(2, self.consecutiveFailures - 1),
|
|
866
864
|
RJ_AUTH_COOLDOWN_MAX_SECONDS
|
|
@@ -180,6 +180,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
180
180
|
*/
|
|
181
181
|
- (BOOL)synchronousUploadWithEvents:(NSArray<NSDictionary *> *)events;
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Persists events to disk for termination, skipping network upload.
|
|
185
|
+
* Use this in appWillTerminate to avoid watchdog kills.
|
|
186
|
+
*
|
|
187
|
+
* @param events Array of event dictionaries.
|
|
188
|
+
*/
|
|
189
|
+
- (void)persistTerminationEvents:(NSArray<NSDictionary *> *)events;
|
|
190
|
+
|
|
183
191
|
/**
|
|
184
192
|
* Uploads a crash report to the dashboard.
|
|
185
193
|
*
|