@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
package/src/index.ts CHANGED
@@ -100,6 +100,11 @@ function getLogger() {
100
100
  logRecordingRemoteDisabled: () => { },
101
101
  logInvalidProjectKey: () => { },
102
102
  logPackageMismatch: () => { },
103
+ logNetworkRequest: () => { },
104
+ logFrustration: () => { },
105
+ logError: () => { },
106
+ logUploadStats: () => { },
107
+ logLifecycleEvent: () => { },
103
108
  };
104
109
  }
105
110
 
@@ -126,6 +131,11 @@ function getLogger() {
126
131
  logRecordingRemoteDisabled: () => { },
127
132
  logInvalidProjectKey: () => { },
128
133
  logPackageMismatch: () => { },
134
+ logNetworkRequest: () => { },
135
+ logFrustration: () => { },
136
+ logError: () => { },
137
+ logUploadStats: () => { },
138
+ logLifecycleEvent: () => { },
129
139
  };
130
140
  }
131
141
  }
@@ -194,6 +204,7 @@ function getAutoTracking() {
194
204
  let _isInitialized = false;
195
205
  let _isRecording = false;
196
206
  let _initializationFailed = false;
207
+ let _metricsInterval: ReturnType<typeof setInterval> | null = null;
197
208
  let _appStateSubscription: { remove: () => void } | null = null;
198
209
  let _authErrorSubscription: { remove: () => void } | null = null;
199
210
  let _currentAppState: string = 'active'; // Default to active, will be updated on init
@@ -415,10 +426,8 @@ const Rejourney: RejourneyAPI = {
415
426
 
416
427
  getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
417
428
 
418
- // Use user identity if set, otherwise use anonymous device ID
419
429
  const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
420
430
 
421
- // Try to load persisted user identity if not already set in memory
422
431
  if (!_userIdentity) {
423
432
  _userIdentity = await loadPersistedUserIdentity();
424
433
  }
@@ -426,7 +435,6 @@ const Rejourney: RejourneyAPI = {
426
435
  const userId = _userIdentity || deviceId;
427
436
  getLogger().debug(`userId=${userId.substring(0, 8)}...`);
428
437
 
429
- // Start native session
430
438
  const result = await nativeModule.startSession(userId, apiUrl, publicKey);
431
439
  getLogger().debug('Native startSession returned:', JSON.stringify(result));
432
440
 
@@ -441,10 +449,28 @@ const Rejourney: RejourneyAPI = {
441
449
 
442
450
  _isRecording = true;
443
451
  getLogger().debug(`✅ Session started: ${result.sessionId}`);
444
- // Use lifecycle log for session start - only shown in dev builds
445
452
  getLogger().logSessionStart(result.sessionId);
453
+ // Start polling for upload stats in dev mode
454
+ if (__DEV__) {
455
+ _metricsInterval = setInterval(async () => {
456
+ if (!_isRecording) {
457
+ if (_metricsInterval) clearInterval(_metricsInterval);
458
+ return;
459
+ }
460
+ try {
461
+ const native = getRejourneyNative();
462
+ if (native) {
463
+ const metrics = await native.getSDKMetrics();
464
+ if (metrics) {
465
+ getLogger().logUploadStats(metrics);
466
+ }
467
+ }
468
+ } catch (e) {
469
+ getLogger().debug('Failed to fetch metrics:', e);
470
+ }
471
+ }, 10000); // Poll more frequently in dev (10s) for better feedback
472
+ }
446
473
 
447
- // Initialize auto tracking features
448
474
  getAutoTracking().initAutoTracking(
449
475
  {
450
476
  rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
@@ -464,7 +490,7 @@ const Rejourney: RejourneyAPI = {
464
490
  x,
465
491
  y,
466
492
  });
467
- // logger.debug(`Rage tap detected: ${count} taps at (${x}, ${y})`);
493
+ getLogger().logFrustration(`Rage tap (${count} taps)`);
468
494
  },
469
495
  // Error callback - log as error event
470
496
  onError: (error: { message: string; stack?: string; name?: string }) => {
@@ -473,18 +499,13 @@ const Rejourney: RejourneyAPI = {
473
499
  stack: error.stack,
474
500
  name: error.name,
475
501
  });
476
- // logger.debug(`Error captured: ${error.message}`);
502
+ getLogger().logError(error.message);
477
503
  },
478
- // Screen change callback - log screen change
479
504
  onScreen: (_screenName: string, _previousScreen?: string) => {
480
- // Native module already handles screen changes
481
- // This is just for metrics tracking
482
- // logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
483
505
  },
484
506
  }
485
507
  );
486
508
 
487
- // Collect and log device info
488
509
  if (_storedConfig?.collectDeviceInfo !== false) {
489
510
  try {
490
511
  const deviceInfo = await getAutoTracking().collectDeviceInfo();
@@ -494,7 +515,6 @@ const Rejourney: RejourneyAPI = {
494
515
  }
495
516
  }
496
517
 
497
- // Setup automatic network interception
498
518
  if (_storedConfig?.autoTrackNetwork !== false) {
499
519
  try {
500
520
  const ignoreUrls: (string | RegExp)[] = [
@@ -507,13 +527,13 @@ const Rejourney: RejourneyAPI = {
507
527
 
508
528
  getNetworkInterceptor().initNetworkInterceptor(
509
529
  (request: NetworkRequestParams) => {
510
- this.logNetworkRequest(request);
511
530
  getAutoTracking().trackAPIRequest(
512
531
  request.success || false,
513
532
  request.statusCode,
514
533
  request.duration || 0,
515
534
  request.responseBodySize || 0
516
535
  );
536
+ Rejourney.logNetworkRequest(request);
517
537
  },
518
538
  {
519
539
  ignoreUrls,
@@ -554,6 +574,11 @@ const Rejourney: RejourneyAPI = {
554
574
 
555
575
  await safeNativeCall('stopSession', () => getRejourneyNative()!.stopSession(), undefined);
556
576
 
577
+ if (_metricsInterval) {
578
+ clearInterval(_metricsInterval);
579
+ _metricsInterval = null;
580
+ }
581
+
557
582
  _isRecording = false;
558
583
  getLogger().logSessionEnd('current');
559
584
  } catch (error) {
@@ -573,7 +598,6 @@ const Rejourney: RejourneyAPI = {
573
598
  safeNativeCallSync(
574
599
  'logEvent',
575
600
  () => {
576
- // Fire and forget - don't await
577
601
  getRejourneyNative()!.logEvent(name, properties || {}).catch(() => { });
578
602
  },
579
603
  undefined
@@ -689,7 +713,7 @@ const Rejourney: RejourneyAPI = {
689
713
  eventCount: 0,
690
714
  videoSegmentCount: 0,
691
715
  storageSize: 0,
692
- sdkVersion: '1.0.0',
716
+ sdkVersion: SDK_VERSION,
693
717
  isComplete: false,
694
718
  },
695
719
  events: [],
@@ -719,7 +743,6 @@ const Rejourney: RejourneyAPI = {
719
743
  * @returns Path to export file (not implemented)
720
744
  */
721
745
  async exportSession(_sessionId: string): Promise<string> {
722
- // Return empty string - actual export should be done from dashboard server
723
746
  getLogger().warn('exportSession not implemented - export from dashboard server');
724
747
  return '';
725
748
  },
@@ -975,7 +998,6 @@ const Rejourney: RejourneyAPI = {
975
998
  cached: request.cached,
976
999
  };
977
1000
 
978
- // Fire and forget - don't await, this is low priority
979
1001
  getRejourneyNative()!.logEvent('network_request', networkEvent).catch(() => { });
980
1002
  },
981
1003
  undefined
@@ -1095,10 +1117,10 @@ function handleAppStateChange(nextAppState: string): void {
1095
1117
  try {
1096
1118
  if (_currentAppState.match(/active/) && nextAppState === 'background') {
1097
1119
  // App going to background - native module handles this automatically
1098
- getLogger().debug('App moving to background');
1120
+ getLogger().logLifecycleEvent('App moving to background');
1099
1121
  } else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
1100
1122
  // App coming back to foreground
1101
- getLogger().debug('App returning to foreground');
1123
+ getLogger().logLifecycleEvent('App returning to foreground');
1102
1124
  }
1103
1125
  _currentAppState = nextAppState;
1104
1126
  } catch (error) {
@@ -572,8 +572,6 @@ function setupNavigationTracking(): void {
572
572
  logger.debug('Setting up navigation tracking...');
573
573
  }
574
574
 
575
- // Delay to ensure navigation is initialized - Expo Router needs more time
576
- // We retry a few times with increasing delays
577
575
  let attempts = 0;
578
576
  const maxAttempts = 5;
579
577
 
@@ -2,31 +2,31 @@
2
2
  * Rejourney SDK Constants
3
3
  */
4
4
 
5
- export const SDK_VERSION = '1.0.0';
5
+ export const SDK_VERSION = '1.0.3';
6
6
 
7
7
  /** Default configuration values */
8
8
  export const DEFAULT_CONFIG = {
9
9
  enabled: true,
10
- captureFPS: 0.5,
11
- captureOnEvents: true,
12
- maxSessionDuration: 10 * 60 * 1000,
13
- maxStorageSize: 50 * 1024 * 1024,
10
+ captureFPS: 0.5,
11
+ captureOnEvents: true,
12
+ maxSessionDuration: 10 * 60 * 1000,
13
+ maxStorageSize: 50 * 1024 * 1024,
14
14
  autoScreenTracking: true,
15
15
  autoGestureTracking: true,
16
16
  privacyOcclusion: true,
17
17
  enableCompression: true,
18
- inactivityThreshold: 5000,
18
+ inactivityThreshold: 5000,
19
19
  disableInDev: false,
20
20
  detectRageTaps: true,
21
21
  rageTapThreshold: 3,
22
- rageTapTimeWindow: 1000,
22
+ rageTapTimeWindow: 1000,
23
23
  debug: false,
24
24
  autoStartRecording: true,
25
- collectDeviceInfo: true,
26
- collectGeoLocation: true,
27
- postNavigationDelay: 300,
28
- postGestureDelay: 200,
29
- postModalDelay: 400,
25
+ collectDeviceInfo: true,
26
+ collectGeoLocation: true,
27
+ postNavigationDelay: 300,
28
+ postGestureDelay: 200,
29
+ postModalDelay: 400,
30
30
  } as const;
31
31
 
32
32
  /** Event type constants */
@@ -61,8 +61,8 @@ export const CAPTURE_SETTINGS = {
61
61
  DEFAULT_FPS: 0.5,
62
62
  MIN_FPS: 0.1,
63
63
  MAX_FPS: 2,
64
- CAPTURE_SCALE: 0.25,
65
- MIN_CAPTURE_DELTA_TIME: 0.5,
64
+ CAPTURE_SCALE: 0.25,
65
+ MIN_CAPTURE_DELTA_TIME: 0.5,
66
66
  } as const;
67
67
 
68
68
  /** Memory management settings */
@@ -129,8 +129,6 @@ function flushPendingRequests(): void {
129
129
  flushTimer = null;
130
130
 
131
131
  if (!logCallback || pendingCount === 0) return;
132
-
133
- // Process all pending requests
134
132
  while (pendingCount > 0) {
135
133
  const request = pendingRequests[pendingHead];
136
134
  pendingRequests[pendingHead] = null; // Allow GC
@@ -209,14 +207,10 @@ function interceptFetch(): void {
209
207
  return originalFetch!(input, init);
210
208
  }
211
209
 
212
- // Capture start time (only synchronous work)
213
210
  const startTime = Date.now();
214
211
  const method = ((init?.method || 'GET').toUpperCase()) as NetworkRequestParams['method'];
215
-
216
- // Call original fetch
217
212
  return originalFetch!(input, init).then(
218
213
  (response) => {
219
- // Success - queue the log asynchronously
220
214
  queueRequest({
221
215
  requestId: `f${startTime}`,
222
216
  method,
@@ -281,8 +275,6 @@ function interceptXHR(): void {
281
275
  if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
282
276
  return originalXHRSend!.call(this, body);
283
277
  }
284
-
285
- // Check sampling
286
278
  const { path } = parseUrlFast(data.u);
287
279
  if (!shouldSampleRequest(path)) {
288
280
  return originalXHRSend!.call(this, body);
@@ -344,7 +336,6 @@ export function initNetworkInterceptor(
344
336
  export function disableNetworkInterceptor(): void {
345
337
  config.enabled = false;
346
338
 
347
- // Flush any pending requests
348
339
  if (flushTimer) {
349
340
  clearTimeout(flushTimer);
350
341
  flushTimer = null;
package/src/sdk/utils.ts CHANGED
@@ -236,7 +236,7 @@ export enum LogLevel {
236
236
  */
237
237
  class Logger {
238
238
  private prefix = '[Rejourney]';
239
- private debugMode = false;
239
+
240
240
 
241
241
  /**
242
242
  * Minimum log level to display.
@@ -260,7 +260,6 @@ class Logger {
260
260
  }
261
261
 
262
262
  setDebugMode(enabled: boolean): void {
263
- this.debugMode = enabled;
264
263
  this.minimumLogLevel = enabled
265
264
  ? LogLevel.DEBUG
266
265
  : typeof __DEV__ !== 'undefined' && __DEV__
@@ -285,14 +284,26 @@ class Logger {
285
284
  /** Log a warning message */
286
285
  warn(...args: any[]): void {
287
286
  if (this.minimumLogLevel <= LogLevel.WARNING) {
288
- console.warn(this.prefix, ...args);
287
+ if (this.minimumLogLevel <= LogLevel.DEBUG) {
288
+ // Explicit Debug Mode: Show YellowBox
289
+ console.warn(this.prefix, ...args);
290
+ } else {
291
+ // Default Dev Mode: Log to console only, avoid YellowBox
292
+ console.log(this.prefix, '[WARN]', ...args);
293
+ }
289
294
  }
290
295
  }
291
296
 
292
297
  /** Log an error message */
293
298
  error(...args: any[]): void {
294
299
  if (this.minimumLogLevel <= LogLevel.ERROR) {
295
- console.error(this.prefix, ...args);
300
+ if (this.minimumLogLevel <= LogLevel.DEBUG) {
301
+ // Explicit Debug Mode: Show RedBox
302
+ console.error(this.prefix, ...args);
303
+ } else {
304
+ // Default Dev Mode: Log to console only, avoid RedBox
305
+ console.log(this.prefix, '[ERROR]', ...args);
306
+ }
296
307
  }
297
308
  }
298
309
 
@@ -307,9 +318,7 @@ class Logger {
307
318
  * Only shown in development builds - this is the minimal "SDK started" log.
308
319
  */
309
320
  logInitSuccess(version: string): void {
310
- if (this.debugMode) {
311
- this.info(`✓ SDK initialized (v${version})`);
312
- }
321
+ this.notice(`✓ SDK initialized (v${version})`);
313
322
  }
314
323
 
315
324
  /**
@@ -325,9 +334,7 @@ class Logger {
325
334
  * Only shown in development builds.
326
335
  */
327
336
  logSessionStart(sessionId: string): void {
328
- if (this.debugMode) {
329
- this.info(`Session started: ${sessionId}`);
330
- }
337
+ this.notice(`Session started: ${sessionId}`);
331
338
  }
332
339
 
333
340
  /**
@@ -335,13 +342,11 @@ class Logger {
335
342
  * Only shown in development builds.
336
343
  */
337
344
  logSessionEnd(sessionId: string): void {
338
- if (this.debugMode) {
339
- this.info(`Session ended: ${sessionId}`);
340
- }
345
+ this.notice(`Session ended: ${sessionId}`);
341
346
  }
342
347
 
343
348
  logObservabilityStart(): void {
344
- this.notice('Starting Rejourney observability');
349
+ this.notice('💧 Starting Rejourney observability');
345
350
  }
346
351
 
347
352
  logRecordingStart(): void {
@@ -359,6 +364,63 @@ class Logger {
359
364
  logPackageMismatch(): void {
360
365
  this.notice('Bundle ID / package name mismatch');
361
366
  }
367
+
368
+ /**
369
+ * Log network request details
370
+ */
371
+ logNetworkRequest(request: { method?: string; url?: string; statusCode?: number; duration?: number; error?: string }): void {
372
+ const statusIcon = request.error || (request.statusCode && request.statusCode >= 400) ? '🔴' : '🟢';
373
+ const method = request.method || 'GET';
374
+ // Shorten URL to just path if possible
375
+ let url = request.url || '';
376
+ try {
377
+ if (url.startsWith('http')) {
378
+ const urlObj = new URL(url);
379
+ url = urlObj.pathname;
380
+ }
381
+ } catch {
382
+ // Keep full URL if parsing fails
383
+ }
384
+
385
+ const duration = request.duration ? `(${Math.round(request.duration)}ms)` : '';
386
+ const status = request.statusCode ? `${request.statusCode}` : 'ERR';
387
+
388
+ this.notice(`${statusIcon} [NET] ${status} ${method} ${url} ${duration} ${request.error ? `Error: ${request.error}` : ''}`);
389
+ }
390
+
391
+ /**
392
+ * Log frustration event (rage taps, etc)
393
+ */
394
+ logFrustration(kind: string): void {
395
+ this.notice(`🤬 Frustration detected: ${kind}`);
396
+ }
397
+
398
+ /**
399
+ * Log error captured by SDK
400
+ */
401
+ logError(message: string): void {
402
+ this.notice(`X Error captured: ${message}`);
403
+ }
404
+
405
+ /**
406
+ * Log lifecycle event (Background/Foreground)
407
+ * Visible in development builds.
408
+ */
409
+ logLifecycleEvent(event: string): void {
410
+ this.notice(`🔄 Lifecycle: ${event}`);
411
+ }
412
+
413
+ /**
414
+ * Log upload statistics
415
+ */
416
+ logUploadStats(metrics: { uploadSuccessCount: number; uploadFailureCount: number; totalBytesUploaded: number }): void {
417
+ const success = metrics.uploadSuccessCount;
418
+ const failed = metrics.uploadFailureCount;
419
+ const bytes = formatBytes(metrics.totalBytesUploaded);
420
+
421
+ // Always show in dev mode for reassurance, even if 0
422
+ this.notice(`📡 Upload Stats: ${success} success, ${failed} failed (${bytes} uploaded)`);
423
+ }
362
424
  }
363
425
 
364
426
  export const logger = new Logger();