@rejourneyco/react-native 1.0.0

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 (152) hide show
  1. package/android/build.gradle.kts +135 -0
  2. package/android/consumer-rules.pro +10 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/AndroidManifest.xml +15 -0
  5. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
  6. package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
  7. package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
  8. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
  9. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
  10. package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
  11. package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
  12. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
  13. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
  14. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
  15. package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
  16. package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
  17. package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
  18. package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
  19. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
  20. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
  21. package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
  22. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
  23. package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
  24. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
  25. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
  26. package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
  27. package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
  28. package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
  29. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
  30. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
  31. package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
  32. package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
  33. package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
  34. package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
  35. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
  36. package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
  37. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
  38. package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
  39. package/ios/Capture/RJANRHandler.h +42 -0
  40. package/ios/Capture/RJANRHandler.m +328 -0
  41. package/ios/Capture/RJCaptureEngine.h +275 -0
  42. package/ios/Capture/RJCaptureEngine.m +2062 -0
  43. package/ios/Capture/RJCaptureHeuristics.h +80 -0
  44. package/ios/Capture/RJCaptureHeuristics.m +903 -0
  45. package/ios/Capture/RJCrashHandler.h +46 -0
  46. package/ios/Capture/RJCrashHandler.m +313 -0
  47. package/ios/Capture/RJMotionEvent.h +183 -0
  48. package/ios/Capture/RJMotionEvent.m +183 -0
  49. package/ios/Capture/RJPerformanceManager.h +100 -0
  50. package/ios/Capture/RJPerformanceManager.m +373 -0
  51. package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
  52. package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
  53. package/ios/Capture/RJSegmentUploader.h +146 -0
  54. package/ios/Capture/RJSegmentUploader.m +778 -0
  55. package/ios/Capture/RJVideoEncoder.h +247 -0
  56. package/ios/Capture/RJVideoEncoder.m +1036 -0
  57. package/ios/Capture/RJViewControllerTracker.h +73 -0
  58. package/ios/Capture/RJViewControllerTracker.m +508 -0
  59. package/ios/Capture/RJViewHierarchyScanner.h +215 -0
  60. package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
  61. package/ios/Capture/RJViewSerializer.h +119 -0
  62. package/ios/Capture/RJViewSerializer.m +498 -0
  63. package/ios/Core/RJConstants.h +124 -0
  64. package/ios/Core/RJConstants.m +88 -0
  65. package/ios/Core/RJLifecycleManager.h +85 -0
  66. package/ios/Core/RJLifecycleManager.m +308 -0
  67. package/ios/Core/RJLogger.h +61 -0
  68. package/ios/Core/RJLogger.m +211 -0
  69. package/ios/Core/RJTypes.h +176 -0
  70. package/ios/Core/RJTypes.m +66 -0
  71. package/ios/Core/Rejourney.h +64 -0
  72. package/ios/Core/Rejourney.mm +2495 -0
  73. package/ios/Network/RJDeviceAuthManager.h +94 -0
  74. package/ios/Network/RJDeviceAuthManager.m +967 -0
  75. package/ios/Network/RJNetworkMonitor.h +68 -0
  76. package/ios/Network/RJNetworkMonitor.m +267 -0
  77. package/ios/Network/RJRetryManager.h +73 -0
  78. package/ios/Network/RJRetryManager.m +325 -0
  79. package/ios/Network/RJUploadManager.h +267 -0
  80. package/ios/Network/RJUploadManager.m +2296 -0
  81. package/ios/Privacy/RJPrivacyMask.h +163 -0
  82. package/ios/Privacy/RJPrivacyMask.m +922 -0
  83. package/ios/Rejourney.h +63 -0
  84. package/ios/Touch/RJGestureClassifier.h +130 -0
  85. package/ios/Touch/RJGestureClassifier.m +333 -0
  86. package/ios/Touch/RJTouchInterceptor.h +169 -0
  87. package/ios/Touch/RJTouchInterceptor.m +772 -0
  88. package/ios/Utils/RJEventBuffer.h +112 -0
  89. package/ios/Utils/RJEventBuffer.m +358 -0
  90. package/ios/Utils/RJGzipUtils.h +33 -0
  91. package/ios/Utils/RJGzipUtils.m +89 -0
  92. package/ios/Utils/RJKeychainManager.h +48 -0
  93. package/ios/Utils/RJKeychainManager.m +111 -0
  94. package/ios/Utils/RJPerfTiming.h +209 -0
  95. package/ios/Utils/RJPerfTiming.m +264 -0
  96. package/ios/Utils/RJTelemetry.h +92 -0
  97. package/ios/Utils/RJTelemetry.m +320 -0
  98. package/ios/Utils/RJWindowUtils.h +66 -0
  99. package/ios/Utils/RJWindowUtils.m +133 -0
  100. package/lib/commonjs/NativeRejourney.js +40 -0
  101. package/lib/commonjs/components/Mask.js +79 -0
  102. package/lib/commonjs/index.js +1381 -0
  103. package/lib/commonjs/sdk/autoTracking.js +1259 -0
  104. package/lib/commonjs/sdk/constants.js +151 -0
  105. package/lib/commonjs/sdk/errorTracking.js +199 -0
  106. package/lib/commonjs/sdk/index.js +50 -0
  107. package/lib/commonjs/sdk/metricsTracking.js +204 -0
  108. package/lib/commonjs/sdk/navigation.js +151 -0
  109. package/lib/commonjs/sdk/networkInterceptor.js +412 -0
  110. package/lib/commonjs/sdk/utils.js +363 -0
  111. package/lib/commonjs/types/expo-router.d.js +2 -0
  112. package/lib/commonjs/types/index.js +2 -0
  113. package/lib/module/NativeRejourney.js +38 -0
  114. package/lib/module/components/Mask.js +72 -0
  115. package/lib/module/index.js +1284 -0
  116. package/lib/module/sdk/autoTracking.js +1233 -0
  117. package/lib/module/sdk/constants.js +145 -0
  118. package/lib/module/sdk/errorTracking.js +189 -0
  119. package/lib/module/sdk/index.js +12 -0
  120. package/lib/module/sdk/metricsTracking.js +187 -0
  121. package/lib/module/sdk/navigation.js +143 -0
  122. package/lib/module/sdk/networkInterceptor.js +401 -0
  123. package/lib/module/sdk/utils.js +342 -0
  124. package/lib/module/types/expo-router.d.js +2 -0
  125. package/lib/module/types/index.js +2 -0
  126. package/lib/typescript/NativeRejourney.d.ts +147 -0
  127. package/lib/typescript/components/Mask.d.ts +39 -0
  128. package/lib/typescript/index.d.ts +117 -0
  129. package/lib/typescript/sdk/autoTracking.d.ts +204 -0
  130. package/lib/typescript/sdk/constants.d.ts +120 -0
  131. package/lib/typescript/sdk/errorTracking.d.ts +32 -0
  132. package/lib/typescript/sdk/index.d.ts +9 -0
  133. package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
  134. package/lib/typescript/sdk/navigation.d.ts +33 -0
  135. package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
  136. package/lib/typescript/sdk/utils.d.ts +148 -0
  137. package/lib/typescript/types/index.d.ts +624 -0
  138. package/package.json +102 -0
  139. package/rejourney.podspec +21 -0
  140. package/src/NativeRejourney.ts +165 -0
  141. package/src/components/Mask.tsx +80 -0
  142. package/src/index.ts +1459 -0
  143. package/src/sdk/autoTracking.ts +1373 -0
  144. package/src/sdk/constants.ts +134 -0
  145. package/src/sdk/errorTracking.ts +231 -0
  146. package/src/sdk/index.ts +11 -0
  147. package/src/sdk/metricsTracking.ts +232 -0
  148. package/src/sdk/navigation.ts +157 -0
  149. package/src/sdk/networkInterceptor.ts +440 -0
  150. package/src/sdk/utils.ts +369 -0
  151. package/src/types/expo-router.d.ts +7 -0
  152. package/src/types/index.ts +739 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Rejourney SDK Constants
