@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.
Files changed (49) hide show
  1. package/README.md +29 -0
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +47 -30
  3. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +25 -1
  4. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +70 -32
  5. package/android/src/main/java/com/rejourney/core/Constants.kt +4 -4
  6. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
  7. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
  8. package/ios/Capture/RJCaptureEngine.m +72 -34
  9. package/ios/Capture/RJCaptureHeuristics.h +7 -5
  10. package/ios/Capture/RJCaptureHeuristics.m +138 -112
  11. package/ios/Capture/RJVideoEncoder.m +0 -26
  12. package/ios/Core/Rejourney.mm +64 -102
  13. package/ios/Utils/RJPerfTiming.m +0 -5
  14. package/ios/Utils/RJWindowUtils.m +0 -1
  15. package/lib/commonjs/components/Mask.js +1 -6
  16. package/lib/commonjs/index.js +12 -101
  17. package/lib/commonjs/sdk/autoTracking.js +55 -353
  18. package/lib/commonjs/sdk/constants.js +2 -13
  19. package/lib/commonjs/sdk/errorTracking.js +1 -29
  20. package/lib/commonjs/sdk/metricsTracking.js +3 -24
  21. package/lib/commonjs/sdk/navigation.js +3 -42
  22. package/lib/commonjs/sdk/networkInterceptor.js +7 -49
  23. package/lib/commonjs/sdk/utils.js +0 -5
  24. package/lib/module/components/Mask.js +1 -6
  25. package/lib/module/index.js +11 -105
  26. package/lib/module/sdk/autoTracking.js +55 -354
  27. package/lib/module/sdk/constants.js +2 -13
  28. package/lib/module/sdk/errorTracking.js +1 -29
  29. package/lib/module/sdk/index.js +0 -2
  30. package/lib/module/sdk/metricsTracking.js +3 -24
  31. package/lib/module/sdk/navigation.js +3 -42
  32. package/lib/module/sdk/networkInterceptor.js +7 -49
  33. package/lib/module/sdk/utils.js +0 -5
  34. package/lib/typescript/NativeRejourney.d.ts +2 -0
  35. package/lib/typescript/sdk/autoTracking.d.ts +5 -6
  36. package/lib/typescript/types/index.d.ts +0 -1
  37. package/package.json +11 -3
  38. package/src/NativeRejourney.ts +4 -0
  39. package/src/components/Mask.tsx +0 -3
  40. package/src/index.ts +11 -88
  41. package/src/sdk/autoTracking.ts +72 -331
  42. package/src/sdk/constants.ts +13 -13
  43. package/src/sdk/errorTracking.ts +1 -17
  44. package/src/sdk/index.ts +0 -2
  45. package/src/sdk/metricsTracking.ts +5 -33
  46. package/src/sdk/navigation.ts +8 -29
  47. package/src/sdk/networkInterceptor.ts +9 -33
  48. package/src/sdk/utils.ts +0 -5
  49. package/src/types/index.ts +0 -29
@@ -8,35 +8,25 @@ export const SDK_VERSION = '1.0.0';
8
8
  export const DEFAULT_CONFIG = {
9
9
  enabled: true,
10
10
  captureFPS: 0.5,
11
- // Every 2 seconds (only used in timer mode)
12
11
  captureOnEvents: true,
13
- // Event-driven capture (not time-based)
14
12
  maxSessionDuration: 10 * 60 * 1000,
15
- // 10 minutes (project-level configurable, clamped 1–10)
16
13
  maxStorageSize: 50 * 1024 * 1024,
17
- // 50MB
18
14
  autoScreenTracking: true,
19
15
  autoGestureTracking: true,
20
16
  privacyOcclusion: true,
21
17
  enableCompression: true,
22
18
  inactivityThreshold: 5000,
23
- // 5 seconds
24
19
  disableInDev: false,
25
20
  detectRageTaps: true,
26
21
  rageTapThreshold: 3,
27
22
  rageTapTimeWindow: 1000,
28
- // 1 second
29
23
  debug: false,
30
24
  autoStartRecording: true,
31
25
  collectDeviceInfo: true,
32
- // Collect detailed device information
33
26
  collectGeoLocation: true,
34
- // Collect IP address and geolocation
35
27
  postNavigationDelay: 300,
36
- // 300ms - allow navigation animations to complete
37
28
  postGestureDelay: 200,
38
- // 200ms - show result of taps, not animations
39
- postModalDelay: 400 // 400ms - ensure modals/alerts are fully rendered
29
+ postModalDelay: 400
40
30
  };
