@rejourneyco/react-native 1.0.1 → 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/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +35 -29
- 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/Core/Rejourney.mm +32 -95
- 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 +4 -87
- package/lib/commonjs/sdk/autoTracking.js +39 -310
- 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 +3 -91
- package/lib/module/sdk/autoTracking.js +39 -311
- 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 +1 -0
- package/lib/typescript/sdk/autoTracking.d.ts +4 -4
- package/lib/typescript/types/index.d.ts +0 -1
- package/package.json +2 -8
- package/src/NativeRejourney.ts +2 -0
- package/src/components/Mask.tsx +0 -3
- package/src/index.ts +3 -73
- package/src/sdk/autoTracking.ts +51 -282
- 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;
|
|
@@ -947,7 +976,6 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
|
|
|
947
976
|
@try {
|
|
948
977
|
NSString *safeUserId = userId.length > 0 ? userId : @"anonymous";
|
|
949
978
|
|
|
950
|
-
// KEY CHANGE: Persist directly to NSUserDefaults (Native Storage)
|
|
951
979
|
[[NSUserDefaults standardUserDefaults] setObject:safeUserId
|
|
952
980
|
forKey:@"rj_user_identity"];
|
|
953
981
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
@@ -960,13 +988,11 @@ RCT_EXPORT_METHOD(setUserIdentity : (NSString *)userId resolve : (
|
|
|
960
988
|
RJLogDebug(@"User identity updated and persisted: %@", safeUserId);
|
|
961
989
|
|
|
962
990
|
if (self.isRecording) {
|
|
963
|
-
// Log event for tracking
|
|
964
991
|
NSMutableDictionary *event = [NSMutableDictionary new];
|
|
965
992
|
event[@"type"] = @"user_identity_changed";
|
|
966
993
|
event[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
|
|
967
994
|
event[@"userId"] = safeUserId;
|
|
968
995
|
|
|
969
|
-
// Helper to log event safely
|
|
970
996
|
if (self.eventBuffer) {
|
|
971
997
|
[self.eventBuffer appendEvent:event];
|
|
972
998
|
}
|
|
@@ -1008,17 +1034,15 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1008
1034
|
return;
|
|
1009
1035
|
}
|
|
1010
1036
|
|
|
1011
|
-
// Throttle scroll events to avoid spamming the main thread/logs
|
|
1012
1037
|
if ([gestureType hasPrefix:@"scroll"]) {
|
|
1013
1038
|
static NSTimeInterval lastScrollLogTime = 0;
|
|
1014
1039
|
NSTimeInterval now = CACurrentMediaTime();
|
|
1015
|
-
if (now - lastScrollLogTime < 0.5) {
|
|
1040
|
+
if (now - lastScrollLogTime < 0.5) {
|
|
1016
1041
|
return;
|
|
1017
1042
|
}
|
|
1018
1043
|
lastScrollLogTime = now;
|
|
1019
1044
|
}
|
|
1020
1045
|
|
|
1021
|
-
// Move ALL processing to the background state queue to unblock Main Thread
|
|
1022
1046
|
dispatch_async(self.stateQueue, ^{
|
|
1023
1047
|
@try {
|
|
1024
1048
|
NSMutableDictionary *details =
|
|
@@ -1030,18 +1054,8 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1030
1054
|
@"targetLabel" : targetLabel ?: [NSNull null]
|
|
1031
1055
|
}];
|
|
1032
1056
|
|
|
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
1057
|
[self logEventInternal:RJEventTypeGesture details:[details copy]];
|
|
1043
1058
|
|
|
1044
|
-
// Notify engine on main thread (it's lightweight)
|
|
1045
1059
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1046
1060
|
if (self.captureEngine) {
|
|
1047
1061
|
[self.captureEngine notifyGesture:gestureType];
|
|
@@ -1123,11 +1137,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1123
1137
|
RJLogError(@"Device registration failed: %@",
|
|
1124
1138
|
error);
|
|
1125
1139
|
|
|
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
1140
|
if (error.code == 403) {
|
|
1132
1141
|
RJLogError(@"SECURITY: Bundle ID mismatch or "
|
|
1133
1142
|
@"access forbidden. "
|
|
@@ -1136,9 +1145,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1136
1145
|
strongSelf.authPermanentlyFailed = YES;
|
|
1137
1146
|
[strongSelf handleAuthenticationFailure:error];
|
|
1138
1147
|
} else {
|
|
1139
|
-
// For 404 and other errors, schedule retry with
|
|
1140
|
-
// exponential backoff Recording continues locally
|
|
1141
|
-
// - events queued for later upload
|
|
1142
1148
|
[strongSelf scheduleAuthRetryWithError:error
|
|
1143
1149
|
publicKey:publicKey
|
|
1144
1150
|
apiUrl:apiUrl];
|
|
@@ -1146,7 +1152,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1146
1152
|
} else {
|
|
1147
1153
|
RJLogDebug(@"Device registered: %@", credId);
|
|
1148
1154
|
|
|
1149
|
-
// Auth succeeded - reset retry state
|
|
1150
1155
|
[strongSelf resetAuthRetryState];
|
|
1151
1156
|
|
|
1152
1157
|
[strongSelf
|
|
@@ -1161,21 +1166,16 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1161
1166
|
- (void)handleAuthenticationFailure:(NSError *)error {
|
|
1162
1167
|
RJLogError(@"Authentication failure - stopping recording. Error: %@", error);
|
|
1163
1168
|
|
|
1164
|
-
// Stop recording to prevent data accumulation that can't be uploaded
|
|
1165
1169
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1166
1170
|
if (self.isRecording) {
|
|
1167
1171
|
self.isRecording = NO;
|
|
1168
1172
|
|
|
1169
|
-
// Stop capture engine
|
|
1170
1173
|
if (self.captureEngine) {
|
|
1171
1174
|
[self.captureEngine stopSession];
|
|
1172
1175
|
}
|
|
1173
1176
|
|
|
1174
|
-
// Clear auth data so next attempt starts fresh
|
|
1175
1177
|
[[RJDeviceAuthManager sharedManager] clearAllAuthData];
|
|
1176
1178
|
|
|
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
1179
|
@try {
|
|
1180
1180
|
if (self.bridge) {
|
|
1181
1181
|
[self.bridge enqueueJSCall:@"RCTDeviceEventEmitter"
|
|
@@ -1202,22 +1202,18 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1202
1202
|
- (void)scheduleAuthRetryWithError:(NSError *)error
|
|
1203
1203
|
publicKey:(NSString *)publicKey
|
|
1204
1204
|
apiUrl:(NSString *)apiUrl {
|
|
1205
|
-
// Check if already permanently failed (403 security error)
|
|
1206
1205
|
if (self.authPermanentlyFailed) {
|
|
1207
1206
|
RJLogWarning(@"Auth permanently failed - not scheduling retry");
|
|
1208
1207
|
return;
|
|
1209
1208
|
}
|
|
1210
1209
|
|
|
1211
|
-
// Increment retry count
|
|
1212
1210
|
self.authRetryCount++;
|
|
1213
1211
|
|
|
1214
|
-
// Check max retries
|
|
1215
1212
|
if (self.authRetryCount > RJ_MAX_AUTH_RETRIES) {
|
|
1216
1213
|
RJLogError(@"Auth failed after %ld retries. Recording continues locally, "
|
|
1217
1214
|
@"events will be uploaded when auth succeeds.",
|
|
1218
1215
|
(long)RJ_MAX_AUTH_RETRIES);
|
|
1219
1216
|
|
|
1220
|
-
// Notify JS but DON'T stop recording - events queue locally
|
|
1221
1217
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1222
1218
|
@try {
|
|
1223
1219
|
if (self.bridge) {
|
|
@@ -1234,18 +1230,15 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1234
1230
|
completion:nil];
|
|
1235
1231
|
}
|
|
1236
1232
|
} @catch (NSException *exception) {
|
|
1237
|
-
// Ignore JS notification failures
|
|
1238
1233
|
}
|
|
1239
1234
|
});
|
|
1240
1235
|
|
|
1241
|
-
// Schedule a much longer retry (5 minutes) to try again later
|
|
1242
1236
|
[self scheduleBackgroundAuthRetryAfter:300.0
|
|
1243
1237
|
publicKey:publicKey
|
|
1244
1238
|
apiUrl:apiUrl];
|
|
1245
1239
|
return;
|
|
1246
1240
|
}
|
|
1247
1241
|
|
|
1248
|
-
// Calculate exponential backoff delay: 2, 4, 8, 16, 32... capped at 60s
|
|
1249
1242
|
NSTimeInterval delay =
|
|
1250
1243
|
MIN(RJ_AUTH_RETRY_BASE_DELAY * pow(2, self.authRetryCount - 1),
|
|
1251
1244
|
RJ_AUTH_RETRY_MAX_DELAY);
|
|
@@ -1263,7 +1256,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1263
1256
|
- (void)scheduleBackgroundAuthRetryAfter:(NSTimeInterval)delay
|
|
1264
1257
|
publicKey:(NSString *)publicKey
|
|
1265
1258
|
apiUrl:(NSString *)apiUrl {
|
|
1266
|
-
// Cancel any existing retry timer
|
|
1267
1259
|
if (self.authRetryTimer) {
|
|
1268
1260
|
[self.authRetryTimer invalidate];
|
|
1269
1261
|
self.authRetryTimer = nil;
|
|
@@ -1296,14 +1288,11 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1296
1288
|
|
|
1297
1289
|
RJLogInfo(@"Retrying auth (attempt %ld)...", (long)(self.authRetryCount + 1));
|
|
1298
1290
|
|
|
1299
|
-
// After 2 failed attempts, clear cached auth data and re-register fresh
|
|
1300
|
-
// This handles expired/corrupted tokens or server-side revocations
|
|
1301
1291
|
if (self.authRetryCount >= 2) {
|
|
1302
1292
|
RJLogInfo(@"Clearing cached auth data and re-registering fresh...");
|
|
1303
1293
|
[[RJDeviceAuthManager sharedManager] clearAllAuthData];
|
|
1304
1294
|
}
|
|
1305
1295
|
|
|
1306
|
-
// Re-attempt device auth setup
|
|
1307
1296
|
[self setupDeviceAuthWithPublicKey:publicKey apiUrl:apiUrl];
|
|
1308
1297
|
}
|
|
1309
1298
|
|
|
@@ -1372,8 +1361,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1372
1361
|
apiUrl
|
|
1373
1362
|
isRetry:YES];
|
|
1374
1363
|
} else {
|
|
1375
|
-
// Re-registration also failed - this is a
|
|
1376
|
-
// security error
|
|
1377
1364
|
RJLogError(@"Re-registration failed: %@",
|
|
1378
1365
|
retryError);
|
|
1379
1366
|
if (retryError.code == 403 ||
|
|
@@ -1384,12 +1371,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1384
1371
|
}
|
|
1385
1372
|
}];
|
|
1386
1373
|
} else {
|
|
1387
|
-
// Already retried - this is a persistent security error
|
|
1388
1374
|
RJLogError(@"Token fetch failed after retry: %@", error);
|
|
1389
1375
|
[strongSelf handleAuthenticationFailure:error];
|
|
1390
1376
|
}
|
|
1391
1377
|
} else {
|
|
1392
|
-
// Network or other transient errors
|
|
1393
1378
|
RJLogWarning(@"Failed to get upload token (transient): %@", error);
|
|
1394
1379
|
}
|
|
1395
1380
|
}];
|
|
@@ -1557,7 +1542,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1557
1542
|
if (self.isShuttingDown || !eventType)
|
|
1558
1543
|
return;
|
|
1559
1544
|
|
|
1560
|
-
// Log gesture events specifically for debugging
|
|
1561
1545
|
if ([eventType isEqualToString:@"gesture"]) {
|
|
1562
1546
|
NSString *gestureType = details[@"gestureType"] ?: @"unknown";
|
|
1563
1547
|
RJLogInfo(
|
|
@@ -1774,9 +1758,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1774
1758
|
});
|
|
1775
1759
|
}
|
|
1776
1760
|
|
|
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
1761
|
- (void)flushDataWithCompletion:(RJCompletionHandler)completion
|
|
1781
1762
|
isFinal:(BOOL)isFinal {
|
|
1782
1763
|
|
|
@@ -1834,7 +1815,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1834
1815
|
return;
|
|
1835
1816
|
}
|
|
1836
1817
|
|
|
1837
|
-
// For non-final flushes, skip promotion evaluation and just upload
|
|
1838
1818
|
if (!isFinal) {
|
|
1839
1819
|
RJLogInfo(
|
|
1840
1820
|
@"[RJ-FLUSH] Non-final background flush - uploading %lu "
|
|
@@ -1915,13 +1895,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1915
1895
|
}
|
|
1916
1896
|
}
|
|
1917
1897
|
|
|
1918
|
-
/// Convenience method for final flush (ends session).
|
|
1919
1898
|
- (void)flushAllDataWithCompletion:(RJCompletionHandler)completion {
|
|
1920
1899
|
[self flushDataWithCompletion:completion isFinal:YES];
|
|
1921
1900
|
}
|
|
1922
1901
|
|
|
1923
|
-
/// Flush pending data for background transition without ending the session.
|
|
1924
|
-
/// Used when app goes to background but may return quickly.
|
|
1925
1902
|
- (void)flushDataForBackgroundWithCompletion:(RJCompletionHandler)completion {
|
|
1926
1903
|
[self flushDataWithCompletion:completion isFinal:NO];
|
|
1927
1904
|
}
|
|
@@ -1960,7 +1937,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
1960
1937
|
if (self.isRecording) {
|
|
1961
1938
|
RJLogDebug(@"[KEYBOARD] Keyboard shown (height=%.0f)",
|
|
1962
1939
|
keyboardFrame.size.height);
|
|
1963
|
-
// Include keyboard height so web UI can render overlay
|
|
1964
1940
|
[self logEventInternal:RJEventTypeKeyboardShow
|
|
1965
1941
|
details:@{
|
|
1966
1942
|
@"keyboardHeight" : @(keyboardFrame.size.height),
|
|
@@ -2020,9 +1996,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2020
1996
|
@try {
|
|
2021
1997
|
[self stopBatchUploadTimer];
|
|
2022
1998
|
|
|
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
1999
|
if (self.uploadManager && self.lifecycleManager) {
|
|
2027
2000
|
NSTimeInterval currentBgTime =
|
|
2028
2001
|
self.lifecycleManager.totalBackgroundTimeMs;
|
|
@@ -2032,14 +2005,10 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2032
2005
|
currentBgTime);
|
|
2033
2006
|
}
|
|
2034
2007
|
|
|
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
2008
|
NSMutableDictionary *bgEvent =
|
|
2038
2009
|
[NSMutableDictionary dictionaryWithCapacity:3];
|
|
2039
2010
|
bgEvent[@"type"] = RJEventTypeAppBackground;
|
|
2040
2011
|
bgEvent[@"timestamp"] = @([RJWindowUtils currentTimestampMillis]);
|
|
2041
|
-
|
|
2042
|
-
// Add directly to session events synchronously
|
|
2043
2012
|
[self performStateSync:^{
|
|
2044
2013
|
if (self.sessionEvents) {
|
|
2045
2014
|
[self.sessionEvents addObject:bgEvent];
|
|
@@ -2049,17 +2018,12 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2049
2018
|
}
|
|
2050
2019
|
}];
|
|
2051
2020
|
|
|
2052
|
-
// Also add to event buffer for persistence
|
|
2053
2021
|
if (self.eventBuffer) {
|
|
2054
2022
|
[self.eventBuffer appendEvent:bgEvent];
|
|
2055
2023
|
}
|
|
2056
2024
|
|
|
2057
2025
|
if (self.captureEngine) {
|
|
2058
2026
|
@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
2027
|
RJLogInfo(@"[RJ-VIDEO] Pausing video capture for background (ASYNC)");
|
|
2064
2028
|
[self.captureEngine pauseVideoCapture];
|
|
2065
2029
|
RJLogInfo(@"[RJ-VIDEO] Video capture pause initiated");
|
|
@@ -2074,9 +2038,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2074
2038
|
beginBackgroundTaskWithName:@"RejourneySessionFlush"];
|
|
2075
2039
|
}
|
|
2076
2040
|
|
|
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
2041
|
RJLogInfo(@"[RJ-FLUSH] Starting non-final background flush (session will "
|
|
2081
2042
|
@"resume if user returns)");
|
|
2082
2043
|
[self flushDataForBackgroundWithCompletion:^(BOOL success) {
|
|
@@ -2129,13 +2090,9 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2129
2090
|
@try {
|
|
2130
2091
|
[self stopBatchUploadTimer];
|
|
2131
2092
|
|
|
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
2093
|
if (self.uploadManager && self.lifecycleManager) {
|
|
2136
2094
|
NSTimeInterval totalBgTime = self.lifecycleManager.totalBackgroundTimeMs;
|
|
2137
2095
|
|
|
2138
|
-
// If we're currently in background, add the ongoing duration
|
|
2139
2096
|
if (self.lifecycleManager.isInBackground &&
|
|
2140
2097
|
self.lifecycleManager.backgroundEntryTime > 0) {
|
|
2141
2098
|
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
|
|
@@ -2155,15 +2112,12 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2155
2112
|
}
|
|
2156
2113
|
|
|
2157
2114
|
if (self.captureEngine) {
|
|
2158
|
-
|
|
2159
|
-
// triggered before we try to upload events. This prevents video segments
|
|
2160
|
-
// from being lost when app is terminated.
|
|
2115
|
+
|
|
2161
2116
|
RJLogInfo(@"[RJ-TERMINATE] Stopping capture engine synchronously");
|
|
2162
2117
|
[self.captureEngine stopSessionSync];
|
|
2163
2118
|
RJLogInfo(@"[RJ-TERMINATE] Capture engine stopped");
|
|
2164
2119
|
}
|
|
2165
2120
|
|
|
2166
|
-
// Add terminated event SYNCHRONOUSLY before we copy events
|
|
2167
2121
|
NSMutableDictionary *terminateEvent =
|
|
2168
2122
|
[NSMutableDictionary dictionaryWithCapacity:3];
|
|
2169
2123
|
terminateEvent[@"type"] = RJEventTypeAppTerminated;
|
|
@@ -2219,7 +2173,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2219
2173
|
@try {
|
|
2220
2174
|
NSTimeInterval bgTimeMs = self.lifecycleManager.totalBackgroundTimeMs;
|
|
2221
2175
|
|
|
2222
|
-
// Keep local state in sync so stopSession/flush paths can rely on it.
|
|
2223
2176
|
self.totalBackgroundTimeMs = bgTimeMs;
|
|
2224
2177
|
|
|
2225
2178
|
if (self.uploadManager) {
|
|
@@ -2259,10 +2212,7 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2259
2212
|
[self handleSessionTimeout:backgroundDuration currentTime:currentTime];
|
|
2260
2213
|
}
|
|
2261
2214
|
|
|
2262
|
-
|
|
2263
|
-
/// This cleanly ends the old session and starts a fresh one.
|
|
2264
|
-
///
|
|
2265
|
-
/// Flow:
|
|
2215
|
+
|
|
2266
2216
|
/// 1. Capture final background time from lifecycle manager
|
|
2267
2217
|
/// 2. Stop timers and capture engine for old session
|
|
2268
2218
|
/// 3. Synchronously end the old session with correct background time
|
|
@@ -2326,7 +2276,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2326
2276
|
if (finalEvents.count > 0) {
|
|
2327
2277
|
[self.uploadManager synchronousUploadWithEvents:finalEvents];
|
|
2328
2278
|
} else {
|
|
2329
|
-
// Even with no events, end the session to record background time
|
|
2330
2279
|
[self.uploadManager endSessionSync];
|
|
2331
2280
|
}
|
|
2332
2281
|
|
|
@@ -2343,8 +2292,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2343
2292
|
self.sessionStartTime = currentTime;
|
|
2344
2293
|
self.totalBackgroundTimeMs = 0;
|
|
2345
2294
|
[self.sessionEvents removeAllObjects];
|
|
2346
|
-
|
|
2347
|
-
// Persist for crash recovery
|
|
2348
2295
|
[[NSUserDefaults standardUserDefaults]
|
|
2349
2296
|
setObject:newSessionId
|
|
2350
2297
|
forKey:@"rj_current_session_id"];
|
|
@@ -2353,7 +2300,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2353
2300
|
|
|
2354
2301
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] New session ID: %@", newSessionId);
|
|
2355
2302
|
|
|
2356
|
-
// Reset upload manager for new session
|
|
2357
2303
|
if (self.uploadManager) {
|
|
2358
2304
|
@try {
|
|
2359
2305
|
[self.uploadManager resetForNewSession];
|
|
@@ -2365,7 +2311,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2365
2311
|
self.uploadManager.sessionStartTime = currentTime;
|
|
2366
2312
|
self.uploadManager.totalBackgroundTimeMs = 0;
|
|
2367
2313
|
|
|
2368
|
-
// Preserve user identity
|
|
2369
2314
|
if (!self.userId) {
|
|
2370
2315
|
self.userId = [[NSUserDefaults standardUserDefaults]
|
|
2371
2316
|
stringForKey:@"rj_user_identity"];
|
|
@@ -2373,17 +2318,14 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2373
2318
|
self.uploadManager.userId = self.userId ?: @"anonymous";
|
|
2374
2319
|
}
|
|
2375
2320
|
|
|
2376
|
-
// Reset lifecycle manager background tracking
|
|
2377
2321
|
if (self.lifecycleManager) {
|
|
2378
2322
|
[self.lifecycleManager resetBackgroundTime];
|
|
2379
2323
|
self.lifecycleManager.isRecording = YES;
|
|
2380
2324
|
}
|
|
2381
2325
|
|
|
2382
|
-
// Reset event buffer for new session - recreate since sessionId is readonly
|
|
2383
2326
|
if (self.eventBuffer) {
|
|
2384
2327
|
[self.eventBuffer clearAllEvents];
|
|
2385
2328
|
}
|
|
2386
|
-
// Create new event buffer for new session (sessionId is readonly)
|
|
2387
2329
|
NSString *pendingPath = self.eventBuffer.pendingRootPath;
|
|
2388
2330
|
if (pendingPath.length == 0) {
|
|
2389
2331
|
pendingPath =
|
|
@@ -2417,15 +2359,11 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2417
2359
|
}
|
|
2418
2360
|
|
|
2419
2361
|
self.isRecording = YES;
|
|
2420
|
-
|
|
2421
|
-
// Verify touch tracking
|
|
2422
2362
|
RJTouchInterceptor *touchInterceptor = [RJTouchInterceptor sharedInstance];
|
|
2423
2363
|
if (touchInterceptor && !touchInterceptor.isTrackingEnabled) {
|
|
2424
2364
|
RJLogInfo(@"[RJ-SESSION-TIMEOUT] Re-enabling touch tracking");
|
|
2425
2365
|
[self setupTouchTracking];
|
|
2426
2366
|
}
|
|
2427
|
-
|
|
2428
|
-
// Start timers for new session
|
|
2429
2367
|
[self startBatchUploadTimer];
|
|
2430
2368
|
[self startDurationLimitTimer];
|
|
2431
2369
|
|
|
@@ -2447,7 +2385,6 @@ RCT_EXPORT_METHOD(getUserIdentity : (RCTPromiseResolveBlock)
|
|
|
2447
2385
|
[self.eventBuffer appendEvent:sessionStartEvent];
|
|
2448
2386
|
}
|
|
2449
2387
|
|
|
2450
|
-
// Immediate upload to register session with backend
|
|
2451
2388
|
dispatch_after(
|
|
2452
2389
|
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
|
|
2453
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],
|