@rejourneyco/react-native 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
  2. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
  3. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
  4. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
  5. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
  6. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
  7. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +14 -27
  8. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
  9. package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
  10. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
  11. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
  12. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
  13. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
  14. package/ios/Capture/RJCaptureEngine.m +9 -61
  15. package/ios/Capture/RJViewHierarchyScanner.m +68 -51
  16. package/ios/Core/RJLifecycleManager.m +0 -14
  17. package/ios/Core/Rejourney.mm +24 -37
  18. package/ios/Network/RJDeviceAuthManager.m +0 -2
  19. package/ios/Network/RJUploadManager.h +8 -0
  20. package/ios/Network/RJUploadManager.m +45 -0
  21. package/ios/Privacy/RJPrivacyMask.m +5 -31
  22. package/ios/Rejourney.h +0 -14
  23. package/ios/Touch/RJTouchInterceptor.m +21 -15
  24. package/ios/Utils/RJEventBuffer.m +57 -69
  25. package/ios/Utils/RJWindowUtils.m +87 -86
  26. package/lib/commonjs/index.js +44 -31
  27. package/lib/commonjs/sdk/autoTracking.js +0 -3
  28. package/lib/commonjs/sdk/constants.js +1 -1
  29. package/lib/commonjs/sdk/networkInterceptor.js +0 -11
  30. package/lib/commonjs/sdk/utils.js +73 -14
  31. package/lib/module/index.js +44 -31
  32. package/lib/module/sdk/autoTracking.js +0 -3
  33. package/lib/module/sdk/constants.js +1 -1
  34. package/lib/module/sdk/networkInterceptor.js +0 -11
  35. package/lib/module/sdk/utils.js +73 -14
  36. package/lib/typescript/sdk/constants.d.ts +1 -1
  37. package/lib/typescript/sdk/utils.d.ts +31 -1
  38. package/package.json +16 -4
  39. package/src/index.ts +42 -20
  40. package/src/sdk/autoTracking.ts +0 -2
  41. package/src/sdk/constants.ts +14 -14
  42. package/src/sdk/networkInterceptor.ts +0 -9
  43. package/src/sdk/utils.ts +76 -14
@@ -36,9 +36,10 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
36
36
  if (self) {
37
37
  _sessionId = [sessionId copy];
38
38
  if (pendingRootPath.length == 0) {
39
- NSString *defaultPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
40
- NSUserDomainMask, YES)
41
- .firstObject stringByAppendingPathComponent:@"rj_pending"];
39
+ NSString *defaultPath =
40
+ [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
41
+ NSUserDomainMask, YES)
42
+ .firstObject stringByAppendingPathComponent:@"rj_pending"];
42
43
  _pendingRootPath = [defaultPath copy];
