@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.
- package/README.md +29 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +47 -30
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +25 -1
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +70 -32
- package/android/src/main/java/com/rejourney/core/Constants.kt +4 -4
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
- package/ios/Capture/RJCaptureEngine.m +72 -34
- package/ios/Capture/RJCaptureHeuristics.h +7 -5
- package/ios/Capture/RJCaptureHeuristics.m +138 -112
- package/ios/Capture/RJVideoEncoder.m +0 -26
- package/ios/Core/Rejourney.mm +64 -102
- package/ios/Utils/RJPerfTiming.m +0 -5
- package/ios/Utils/RJWindowUtils.m +0 -1
- package/lib/commonjs/components/Mask.js +1 -6
- package/lib/commonjs/index.js +12 -101
- package/lib/commonjs/sdk/autoTracking.js +55 -353
- 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 -49
- package/lib/commonjs/sdk/utils.js +0 -5
- package/lib/module/components/Mask.js +1 -6
- package/lib/module/index.js +11 -105
- package/lib/module/sdk/autoTracking.js +55 -354
- 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 -49
- package/lib/module/sdk/utils.js +0 -5
- package/lib/typescript/NativeRejourney.d.ts +2 -0
- package/lib/typescript/sdk/autoTracking.d.ts +5 -6
- package/lib/typescript/types/index.d.ts +0 -1
- package/package.json +11 -3
- package/src/NativeRejourney.ts +4 -0
- package/src/components/Mask.tsx +0 -3
- package/src/index.ts +11 -88
- package/src/sdk/autoTracking.ts +72 -331
- 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 -33
- package/src/sdk/utils.ts +0 -5
- 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;
|
|
@@ -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(
|
|
975
|
+
dispatch_async(self.stateQueue, ^{
|
|
947
976
|
@try {
|
|
948
977
|
NSString *safeUserId = userId.length > 0 ? userId : @"anonymous";
|
|
949
978
|
|
|
950
|
-
[
|
|
951
|
-
|
|
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
|
-
[
|
|
962
|
-
|
|
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
|
|
1004
|
+
if (resolve)
|
|
1005
|
+
resolve(@{@"success" : @YES});
|
|
966
1006
|
} @catch (NSException *exception) {
|
|
967
|
-
|
|
968
|
-
|
|
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) {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(), ^{
|
package/ios/Utils/RJPerfTiming.m
CHANGED
|
@@ -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);
|
|
@@ -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],
|