41
31
 
42
32
  /** Event type constants */
@@ -72,8 +62,7 @@ export const CAPTURE_SETTINGS = {
72
62
  MIN_FPS: 0.1,
73
63
  MAX_FPS: 2,
74
64
  CAPTURE_SCALE: 0.25,
75
- // 25% resolution for video segments
76
- MIN_CAPTURE_DELTA_TIME: 0.5 // Minimum 0.5s between captures (rate limiting)
65
+ MIN_CAPTURE_DELTA_TIME: 0.5
77
66
  };
78
67
 
79
68
  /** Memory management settings */
@@ -5,20 +5,11 @@
5
5
  * Split from autoTracking.ts for better code organization.
6
6
  */
7
7
 
8
- // Type declarations for browser globals (only used in hybrid apps where DOM is available)
9
-
10
- // Cast globalThis to work with both RN and hybrid scenarios
11
8
  const _globalThis = globalThis;
12
-
13
- // Original error handlers (for restoration)
14
9
  let originalErrorHandler;
15
10
  let originalOnError = null;
16
11
  let originalOnUnhandledRejection = null;
17
-
18
- // Callbacks
19
12
  let onErrorCallback = null;
20
-
21
- // Metrics
22
13
  let errorCount = 0;
23
14
 
24
15
  /**
@@ -27,18 +18,12 @@ let errorCount = 0;
27
18
  export function setupErrorTracking(config, onError) {
28
19
  onErrorCallback = onError;
29
20
  errorCount = 0;
30
-
31
- // Track React Native errors
32
21
  if (config.trackReactNativeErrors !== false) {
33
22
  setupReactNativeErrorHandler();
34
23
  }
35
-
36
- // Track JavaScript errors (only works in web/debug)
37
24
  if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
38
25
  setupJSErrorHandler();
39
26
  }
40
-
41
- // Track unhandled promise rejections
42
27
  if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
43
28
  setupPromiseRejectionHandler();
44
29
  }
@@ -48,7 +33,6 @@ export function setupErrorTracking(config, onError) {
48
33
  * Cleanup error tracking and restore original handlers
49
34
  */