3
+ */
4
+
5
+ export const SDK_VERSION = '1.0.0';
6
+
7
+ /** Default configuration values */
8
+ export const DEFAULT_CONFIG = {
9
+ enabled: true,
10
+ captureFPS: 0.5,
11
+ // Every 2 seconds (only used in timer mode)
12
+ captureOnEvents: true,
13
+ // Event-driven capture (not time-based)
14
+ maxSessionDuration: 10 * 60 * 1000,
15
+ // 10 minutes (project-level configurable, clamped 1–10)
16
+ maxStorageSize: 50 * 1024 * 1024,
17
+ // 50MB
18
+ autoScreenTracking: true,
19
+ autoGestureTracking: true,
20
+ privacyOcclusion: true,
21
+ enableCompression: true,
22
+ inactivityThreshold: 5000,
23
+ // 5 seconds
24
+ disableInDev: false,
25
+ detectRageTaps: true,
26
+ rageTapThreshold: 3,
27
+ rageTapTimeWindow: 1000,
28
+ // 1 second
29
+ debug: false,
30
+ autoStartRecording: true,
31
+ collectDeviceInfo: true,
32
+ // Collect detailed device information
33
+ collectGeoLocation: true,
34
+ // Collect IP address and geolocation
35
+ postNavigationDelay: 300,
36
+ // 300ms - allow navigation animations to complete
37
+ postGestureDelay: 200,
38
+ // 200ms - show result of taps, not animations
39
+ postModalDelay: 400 // 400ms - ensure modals/alerts are fully rendered
40
+ };
41
+
42
+ /** Event type constants */
43
+ export const EVENT_TYPES = {
44
+ GESTURE: 'gesture',
45
+ SCREEN_CHANGE: 'screen_change',
46
+ CUSTOM: 'custom',
47
+ APP_STATE: 'app_state',
48
+ FRUSTRATION: 'frustration',
49
+ ERROR: 'error'
50
+ };
51
+
52
+ /** Gesture type constants */
53
+ export const GESTURE_TYPES = {
54
+ TAP: 'tap',
55
+ DOUBLE_TAP: 'double_tap',
56
+ LONG_PRESS: 'long_press',
57
+ SWIPE_LEFT: 'swipe_left',
58
+ SWIPE_RIGHT: 'swipe_right',
59
+ SWIPE_UP: 'swipe_up',
60
+ SWIPE_DOWN: 'swipe_down',
61
+ PINCH: 'pinch',
62
+ SCROLL: 'scroll',
63
+ RAGE_TAP: 'rage_tap'
64
+ };
65
+
66
+ /** Playback speeds */
67
+ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
68
+
69
+ /** Capture settings */
70
+ export const CAPTURE_SETTINGS = {
71
+ DEFAULT_FPS: 0.5,
72
+ MIN_FPS: 0.1,
73
+ MAX_FPS: 2,
74
+ CAPTURE_SCALE: 0.25,
75
+ // 25% resolution for video segments
76
+ MIN_CAPTURE_DELTA_TIME: 0.5 // Minimum 0.5s between captures (rate limiting)
77
+ };
78
+
79
+ /** Memory management settings */
80
+ export const MEMORY_SETTINGS = {
81
+ /** Maximum events to keep in memory before flushing */
82
+ MAX_EVENTS_IN_MEMORY: 100,
83
+ /** Memory warning threshold in MB (flush when exceeded) */
84
+ MEMORY_WARNING_THRESHOLD_MB: 100,
85
+ /** Enable aggressive memory cleanup during low memory */
86
+ AGGRESSIVE_CLEANUP_ENABLED: true,
87
+ /** Bitmap pool size for reusing bitmaps (Android) */
88
+ BITMAP_POOL_SIZE: 3
89
+ };
90
+
91
+ /** CPU throttling settings */
92
+ export const CPU_SETTINGS = {
93
+ /** Throttle captures when CPU usage exceeds this percentage */
94
+ CPU_THROTTLE_THRESHOLD: 80,
95
+ /** Minimum interval between captures when throttled (seconds) */
96
+ THROTTLED_MIN_INTERVAL: 2.0,
97
+ /** Skip captures when battery is below this level (0-100) */
98
+ LOW_BATTERY_THRESHOLD: 15,
99
+ /** Skip captures when device is thermally throttled */
100
+ THERMAL_THROTTLE_ENABLED: true,
101
+ /** Maximum consecutive captures before forced cooldown */
102
+ MAX_CONSECUTIVE_CAPTURES: 10,
103
+ /** Cooldown period after max consecutive captures (ms) */
104
+ CAPTURE_COOLDOWN_MS: 1000
105
+ };
106
+
107
+ /** Storage management settings */
108
+ export const STORAGE_SETTINGS = {
109
+ /** Maximum total storage for session data (bytes) */
110
+ MAX_STORAGE_SIZE: 50 * 1024 * 1024,
111
+ // 50MB
112
+ /** Storage warning threshold - start cleanup at this level */
113
+ STORAGE_WARNING_THRESHOLD: 0.8,
114
+ // 80% of max
115
+ /** Number of old sessions to keep */
116
+ MAX_SESSIONS_TO_KEEP: 5,
117
+ /** Auto-delete sessions older than this (hours) */
118
+ SESSION_EXPIRY_HOURS: 24,
119
+ /** Use efficient binary storage format */
120
+ USE_BINARY_FORMAT: true
121
+ };
122
+
123
+ /** Network/Upload settings */
124
+ export const UPLOAD_SETTINGS = {
125
+ /** Batch upload interval (ms) */
126
+ BATCH_INTERVAL_MS: 30000,
127
+ // 30 seconds
128
+ /** Max retry attempts for failed uploads */
129
+ MAX_RETRY_ATTEMPTS: 3,
130
+ /** Retry delay multiplier (exponential backoff) */
131
+ RETRY_DELAY_MULTIPLIER: 2,
132
+ /** Initial retry delay (ms) */
133
+ INITIAL_RETRY_DELAY_MS: 1000,
134
+ /** Max events per upload batch */
135
+ MAX_EVENTS_PER_BATCH: 50,
136
+ /** Skip uploads when on cellular and battery is low */
137
+ CELLULAR_BATTERY_AWARE: true
138
+ };
139
+
140
+ /** Privacy constants */
141
+ export const PRIVACY = {
142
+ OCCLUSION_COLOR: '#808080',
143
+ SENSITIVE_COMPONENT_TYPES: ['TextInput', 'SecureTextEntry', 'PasswordField']
144
+ };
145
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Error Tracking Module for Rejourney SDK
3
+ *
4
+ * Handles JS error capture, React Native ErrorUtils, and unhandled promise rejections.
5
+ * Split from autoTracking.ts for better code organization.
6
+ */
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
+ const _globalThis = globalThis;
12
+
13
+ // Original error handlers (for restoration)
14
+ let originalErrorHandler;
15
+ let originalOnError = null;
16
+ let originalOnUnhandledRejection = null;
17
+
18
+ // Callbacks
19
+ let onErrorCallback = null;
20
+
21
+ // Metrics
22
+ let errorCount = 0;
23
+
24
+ /**
25
+ * Setup error tracking with the given callback
26
+ */
27
+ export function setupErrorTracking(config, onError) {
28
+ onErrorCallback = onError;
29
+ errorCount = 0;
30
+
31
+ // Track React Native errors
32
+ if (config.trackReactNativeErrors !== false) {
33
+ setupReactNativeErrorHandler();
34
+ }
35
+
36
+ // Track JavaScript errors (only works in web/debug)
37
+ if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
38
+ setupJSErrorHandler();
39
+ }
40
+
41
+ // Track unhandled promise rejections
42
+ if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
43
+ setupPromiseRejectionHandler();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Cleanup error tracking and restore original handlers
49
+ */
50
+ export function cleanupErrorTracking() {
51
+ // Restore React Native handler
52
+ if (originalErrorHandler) {
53
+ try {
54
+ const ErrorUtils = _globalThis.ErrorUtils;
55
+ if (ErrorUtils) {
56
+ ErrorUtils.setGlobalHandler(originalErrorHandler);
57
+ }
58
+ } catch {
59
+ // Ignore
60
+ }
61
+ originalErrorHandler = undefined;
62
+ }
63
+
64
+ // Restore global onerror
65
+ if (originalOnError !== null) {
66
+ _globalThis.onerror = originalOnError;
67
+ originalOnError = null;
68
+ }
69
+
70
+ // Remove promise rejection handler
71
+ if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
72
+ _globalThis.removeEventListener('unhandledrejection', originalOnUnhandledRejection);
73
+ originalOnUnhandledRejection = null;
74
+ }
75
+ onErrorCallback = null;
76
+ }
77
+
78
+ /**
79
+ * Get current error count
80
+ */
81
+ export function getErrorCount() {
82
+ return errorCount;
83
+ }
84
+
85
+ /**
86
+ * Reset error count
87
+ */
88
+ export function resetErrorCount() {
89
+ errorCount = 0;
90
+ }
91
+
92
+ /**
93
+ * Manually capture an error
94
+ */
95
+ export function captureError(message, stack, name) {
96
+ trackError({
97
+ type: 'error',
98
+ timestamp: Date.now(),
99
+ message,
100
+ stack,
101
+ name: name || 'Error'
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Track an error internally
107
+ */
108
+ function trackError(error) {
109
+ errorCount++;
110
+ if (onErrorCallback) {
111
+ onErrorCallback(error);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Setup React Native ErrorUtils handler
117
+ */
118
+ function setupReactNativeErrorHandler() {
119
+ try {
120
+ const ErrorUtils = _globalThis.ErrorUtils;
121
+ if (!ErrorUtils) return;
122
+
123
+ // Store original handler
124
+ originalErrorHandler = ErrorUtils.getGlobalHandler();
125
+
126
+ // Set new handler
127
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
128
+ trackError({
129
+ type: 'error',
130
+ timestamp: Date.now(),
131
+ message: error.message || String(error),
132
+ stack: error.stack,
133
+ name: error.name || 'Error'
134
+ });
135
+
136
+ // Call original handler
137
+ if (originalErrorHandler) {
138
+ originalErrorHandler(error, isFatal);
139
+ }
140
+ });
141
+ } catch {
142
+ // ErrorUtils not available
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Setup global JS error handler
148
+ */
149
+ function setupJSErrorHandler() {
150
+ if (typeof _globalThis.onerror !== 'undefined') {
151
+ originalOnError = _globalThis.onerror;
152
+ _globalThis.onerror = (message, source, lineno, colno, error) => {
153
+ trackError({
154
+ type: 'error',
155
+ timestamp: Date.now(),
156
+ message: typeof message === 'string' ? message : 'Unknown error',
157
+ stack: error?.stack || `${source}:${lineno}:${colno}`,
158
+ name: error?.name || 'Error'
159
+ });
160
+
161
+ // Call original handler
162
+ if (originalOnError) {
163
+ return originalOnError(message, source, lineno, colno, error);
164
+ }
165
+ return false;
166
+ };
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Setup unhandled promise rejection handler
172
+ */
173
+ function setupPromiseRejectionHandler() {
174
+ if (typeof _globalThis.addEventListener !== 'undefined') {
175
+ const handler = event => {
176
+ const reason = event.reason;
177
+ trackError({
178
+ type: 'error',
179
+ timestamp: Date.now(),
180
+ message: reason?.message || String(reason) || 'Unhandled Promise Rejection',
181
+ stack: reason?.stack,
182
+ name: reason?.name || 'UnhandledRejection'
183
+ });
184
+ };
185
+ originalOnUnhandledRejection = handler;
186
+ _globalThis.addEventListener('unhandledrejection', handler);
187
+ }
188
+ }
189
+ //# sourceMappingURL=errorTracking.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Rejourney SDK
3
+ * Session recording and replay for React Native
4
+ */
5
+
6
+ export * from './constants';
7
+ export * from './utils';
8
+ export * from './autoTracking';
9
+ export * from './networkInterceptor';
10
+ // Note: errorTracking and metricsTracking are internal modules
11
+ // Their exports are re-exported through autoTracking for backward compatibility
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Session Metrics Module for Rejourney SDK
3
+ *
4
+ * Tracks and calculates session metrics including interaction scores,
5
+ * API performance, and user engagement metrics.
6
+ * Split from autoTracking.ts for better code organization.
7
+ */
8
+
9
+ /**
10
+ * Session metrics structure
11
+ */
12
+
13
+ // Metrics state
14
+ let metrics = createEmptyMetrics();
15
+ let sessionStartTime = 0;
16
+ let maxSessionDurationMs = 10 * 60 * 1000; // 10 minutes default
17
+
18
+ /**
19
+ * Create empty metrics object
20
+ */
21
+ export function createEmptyMetrics() {
22
+ return {
23
+ totalEvents: 0,
24
+ touchCount: 0,
25
+ scrollCount: 0,
26
+ gestureCount: 0,
27
+ inputCount: 0,
28
+ navigationCount: 0,
29
+ errorCount: 0,
30
+ rageTapCount: 0,
31
+ apiSuccessCount: 0,
32
+ apiErrorCount: 0,
33
+ apiTotalCount: 0,
34
+ netTotalDurationMs: 0,
35
+ netTotalBytes: 0,
36
+ screensVisited: [],
37
+ uniqueScreensCount: 0,
38
+ interactionScore: 100,
39
+ explorationScore: 100,
40
+ uxScore: 100
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Reset all metrics
46
+ */
47
+ export function resetMetrics() {
48
+ metrics = createEmptyMetrics();
49
+ sessionStartTime = 0;
50
+ }
51
+
52
+ /**
53
+ * Initialize metrics for new session
54
+ */
55
+ export function initMetrics() {
56
+ metrics = createEmptyMetrics();
57
+ sessionStartTime = Date.now();
58
+ }
59
+
60
+ /**
61
+ * Get current session metrics with calculated scores
62
+ */
63
+ export function getSessionMetrics() {
64
+ // Calculate duration, clamped to max session duration
65
+ const rawDuration = Date.now() - sessionStartTime;
66
+ const durationMs = Math.min(rawDuration, maxSessionDurationMs);
67
+
68
+ // Calculate scores
69
+ const interactionScore = calculateInteractionScore(durationMs);
70
+ const explorationScore = calculateExplorationScore();
71
+ const uxScore = calculateUXScore();
72
+ return {
73
+ ...metrics,
74
+ interactionScore,
75
+ explorationScore,
76
+ uxScore
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Set max session duration (in minutes)
82
+ */
83
+ export function setMaxSessionDurationMinutes(minutes) {
84
+ if (minutes !== undefined && minutes > 0) {
85
+ // Clamp to 1-10 minutes
86
+ const clampedMinutes = Math.max(1, Math.min(10, minutes));
87
+ maxSessionDurationMs = clampedMinutes * 60 * 1000;
88
+ }
89
+ }
90
+
91
+ // ==================== Metric Increment Methods ====================
92
+
93
+ export function incrementTouchCount() {
94
+ metrics.touchCount++;
95
+ metrics.totalEvents++;
96
+ }
97
+ export function incrementScrollCount() {
98
+ metrics.scrollCount++;
99
+ metrics.totalEvents++;
100
+ }
101
+ export function incrementNavigationCount() {
102
+ metrics.navigationCount++;
103
+ metrics.totalEvents++;
104
+ }
105
+ export function incrementRageTapCount() {
106
+ metrics.rageTapCount++;
107
+ }
108
+ export function incrementErrorCount() {
109
+ metrics.errorCount++;
110
+ metrics.totalEvents++;
111
+ }
112
+ export function addScreenVisited(screenName) {
113
+ metrics.screensVisited.push(screenName);
114
+ metrics.uniqueScreensCount = new Set(metrics.screensVisited).size;
115
+ }
116
+ export function trackAPIMetrics(success, durationMs = 0, responseBytes = 0) {
117
+ metrics.apiTotalCount++;
118
+ if (durationMs > 0) {
119
+ metrics.netTotalDurationMs += durationMs;
120
+ }
121
+ if (responseBytes > 0) {
122
+ metrics.netTotalBytes += responseBytes;
123
+ }
124
+ if (success) {
125
+ metrics.apiSuccessCount++;
126
+ } else {
127
+ metrics.apiErrorCount++;
128
+ metrics.errorCount++;
129
+ }
130
+ }
131
+
132
+ // ==================== Score Calculations ====================
133
+
134
+ /**
135
+ * Calculate interaction score based on engagement with app
136
+ * Higher = more engaged (more interactions per minute)
137
+ */
138
+ function calculateInteractionScore(durationMs) {
139
+ if (durationMs <= 0) return 100;
140
+ const durationMinutes = durationMs / 60000;
141
+ 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
+ if (interactionsPerMinute < 2) return 20;
147
+ if (interactionsPerMinute < 5) return 50;
148
+ if (interactionsPerMinute < 10) return 70;
149
+ if (interactionsPerMinute <= 30) return 100;
150
+ if (interactionsPerMinute <= 60) return 80;
151
+ return 50; // Very high might indicate frustration
152
+ }
153
+
154
+ /**
155
+ * Calculate exploration score based on screens visited
156
+ * Higher = user explored more of the app
157
+ */
158
+ function calculateExplorationScore() {
159
+ const uniqueScreens = metrics.uniqueScreensCount;
160
+
161
+ // More unique screens = better exploration
162
+ if (uniqueScreens >= 10) return 100;
163
+ if (uniqueScreens >= 7) return 90;
164
+ if (uniqueScreens >= 5) return 80;
165
+ if (uniqueScreens >= 3) return 60;
166
+ if (uniqueScreens >= 2) return 40;
167
+ return 20; // Single screen visit
168
+ }
169
+
170
+ /**
171
+ * Calculate UX score based on errors and frustration
172
+ * Higher = better experience (fewer issues)
173
+ */
174
+ function calculateUXScore() {
175
+ let score = 100;
176
+
177
+ // Deduct for errors
178
+ score -= metrics.errorCount * 10;
179
+
180
+ // Deduct heavily for rage taps
181
+ score -= metrics.rageTapCount * 20;
182
+
183
+ // Deduct for API errors (less severe)
184
+ score -= metrics.apiErrorCount * 5;
185
+ return Math.max(0, Math.min(100, score));
186
+ }
187
+ //# sourceMappingURL=metricsTracking.js.map
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Rejourney Navigation Utilities
3
+ *
4
+ * Helper functions for extracting human-readable screen names from
5
+ * React Navigation / Expo Router state.
6
+ *
7
+ * These functions are used internally by the SDK's automatic navigation
8
+ * detection. You don't need to import or use these directly.
9
+ */
10
+
11
+ /**
12
+ * Normalize a screen name to be human-readable
13
+ * Handles common patterns from React Native / Expo Router
14
+ *
15
+ * @param raw - Raw screen name to normalize
16
+ * @returns Cleaned, human-readable screen name
17
+ */
18
+ export function normalizeScreenName(raw) {
19
+ if (!raw) return 'Unknown';
20
+ let name = raw;
21
+
22
+ // 1. Remove non-printable characters and weird symbols (often from icons)
23
+ name = name.replace(/[^\x20-\x7E\s]/g, '');
24
+
25
+ // 2. Handle hyphens: my-recipes -> My Recipes
26
+ name = name.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
27
+
28
+ // 3. Remove common suffixes
29
+ const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
30
+ for (const suffix of suffixes) {
31
+ if (name.endsWith(suffix) && name.length > suffix.length) {
32
+ name = name.slice(0, -suffix.length).trim();
33
+ }
34
+ }
35
+
36
+ // 4. Remove common prefixes (React Native internals)
37
+ const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
38
+ for (const prefix of prefixes) {
39
+ if (name.startsWith(prefix) && name.length > prefix.length + 2) {
40
+ name = name.slice(prefix.length).trim();
41
+ }
42
+ }
43
+
44
+ // 5. Handle dynamic route params: [id] -> (omit), [userId] -> "User"
45
+ name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
46
+ const clean = param.replace(/Id$/i, '');
47
+ if (clean.length < 2) return '';
48
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
49
+ });
50
+
51
+ // 6. Remove leftover brackets
52
+ name = name.replace(/\[\]/g, '');
53
+
54
+ // 7. Convert camelCase/PascalCase to Title Case with spaces
55
+ name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
56
+
57
+ // 8. Clean up multiple spaces and trim
58
+ name = name.replace(/\s+/g, ' ').trim();
59
+
60
+ // 9. Capitalize first letter
61
+ if (name.length > 0) {
62
+ name = name.charAt(0).toUpperCase() + name.slice(1);
63
+ }
64
+ return name || 'Unknown';
65
+ }
66
+
67
+ /**
68
+ * Get a human-readable screen name from Expo Router path and segments
69
+ *
70
+ * @param pathname - The current route pathname
71
+ * @param segments - Route segments from useSegments()
72
+ * @returns Human-readable screen name
73
+ */
74
+ export function getScreenNameFromPath(pathname, segments) {
75
+ // Use segments for cleaner names if available
76
+ if (segments.length > 0) {
77
+ // Filter out group markers like (tabs), (auth), etc.
78
+ const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
79
+ if (cleanSegments.length > 0) {
80
+ // Process each segment
81
+ const processedSegments = cleanSegments.map(s => {
82
+ // Handle dynamic params like [id]
83
+ if (s.startsWith('[') && s.endsWith(']')) {
84
+ const param = s.slice(1, -1);
85
+ // Skip pure ID params, keep meaningful ones
86
+ if (param === 'id' || param === 'slug') return null;
87
+ // Extract meaningful part: userId -> User
88
+ const clean = param.replace(/Id$/i, '');
89
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
90
+ }
91
+ // Regular segment - capitalize
92
+ return s.charAt(0).toUpperCase() + s.slice(1);
93
+ }).filter(Boolean);
94
+ if (processedSegments.length > 0) {
95
+ return processedSegments.join(' > ');
96
+ }
97
+ }
98
+ }
99
+
100
+ // Fall back to pathname
101
+ if (!pathname || pathname === '/') {
102
+ return 'Home';
103
+ }
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
110
+ if (param === 'id' || param === 'slug') return '';
111
+ const clean = param.replace(/Id$/i, '');
112
+ 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();
118
+ if (!cleanPath) {
119
+ return 'Home';
120
+ }
121
+
122
+ // Capitalize first letter of each word
123
+ return cleanPath.split(' > ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).filter(s => s.length > 0).join(' > ') || 'Home';
124
+ }
125
+
126
+ /**
127
+ * Get the current route name from a navigation state object
128
+ *
129
+ * @param state - React Navigation state object
130
+ * @returns Current route name or null
131
+ */
132
+ export function getCurrentRouteFromState(state) {
133
+ if (!state || !state.routes) return null;
134
+ const route = state.routes[state.index ?? state.routes.length - 1];
135
+ if (!route) return null;
136
+
137
+ // If nested state, recurse
138
+ if (route.state) {
139
+ return getCurrentRouteFromState(route.state);
140
+ }
141
+ return route.name || null;
142
+ }
143
+ //# sourceMappingURL=navigation.js.map