43
44
  } else {
44
45
  _pendingRootPath = [pendingRootPath copy];
@@ -68,7 +69,6 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
68
69
  NSString *sessionDir =
69
70
  [self.pendingRootPath stringByAppendingPathComponent:self.sessionId];
70
71
 
71
-
72
72
  NSFileManager *fm = [NSFileManager defaultManager];
73
73
  if (![fm fileExistsAtPath:sessionDir]) {
74
74
  NSError *error = nil;
@@ -87,12 +87,11 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
87
87
 
88
88
  if (![fm fileExistsAtPath:self.eventsFilePath]) {
89
89
  NSDictionary *attrs = @{
90
- NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication
90
+ NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication
91
91
  };
92
92
  [fm createFileAtPath:self.eventsFilePath contents:nil attributes:attrs];
93
93
  }
94
94
 
95
-
96
95
  NSError *error = nil;
97
96
  self.fileHandle =
98
97
  [NSFileHandle fileHandleForWritingAtPath:self.eventsFilePath];
@@ -102,14 +101,12 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
102
101
  return;
103
102
  }
104
103
 
105
-
106
104
  if (@available(iOS 13.0, *)) {
107
105
  [self.fileHandle seekToEndReturningOffset:nil error:&error];
108
106
  } else {
109
107
  [self.fileHandle seekToEndOfFile];
110
108
  }
111
109
 
112
-
113
110
  [self countExistingEvents];
114
111
 
115
112
  RJLogDebug(@"Event buffer ready: %@ (%ld existing events)",
@@ -117,40 +114,57 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
117
114
  }
118
115
 
119
116
  - (void)countExistingEvents {
120
- NSError *error = nil;
121
- NSString *content = [NSString stringWithContentsOfFile:self.eventsFilePath
122
- encoding:NSUTF8StringEncoding
123
- error:&error];
124
- if (error || !content) {
125
- self.eventCount = 0;
126
- return;
127
- }
117
+ __block NSInteger count = 0;
118
+ __block NSTimeInterval lastTs = 0;
128
119
 
129
- NSArray<NSString *> *lines = [content componentsSeparatedByString:@"\n"];
130
- NSInteger count = 0;
131
- NSTimeInterval lastTs = 0;
132
-
133
- for (NSString *line in lines) {
134
- if (line.length == 0)
135
- continue;
136
-
137
- NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
138
- NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
139
- options:0
140
- error:nil];
141
- if (event) {
142
- count++;
143
- NSNumber *ts = event[@"timestamp"];
144
- if (ts && [ts doubleValue] > lastTs) {
145
- lastTs = [ts doubleValue];
146
- }
120
+ [self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
121
+ count++;
122
+ NSNumber *ts = event[@"timestamp"];
123
+ if (ts && [ts doubleValue] > lastTs) {
124
+ lastTs = [ts doubleValue];
147
125
  }
148
- }
126
+ }];
149
127
 
150
128
  self.eventCount = count;
151
129
  self.lastEventTimestamp = lastTs;
152
130
  }
153
131
 
132
+ - (void)enumerateEventsWithBlock:(void (^)(NSDictionary *event,
133
+ BOOL *stop))block {
134
+ if (!block || !self.eventsFilePath)
135
+ return;
136
+
137
+ FILE *file = fopen([self.eventsFilePath UTF8String], "r");
138
+ if (!file)
139
+ return;
140
+
141
+ char *line = NULL;
142
+ size_t linecap = 0;
143
+ ssize_t linelen;
144
+ BOOL stop = NO;
145
+
146
+ while (!stop && (linelen = getline(&line, &linecap, file)) > 0) {
147
+ @autoreleasepool {
148
+ if (linelen <= 1)
149
+ continue;
150
+
151
+ NSData *data = [NSData dataWithBytesNoCopy:line
152
+ length:linelen
153
+ freeWhenDone:NO];
154
+ NSError *error = nil;
155
+ NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
156
+ options:0
157
+ error:&error];
158
+ if (event) {
159
+ block(event, &stop);
160
+ }
161
+ }
162
+ }
163
+ if (line)
164
+ free(line);
165
+ fclose(file);
166
+ }
167
+
154
168
  - (void)closeFileHandle {
155
169
  if (self.fileHandle) {
156
170
  if (@available(iOS 13.0, *)) {
@@ -222,11 +236,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
222
236
  return NO;
223
237
  }
224
238
 
225
-
226
239
  NSMutableData *lineData = [jsonData mutableCopy];
227
240
  [lineData appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
228
241
 
229
-
230
242
  if (@available(iOS 13.4, *)) {
231
243
  NSError *writeError = nil;
232
244
  [self.fileHandle writeData:lineData error:&writeError];
@@ -243,14 +255,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
243
255
  }
244
256
  }
245
257
 
246
-
247
- if (@available(iOS 13.0, *)) {
248
- [self.fileHandle synchronizeAndReturnError:nil];
249
- } else {
250
- [self.fileHandle synchronizeFile];
251
- }
258
+ // Removed redundant synchronizeFile (fsync) calls to improve performance.
259
+ // The serial writeQueue ensures order, and the OS will manage buffering.
252
260
 
253
-
254
261
  self.eventCount++;
255
262
  NSNumber *ts = event[@"timestamp"];
256
263
  if (ts) {
@@ -269,27 +276,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
269
276
  __block NSMutableArray<NSDictionary *> *events = [NSMutableArray new];
270
277
 
271
278
  [self performWriteSync:^{
272
- NSError *error = nil;
273
- NSString *content = [NSString stringWithContentsOfFile:self.eventsFilePath
274
- encoding:NSUTF8StringEncoding
275
- error:&error];
276
- if (error || !content) {
277
- return;
278
- }
279
-
280
- NSArray<NSString *> *lines = [content componentsSeparatedByString:@"\n"];
281
- for (NSString *line in lines) {
282
- if (line.length == 0)
283
- continue;
284
-
285
- NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
286
- NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
287
- options:0
288
- error:nil];
289
- if (event) {
290
- [events addObject:event];
291
- }
292
- }
279
+ [self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
280
+ [events addObject:event];
281
+ }];
293
282
  }];
294
283
 
295
284
  return events;
@@ -297,7 +286,7 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
297
286
 
298
287
  - (NSArray<NSDictionary *> *)readEventsAfterBatchNumber:
299
288
  (NSInteger)afterBatchNumber {
300
-
289
+
301
290
  NSArray<NSDictionary *> *allEvents = [self readAllEvents];
302
291
 
303
292
  __block NSInteger uploadedCount = 0;
@@ -310,9 +299,8 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
310
299
  return @[];
311
300
  }
312
301
 
313
- return [allEvents subarrayWithRange:NSMakeRange(
314
- startIndex,
315
- allEvents.count - startIndex)];
302
+ return [allEvents
303
+ subarrayWithRange:NSMakeRange(startIndex, allEvents.count - startIndex)];
316
304
  }