50
35
  export function cleanupErrorTracking() {
51
- // Restore React Native handler
52
36
  if (originalErrorHandler) {
53
37
  try {
54
38
  const ErrorUtils = _globalThis.ErrorUtils;
@@ -60,14 +44,10 @@ export function cleanupErrorTracking() {
60
44
  }
61
45
  originalErrorHandler = undefined;
62
46
  }
63
-
64
- // Restore global onerror
65
47
  if (originalOnError !== null) {
66
48
  _globalThis.onerror = originalOnError;
67
49
  originalOnError = null;
68
50
  }
69
-
70
- // Remove promise rejection handler
71
51
  if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
72
52
  _globalThis.removeEventListener('unhandledrejection', originalOnUnhandledRejection);
73
53
  originalOnUnhandledRejection = null;
@@ -119,11 +99,7 @@ function setupReactNativeErrorHandler() {
119
99
  try {
120
100
  const ErrorUtils = _globalThis.ErrorUtils;
121
101
  if (!ErrorUtils) return;
122
-
123
- // Store original handler
124
102
  originalErrorHandler = ErrorUtils.getGlobalHandler();
125
-
126
- // Set new handler
127
103
  ErrorUtils.setGlobalHandler((error, isFatal) => {
128
104
  trackError({
129
105
  type: 'error',
@@ -132,14 +108,12 @@ function setupReactNativeErrorHandler() {
132
108
  stack: error.stack,
133
109
  name: error.name || 'Error'
134
110
  });
135
-
136
- // Call original handler
137
111
  if (originalErrorHandler) {
138
112
  originalErrorHandler(error, isFatal);
139
113
  }
140
114
  });
141
115
  } catch {
142
- // ErrorUtils not available
116
+ // Ignore
143
117
  }
144
118
  }
145
119
 
@@ -157,8 +131,6 @@ function setupJSErrorHandler() {
157
131
  stack: error?.stack || `${source}:${lineno}:${colno}`,
158
132
  name: error?.name || 'Error'
159
133
  });
160
-
161
- // Call original handler
162
134
  if (originalOnError) {
163
135
  return originalOnError(message, source, lineno, colno, error);
164
136
  }
@@ -7,6 +7,4 @@ export * from './constants';
7
7
  export * from './utils';
8
8
  export * from './autoTracking';
9
9
  export * from './networkInterceptor';
10
- // Note: errorTracking and metricsTracking are internal modules
11
- // Their exports are re-exported through autoTracking for backward compatibility
12
10
  //# sourceMappingURL=index.js.map
@@ -10,10 +10,9 @@
10
10
  * Session metrics structure
11
11
  */
12
12
 
13
- // Metrics state
14
13
  let metrics = createEmptyMetrics();
15
14
  let sessionStartTime = 0;
16
- let maxSessionDurationMs = 10 * 60 * 1000; // 10 minutes default
15
+ let maxSessionDurationMs = 10 * 60 * 1000;
17
16
 
18
17
  /**
19
18
  * Create empty metrics object
@@ -61,11 +60,8 @@ export function initMetrics() {
61
60
  * Get current session metrics with calculated scores
62
61
  */
63
62
  export function getSessionMetrics() {
64
- // Calculate duration, clamped to max session duration
65
63
  const rawDuration = Date.now() - sessionStartTime;
66
64
  const durationMs = Math.min(rawDuration, maxSessionDurationMs);
67
-
68
- // Calculate scores
69
65
  const interactionScore = calculateInteractionScore(durationMs);
70
66
  const explorationScore = calculateExplorationScore();
71
67
  const uxScore = calculateUXScore();
@@ -87,9 +83,6 @@ export function setMaxSessionDurationMinutes(minutes) {
87
83
  maxSessionDurationMs = clampedMinutes * 60 * 1000;
88
84
  }
89
85
  }
90
-
91
- // ==================== Metric Increment Methods ====================
92
-
93
86
  export function incrementTouchCount() {
94
87
  metrics.touchCount++;
95
88
  metrics.totalEvents++;
@@ -129,8 +122,6 @@ export function trackAPIMetrics(success, durationMs = 0, responseBytes = 0) {
129
122
  }
130
123
  }
131
124
 
132
- // ==================== Score Calculations ====================
133
-
134
125
  /**
135
126
  * Calculate interaction score based on engagement with app
136
127
  * Higher = more engaged (more interactions per minute)
@@ -139,16 +130,12 @@ function calculateInteractionScore(durationMs) {
139
130
  if (durationMs <= 0) return 100;
140
131
  const durationMinutes = durationMs / 60000;
141
132
  const interactionsPerMinute = metrics.touchCount / Math.max(0.5, durationMinutes);
142
-
143
- // Ideal: 10-30 interactions per minute
144
- // Low (< 5): user passive/confused
145
- // Very high (> 60): rage tapping
146
133
  if (interactionsPerMinute < 2) return 20;
147
134
  if (interactionsPerMinute < 5) return 50;
148
135
  if (interactionsPerMinute < 10) return 70;
149
136
  if (interactionsPerMinute <= 30) return 100;
150
137
  if (interactionsPerMinute <= 60) return 80;
151
- return 50; // Very high might indicate frustration
138
+ return 50;
152
139
  }
153
140
 
154
141
  /**
@@ -157,14 +144,12 @@ function calculateInteractionScore(durationMs) {
157
144
  */
158
145
  function calculateExplorationScore() {
159
146
  const uniqueScreens = metrics.uniqueScreensCount;
160
-
161
- // More unique screens = better exploration
162
147
  if (uniqueScreens >= 10) return 100;
163
148
  if (uniqueScreens >= 7) return 90;
164
149
  if (uniqueScreens >= 5) return 80;
165
150
  if (uniqueScreens >= 3) return 60;
166
151
  if (uniqueScreens >= 2) return 40;
167
- return 20; // Single screen visit
152
+ return 20;
168
153
  }
169
154
 
170
155
  /**
@@ -173,14 +158,8 @@ function calculateExplorationScore() {
173
158
  */
174
159
  function calculateUXScore() {
175
160
  let score = 100;
176
-
177
- // Deduct for errors
178
161
  score -= metrics.errorCount * 10;
179
-
180
- // Deduct heavily for rage taps
181
162
  score -= metrics.rageTapCount * 20;
182
-
183
- // Deduct for API errors (less severe)
184
163
  score -= metrics.apiErrorCount * 5;
185
164
  return Math.max(0, Math.min(100, score));
186
165
  }
@@ -18,46 +18,28 @@
18
18
  export function normalizeScreenName(raw) {
19
19
  if (!raw) return 'Unknown';
20
20
  let name = raw;
21
-
22
- // 1. Remove non-printable characters and weird symbols (often from icons)
23
21
  name = name.replace(/[^\x20-\x7E\s]/g, '');
24
-
25
- // 2. Handle hyphens: my-recipes -> My Recipes
26
22
  name = name.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
27
-
28
- // 3. Remove common suffixes
29
23
  const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
30
24
  for (const suffix of suffixes) {
31
25
  if (name.endsWith(suffix) && name.length > suffix.length) {
32
26
  name = name.slice(0, -suffix.length).trim();
33
27
  }
34
28
  }
35
-
36
- // 4. Remove common prefixes (React Native internals)
37
29
  const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
38
30
  for (const prefix of prefixes) {
39
31
  if (name.startsWith(prefix) && name.length > prefix.length + 2) {
40
32
  name = name.slice(prefix.length).trim();
41
33
  }
42
34
  }
43
-
44
- // 5. Handle dynamic route params: [id] -> (omit), [userId] -> "User"
45
35
  name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
46
36
  const clean = param.replace(/Id$/i, '');
47
37
  if (clean.length < 2) return '';
48
38
  return clean.charAt(0).toUpperCase() + clean.slice(1);
49
39
  });
50
-
51
- // 6. Remove leftover brackets
52
40
  name = name.replace(/\[\]/g, '');
53
-
54
- // 7. Convert camelCase/PascalCase to Title Case with spaces
55
41
  name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
56
-
57
- // 8. Clean up multiple spaces and trim
58
42
  name = name.replace(/\s+/g, ' ').trim();
59
-
60
- // 9. Capitalize first letter
61
43
  if (name.length > 0) {
62
44
  name = name.charAt(0).toUpperCase() + name.slice(1);
63
45
  }
@@ -72,23 +54,17 @@ export function normalizeScreenName(raw) {
72
54
  * @returns Human-readable screen name
73
55
  */
74
56
  export function getScreenNameFromPath(pathname, segments) {
75
- // Use segments for cleaner names if available
76
57
  if (segments.length > 0) {
77
- // Filter out group markers like (tabs), (auth), etc.
78
58
  const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
79
59
  if (cleanSegments.length > 0) {
80
- // Process each segment
81
60
  const processedSegments = cleanSegments.map(s => {
82
- // Handle dynamic params like [id]
83
61
  if (s.startsWith('[') && s.endsWith(']')) {
84
62
  const param = s.slice(1, -1);
85
- // Skip pure ID params, keep meaningful ones
86
63
  if (param === 'id' || param === 'slug') return null;
87
- // Extract meaningful part: userId -> User
64
+ if (param === 'id' || param === 'slug') return null;
88
65
  const clean = param.replace(/Id$/i, '');
89
66
  return clean.charAt(0).toUpperCase() + clean.slice(1);
90
67
  }
91
- // Regular segment - capitalize
92
68
  return s.charAt(0).toUpperCase() + s.slice(1);
93
69
  }).filter(Boolean);
94
70
  if (processedSegments.length > 0) {
@@ -96,30 +72,17 @@ export function getScreenNameFromPath(pathname, segments) {
96
72
  }
97
73
  }
98
74
  }
99
-
100
- // Fall back to pathname
101
75
  if (!pathname || pathname === '/') {
102
76
  return 'Home';
103
77
  }
104
-
105
- // Clean up the path
106
- let cleanPath = pathname.replace(/^\/(tabs)?/, '') // Remove leading slash and (tabs)
107
- .replace(/\([^)]+\)/g, '') // Remove all group markers like (settings)
108
- .replace(/\[([^\]]+)\]/g, (_, param) => {
109
- // Handle dynamic params in path
78
+ const cleanPath = pathname.replace(/^\/(tabs)?/, '').replace(/\([^)]+\)/g, '').replace(/\[([^\]]+)\]/g, (_, param) => {
110
79
  if (param === 'id' || param === 'slug') return '';
111
80
  const clean = param.replace(/Id$/i, '');
112
81
  return clean.charAt(0).toUpperCase() + clean.slice(1);
113
- }).replace(/\/+/g, '/') // Collapse multiple slashes
114
- .replace(/^\//, '') // Remove leading slash
115
- .replace(/\/$/, '') // Remove trailing slash
116
- .replace(/\//g, ' > ') // Replace slashes with arrows
117
- .trim();
82
+ }).replace(/\/+/g, '/').replace(/^\//, '').replace(/\/$/, '').replace(/\//g, ' > ').trim();
118
83
  if (!cleanPath) {
119
84
  return 'Home';
120
85
  }
121
-
122
- // Capitalize first letter of each word
123
86
  return cleanPath.split(' > ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).filter(s => s.length > 0).join(' > ') || 'Home';
124
87
  }
125
88
 
@@ -133,8 +96,6 @@ export function getCurrentRouteFromState(state) {
133
96
  if (!state || !state.routes) return null;
134
97
  const route = state.routes[state.index ?? state.routes.length - 1];
135
98
  if (!route) return null;
136
-
137
- // If nested state, recurse
138
99
  if (route.state) {
139
100
  return getCurrentRouteFromState(route.state);
140
101
  }
@@ -13,41 +13,26 @@
13
13
  * - PII Scrubbing for query parameters
14
14
  */
15
15
 
16
- // Store original implementations
17
16
  let originalFetch = null;
18
17
  let originalXHROpen = null;
19
18
  let originalXHRSend = null;
20
-
21
- // Callback to log network requests (called asynchronously)
22
19
  let logCallback = null;
23
-
24
- // Pending requests buffer (circular buffer for memory efficiency)
25
20
  const MAX_PENDING_REQUESTS = 100;
26
21
  const pendingRequests = new Array(MAX_PENDING_REQUESTS).fill(null);
27
22
  let pendingHead = 0;
28
23
  let pendingTail = 0;
29
24
  let pendingCount = 0;
30
-
31
- // Flush timer
32
25
  let flushTimer = null;
33
- const FLUSH_INTERVAL = 500; // Flush every 500ms
34
-
35
- // Sampling for high-frequency endpoints
26
+ const FLUSH_INTERVAL = 500;
36
27
  const endpointCounts = new Map();
37
- const SAMPLE_WINDOW = 10000; // 10 second window
38
- const MAX_PER_ENDPOINT = 20; // Max 20 requests per endpoint per window
39
-
40
- // Configuration
28
+ const SAMPLE_WINDOW = 10000;
29
+ const MAX_PER_ENDPOINT = 20;
41
30
  const config = {
42
31
  enabled: true,
43
32
  ignorePatterns: [],
44
- // Simple string patterns for fast matching
45
33
  maxUrlLength: 300,
46
- // Shorter for efficiency
47
- captureSizes: false // Disabled by default for performance
34
+ captureSizes: false
48
35
  };
49
-
50
- // PII Scrubbing - Sensitive keys to look for in query params
51
36
  const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_token', 'api_key'];
52
37
 
53
38
  /**
@@ -55,7 +40,6 @@ const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_to
55
40
  */
56
41
  function scrubUrl(url) {
57
42
  try {
58
- // Fast check if URL might contain params
59
43
  if (url.indexOf('?') === -1) return url;
60
44
  const urlObj = new URL(url);
61
45
  let modified = false;
@@ -65,13 +49,10 @@ function scrubUrl(url) {
65
49
  modified = true;
66
50
  }
67
51
  });
68
-
69
- // Also scan for partial matches (case-insensitive) if strict scrubbing needed
70
- // But for performance, we stick to exact keys or common variations
71
-
72
52
  return modified ? urlObj.toString() : url;
73
53
  } catch {
74
- // If URL parsing fails (relative URL?), try primitive replacement
54
+ // Ignore error, fallback to primitive scrubbing
55
+
75
56
  let scrubbed = url;
76
57
  SENSITIVE_KEYS.forEach(key => {
77
58
  const regex = new RegExp(`([?&])${key}=[^&]*`, 'gi');
@@ -119,14 +100,10 @@ function queueRequest(request) {
119
100
  pendingHead = (pendingHead + 1) % MAX_PENDING_REQUESTS;
120
101
  pendingCount--;
121
102
  }
122
-
123
- // Scrub URL before queuing
124
103
  request.url = scrubUrl(request.url);
125
104
  pendingRequests[pendingTail] = request;
126
105
  pendingTail = (pendingTail + 1) % MAX_PENDING_REQUESTS;
127
106
  pendingCount++;
128
-
129
- // Schedule flush if not already scheduled
130
107
  if (!flushTimer) {
131
108
  flushTimer = setTimeout(flushPendingRequests, FLUSH_INTERVAL);
132
109
  }
@@ -149,7 +126,7 @@ function flushPendingRequests() {
149
126
  try {
150
127
  logCallback(request);
151
128
  } catch {
152
- // Ignore logging errors
129
+ // Ignore
153
130
  }
154
131
  }
155
132
  }
@@ -162,12 +139,9 @@ function parseUrlFast(url) {
162
139
  // Fast path for common patterns
163
140
  let hostEnd = -1;
164
141
  let pathStart = -1;
165
-
166
- // Find ://
167
142
  const protoEnd = url.indexOf('://');
168
143
  if (protoEnd !== -1) {
169
144
  const afterProto = protoEnd + 3;
170
- // Find end of host (first / after ://)
171
145
  const slashPos = url.indexOf('/', afterProto);
172
146
  if (slashPos !== -1) {
173
147
  hostEnd = slashPos;
@@ -181,8 +155,6 @@ function parseUrlFast(url) {
181
155
  path: pathStart < url.length ? url.substring(pathStart) : '/'
182
156
  };
183
157
  }
184
-
185
- // Relative URL
186
158
  return {
187
159
  host: '',
188
160
  path: url
@@ -197,15 +169,10 @@ function interceptFetch() {
197
169
  if (originalFetch) return;
198
170
  originalFetch = globalThis.fetch;
199
171
  globalThis.fetch = function optimizedFetch(input, init) {
200
- // Fast path: if disabled or no callback, skip entirely
201
172
  if (!config.enabled || !logCallback) {
202
173
  return originalFetch(input, init);
203
174
  }
204
-
205
- // Extract URL string (minimal work)
206
175
  const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
207
-
208
- // Fast ignore check
209
176
  if (shouldIgnoreUrl(url)) {
210
177
  return originalFetch(input, init);
211
178
  }
@@ -237,7 +204,6 @@ function interceptFetch() {
237
204
  });
238
205
  return response;
239
206
  }, error => {
240
- // Error - queue the log asynchronously
241
207
  queueRequest({
242
208
  requestId: `f${startTime}`,
243
209
  method,
@@ -264,8 +230,6 @@ function interceptXHR() {
264
230
  originalXHRSend = XMLHttpRequest.prototype.send;
265
231
  XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
266
232
  const urlString = typeof url === 'string' ? url : url.toString();
267
-
268
- // Store minimal info
269
233
  this.__rj = {
270
234
  m: method.toUpperCase(),
271
235
  u: urlString,
@@ -301,9 +265,6 @@ function interceptXHR() {
301
265
  errorMessage: this.status === 0 ? 'Network error' : undefined
302
266
  });
303
267
  };
304
-
305
- // Use load/error events (more efficient than readystatechange)
306
- // Note: We use basic addEventListener without options for RN compatibility
307
268
  this.addEventListener('load', onComplete);
308
269
  this.addEventListener('error', onComplete);
309
270
  this.addEventListener('abort', onComplete);
@@ -316,11 +277,8 @@ function interceptXHR() {
316
277
  */
317
278
  export function initNetworkInterceptor(callback, options) {
318
279
  logCallback = callback;
319
-
320
- // Convert patterns to simple strings for fast matching
321
280
  if (options?.ignoreUrls) {
322
281
  config.ignorePatterns = options.ignoreUrls.filter(p => typeof p === 'string');
323
- // Note: RegExp patterns are not supported in optimized version for performance
324
282
  }
325
283
  if (options?.captureSizes !== undefined) {
326
284
  config.captureSizes = options.captureSizes;
@@ -280,11 +280,6 @@ class Logger {
280
280
  }
281
281
  }
282
282
 
283
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
284
- // Lifecycle Logs - Industry standard minimal logging
285
- // These are the only logs integrators will see in debug builds
286
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
287
-
288
283
  /**
289
284
  * Log SDK initialization success.
290
285
  * Only shown in development builds - this is the minimal "SDK started" log.
@@ -129,9 +129,11 @@ export interface Spec extends TurboModule {
129
129
  setUserIdentity(userId: string): Promise<{
130
130
  success: boolean;
131
131
  }>;
132
+ getUserIdentity(): Promise<string | null>;
132
133
  setDebugMode(enabled: boolean): Promise<{
133
134
  success: boolean;
134
135
  }>;
136
+ getDeviceInfo(): Promise<Object>;
135
137
  }
136
138
  /**
137
139
  * Default export for Codegen.
@@ -155,19 +155,18 @@ export declare function getSessionMetrics(): SessionMetrics & {
155
155
  * Reset metrics for new session
156
156
  */
157
157
  export declare function resetMetrics(): void;
158
- /** Clamp and set max session duration in minutes (1–10). Defaults to 10. */
159
158
  export declare function setMaxSessionDurationMinutes(minutes?: number): void;
160
- /** Returns true if the current session exceeded the configured max duration. */
161
159
  export declare function hasExceededMaxSessionDuration(): boolean;
162
- /** Returns remaining milliseconds until the session should stop. */
163
160
  export declare function getRemainingSessionDurationMs(): number;
164
161
  /**
165
162
  * Collect device information
166
163
  */
167
- export declare function collectDeviceInfo(): DeviceInfo;
164
+ /**
165
+ * Collect device information
166
+ */
167
+ export declare function collectDeviceInfo(): Promise<DeviceInfo>;
168
168
  /**
169
169
  * Get the anonymous ID (synchronous - returns generated ID immediately)
170
- * For persistent ID, call initAnonymousId() first
171
170
  */
172
171
  export declare function getAnonymousId(): string;
173
172
  /**
@@ -181,7 +180,7 @@ export declare function ensurePersistentAnonymousId(): Promise<string>;
181
180
  */
182
181
  export declare function loadAnonymousId(): Promise<string>;
183
182
  /**
184
- * Set a custom anonymous ID (e.g., from persistent storage)
183
+ * Set a custom anonymous ID
185
184
  */
186
185
  export declare function setAnonymousId(id: string): void;
187
186
  declare const _default: {
@@ -352,7 +352,6 @@ export interface SessionSummary {
352
352
  videoSegmentCount?: number;
353
353
  storageSize: number;
354
354
  isComplete: boolean;
355
- /** Path to session data file */
356
355
  filePath: string;
357
356
  }
358
357
  export interface ReplayState {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rejourneyco/react-native",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Rejourney Session Recording SDK for React Native",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -40,7 +40,10 @@
40
40
  "test:coverage": "vitest run --coverage",
41
41
  "release": "release-it",
42
42
  "verify:ios": "./scripts/verify-ios-structure.sh",
43
- "test:ios-install": "./scripts/test-ios-install.sh"
43
+ "verify:package": "./scripts/verify-package-content.sh",
44
+ "test:ios-install": "./scripts/test-ios-install.sh",
45
+ "test:android-install": "./scripts/test-android-install.sh",
46
+ "audit:deps": "depcruise src --config .dependency-cruiser.js"
44
47
  },
45
48
  "keywords": [
46
49
  "react-native",
@@ -61,6 +64,9 @@
61
64
  "@typescript-eslint/eslint-plugin": "^8.15.0",
62
65
  "@typescript-eslint/parser": "^8.15.0",
63
66
  "@vitest/coverage-v8": "^2.1.0",
67
+ "dependency-cruiser": "^16.10.4",
68
+ "@react-navigation/native": "*",
69
+ "expo-router": "*",
64
70
  "eslint": "^9.39.2",
65
71
  "react": "*",
66
72
  "react-native": "*",
@@ -70,7 +76,9 @@
70
76
  },
71
77
  "peerDependencies": {
72
78
  "react": "*",
73
- "react-native": "*"
79
+ "react-native": "*",
80
+ "@react-navigation/native": ">=6.0.0",
81
+ "expo-router": ">=3.0.0"
74
82
  },
75
83
  "codegenConfig": {
76
84
  "name": "RejourneySpec",
@@ -150,7 +150,11 @@ export interface Spec extends TurboModule {
150
150
 
151
151
  setUserIdentity(userId: string): Promise<{ success: boolean }>;
152
152
 
153
+ getUserIdentity(): Promise<string | null>;
154
+
153
155
  setDebugMode(enabled: boolean): Promise<{ success: boolean }>;
156
+
157
+ getDeviceInfo(): Promise<Object>;
154
158
  }
155
159
 
156
160
  /**
@@ -25,7 +25,6 @@
25
25
  import React from 'react';
26
26
  import type { ViewProps } from 'react-native';
27
27
 
28
- // Lazy-loaded React Native modules
29
28
  let _RN: typeof import('react-native') | null = null;
30
29
 
31
30
  function getRN(): typeof import('react-native') | null {
@@ -52,7 +51,6 @@ export interface MaskProps extends ViewProps {
52
51
  export const Mask: React.FC<MaskProps> = ({ children, style, ...props }) => {
53
52
  const RN = getRN();
54
53
 
55
- // If RN isn't loaded yet (shouldn't happen in practice), render children directly
56
54
  if (!RN) {
57
55
  return <>{children}</>;
58
56
  }
@@ -61,7 +59,6 @@ export const Mask: React.FC<MaskProps> = ({ children, style, ...props }) => {
61
59
 
62
60
  const styles = StyleSheet.create({
63
61
  container: {
64
- // Minimal container style - doesn't affect layout
65
62
  },
66
63
  });
67
64