@rejourneyco/react-native 1.0.7

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 (105) hide show
  1. package/README.md +29 -0
  2. package/android/build.gradle.kts +135 -0
  3. package/android/consumer-rules.pro +10 -0
  4. package/android/proguard-rules.pro +1 -0
  5. package/android/src/main/AndroidManifest.xml +15 -0
  6. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +860 -0
  7. package/android/src/main/java/com/rejourney/engine/DeviceRegistrar.kt +290 -0
  8. package/android/src/main/java/com/rejourney/engine/DiagnosticLog.kt +385 -0
  9. package/android/src/main/java/com/rejourney/engine/RejourneyImpl.kt +512 -0
  10. package/android/src/main/java/com/rejourney/platform/OEMDetector.kt +173 -0
  11. package/android/src/main/java/com/rejourney/platform/PerfTiming.kt +384 -0
  12. package/android/src/main/java/com/rejourney/platform/SessionLifecycleService.kt +160 -0
  13. package/android/src/main/java/com/rejourney/platform/Telemetry.kt +301 -0
  14. package/android/src/main/java/com/rejourney/platform/WindowUtils.kt +100 -0
  15. package/android/src/main/java/com/rejourney/recording/AnrSentinel.kt +129 -0
  16. package/android/src/main/java/com/rejourney/recording/EventBuffer.kt +330 -0
  17. package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +519 -0
  18. package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +740 -0
  19. package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +559 -0
  20. package/android/src/main/java/com/rejourney/recording/StabilityMonitor.kt +238 -0
  21. package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +633 -0
  22. package/android/src/main/java/com/rejourney/recording/ViewHierarchyScanner.kt +232 -0
  23. package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +474 -0
  24. package/android/src/main/java/com/rejourney/utility/DataCompression.kt +63 -0
  25. package/android/src/main/java/com/rejourney/utility/ImageBlur.kt +412 -0
  26. package/android/src/main/java/com/rejourney/utility/ViewIdentifier.kt +169 -0
  27. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +232 -0
  28. package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
  29. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +268 -0
  30. package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
  31. package/ios/Engine/DeviceRegistrar.swift +288 -0
  32. package/ios/Engine/DiagnosticLog.swift +387 -0
  33. package/ios/Engine/RejourneyImpl.swift +719 -0
  34. package/ios/Recording/AnrSentinel.swift +142 -0
  35. package/ios/Recording/EventBuffer.swift +326 -0
  36. package/ios/Recording/InteractionRecorder.swift +428 -0
  37. package/ios/Recording/ReplayOrchestrator.swift +624 -0
  38. package/ios/Recording/SegmentDispatcher.swift +492 -0
  39. package/ios/Recording/StabilityMonitor.swift +223 -0
  40. package/ios/Recording/TelemetryPipeline.swift +547 -0
  41. package/ios/Recording/ViewHierarchyScanner.swift +156 -0
  42. package/ios/Recording/VisualCapture.swift +675 -0
  43. package/ios/Rejourney.h +38 -0
  44. package/ios/Rejourney.mm +375 -0
  45. package/ios/Utility/DataCompression.swift +55 -0
  46. package/ios/Utility/ImageBlur.swift +89 -0
  47. package/ios/Utility/RuntimeMethodSwap.swift +41 -0
  48. package/ios/Utility/ViewIdentifier.swift +37 -0
  49. package/lib/commonjs/NativeRejourney.js +40 -0
  50. package/lib/commonjs/components/Mask.js +88 -0
  51. package/lib/commonjs/index.js +1443 -0
  52. package/lib/commonjs/sdk/autoTracking.js +1087 -0
  53. package/lib/commonjs/sdk/constants.js +166 -0
  54. package/lib/commonjs/sdk/errorTracking.js +187 -0
  55. package/lib/commonjs/sdk/index.js +50 -0
  56. package/lib/commonjs/sdk/metricsTracking.js +205 -0
  57. package/lib/commonjs/sdk/navigation.js +128 -0
  58. package/lib/commonjs/sdk/networkInterceptor.js +375 -0
  59. package/lib/commonjs/sdk/utils.js +433 -0
  60. package/lib/commonjs/sdk/version.js +13 -0
  61. package/lib/commonjs/types/expo-router.d.js +2 -0
  62. package/lib/commonjs/types/index.js +2 -0
  63. package/lib/module/NativeRejourney.js +38 -0
  64. package/lib/module/components/Mask.js +83 -0
  65. package/lib/module/index.js +1341 -0
  66. package/lib/module/sdk/autoTracking.js +1059 -0
  67. package/lib/module/sdk/constants.js +154 -0
  68. package/lib/module/sdk/errorTracking.js +177 -0
  69. package/lib/module/sdk/index.js +26 -0
  70. package/lib/module/sdk/metricsTracking.js +187 -0
  71. package/lib/module/sdk/navigation.js +120 -0
  72. package/lib/module/sdk/networkInterceptor.js +364 -0
  73. package/lib/module/sdk/utils.js +412 -0
  74. package/lib/module/sdk/version.js +7 -0
  75. package/lib/module/types/expo-router.d.js +2 -0
  76. package/lib/module/types/index.js +2 -0
  77. package/lib/typescript/NativeRejourney.d.ts +160 -0
  78. package/lib/typescript/components/Mask.d.ts +54 -0
  79. package/lib/typescript/index.d.ts +117 -0
  80. package/lib/typescript/sdk/autoTracking.d.ts +226 -0
  81. package/lib/typescript/sdk/constants.d.ts +138 -0
  82. package/lib/typescript/sdk/errorTracking.d.ts +47 -0
  83. package/lib/typescript/sdk/index.d.ts +24 -0
  84. package/lib/typescript/sdk/metricsTracking.d.ts +75 -0
  85. package/lib/typescript/sdk/navigation.d.ts +48 -0
  86. package/lib/typescript/sdk/networkInterceptor.d.ts +62 -0
  87. package/lib/typescript/sdk/utils.d.ts +193 -0
  88. package/lib/typescript/sdk/version.d.ts +6 -0
  89. package/lib/typescript/types/index.d.ts +618 -0
  90. package/package.json +122 -0
  91. package/rejourney.podspec +23 -0
  92. package/src/NativeRejourney.ts +185 -0
  93. package/src/components/Mask.tsx +93 -0
  94. package/src/index.ts +1555 -0
  95. package/src/sdk/autoTracking.ts +1245 -0
  96. package/src/sdk/constants.ts +155 -0
  97. package/src/sdk/errorTracking.ts +231 -0
  98. package/src/sdk/index.ts +25 -0
  99. package/src/sdk/metricsTracking.ts +227 -0
  100. package/src/sdk/navigation.ts +152 -0
  101. package/src/sdk/networkInterceptor.ts +423 -0
  102. package/src/sdk/utils.ts +442 -0
  103. package/src/sdk/version.ts +6 -0
  104. package/src/types/expo-router.d.ts +7 -0
  105. package/src/types/index.ts +709 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Copyright 2026 Rejourney
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Rejourney SDK Constants
19
+ */
20
+
21
+ // Import version from auto-generated file (generated from package.json by scripts/generate-version.js)
22
+ import { SDK_VERSION } from './version';
23
+ export { SDK_VERSION };
24
+
25
+ /** Default configuration values */
26
+ export const DEFAULT_CONFIG = {
27
+ enabled: true,
28
+ captureFPS: 0.5,
29
+ captureOnEvents: true,
30
+ maxSessionDuration: 10 * 60 * 1000,
31
+ maxStorageSize: 50 * 1024 * 1024,
32
+ autoScreenTracking: true,
33
+ autoGestureTracking: true,
34
+ privacyOcclusion: true,
35
+ enableCompression: true,
36
+ inactivityThreshold: 5000,
37
+ disableInDev: false,
38
+ detectRageTaps: true,
39
+ detectDeadTaps: true,
40
+ rageTapThreshold: 3,
41
+ rageTapTimeWindow: 1000,
42
+ debug: false,
43
+ autoStartRecording: true,
44
+ collectDeviceInfo: true,
45
+ collectGeoLocation: true,
46
+ postNavigationDelay: 300,
47
+ postGestureDelay: 200,
48
+ postModalDelay: 400
49
+ };
50
+
51
+ /** Event type constants */
52
+ export const EVENT_TYPES = {
53
+ GESTURE: 'gesture',
54
+ SCREEN_CHANGE: 'screen_change',
55
+ CUSTOM: 'custom',
56
+ APP_STATE: 'app_state',
57
+ FRUSTRATION: 'frustration',
58
+ ERROR: 'error'
59
+ };
60
+
61
+ /** Gesture type constants */
62
+ export const GESTURE_TYPES = {
63
+ TAP: 'tap',
64
+ DOUBLE_TAP: 'double_tap',
65
+ LONG_PRESS: 'long_press',
66
+ SWIPE_LEFT: 'swipe_left',
67
+ SWIPE_RIGHT: 'swipe_right',
68
+ SWIPE_UP: 'swipe_up',
69
+ SWIPE_DOWN: 'swipe_down',
70
+ PINCH: 'pinch',
71
+ SCROLL: 'scroll',
72
+ RAGE_TAP: 'rage_tap',
73
+ DEAD_TAP: 'dead_tap'
74
+ };
75
+
76
+ /** Playback speeds */
77
+ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
78
+
79
+ /** Capture settings */
80
+ export const CAPTURE_SETTINGS = {
81
+ DEFAULT_FPS: 0.5,
82
+ MIN_FPS: 0.1,
83
+ MAX_FPS: 2,
84
+ CAPTURE_SCALE: 0.25,
85
+ MIN_CAPTURE_DELTA_TIME: 0.5
86
+ };
87
+
88
+ /** Memory management settings */
89
+ export const MEMORY_SETTINGS = {
90
+ /** Maximum events to keep in memory before flushing */
91
+ MAX_EVENTS_IN_MEMORY: 100,
92
+ /** Memory warning threshold in MB (flush when exceeded) */
93
+ MEMORY_WARNING_THRESHOLD_MB: 100,
94
+ /** Enable aggressive memory cleanup during low memory */
95
+ AGGRESSIVE_CLEANUP_ENABLED: true,
96
+ /** Bitmap pool size for reusing bitmaps (Android) */
97
+ BITMAP_POOL_SIZE: 3
98
+ };
99
+
100
+ /** CPU throttling settings */
101
+ export const CPU_SETTINGS = {
102
+ /** Throttle captures when CPU usage exceeds this percentage */
103
+ CPU_THROTTLE_THRESHOLD: 80,
104
+ /** Minimum interval between captures when throttled (seconds) */
105
+ THROTTLED_MIN_INTERVAL: 2.0,
106
+ /** Skip captures when battery is below this level (0-100) */
107
+ LOW_BATTERY_THRESHOLD: 15,
108
+ /** Skip captures when device is thermally throttled */
109
+ THERMAL_THROTTLE_ENABLED: true,
110
+ /** Maximum consecutive captures before forced cooldown */
111
+ MAX_CONSECUTIVE_CAPTURES: 10,
112
+ /** Cooldown period after max consecutive captures (ms) */
113
+ CAPTURE_COOLDOWN_MS: 1000
114
+ };
115
+
116
+ /** Storage management settings */
117
+ export const STORAGE_SETTINGS = {
118
+ /** Maximum total storage for session data (bytes) */
119
+ MAX_STORAGE_SIZE: 50 * 1024 * 1024,
120
+ // 50MB
121
+ /** Storage warning threshold - start cleanup at this level */
122
+ STORAGE_WARNING_THRESHOLD: 0.8,
123
+ // 80% of max
124
+ /** Number of old sessions to keep */
125
+ MAX_SESSIONS_TO_KEEP: 5,
126
+ /** Auto-delete sessions older than this (hours) */
127
+ SESSION_EXPIRY_HOURS: 24,
128
+ /** Use efficient binary storage format */
129
+ USE_BINARY_FORMAT: true
130
+ };
131
+
132
+ /** Network/Upload settings */
133
+ export const UPLOAD_SETTINGS = {
134
+ /** Batch upload interval (ms) */
135
+ BATCH_INTERVAL_MS: 30000,
136
+ // 30 seconds
137
+ /** Max retry attempts for failed uploads */
138
+ MAX_RETRY_ATTEMPTS: 3,
139
+ /** Retry delay multiplier (exponential backoff) */
140
+ RETRY_DELAY_MULTIPLIER: 2,
141
+ /** Initial retry delay (ms) */
142
+ INITIAL_RETRY_DELAY_MS: 1000,
143
+ /** Max events per upload batch */
144
+ MAX_EVENTS_PER_BATCH: 50,
145
+ /** Skip uploads when on cellular and battery is low */
146
+ CELLULAR_BATTERY_AWARE: true
147
+ };
148
+
149
+ /** Privacy constants */
150
+ export const PRIVACY = {
151
+ OCCLUSION_COLOR: '#808080',
152
+ SENSITIVE_COMPONENT_TYPES: ['TextInput', 'SecureTextEntry', 'PasswordField']
153
+ };
154
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Copyright 2026 Rejourney
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Error Tracking Module for Rejourney SDK
19
+ *
20
+ * Handles JS error capture, React Native ErrorUtils, and unhandled promise rejections.
21
+ * Split from autoTracking.ts for better code organization.
22
+ */
23
+
24
+ const _globalThis = globalThis;
25
+ let originalErrorHandler;
26
+ let originalOnError = null;
27
+ let originalOnUnhandledRejection = null;
28
+ let onErrorCallback = null;
29
+ let errorCount = 0;
30
+
31
+ /**
32
+ * Setup error tracking with the given callback
33
+ */
34
+ export function setupErrorTracking(config, onError) {
35
+ onErrorCallback = onError;
36
+ errorCount = 0;
37
+ if (config.trackReactNativeErrors !== false) {
38
+ setupReactNativeErrorHandler();
39
+ }
40
+ if (config.trackJSErrors !== false && typeof _globalThis !== 'undefined') {
41
+ setupJSErrorHandler();
42
+ }
43
+ if (config.trackPromiseRejections !== false && typeof _globalThis !== 'undefined') {
44
+ setupPromiseRejectionHandler();
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Cleanup error tracking and restore original handlers
50
+ */
51
+ export function cleanupErrorTracking() {
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
+ if (originalOnError !== null) {
64
+ _globalThis.onerror = originalOnError;
65
+ originalOnError = null;
66
+ }
67
+ if (originalOnUnhandledRejection && typeof _globalThis.removeEventListener !== 'undefined') {
68
+ _globalThis.removeEventListener('unhandledrejection', originalOnUnhandledRejection);
69
+ originalOnUnhandledRejection = null;
70
+ }
71
+ onErrorCallback = null;
72
+ }
73
+
74
+ /**
75
+ * Get current error count
76
+ */
77
+ export function getErrorCount() {
78
+ return errorCount;
79
+ }
80
+
81
+ /**
82
+ * Reset error count
83
+ */
84
+ export function resetErrorCount() {
85
+ errorCount = 0;
86
+ }
87
+
88
+ /**
89
+ * Manually capture an error
90
+ */
91
+ export function captureError(message, stack, name) {
92
+ trackError({
93
+ type: 'error',
94
+ timestamp: Date.now(),
95
+ message,
96
+ stack,
97
+ name: name || 'Error'
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Track an error internally
103
+ */
104
+ function trackError(error) {
105
+ errorCount++;
106
+ if (onErrorCallback) {
107
+ onErrorCallback(error);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Setup React Native ErrorUtils handler
113
+ */
114
+ function setupReactNativeErrorHandler() {
115
+ try {
116
+ const ErrorUtils = _globalThis.ErrorUtils;
117
+ if (!ErrorUtils) return;
118
+ originalErrorHandler = ErrorUtils.getGlobalHandler();
119
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
120
+ trackError({
121
+ type: 'error',
122
+ timestamp: Date.now(),
123
+ message: error.message || String(error),
124
+ stack: error.stack,
125
+ name: error.name || 'Error'
126
+ });
127
+ if (originalErrorHandler) {
128
+ originalErrorHandler(error, isFatal);
129
+ }
130
+ });
131
+ } catch {
132
+ // Ignore
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Setup global JS error handler
138
+ */
139
+ function setupJSErrorHandler() {
140
+ if (typeof _globalThis.onerror !== 'undefined') {
141
+ originalOnError = _globalThis.onerror;
142
+ _globalThis.onerror = (message, source, lineno, colno, error) => {
143
+ trackError({
144
+ type: 'error',
145
+ timestamp: Date.now(),
146
+ message: typeof message === 'string' ? message : 'Unknown error',
147
+ stack: error?.stack || `${source}:${lineno}:${colno}`,
148
+ name: error?.name || 'Error'
149
+ });
150
+ if (originalOnError) {
151
+ return originalOnError(message, source, lineno, colno, error);
152
+ }
153
+ return false;
154
+ };
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Setup unhandled promise rejection handler
160
+ */
161
+ function setupPromiseRejectionHandler() {
162
+ if (typeof _globalThis.addEventListener !== 'undefined') {
163
+ const handler = event => {
164
+ const reason = event.reason;
165
+ trackError({
166
+ type: 'error',
167
+ timestamp: Date.now(),
168
+ message: reason?.message || String(reason) || 'Unhandled Promise Rejection',
169
+ stack: reason?.stack,
170
+ name: reason?.name || 'UnhandledRejection'
171
+ });
172
+ };
173
+ originalOnUnhandledRejection = handler;
174
+ _globalThis.addEventListener('unhandledrejection', handler);
175
+ }
176
+ }
177
+ //# sourceMappingURL=errorTracking.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright 2026 Rejourney
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Rejourney SDK
19
+ * Session recording and replay for React Native
20
+ */
21
+
22
+ export * from './constants';
23
+ export * from './utils';
24
+ export * from './autoTracking';
25
+ export * from './networkInterceptor';
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Copyright 2026 Rejourney
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Session Metrics Module for Rejourney SDK
19
+ *
20
+ * Tracks and calculates session metrics including interaction scores,
21
+ * API performance, and user engagement metrics.
22
+ * Split from autoTracking.ts for better code organization.
23
+ */
24
+
25
+ /**
26
+ * Session metrics structure
27
+ */
28
+
29
+ let metrics = createEmptyMetrics();
30
+ let sessionStartTime = 0;
31
+ let maxSessionDurationMs = 10 * 60 * 1000;
32
+
33
+ /**
34
+ * Create empty metrics object
35
+ */
36
+ export function createEmptyMetrics() {
37
+ return {
38
+ totalEvents: 0,
39
+ touchCount: 0,
40
+ scrollCount: 0,
41
+ gestureCount: 0,
42
+ inputCount: 0,
43
+ navigationCount: 0,
44
+ errorCount: 0,
45
+ rageTapCount: 0,
46
+ deadTapCount: 0,
47
+ apiSuccessCount: 0,
48
+ apiErrorCount: 0,
49
+ apiTotalCount: 0,
50
+ netTotalDurationMs: 0,
51
+ netTotalBytes: 0,
52
+ screensVisited: [],
53
+ uniqueScreensCount: 0,
54
+ interactionScore: 100,
55
+ explorationScore: 100,
56
+ uxScore: 100
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Reset all metrics
62
+ */
63
+ export function resetMetrics() {
64
+ metrics = createEmptyMetrics();
65
+ sessionStartTime = 0;
66
+ }
67
+
68
+ /**
69
+ * Initialize metrics for new session
70
+ */
71
+ export function initMetrics() {
72
+ metrics = createEmptyMetrics();
73
+ sessionStartTime = Date.now();
74
+ }
75
+
76
+ /**
77
+ * Get current session metrics with calculated scores
78
+ */
79
+ export function getSessionMetrics() {
80
+ const rawDuration = Date.now() - sessionStartTime;
81
+ const durationMs = Math.min(rawDuration, maxSessionDurationMs);
82
+ const interactionScore = calculateInteractionScore(durationMs);
83
+ const explorationScore = calculateExplorationScore();
84
+ const uxScore = calculateUXScore();
85
+ return {
86
+ ...metrics,
87
+ interactionScore,
88
+ explorationScore,
89
+ uxScore
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Set max session duration (in minutes)
95
+ */
96
+ export function setMaxSessionDurationMinutes(minutes) {
97
+ if (minutes !== undefined && minutes > 0) {
98
+ // Clamp to 1-10 minutes
99
+ const clampedMinutes = Math.max(1, Math.min(10, minutes));
100
+ maxSessionDurationMs = clampedMinutes * 60 * 1000;
101
+ }
102
+ }
103
+ export function incrementTouchCount() {
104
+ metrics.touchCount++;
105
+ metrics.totalEvents++;
106
+ }
107
+ export function incrementScrollCount() {
108
+ metrics.scrollCount++;
109
+ metrics.totalEvents++;
110
+ }
111
+ export function incrementNavigationCount() {
112
+ metrics.navigationCount++;
113
+ metrics.totalEvents++;
114
+ }
115
+ export function incrementRageTapCount() {
116
+ metrics.rageTapCount++;
117
+ }
118
+ export function incrementDeadTapCount() {
119
+ metrics.deadTapCount++;
120
+ }
121
+ export function incrementErrorCount() {
122
+ metrics.errorCount++;
123
+ metrics.totalEvents++;
124
+ }
125
+ export function addScreenVisited(screenName) {
126
+ metrics.screensVisited.push(screenName);
127
+ metrics.uniqueScreensCount = new Set(metrics.screensVisited).size;
128
+ }
129
+ export function trackAPIMetrics(success, durationMs = 0, responseBytes = 0) {
130
+ metrics.apiTotalCount++;
131
+ if (durationMs > 0) {
132
+ metrics.netTotalDurationMs += durationMs;
133
+ }
134
+ if (responseBytes > 0) {
135
+ metrics.netTotalBytes += responseBytes;
136
+ }
137
+ if (success) {
138
+ metrics.apiSuccessCount++;
139
+ } else {
140
+ metrics.apiErrorCount++;
141
+ metrics.errorCount++;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Calculate interaction score based on engagement with app
147
+ * Higher = more engaged (more interactions per minute)
148
+ */
149
+ function calculateInteractionScore(durationMs) {
150
+ if (durationMs <= 0) return 100;
151
+ const durationMinutes = durationMs / 60000;
152
+ const interactionsPerMinute = metrics.touchCount / Math.max(0.5, durationMinutes);
153
+ if (interactionsPerMinute < 2) return 20;
154
+ if (interactionsPerMinute < 5) return 50;
155
+ if (interactionsPerMinute < 10) return 70;
156
+ if (interactionsPerMinute <= 30) return 100;
157
+ if (interactionsPerMinute <= 60) return 80;
158
+ return 50;
159
+ }
160
+
161
+ /**
162
+ * Calculate exploration score based on screens visited
163
+ * Higher = user explored more of the app
164
+ */
165
+ function calculateExplorationScore() {
166
+ const uniqueScreens = metrics.uniqueScreensCount;
167
+ if (uniqueScreens >= 10) return 100;
168
+ if (uniqueScreens >= 7) return 90;
169
+ if (uniqueScreens >= 5) return 80;
170
+ if (uniqueScreens >= 3) return 60;
171
+ if (uniqueScreens >= 2) return 40;
172
+ return 20;
173
+ }
174
+
175
+ /**
176
+ * Calculate UX score based on errors and frustration
177
+ * Higher = better experience (fewer issues)
178
+ */
179
+ function calculateUXScore() {
180
+ let score = 100;
181
+ score -= metrics.errorCount * 10;
182
+ score -= metrics.rageTapCount * 20;
183
+ score -= metrics.deadTapCount * 10;
184
+ score -= metrics.apiErrorCount * 5;
185
+ return Math.max(0, Math.min(100, score));
186
+ }
187
+ //# sourceMappingURL=metricsTracking.js.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Copyright 2026 Rejourney
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Rejourney Navigation Utilities
19
+ *
20
+ * Helper functions for extracting human-readable screen names from
21
+ * React Navigation / Expo Router state.
22
+ *
23
+ * These functions are used internally by the SDK's automatic navigation
24
+ * detection. You don't need to import or use these directly.
25
+ */
26
+
27
+ /**
28
+ * Normalize a screen name to be human-readable
29
+ * Handles common patterns from React Native / Expo Router
30
+ *
31
+ * @param raw - Raw screen name to normalize
32
+ * @returns Cleaned, human-readable screen name
33
+ */
34
+ export function normalizeScreenName(raw) {
35
+ if (!raw) return 'Unknown';
36
+ let name = raw;
37
+ name = name.replace(/[^\x20-\x7E\s]/g, '');
38
+ name = name.split(/[-_]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
39
+ const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
40
+ for (const suffix of suffixes) {
41
+ if (name.endsWith(suffix) && name.length > suffix.length) {
42
+ name = name.slice(0, -suffix.length).trim();
43
+ }
44
+ }
45
+ const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
46
+ for (const prefix of prefixes) {
47
+ if (name.startsWith(prefix) && name.length > prefix.length + 2) {
48
+ name = name.slice(prefix.length).trim();
49
+ }
50
+ }
51
+ name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
52
+ const clean = param.replace(/Id$/i, '');
53
+ if (clean.length < 2) return '';
54
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
55
+ });
56
+ name = name.replace(/\[\]/g, '');
57
+ name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
58
+ name = name.replace(/\s+/g, ' ').trim();
59
+ if (name.length > 0) {
60
+ name = name.charAt(0).toUpperCase() + name.slice(1);
61
+ }
62
+ return name || 'Unknown';
63
+ }
64
+
65
+ /**
66
+ * Get a human-readable screen name from Expo Router path and segments
67
+ *
68
+ * @param pathname - The current route pathname
69
+ * @param segments - Route segments from useSegments()
70
+ * @returns Human-readable screen name
71
+ */
72
+ export function getScreenNameFromPath(pathname, segments) {
73
+ if (segments.length > 0) {
74
+ const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
75
+ if (cleanSegments.length > 0) {
76
+ const processedSegments = cleanSegments.map(s => {
77
+ if (s.startsWith('[') && s.endsWith(']')) {
78
+ const param = s.slice(1, -1);
79
+ if (param === 'id' || param === 'slug') return null;
80
+ if (param === 'id' || param === 'slug') return null;
81
+ const clean = param.replace(/Id$/i, '');
82
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
83
+ }
84
+ return s.charAt(0).toUpperCase() + s.slice(1);
85
+ }).filter(Boolean);
86
+ if (processedSegments.length > 0) {
87
+ return processedSegments.join(' > ');
88
+ }
89
+ }
90
+ }
91
+ if (!pathname || pathname === '/') {
92
+ return 'Home';
93
+ }
94
+ const cleanPath = pathname.replace(/^\/(tabs)?/, '').replace(/\([^)]+\)/g, '').replace(/\[([^\]]+)\]/g, (_, param) => {
95
+ if (param === 'id' || param === 'slug') return '';
96
+ const clean = param.replace(/Id$/i, '');
97
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
98
+ }).replace(/\/+/g, '/').replace(/^\//, '').replace(/\/$/, '').replace(/\//g, ' > ').trim();
99
+ if (!cleanPath) {
100
+ return 'Home';
101
+ }
102
+ return cleanPath.split(' > ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).filter(s => s.length > 0).join(' > ') || 'Home';
103
+ }
104
+
105
+ /**
106
+ * Get the current route name from a navigation state object
107
+ *
108
+ * @param state - React Navigation state object
109
+ * @returns Current route name or null
110
+ */
111
+ export function getCurrentRouteFromState(state) {
112
+ if (!state || !state.routes) return null;
113
+ const route = state.routes[state.index ?? state.routes.length - 1];
114
+ if (!route) return null;
115
+ if (route.state) {
116
+ return getCurrentRouteFromState(route.state);
117
+ }
118
+ return route.name || null;
119
+ }
120
+ //# sourceMappingURL=navigation.js.map