317
305
 
318
306
  - (void)markEventsUploadedUpToIndex:(NSInteger)eventIndex {
@@ -23,110 +23,111 @@
23
23
 
24
24
  @implementation RJWindowUtils
25
25
 
26
+ static __weak UIWindow *_cachedKeyWindow = nil;
27
+ static NSTimeInterval _lastCacheTime = 0;
28
+ static const NSTimeInterval kKeyWindowCacheTTL = 0.5; // Cache for 500ms
29
+
26
30
  + (UIWindow *)keyWindow {
27
- if (@available(iOS 13.0, *)) {
28
- // IMPORTANT:
29
- // "Key window" can temporarily become a system window such as:
30
- // - UITextEffectsWindow
31
- // - UIRemoteKeyboardWindow / UIInputWindowController-hosted windows
32
- // Capturing those can trigger keyboard autolayout warnings/spikes even if the
33
- // user never focused a text field. Prefer a normal-level app window.
34
- //
35
- // ALSO IMPORTANT:
36
- // Some frameworks (including React Native/Expo modals/overlays) may present
37
- // content in a separate UIWindow with a windowLevel > UIWindowLevelNormal.
38
- // If we only consider "normal" level, we may capture the wrong window and
39
- // miss TextInputs entirely (privacy masking failure).
40
- //
41
- // We therefore prefer the TOP-MOST visible non-system window, while still
42
- // excluding keyboard/text-effects windows.
43
-
44
- UIWindow *bestKeyApp = nil;
45
- CGFloat bestKeyAppLevel = -CGFLOAT_MAX;
46
- UIWindow *bestApp = nil;
47
- CGFloat bestAppLevel = -CGFLOAT_MAX;
48
- UIWindow *anyKey = nil;
49
-
50
- for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
51
- if (![scene isKindOfClass:[UIWindowScene class]]) {
52
- continue;
53
- }
54
- UIWindowScene *windowScene = (UIWindowScene *)scene;
55
- if (windowScene.activationState != UISceneActivationStateForegroundActive) {
56
- continue;
57
- }
58
-
59
- for (UIWindow *window in windowScene.windows) {
60
- if (!window || window.isHidden || window.alpha <= 0.01) {
61
- continue;
62
- }
63
-
64
- NSString *cls = NSStringFromClass([window class]);
65
- BOOL isSystemInputWindow =
66
- ([cls containsString:@"Keyboard"] ||
67
- [cls containsString:@"TextEffects"] ||
68
- [cls containsString:@"InputWindow"] ||
69
- [cls containsString:@"RemoteKeyboard"]);
70
-
71
- BOOL hasRoot = (window.rootViewController != nil);
72
- BOOL isAppCandidate = (!isSystemInputWindow && hasRoot);
73
- CGFloat level = window.windowLevel;
74
-
75
- if (window.isKeyWindow) {
76
- if (!anyKey) {
77
- anyKey = window;
78
- }
79
- if (isAppCandidate && level > bestKeyAppLevel) {
80
- bestKeyAppLevel = level;
81
- bestKeyApp = window;
82
- }
83
- }
84
-
85
- if (isAppCandidate && level > bestAppLevel) {
86
- bestAppLevel = level;
87
- bestApp = window;
88
- }
89
- }
90
- }
31
+ NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
32
+ if (_cachedKeyWindow && (now - _lastCacheTime < kKeyWindowCacheTTL)) {
33
+ return _cachedKeyWindow;
34
+ }
35
+
36
+ UIWindow *window = [self findKeyWindowInternal];
37
+ _cachedKeyWindow = window;
38
+ _lastCacheTime = now;
39
+ return window;
40
+ }
41
+
42
+ + (UIWindow *)findKeyWindowInternal {
43
+ if (@available(iOS 13.0, *)) {
44
+ UIWindow *bestKeyApp = nil;
45
+ CGFloat bestKeyAppLevel = -CGFLOAT_MAX;
46
+ UIWindow *bestApp = nil;
47
+ CGFloat bestAppLevel = -CGFLOAT_MAX;
48
+ UIWindow *anyKey = nil;
91
49
 
92
- if (bestKeyApp) {
93
- return bestKeyApp;
50
+ for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
51
+ if (![scene isKindOfClass:[UIWindowScene class]]) {
52
+ continue;
53
+ }
54
+ UIWindowScene *windowScene = (UIWindowScene *)scene;
55
+ if (windowScene.activationState !=
56
+ UISceneActivationStateForegroundActive) {
57
+ continue;
58
+ }
59
+
60
+ for (UIWindow *window in windowScene.windows) {
61
+ if (!window || window.isHidden || window.alpha <= 0.01) {
62
+ continue;
94
63
  }
95
- if (bestApp) {
96
- return bestApp;
64
+
65
+ NSString *cls = NSStringFromClass([window class]);
66
+ BOOL isSystemInputWindow = ([cls containsString:@"Keyboard"] ||
67
+ [cls containsString:@"TextEffects"] ||
68
+ [cls containsString:@"InputWindow"] ||
69
+ [cls containsString:@"RemoteKeyboard"]);
70
+
71
+ BOOL hasRoot = (window.rootViewController != nil);
72
+ BOOL isAppCandidate = (!isSystemInputWindow && hasRoot);
73
+ CGFloat level = window.windowLevel;
74
+
75
+ if (window.isKeyWindow) {
76
+ if (!anyKey) {
77
+ anyKey = window;
78
+ }
79
+ if (isAppCandidate && level > bestKeyAppLevel) {
80
+ bestKeyAppLevel = level;
81
+ bestKeyApp = window;
82
+ }
97
83
  }
98
- if (anyKey) {
99
- return anyKey;
84
+
85
+ if (isAppCandidate && level > bestAppLevel) {
86
+ bestAppLevel = level;
87
+ bestApp = window;
100
88
  }
101
- } else {
89
+ }
90
+ }
91
+
92
+ if (bestKeyApp) {
93
+ return bestKeyApp;
94
+ }
95
+ if (bestApp) {
96
+ return bestApp;
97
+ }
98
+ if (anyKey) {
99
+ return anyKey;
100
+ }
101
+ } else {
102
102
  #pragma clang diagnostic push
103
103
  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
104
- return [UIApplication sharedApplication].keyWindow;
104
+ return [UIApplication sharedApplication].keyWindow;
105
105
  #pragma clang diagnostic pop
106
- }
107
- return nil;
106
+ }
107
+ return nil;
108
108
  }
109
109
 
110
110
  + (NSString *)accessibilityLabelForView:(UIView *)view {
111
- UIView *current = view;
112
- while (current) {
113
- if (current.accessibilityLabel.length > 0) {
114
- return current.accessibilityLabel;
115
- }
116
- current = current.superview;
111
+ UIView *current = view;
112
+ while (current) {
113
+ if (current.accessibilityLabel.length > 0) {
114
+ return current.accessibilityLabel;
117
115
  }
118
- return nil;
116
+ current = current.superview;
117
+ }
118
+ return nil;
119
119
  }
120
120
 
121
121
  + (NSString *)generateSessionId {
122
- NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
123
- NSString *timestampStr = [NSString stringWithFormat:@"%.0f", timestamp * 1000];
124
- NSString *randomHex = [NSString stringWithFormat:@"%08X", arc4random()];
125
- return [NSString stringWithFormat:@"session_%@_%@", timestampStr, randomHex];
122
+ NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
123
+ NSString *timestampStr =
124
+ [NSString stringWithFormat:@"%.0f", timestamp * 1000];
125
+ NSString *randomHex = [NSString stringWithFormat:@"%08X", arc4random()];
126
+ return [NSString stringWithFormat:@"session_%@_%@", timestampStr, randomHex];
126
127
  }
127
128
 
128
129
  + (NSTimeInterval)currentTimestampMillis {
129
- return [[NSDate date] timeIntervalSince1970] * 1000;
130
+ return [[NSDate date] timeIntervalSince1970] * 1000;
130
131
  }
131
132
 
132
133
  @end
@@ -194,7 +194,12 @@ function getLogger() {
194
194
  logRecordingStart: () => {},
195
195
  logRecordingRemoteDisabled: () => {},
196
196
  logInvalidProjectKey: () => {},
197
- logPackageMismatch: () => {}
197
+ logPackageMismatch: () => {},
198
+ logNetworkRequest: () => {},
199
+ logFrustration: () => {},
200
+ logError: () => {},
201
+ logUploadStats: () => {},
202
+ logLifecycleEvent: () => {}
198
203
  };
199
204
  }
200
205
  try {
@@ -219,7 +224,12 @@ function getLogger() {
219
224
  logRecordingStart: () => {},
220
225
  logRecordingRemoteDisabled: () => {},
221
226
  logInvalidProjectKey: () => {},
222
- logPackageMismatch: () => {}
227
+ logPackageMismatch: () => {},
228
+ logNetworkRequest: () => {},
229
+ logFrustration: () => {},
230
+ logError: () => {},
231
+ logUploadStats: () => {},
232
+ logLifecycleEvent: () => {}
223
233
  };
224
234
  }
225
235
  }
@@ -276,6 +286,7 @@ function getAutoTracking() {
276
286
  let _isInitialized = false;
277
287
  let _isRecording = false;
278
288
  let _initializationFailed = false;
289
+ let _metricsInterval = null;
279
290
  let _appStateSubscription = null;
280
291
  let _authErrorSubscription = null;
281
292
  let _currentAppState = 'active'; // Default to active, will be updated on init
@@ -478,18 +489,12 @@ const Rejourney = {
478
489
  const apiUrl = _storedConfig.apiUrl || 'https://api.rejourney.co';
479
490
  const publicKey = _storedConfig.publicRouteKey || '';
480
491
  getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
481
-
482
- // Use user identity if set, otherwise use anonymous device ID
483
492
  const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
484
-
485
- // Try to load persisted user identity if not already set in memory
486
493
  if (!_userIdentity) {
487
494
  _userIdentity = await loadPersistedUserIdentity();
488
495
  }
489
496
  const userId = _userIdentity || deviceId;
490
497
  getLogger().debug(`userId=${userId.substring(0, 8)}...`);
491
-
492
- // Start native session
493
498
  const result = await nativeModule.startSession(userId, apiUrl, publicKey);
494
499
  getLogger().debug('Native startSession returned:', JSON.stringify(result));
495
500
  if (!result?.success) {
@@ -502,10 +507,27 @@ const Rejourney = {
502
507
  }
503
508
  _isRecording = true;
504
509
  getLogger().debug(`✅ Session started: ${result.sessionId}`);
505
- // Use lifecycle log for session start - only shown in dev builds
506
510
  getLogger().logSessionStart(result.sessionId);
507
-
508
- // Initialize auto tracking features
511
+ // Start polling for upload stats in dev mode
512
+ if (__DEV__) {
513
+ _metricsInterval = setInterval(async () => {
514
+ if (!_isRecording) {
515
+ if (_metricsInterval) clearInterval(_metricsInterval);
516
+ return;
517
+ }
518
+ try {
519
+ const native = getRejourneyNative();
520
+ if (native) {
521
+ const metrics = await native.getSDKMetrics();
522
+ if (metrics) {
523
+ getLogger().logUploadStats(metrics);
524
+ }
525
+ }
526
+ } catch (e) {
527
+ getLogger().debug('Failed to fetch metrics:', e);
528
+ }
529
+ }, 10000); // Poll more frequently in dev (10s) for better feedback
530
+ }
509
531
  getAutoTracking().initAutoTracking({
510
532
  rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
511
533
  rageTapTimeWindow: _storedConfig?.rageTapTimeWindow ?? 500,
@@ -523,7 +545,7 @@ const Rejourney = {
523
545
  x,
524
546
  y
525
547
  });
526
- // logger.debug(`Rage tap detected: ${count} taps at (${x}, ${y})`);
548
+ getLogger().logFrustration(`Rage tap (${count} taps)`);
527
549
  },
528
550
  // Error callback - log as error event
529
551
  onError: error => {
@@ -532,17 +554,10 @@ const Rejourney = {
532
554
  stack: error.stack,
533
555
  name: error.name
534
556
  });
535
- // logger.debug(`Error captured: ${error.message}`);
557
+ getLogger().logError(error.message);
536
558
  },
537
- // Screen change callback - log screen change
538
- onScreen: (_screenName, _previousScreen) => {
539
- // Native module already handles screen changes
540
- // This is just for metrics tracking
541
- // logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
542
- }
559
+ onScreen: (_screenName, _previousScreen) => {}
543
560
  });
544
-
545
- // Collect and log device info
546
561
  if (_storedConfig?.collectDeviceInfo !== false) {
547
562
  try {
548
563
  const deviceInfo = await getAutoTracking().collectDeviceInfo();
@@ -551,14 +566,12 @@ const Rejourney = {
551
566
  getLogger().warn('Failed to collect device info:', deviceError);
552
567
  }
553
568
  }
554
-
555
- // Setup automatic network interception
556
569
  if (_storedConfig?.autoTrackNetwork !== false) {
557
570
  try {
558
571
  const ignoreUrls = [apiUrl, '/api/ingest/presign', '/api/ingest/batch/complete', '/api/ingest/session/end', ...(_storedConfig?.networkIgnoreUrls || [])];
559
572
  getNetworkInterceptor().initNetworkInterceptor(request => {
560
- this.logNetworkRequest(request);
561
573
  getAutoTracking().trackAPIRequest(request.success || false, request.statusCode, request.duration || 0, request.responseBodySize || 0);
574
+ Rejourney.logNetworkRequest(request);
562
575
  }, {
563
576
  ignoreUrls,
564
577
  captureSizes: _storedConfig?.networkCaptureSizes !== false
@@ -589,6 +602,10 @@ const Rejourney = {
589
602
  getAutoTracking().cleanupAutoTracking();
590
603
  getAutoTracking().resetMetrics();
591
604
  await safeNativeCall('stopSession', () => getRejourneyNative().stopSession(), undefined);
605
+ if (_metricsInterval) {
606
+ clearInterval(_metricsInterval);
607
+ _metricsInterval = null;
608
+ }
592
609
  _isRecording = false;
593
610
  getLogger().logSessionEnd('current');
594
611
  } catch (error) {
@@ -605,7 +622,6 @@ const Rejourney = {
605
622
  */
606
623
  logEvent(name, properties) {
607
624
  safeNativeCallSync('logEvent', () => {
608
- // Fire and forget - don't await
609
625
  getRejourneyNative().logEvent(name, properties || {}).catch(() => {});
610
626
  }, undefined);
611
627
  },
@@ -706,7 +722,7 @@ const Rejourney = {
706
722
  eventCount: 0,
707
723
  videoSegmentCount: 0,
708
724
  storageSize: 0,
709
- sdkVersion: '1.0.0',
725
+ sdkVersion: _constants.SDK_VERSION,
710
726
  isComplete: false
711
727
  },
712
728
  events: []
@@ -733,7 +749,6 @@ const Rejourney = {
733
749
  * @returns Path to export file (not implemented)
734
750
  */
735
751
  async exportSession(_sessionId) {
736
- // Return empty string - actual export should be done from dashboard server
737
752
  getLogger().warn('exportSession not implemented - export from dashboard server');
738
753
  return '';
739
754
  },
@@ -955,8 +970,6 @@ const Rejourney = {
955
970
  errorMessage: request.errorMessage,
956
971
  cached: request.cached
957
972
  };
958
-
959
- // Fire and forget - don't await, this is low priority
960
973
  getRejourneyNative().logEvent('network_request', networkEvent).catch(() => {});
961
974
  }, undefined);
962
975
  },
@@ -1053,10 +1066,10 @@ function handleAppStateChange(nextAppState) {
1053
1066
  try {
1054
1067
  if (_currentAppState.match(/active/) && nextAppState === 'background') {
1055
1068
  // App going to background - native module handles this automatically
1056
- getLogger().debug('App moving to background');
1069
+ getLogger().logLifecycleEvent('App moving to background');
1057
1070
  } else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
1058
1071
  // App coming back to foreground
1059
- getLogger().debug('App returning to foreground');
1072
+ getLogger().logLifecycleEvent('App returning to foreground');
1060
1073
  }
1061
1074
  _currentAppState = nextAppState;
1062
1075
  } catch (error) {
@@ -479,9 +479,6 @@ function setupNavigationTracking() {
479
479
  if (__DEV__) {
480
480
  _utils.logger.debug('Setting up navigation tracking...');
481
481
  }
482
-
483
- // Delay to ensure navigation is initialized - Expo Router needs more time
484
- // We retry a few times with increasing delays
485
482
  let attempts = 0;
486
483
  const maxAttempts = 5;
487
484
  const trySetup = () => {
@@ -8,7 +8,7 @@ exports.UPLOAD_SETTINGS = exports.STORAGE_SETTINGS = exports.SDK_VERSION = expor
8
8
  * Rejourney SDK Constants
9
9
  */
10
10
 
11
- const SDK_VERSION = exports.SDK_VERSION = '1.0.0';
11
+ const SDK_VERSION = exports.SDK_VERSION = '1.0.3';
12
12
 
13
13
  /** Default configuration values */
14
14
  const DEFAULT_CONFIG = exports.DEFAULT_CONFIG = {
@@ -126,8 +126,6 @@ function queueRequest(request) {
126
126
  function flushPendingRequests() {
127
127
  flushTimer = null;
128
128
  if (!logCallback || pendingCount === 0) return;
129
-
130
- // Process all pending requests
131
129
  while (pendingCount > 0) {
132
130
  const request = pendingRequests[pendingHead];
133
131
  pendingRequests[pendingHead] = null; // Allow GC
@@ -195,14 +193,9 @@ function interceptFetch() {
195
193
  if (!shouldSampleRequest(path)) {
196
194
  return originalFetch(input, init);
197
195
  }
198
-
199
- // Capture start time (only synchronous work)
200
196
  const startTime = Date.now();
201
197
  const method = (init?.method || 'GET').toUpperCase();
202
-
203
- // Call original fetch
204
198
  return originalFetch(input, init).then(response => {
205
- // Success - queue the log asynchronously
206
199
  queueRequest({
207
200
  requestId: `f${startTime}`,
208
201
  method,
@@ -253,8 +246,6 @@ function interceptXHR() {
253
246
  if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
254
247
  return originalXHRSend.call(this, body);
255
248
  }
256
-
257
- // Check sampling
258
249
  const {
259
250
  path
260
251
  } = parseUrlFast(data.u);
@@ -303,8 +294,6 @@ function initNetworkInterceptor(callback, options) {
303
294
  */
304
295
  function disableNetworkInterceptor() {
305
296
  config.enabled = false;
306
-
307
- // Flush any pending requests
308
297
  if (flushTimer) {
309
298
  clearTimeout(flushTimer);
310
299
  flushTimer = null;