@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.
- package/android/build.gradle.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
- package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
- package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
- package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
- package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
- package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
- package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
- package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
- package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
- package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
- package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
- package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
- package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
- package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
- package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
- package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
- package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Capture/RJANRHandler.h +42 -0
- package/ios/Capture/RJANRHandler.m +328 -0
- package/ios/Capture/RJCaptureEngine.h +275 -0
- package/ios/Capture/RJCaptureEngine.m +2062 -0
- package/ios/Capture/RJCaptureHeuristics.h +80 -0
- package/ios/Capture/RJCaptureHeuristics.m +903 -0
- package/ios/Capture/RJCrashHandler.h +46 -0
- package/ios/Capture/RJCrashHandler.m +313 -0
- package/ios/Capture/RJMotionEvent.h +183 -0
- package/ios/Capture/RJMotionEvent.m +183 -0
- package/ios/Capture/RJPerformanceManager.h +100 -0
- package/ios/Capture/RJPerformanceManager.m +373 -0
- package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
- package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
- package/ios/Capture/RJSegmentUploader.h +146 -0
- package/ios/Capture/RJSegmentUploader.m +778 -0
- package/ios/Capture/RJVideoEncoder.h +247 -0
- package/ios/Capture/RJVideoEncoder.m +1036 -0
- package/ios/Capture/RJViewControllerTracker.h +73 -0
- package/ios/Capture/RJViewControllerTracker.m +508 -0
- package/ios/Capture/RJViewHierarchyScanner.h +215 -0
- package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
- package/ios/Capture/RJViewSerializer.h +119 -0
- package/ios/Capture/RJViewSerializer.m +498 -0
- package/ios/Core/RJConstants.h +124 -0
- package/ios/Core/RJConstants.m +88 -0
- package/ios/Core/RJLifecycleManager.h +85 -0
- package/ios/Core/RJLifecycleManager.m +308 -0
- package/ios/Core/RJLogger.h +61 -0
- package/ios/Core/RJLogger.m +211 -0
- package/ios/Core/RJTypes.h +176 -0
- package/ios/Core/RJTypes.m +66 -0
- package/ios/Core/Rejourney.h +64 -0
- package/ios/Core/Rejourney.mm +2495 -0
- package/ios/Network/RJDeviceAuthManager.h +94 -0
- package/ios/Network/RJDeviceAuthManager.m +967 -0
- package/ios/Network/RJNetworkMonitor.h +68 -0
- package/ios/Network/RJNetworkMonitor.m +267 -0
- package/ios/Network/RJRetryManager.h +73 -0
- package/ios/Network/RJRetryManager.m +325 -0
- package/ios/Network/RJUploadManager.h +267 -0
- package/ios/Network/RJUploadManager.m +2296 -0
- package/ios/Privacy/RJPrivacyMask.h +163 -0
- package/ios/Privacy/RJPrivacyMask.m +922 -0
- package/ios/Rejourney.h +63 -0
- package/ios/Touch/RJGestureClassifier.h +130 -0
- package/ios/Touch/RJGestureClassifier.m +333 -0
- package/ios/Touch/RJTouchInterceptor.h +169 -0
- package/ios/Touch/RJTouchInterceptor.m +772 -0
- package/ios/Utils/RJEventBuffer.h +112 -0
- package/ios/Utils/RJEventBuffer.m +358 -0
- package/ios/Utils/RJGzipUtils.h +33 -0
- package/ios/Utils/RJGzipUtils.m +89 -0
- package/ios/Utils/RJKeychainManager.h +48 -0
- package/ios/Utils/RJKeychainManager.m +111 -0
- package/ios/Utils/RJPerfTiming.h +209 -0
- package/ios/Utils/RJPerfTiming.m +264 -0
- package/ios/Utils/RJTelemetry.h +92 -0
- package/ios/Utils/RJTelemetry.m +320 -0
- package/ios/Utils/RJWindowUtils.h +66 -0
- package/ios/Utils/RJWindowUtils.m +133 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +79 -0
- package/lib/commonjs/index.js +1381 -0
- package/lib/commonjs/sdk/autoTracking.js +1259 -0
- package/lib/commonjs/sdk/constants.js +151 -0
- package/lib/commonjs/sdk/errorTracking.js +199 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +204 -0
- package/lib/commonjs/sdk/navigation.js +151 -0
- package/lib/commonjs/sdk/networkInterceptor.js +412 -0
- package/lib/commonjs/sdk/utils.js +363 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +72 -0
- package/lib/module/index.js +1284 -0
- package/lib/module/sdk/autoTracking.js +1233 -0
- package/lib/module/sdk/constants.js +145 -0
- package/lib/module/sdk/errorTracking.js +189 -0
- package/lib/module/sdk/index.js +12 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +143 -0
- package/lib/module/sdk/networkInterceptor.js +401 -0
- package/lib/module/sdk/utils.js +342 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +147 -0
- package/lib/typescript/components/Mask.d.ts +39 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +204 -0
- package/lib/typescript/sdk/constants.d.ts +120 -0
- package/lib/typescript/sdk/errorTracking.d.ts +32 -0
- package/lib/typescript/sdk/index.d.ts +9 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
- package/lib/typescript/sdk/navigation.d.ts +33 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
- package/lib/typescript/sdk/utils.d.ts +148 -0
- package/lib/typescript/types/index.d.ts +624 -0
- package/package.json +102 -0
- package/rejourney.podspec +21 -0
- package/src/NativeRejourney.ts +165 -0
- package/src/components/Mask.tsx +80 -0
- package/src/index.ts +1459 -0
- package/src/sdk/autoTracking.ts +1373 -0
- package/src/sdk/constants.ts +134 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/metricsTracking.ts +232 -0
- package/src/sdk/navigation.ts +157 -0
- package/src/sdk/networkInterceptor.ts +440 -0
- package/src/sdk/utils.ts +369 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +739 -0
|
@@ -0,0 +1,1381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
initRejourney: true,
|
|
8
|
+
startRejourney: true,
|
|
9
|
+
stopRejourney: true,
|
|
10
|
+
setLogLevel: true,
|
|
11
|
+
trackTap: true,
|
|
12
|
+
trackScroll: true,
|
|
13
|
+
trackGesture: true,
|
|
14
|
+
trackInput: true,
|
|
15
|
+
trackScreen: true,
|
|
16
|
+
captureError: true,
|
|
17
|
+
getSessionMetrics: true,
|
|
18
|
+
trackNavigationState: true,
|
|
19
|
+
useNavigationTracking: true,
|
|
20
|
+
LogLevel: true,
|
|
21
|
+
Mask: true
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "LogLevel", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () {
|
|
26
|
+
return _utils.LogLevel;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(exports, "Mask", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
get: function () {
|
|
32
|
+
return _Mask.Mask;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(exports, "captureError", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
get: function () {
|
|
38
|
+
return _autoTracking2.captureError;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
exports.default = void 0;
|
|
42
|
+
Object.defineProperty(exports, "getSessionMetrics", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () {
|
|
45
|
+
return _autoTracking2.getSessionMetrics;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
exports.initRejourney = initRejourney;
|
|
49
|
+
exports.setLogLevel = setLogLevel;
|
|
50
|
+
exports.startRejourney = startRejourney;
|
|
51
|
+
exports.stopRejourney = stopRejourney;
|
|
52
|
+
Object.defineProperty(exports, "trackGesture", {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
get: function () {
|
|
55
|
+
return _autoTracking2.trackGesture;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(exports, "trackInput", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
get: function () {
|
|
61
|
+
return _autoTracking2.trackInput;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(exports, "trackNavigationState", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
get: function () {
|
|
67
|
+
return _autoTracking2.trackNavigationState;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(exports, "trackScreen", {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
get: function () {
|
|
73
|
+
return _autoTracking2.trackScreen;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(exports, "trackScroll", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
get: function () {
|
|
79
|
+
return _autoTracking2.trackScroll;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(exports, "trackTap", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
get: function () {
|
|
85
|
+
return _autoTracking2.trackTap;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
Object.defineProperty(exports, "useNavigationTracking", {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
get: function () {
|
|
91
|
+
return _autoTracking2.useNavigationTracking;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
var _constants = require("./sdk/constants");
|
|
95
|
+
var _types = require("./types");
|
|
96
|
+
Object.keys(_types).forEach(function (key) {
|
|
97
|
+
if (key === "default" || key === "__esModule") return;
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
99
|
+
if (key in exports && exports[key] === _types[key]) return;
|
|
100
|
+
Object.defineProperty(exports, key, {
|
|
101
|
+
enumerable: true,
|
|
102
|
+
get: function () {
|
|
103
|
+
return _types[key];
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
var _autoTracking2 = require("./sdk/autoTracking");
|
|
108
|
+
var _utils = require("./sdk/utils");
|
|
109
|
+
var _Mask = require("./components/Mask");
|
|
110
|
+
/**
|
|
111
|
+
* Rejourney - Session Recording and Replay SDK for React Native
|
|
112
|
+
*
|
|
113
|
+
* Captures user interactions, gestures, and screen states for replay and analysis.
|
|
114
|
+
*
|
|
115
|
+
* Just call initRejourney() - everything else is automatic!
|
|
116
|
+
*
|
|
117
|
+
* Copyright (c) 2026 Rejourney
|
|
118
|
+
*
|
|
119
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
120
|
+
* you may not use this file except in compliance with the License.
|
|
121
|
+
* See LICENSE-APACHE for full terms.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { initRejourney } from 'rejourney';
|
|
126
|
+
*
|
|
127
|
+
* // Initialize the SDK with your public key - that's it!
|
|
128
|
+
* initRejourney('pk_live_xxxxxxxxxxxx');
|
|
129
|
+
*
|
|
130
|
+
* // Or with options:
|
|
131
|
+
* initRejourney('pk_live_xxxxxxxxxxxx', { debug: true });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// CRITICAL: Safe Module Loading for React Native 0.81+
|
|
137
|
+
// =============================================================================
|
|
138
|
+
//
|
|
139
|
+
// On React Native 0.81+ with New Architecture (Bridgeless), importing from
|
|
140
|
+
// 'react-native' at module load time can fail with "PlatformConstants could
|
|
141
|
+
// not be found" because the TurboModule runtime may not be fully initialized.
|
|
142
|
+
//
|
|
143
|
+
// This wrapper ensures that all react-native imports happen after the runtime
|
|
144
|
+
// is ready, by catching any initialization errors and deferring actual SDK
|
|
145
|
+
// operations until initRejourney() is called.
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
// Module load confirmation - this runs when the module is first imported
|
|
149
|
+
|
|
150
|
+
// SDK disabled flag - set to true if we detect runtime issues
|
|
151
|
+
let _sdkDisabled = false;
|
|
152
|
+
|
|
153
|
+
// Type-only imports are safe - they're erased at compile time
|
|
154
|
+
|
|
155
|
+
// SDK version is safe - no react-native imports
|
|
156
|
+
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// Lazy Module Loading
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// All modules that import from 'react-native' are loaded lazily to avoid
|
|
161
|
+
// accessing TurboModuleRegistry before it's ready.
|
|
162
|
+
|
|
163
|
+
let _reactNativeLoaded = false;
|
|
164
|
+
let _RN = null;
|
|
165
|
+
function getReactNative() {
|
|
166
|
+
if (_sdkDisabled) return null;
|
|
167
|
+
if (_reactNativeLoaded) return _RN;
|
|
168
|
+
try {
|
|
169
|
+
_RN = require('react-native');
|
|
170
|
+
_reactNativeLoaded = true;
|
|
171
|
+
return _RN;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
getLogger().warn('Failed to load react-native:', error?.message || error);
|
|
174
|
+
_sdkDisabled = true;
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Lazy-loaded logger
|
|
180
|
+
let _logger = null;
|
|
181
|
+
function getLogger() {
|
|
182
|
+
if (_logger) return _logger;
|
|
183
|
+
if (_sdkDisabled) {
|
|
184
|
+
// Return a no-op logger if SDK is disabled
|
|
185
|
+
return {
|
|
186
|
+
debug: () => {},
|
|
187
|
+
info: console.log.bind(console, '[Rejourney]'),
|
|
188
|
+
warn: console.warn.bind(console, '[Rejourney]'),
|
|
189
|
+
error: console.error.bind(console, '[Rejourney]'),
|
|
190
|
+
logSessionStart: () => {},
|
|
191
|
+
logSessionEnd: () => {},
|
|
192
|
+
logInitSuccess: () => {},
|
|
193
|
+
logInitFailure: () => {},
|
|
194
|
+
setLogLevel: () => {},
|
|
195
|
+
setDebugMode: () => {},
|
|
196
|
+
logObservabilityStart: () => {},
|
|
197
|
+
logRecordingStart: () => {},
|
|
198
|
+
logRecordingRemoteDisabled: () => {},
|
|
199
|
+
logInvalidProjectKey: () => {},
|
|
200
|
+
logPackageMismatch: () => {}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const utils = require('./sdk/utils');
|
|
205
|
+
_logger = utils.logger;
|
|
206
|
+
return _logger;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.warn('[Rejourney] Failed to load logger:', error?.message || error);
|
|
209
|
+
// Return fallback logger with working info
|
|
210
|
+
return {
|
|
211
|
+
debug: () => {},
|
|
212
|
+
info: console.log.bind(console, '[Rejourney]'),
|
|
213
|
+
warn: console.warn.bind(console, '[Rejourney]'),
|
|
214
|
+
error: console.error.bind(console, '[Rejourney]'),
|
|
215
|
+
logSessionStart: () => {},
|
|
216
|
+
logSessionEnd: () => {},
|
|
217
|
+
logInitSuccess: () => {},
|
|
218
|
+
logInitFailure: () => {},
|
|
219
|
+
setLogLevel: () => {},
|
|
220
|
+
setDebugMode: () => {},
|
|
221
|
+
logObservabilityStart: () => {},
|
|
222
|
+
logRecordingStart: () => {},
|
|
223
|
+
logRecordingRemoteDisabled: () => {},
|
|
224
|
+
logInvalidProjectKey: () => {},
|
|
225
|
+
logPackageMismatch: () => {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Lazy-loaded network interceptor
|
|
231
|
+
let _networkInterceptor = null;
|
|
232
|
+
function getNetworkInterceptor() {
|
|
233
|
+
if (_sdkDisabled) return {
|
|
234
|
+
initNetworkInterceptor: () => {},
|
|
235
|
+
disableNetworkInterceptor: () => {}
|
|
236
|
+
};
|
|
237
|
+
if (_networkInterceptor) return _networkInterceptor;
|
|
238
|
+
try {
|
|
239
|
+
_networkInterceptor = require('./sdk/networkInterceptor');
|
|
240
|
+
return _networkInterceptor;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
getLogger().warn('Failed to load network interceptor:', error?.message || error);
|
|
243
|
+
return {
|
|
244
|
+
initNetworkInterceptor: () => {},
|
|
245
|
+
disableNetworkInterceptor: () => {}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Lazy-loaded auto tracking module
|
|
251
|
+
let _autoTracking = null;
|
|
252
|
+
|
|
253
|
+
// No-op auto tracking for when SDK is disabled
|
|
254
|
+
const noopAutoTracking = {
|
|
255
|
+
initAutoTracking: () => {},
|
|
256
|
+
cleanupAutoTracking: () => {},
|
|
257
|
+
trackScroll: () => {},
|
|
258
|
+
trackScreen: () => {},
|
|
259
|
+
trackAPIRequest: () => {},
|
|
260
|
+
notifyStateChange: () => {},
|
|
261
|
+
getSessionMetrics: () => ({}),
|
|
262
|
+
resetMetrics: () => {},
|
|
263
|
+
collectDeviceInfo: () => ({}),
|
|
264
|
+
ensurePersistentAnonymousId: async () => 'anonymous'
|
|
265
|
+
};
|
|
266
|
+
function getAutoTracking() {
|
|
267
|
+
if (_sdkDisabled) return noopAutoTracking;
|
|
268
|
+
if (_autoTracking) return _autoTracking;
|
|
269
|
+
try {
|
|
270
|
+
_autoTracking = require('./sdk/autoTracking');
|
|
271
|
+
return _autoTracking;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
getLogger().warn('Failed to load auto tracking:', error?.message || error);
|
|
274
|
+
return noopAutoTracking;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// State
|
|
279
|
+
const USER_IDENTITY_KEY = '@rejourney_user_identity';
|
|
280
|
+
let _isInitialized = false;
|
|
281
|
+
let _isRecording = false;
|
|
282
|
+
let _initializationFailed = false;
|
|
283
|
+
let _appStateSubscription = null;
|
|
284
|
+
let _authErrorSubscription = null;
|
|
285
|
+
let _currentAppState = 'active'; // Default to active, will be updated on init
|
|
286
|
+
let _userIdentity = null;
|
|
287
|
+
|
|
288
|
+
// Scroll throttling - reduce native bridge calls from 60fps to at most 10/sec
|
|
289
|
+
let _lastScrollTime = 0;
|
|
290
|
+
let _lastScrollOffset = 0;
|
|
291
|
+
const SCROLL_THROTTLE_MS = 100;
|
|
292
|
+
|
|
293
|
+
// Helper to save/load user identity
|
|
294
|
+
async function persistUserIdentity(identity) {
|
|
295
|
+
try {
|
|
296
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
297
|
+
if (identity) {
|
|
298
|
+
await AsyncStorage.setItem(USER_IDENTITY_KEY, identity);
|
|
299
|
+
} else {
|
|
300
|
+
await AsyncStorage.removeItem(USER_IDENTITY_KEY);
|
|
301
|
+
}
|
|
302
|
+
} catch (e) {
|
|
303
|
+
// Ignore storage errors
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function loadPersistedUserIdentity() {
|
|
307
|
+
try {
|
|
308
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
309
|
+
return await AsyncStorage.getItem(USER_IDENTITY_KEY);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
let _storedConfig = null;
|
|
315
|
+
|
|
316
|
+
// Lazy-loaded native module reference
|
|
317
|
+
// We don't access TurboModuleRegistry at module load time to avoid
|
|
318
|
+
// "PlatformConstants could not be found" errors on RN 0.81+
|
|
319
|
+
let _rejourneyNative = undefined;
|
|
320
|
+
let _nativeModuleLogged = false;
|
|
321
|
+
let _runtimeReady = false;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if the React Native runtime is ready for native module access.
|
|
325
|
+
* This prevents crashes on RN 0.81+ where accessing modules too early fails.
|
|
326
|
+
*/
|
|
327
|
+
function isRuntimeReady() {
|
|
328
|
+
if (_runtimeReady) return true;
|
|
329
|
+
try {
|
|
330
|
+
// Try to access a core module to verify runtime is ready
|
|
331
|
+
const RN = require('react-native');
|
|
332
|
+
// If we can access NativeModules without error, runtime is ready
|
|
333
|
+
if (RN.NativeModules) {
|
|
334
|
+
_runtimeReady = true;
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
// Runtime not ready yet
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get the native Rejourney module lazily.
|
|
345
|
+
*
|
|
346
|
+
* This function defers access to TurboModuleRegistry/NativeModules until
|
|
347
|
+
* the first time it's actually needed. This is critical for React Native 0.81+
|
|
348
|
+
* where accessing TurboModuleRegistry at module load time can fail because
|
|
349
|
+
* PlatformConstants and other core modules aren't yet initialized.
|
|
350
|
+
*
|
|
351
|
+
* The function caches the result after the first call.
|
|
352
|
+
*/
|
|
353
|
+
function getRejourneyNative() {
|
|
354
|
+
// Return cached result if already resolved
|
|
355
|
+
if (_rejourneyNative !== undefined) {
|
|
356
|
+
return _rejourneyNative;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Check if runtime is ready before attempting to access native modules
|
|
360
|
+
if (!isRuntimeReady()) {
|
|
361
|
+
getLogger().debug('Rejourney: Runtime not ready, deferring native module access');
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const RN = require('react-native');
|
|
366
|
+
const {
|
|
367
|
+
NativeModules,
|
|
368
|
+
TurboModuleRegistry
|
|
369
|
+
} = RN;
|
|
370
|
+
|
|
371
|
+
// Track how the module was loaded
|
|
372
|
+
let loadedVia = 'none';
|
|
373
|
+
let nativeModule = null;
|
|
374
|
+
|
|
375
|
+
// Try TurboModuleRegistry first (New Architecture)
|
|
376
|
+
if (TurboModuleRegistry && typeof TurboModuleRegistry.get === 'function') {
|
|
377
|
+
try {
|
|
378
|
+
nativeModule = TurboModuleRegistry.get('Rejourney');
|
|
379
|
+
if (nativeModule) {
|
|
380
|
+
loadedVia = 'TurboModules';
|
|
381
|
+
}
|
|
382
|
+
} catch (turboError) {
|
|
383
|
+
// TurboModuleRegistry.get failed, will try NativeModules
|
|
384
|
+
getLogger().debug('TurboModuleRegistry.get failed:', turboError);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Fall back to NativeModules (Old Architecture / Interop Layer)
|
|
389
|
+
if (!nativeModule && NativeModules) {
|
|
390
|
+
nativeModule = NativeModules.Rejourney ?? null;
|
|
391
|
+
if (nativeModule) {
|
|
392
|
+
loadedVia = 'NativeModules';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
_rejourneyNative = nativeModule;
|
|
396
|
+
|
|
397
|
+
// Log which method was used to load the module
|
|
398
|
+
if (_rejourneyNative && !_nativeModuleLogged) {
|
|
399
|
+
_nativeModuleLogged = true;
|
|
400
|
+
|
|
401
|
+
// More accurate detection based on actual load method
|
|
402
|
+
if (loadedVia === 'TurboModules') {
|
|
403
|
+
getLogger().debug('Using New Architecture (TurboModules/JSI)');
|
|
404
|
+
} else if (loadedVia === 'NativeModules') {
|
|
405
|
+
// Check if we're in interop mode (New Arch with bridge fallback)
|
|
406
|
+
const hasTurboProxy = !!global.__turboModuleProxy;
|
|
407
|
+
if (hasTurboProxy) {
|
|
408
|
+
getLogger().debug('Using New Architecture (Interop Layer)');
|
|
409
|
+
} else {
|
|
410
|
+
getLogger().debug('Using Old Architecture (Bridge)');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
// If any access fails, log and return null
|
|
416
|
+
getLogger().warn('Rejourney: Failed to access native modules:', error);
|
|
417
|
+
_rejourneyNative = null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Ensure we never return undefined - convert to null
|
|
421
|
+
if (_rejourneyNative === undefined) {
|
|
422
|
+
_rejourneyNative = null;
|
|
423
|
+
}
|
|
424
|
+
return _rejourneyNative;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Safely call a native method with error handling
|
|
429
|
+
* Never throws - logs errors and returns gracefully
|
|
430
|
+
*/
|
|
431
|
+
async function safeNativeCall(methodName, fn, defaultValue) {
|
|
432
|
+
const nativeModule = getRejourneyNative();
|
|
433
|
+
if (!nativeModule || _initializationFailed) {
|
|
434
|
+
return defaultValue;
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
return await fn();
|
|
438
|
+
} catch (error) {
|
|
439
|
+
getLogger().error(`Rejourney.${methodName} failed:`, error);
|
|
440
|
+
return defaultValue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Safely call a synchronous native method with error handling
|
|
446
|
+
* Never throws - logs errors and returns gracefully
|
|
447
|
+
*/
|
|
448
|
+
function safeNativeCallSync(methodName, fn, defaultValue) {
|
|
449
|
+
const nativeModule = getRejourneyNative();
|
|
450
|
+
if (!nativeModule || _initializationFailed) {
|
|
451
|
+
return defaultValue;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
return fn();
|
|
455
|
+
} catch (error) {
|
|
456
|
+
getLogger().error(`Rejourney.${methodName} failed:`, error);
|
|
457
|
+
return defaultValue;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Main Rejourney API (Internal)
|
|
463
|
+
*/
|
|
464
|
+
const Rejourney = {
|
|
465
|
+
/**
|
|
466
|
+
* SDK Version
|
|
467
|
+
*/
|
|
468
|
+
version: _constants.SDK_VERSION,
|
|
469
|
+
/**
|
|
470
|
+
* Internal method to start recording session
|
|
471
|
+
* Called by startRejourney() after user consent
|
|
472
|
+
*/
|
|
473
|
+
async _startSession() {
|
|
474
|
+
getLogger().debug('_startSession() entered');
|
|
475
|
+
if (!_storedConfig) {
|
|
476
|
+
throw new Error('SDK not initialized. Call initRejourney() first.');
|
|
477
|
+
}
|
|
478
|
+
const nativeModule = getRejourneyNative();
|
|
479
|
+
if (!nativeModule) {
|
|
480
|
+
// Common causes:
|
|
481
|
+
// - startRejourney() called too early (RN runtime not ready yet)
|
|
482
|
+
// - native module not linked (pods/gradle/autolinking issue)
|
|
483
|
+
getLogger().warn('Native module not available - cannot start recording');
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
getLogger().debug('Native module found, checking if already recording...');
|
|
487
|
+
if (_isRecording) {
|
|
488
|
+
getLogger().warn('Recording already started');
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
const apiUrl = _storedConfig.apiUrl || 'https://api.rejourney.co';
|
|
493
|
+
const publicKey = _storedConfig.publicRouteKey || '';
|
|
494
|
+
getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
|
|
495
|
+
|
|
496
|
+
// Use user identity if set, otherwise use anonymous device ID
|
|
497
|
+
const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
|
|
498
|
+
|
|
499
|
+
// Try to load persisted user identity if not already set in memory
|
|
500
|
+
if (!_userIdentity) {
|
|
501
|
+
_userIdentity = await loadPersistedUserIdentity();
|
|
502
|
+
}
|
|
503
|
+
const userId = _userIdentity || deviceId;
|
|
504
|
+
getLogger().debug(`userId=${userId.substring(0, 8)}...`);
|
|
505
|
+
|
|
506
|
+
// Start native session
|
|
507
|
+
const result = await nativeModule.startSession(userId, apiUrl, publicKey);
|
|
508
|
+
getLogger().debug('Native startSession returned:', JSON.stringify(result));
|
|
509
|
+
if (!result?.success) {
|
|
510
|
+
const reason = result?.error || 'Native startSession returned success=false';
|
|
511
|
+
if (/disabled|blocked|not enabled/i.test(reason)) {
|
|
512
|
+
getLogger().logRecordingRemoteDisabled();
|
|
513
|
+
}
|
|
514
|
+
getLogger().error('Native startSession failed:', reason);
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
_isRecording = true;
|
|
518
|
+
getLogger().debug(`✅ Session started: ${result.sessionId}`);
|
|
519
|
+
// Use lifecycle log for session start - only shown in dev builds
|
|
520
|
+
getLogger().logSessionStart(result.sessionId);
|
|
521
|
+
|
|
522
|
+
// Initialize auto tracking features
|
|
523
|
+
getAutoTracking().initAutoTracking({
|
|
524
|
+
rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
|
|
525
|
+
rageTapTimeWindow: _storedConfig?.rageTapTimeWindow ?? 500,
|
|
526
|
+
rageTapRadius: 50,
|
|
527
|
+
trackJSErrors: true,
|
|
528
|
+
trackPromiseRejections: true,
|
|
529
|
+
trackReactNativeErrors: true,
|
|
530
|
+
collectDeviceInfo: _storedConfig?.collectDeviceInfo !== false
|
|
531
|
+
}, {
|
|
532
|
+
// Rage tap callback - log as frustration event
|
|
533
|
+
onRageTap: (count, x, y) => {
|
|
534
|
+
this.logEvent('frustration', {
|
|
535
|
+
frustrationKind: 'rage_tap',
|
|
536
|
+
tapCount: count,
|
|
537
|
+
x,
|
|
538
|
+
y
|
|
539
|
+
});
|
|
540
|
+
// logger.debug(`Rage tap detected: ${count} taps at (${x}, ${y})`);
|
|
541
|
+
},
|
|
542
|
+
// Error callback - log as error event
|
|
543
|
+
onError: error => {
|
|
544
|
+
this.logEvent('error', {
|
|
545
|
+
message: error.message,
|
|
546
|
+
stack: error.stack,
|
|
547
|
+
name: error.name
|
|
548
|
+
});
|
|
549
|
+
// logger.debug(`Error captured: ${error.message}`);
|
|
550
|
+
},
|
|
551
|
+
// Screen change callback - log screen change
|
|
552
|
+
onScreen: (_screenName, _previousScreen) => {
|
|
553
|
+
// Native module already handles screen changes
|
|
554
|
+
// This is just for metrics tracking
|
|
555
|
+
// logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Collect and log device info
|
|
560
|
+
if (_storedConfig?.collectDeviceInfo !== false) {
|
|
561
|
+
try {
|
|
562
|
+
const deviceInfo = getAutoTracking().collectDeviceInfo();
|
|
563
|
+
this.logEvent('device_info', deviceInfo);
|
|
564
|
+
} catch (deviceError) {
|
|
565
|
+
getLogger().warn('Failed to collect device info:', deviceError);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Setup automatic network interception
|
|
570
|
+
if (_storedConfig?.autoTrackNetwork !== false) {
|
|
571
|
+
try {
|
|
572
|
+
const ignoreUrls = [apiUrl, '/api/ingest/presign', '/api/ingest/batch/complete', '/api/ingest/session/end', ...(_storedConfig?.networkIgnoreUrls || [])];
|
|
573
|
+
getNetworkInterceptor().initNetworkInterceptor(request => {
|
|
574
|
+
this.logNetworkRequest(request);
|
|
575
|
+
getAutoTracking().trackAPIRequest(request.success || false, request.statusCode, request.duration || 0, request.responseBodySize || 0);
|
|
576
|
+
}, {
|
|
577
|
+
ignoreUrls,
|
|
578
|
+
captureSizes: _storedConfig?.networkCaptureSizes !== false
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// logger.debug('Network interception enabled');
|
|
582
|
+
} catch (networkError) {
|
|
583
|
+
getLogger().warn('Failed to setup network interception:', networkError);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// logger.debug('Auto tracking enabled');
|
|
588
|
+
|
|
589
|
+
return true;
|
|
590
|
+
} catch (error) {
|
|
591
|
+
getLogger().error('Failed to start recording:', error);
|
|
592
|
+
_isRecording = false;
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
/**
|
|
597
|
+
* Stop the current recording session
|
|
598
|
+
*/
|
|
599
|
+
async _stopSession() {
|
|
600
|
+
if (!_isRecording) {
|
|
601
|
+
getLogger().warn('No active recording to stop');
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
// Get session metrics before stopping
|
|
606
|
+
const metrics = getAutoTracking().getSessionMetrics();
|
|
607
|
+
this.logEvent('session_metrics', metrics);
|
|
608
|
+
|
|
609
|
+
// Cleanup
|
|
610
|
+
getNetworkInterceptor().disableNetworkInterceptor();
|
|
611
|
+
getAutoTracking().cleanupAutoTracking();
|
|
612
|
+
getAutoTracking().resetMetrics();
|
|
613
|
+
await safeNativeCall('stopSession', () => getRejourneyNative().stopSession(), undefined);
|
|
614
|
+
_isRecording = false;
|
|
615
|
+
// Use lifecycle log for session end - only shown in dev builds
|
|
616
|
+
getLogger().logSessionEnd('current');
|
|
617
|
+
} catch (error) {
|
|
618
|
+
getLogger().error('Failed to stop recording:', error);
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
/**
|
|
622
|
+
* Log a custom event
|
|
623
|
+
*
|
|
624
|
+
* @param name - Event name
|
|
625
|
+
* @param properties - Optional event properties
|
|
626
|
+
* @example
|
|
627
|
+
* Rejourney.logEvent('button_click', { buttonId: 'submit' });
|
|
628
|
+
*/
|
|
629
|
+
logEvent(name, properties) {
|
|
630
|
+
safeNativeCallSync('logEvent', () => {
|
|
631
|
+
// Fire and forget - don't await
|
|
632
|
+
getRejourneyNative().logEvent(name, properties || {}).catch(() => {});
|
|
633
|
+
}, undefined);
|
|
634
|
+
},
|
|
635
|
+
/**
|
|
636
|
+
* Set user identity for session correlation
|
|
637
|
+
* Associates current and future sessions with a user ID
|
|
638
|
+
*
|
|
639
|
+
* @param userId - User identifier (e.g., email, username, or internal ID)
|
|
640
|
+
* @example
|
|
641
|
+
* Rejourney.setUserIdentity('user_12345');
|
|
642
|
+
* Rejourney.setUserIdentity('john@example.com');
|
|
643
|
+
*/
|
|
644
|
+
setUserIdentity(userId) {
|
|
645
|
+
_userIdentity = userId;
|
|
646
|
+
persistUserIdentity(userId).catch(() => {});
|
|
647
|
+
// logger.debug(`User identity set: ${userId}`);
|
|
648
|
+
|
|
649
|
+
// If recording is active, update the native module immediately
|
|
650
|
+
if (_isRecording && getRejourneyNative()) {
|
|
651
|
+
safeNativeCallSync('setUserIdentity', () => {
|
|
652
|
+
getRejourneyNative().setUserIdentity(userId).catch(() => {});
|
|
653
|
+
}, undefined);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
/**
|
|
657
|
+
* Clear user identity
|
|
658
|
+
* Removes user association from future sessions
|
|
659
|
+
*/
|
|
660
|
+
clearUserIdentity() {
|
|
661
|
+
_userIdentity = null;
|
|
662
|
+
persistUserIdentity(null).catch(() => {});
|
|
663
|
+
// logger.debug('User identity cleared');
|
|
664
|
+
|
|
665
|
+
// If recording is active, update the native module immediately
|
|
666
|
+
if (_isRecording && getRejourneyNative()) {
|
|
667
|
+
safeNativeCallSync('setUserIdentity', () => {
|
|
668
|
+
getRejourneyNative().setUserIdentity('anonymous').catch(() => {});
|
|
669
|
+
}, undefined);
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
/**
|
|
673
|
+
* Tag the current screen
|
|
674
|
+
*
|
|
675
|
+
* @param screenName - Screen name
|
|
676
|
+
* @param params - Optional screen parameters
|
|
677
|
+
*/
|
|
678
|
+
tagScreen(screenName, _params) {
|
|
679
|
+
// Track screen for metrics and funnel tracking
|
|
680
|
+
getAutoTracking().trackScreen(screenName);
|
|
681
|
+
|
|
682
|
+
// Notify state change (kept for API compatibility)
|
|
683
|
+
getAutoTracking().notifyStateChange();
|
|
684
|
+
safeNativeCallSync('tagScreen', () => {
|
|
685
|
+
getRejourneyNative().screenChanged(screenName).catch(() => {});
|
|
686
|
+
}, undefined);
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* Mark a view as sensitive (will be occluded in recordings)
|
|
690
|
+
*
|
|
691
|
+
* @param viewRef - React ref to the view
|
|
692
|
+
* @param occluded - Whether to occlude (default: true)
|
|
693
|
+
*/
|
|
694
|
+
setOccluded(_viewRef, _occluded = true) {
|
|
695
|
+
// No-op - occlusion handled automatically by native module
|
|
696
|
+
},
|
|
697
|
+
/**
|
|
698
|
+
* Add a tag to the current session
|
|
699
|
+
*
|
|
700
|
+
* @param tag - Tag string
|
|
701
|
+
*/
|
|
702
|
+
addSessionTag(tag) {
|
|
703
|
+
this.logEvent('session_tag', {
|
|
704
|
+
tag
|
|
705
|
+
});
|
|
706
|
+
},
|
|
707
|
+
/**
|
|
708
|
+
* Get all recorded sessions
|
|
709
|
+
*
|
|
710
|
+
* @returns Array of session summaries (always empty - sessions on dashboard server)
|
|
711
|
+
*/
|
|
712
|
+
async getSessions() {
|
|
713
|
+
return [];
|
|
714
|
+
},
|
|
715
|
+
/**
|
|
716
|
+
* Get session data for replay
|
|
717
|
+
*
|
|
718
|
+
* @param sessionId - Session ID
|
|
719
|
+
* @returns Session data (not implemented - use dashboard server)
|
|
720
|
+
*/
|
|
721
|
+
async getSessionData(_sessionId) {
|
|
722
|
+
// Return empty session data - actual data should be fetched from dashboard server
|
|
723
|
+
getLogger().warn('getSessionData not implemented - fetch from dashboard server');
|
|
724
|
+
return {
|
|
725
|
+
metadata: {
|
|
726
|
+
sessionId: _sessionId,
|
|
727
|
+
startTime: 0,
|
|
728
|
+
endTime: 0,
|
|
729
|
+
duration: 0,
|
|
730
|
+
deviceInfo: {
|
|
731
|
+
model: '',
|
|
732
|
+
os: 'ios',
|
|
733
|
+
osVersion: '',
|
|
734
|
+
screenWidth: 0,
|
|
735
|
+
screenHeight: 0,
|
|
736
|
+
pixelRatio: 1
|
|
737
|
+
},
|
|
738
|
+
eventCount: 0,
|
|
739
|
+
videoSegmentCount: 0,
|
|
740
|
+
storageSize: 0,
|
|
741
|
+
sdkVersion: '1.0.0',
|
|
742
|
+
isComplete: false
|
|
743
|
+
},
|
|
744
|
+
events: []
|
|
745
|
+
};
|
|
746
|
+
},
|
|
747
|
+
/**
|
|
748
|
+
* Delete a session
|
|
749
|
+
*
|
|
750
|
+
* @param sessionId - Session ID
|
|
751
|
+
*/
|
|
752
|
+
async deleteSession(_sessionId) {
|
|
753
|
+
// No-op - session deletion handled by dashboard server
|
|
754
|
+
},
|
|
755
|
+
/**
|
|
756
|
+
* Delete all sessions
|
|
757
|
+
*/
|
|
758
|
+
async deleteAllSessions() {
|
|
759
|
+
// No-op - session deletion handled by dashboard server
|
|
760
|
+
},
|
|
761
|
+
/**
|
|
762
|
+
* Export session for sharing
|
|
763
|
+
*
|
|
764
|
+
* @param sessionId - Session ID
|
|
765
|
+
* @returns Path to export file (not implemented)
|
|
766
|
+
*/
|
|
767
|
+
async exportSession(_sessionId) {
|
|
768
|
+
// Return empty string - actual export should be done from dashboard server
|
|
769
|
+
getLogger().warn('exportSession not implemented - export from dashboard server');
|
|
770
|
+
return '';
|
|
771
|
+
},
|
|
772
|
+
/**
|
|
773
|
+
* Check if currently recording
|
|
774
|
+
*
|
|
775
|
+
* @returns Whether recording is active
|
|
776
|
+
*/
|
|
777
|
+
async isRecording() {
|
|
778
|
+
return _isRecording;
|
|
779
|
+
},
|
|
780
|
+
/**
|
|
781
|
+
* Get storage usage
|
|
782
|
+
*
|
|
783
|
+
* @returns Storage usage info (always 0 - storage on dashboard server)
|
|
784
|
+
*/
|
|
785
|
+
async getStorageUsage() {
|
|
786
|
+
return {
|
|
787
|
+
used: 0,
|
|
788
|
+
max: 0
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
/**
|
|
792
|
+
* Get ingest auth headers.
|
|
793
|
+
* Authentication now handled by device registration flow.
|
|
794
|
+
*/
|
|
795
|
+
getIngestAuthHeaders() {
|
|
796
|
+
return null;
|
|
797
|
+
},
|
|
798
|
+
/**
|
|
799
|
+
* Mark a visual change that should be captured
|
|
800
|
+
*
|
|
801
|
+
* Use this when your app changes in a visually significant way that should be captured,
|
|
802
|
+
* like showing a success message, updating a cart badge, or displaying an error.
|
|
803
|
+
*
|
|
804
|
+
* @param reason - Description of what changed (e.g., 'cart_updated', 'error_shown')
|
|
805
|
+
* @param importance - How important is this change? 'low', 'medium', 'high', or 'critical'
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* // Mark that an error was shown (high importance)
|
|
810
|
+
* await Rejourney.markVisualChange('checkout_error', 'high');
|
|
811
|
+
*
|
|
812
|
+
* // Mark that a cart badge updated (medium importance)
|
|
813
|
+
* await Rejourney.markVisualChange('cart_badge_update', 'medium');
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
async markVisualChange(reason, importance = 'medium') {
|
|
817
|
+
return safeNativeCall('markVisualChange', async () => {
|
|
818
|
+
await getRejourneyNative().markVisualChange(reason, importance);
|
|
819
|
+
return true;
|
|
820
|
+
}, false);
|
|
821
|
+
},
|
|
822
|
+
/**
|
|
823
|
+
* Report a scroll event for video capture timing
|
|
824
|
+
*
|
|
825
|
+
* Call this from your ScrollView's onScroll handler to improve scroll capture.
|
|
826
|
+
* The SDK captures video at 2 FPS continuously, but this helps log scroll events
|
|
827
|
+
* for timeline correlation during replay.
|
|
828
|
+
*
|
|
829
|
+
* @param scrollOffset - Current scroll offset (vertical or horizontal)
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* ```typescript
|
|
833
|
+
* <ScrollView
|
|
834
|
+
* onScroll={(e) => {
|
|
835
|
+
* Rejourney.onScroll(e.nativeEvent.contentOffset.y);
|
|
836
|
+
* }}
|
|
837
|
+
* scrollEventThrottle={16}
|
|
838
|
+
* >
|
|
839
|
+
* {content}
|
|
840
|
+
* </ScrollView>
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
async onScroll(scrollOffset) {
|
|
844
|
+
// Throttle scroll events to reduce native bridge traffic
|
|
845
|
+
// Scroll events can fire at 60fps, but we only need ~10/sec for smooth replay
|
|
846
|
+
const now = Date.now();
|
|
847
|
+
const offsetDelta = Math.abs(scrollOffset - _lastScrollOffset);
|
|
848
|
+
|
|
849
|
+
// Only forward to native if enough time passed OR significant scroll distance
|
|
850
|
+
if (now - _lastScrollTime < SCROLL_THROTTLE_MS && offsetDelta < 50) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
_lastScrollTime = now;
|
|
854
|
+
_lastScrollOffset = scrollOffset;
|
|
855
|
+
|
|
856
|
+
// Track scroll for metrics
|
|
857
|
+
getAutoTracking().trackScroll();
|
|
858
|
+
await safeNativeCall('onScroll', () => getRejourneyNative().onScroll(scrollOffset), undefined);
|
|
859
|
+
},
|
|
860
|
+
// ========================================================================
|
|
861
|
+
// OAuth / External URL Tracking
|
|
862
|
+
// ========================================================================
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Notify the SDK that an OAuth flow is starting
|
|
866
|
+
*
|
|
867
|
+
* Call this before opening an OAuth URL (e.g., before opening Safari for Google/Apple sign-in).
|
|
868
|
+
* This captures the current screen and marks the session as entering an OAuth flow.
|
|
869
|
+
*
|
|
870
|
+
* @param provider - The OAuth provider name (e.g., 'google', 'apple', 'facebook')
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```typescript
|
|
874
|
+
* // Before opening OAuth URL
|
|
875
|
+
* await Rejourney.onOAuthStarted('google');
|
|
876
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
877
|
+
* ```
|
|
878
|
+
*/
|
|
879
|
+
async onOAuthStarted(provider) {
|
|
880
|
+
return safeNativeCall('onOAuthStarted', async () => {
|
|
881
|
+
await getRejourneyNative().onOAuthStarted(provider);
|
|
882
|
+
return true;
|
|
883
|
+
}, false);
|
|
884
|
+
},
|
|
885
|
+
/**
|
|
886
|
+
* Notify the SDK that an OAuth flow has completed
|
|
887
|
+
*
|
|
888
|
+
* Call this after the user returns from an OAuth flow (successful or not).
|
|
889
|
+
* This captures the result screen and logs the OAuth outcome.
|
|
890
|
+
*
|
|
891
|
+
* @param provider - The OAuth provider name (e.g., 'google', 'apple', 'facebook')
|
|
892
|
+
* @param success - Whether the OAuth flow was successful
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* ```typescript
|
|
896
|
+
* // After OAuth returns
|
|
897
|
+
* const result = await WebBrowser.openAuthSessionAsync(authUrl);
|
|
898
|
+
* await Rejourney.onOAuthCompleted('google', result.type === 'success');
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
async onOAuthCompleted(provider, success) {
|
|
902
|
+
return safeNativeCall('onOAuthCompleted', async () => {
|
|
903
|
+
await getRejourneyNative().onOAuthCompleted(provider, success);
|
|
904
|
+
return true;
|
|
905
|
+
}, false);
|
|
906
|
+
},
|
|
907
|
+
/**
|
|
908
|
+
* Notify the SDK that an external URL is being opened
|
|
909
|
+
*
|
|
910
|
+
* Call this when your app opens an external URL (browser, maps, phone, etc.).
|
|
911
|
+
* This is automatically detected for app lifecycle events, but you can use this
|
|
912
|
+
* for more granular tracking.
|
|
913
|
+
*
|
|
914
|
+
* @param urlScheme - The URL scheme being opened (e.g., 'https', 'tel', 'maps')
|
|
915
|
+
*
|
|
916
|
+
* @example
|
|
917
|
+
* ```typescript
|
|
918
|
+
* // Before opening external URL
|
|
919
|
+
* await Rejourney.onExternalURLOpened('https');
|
|
920
|
+
* Linking.openURL('https://example.com');
|
|
921
|
+
* ```
|
|
922
|
+
*/
|
|
923
|
+
async onExternalURLOpened(urlScheme) {
|
|
924
|
+
return safeNativeCall('onExternalURLOpened', async () => {
|
|
925
|
+
await getRejourneyNative().onExternalURLOpened(urlScheme);
|
|
926
|
+
return true;
|
|
927
|
+
}, false);
|
|
928
|
+
},
|
|
929
|
+
/**
|
|
930
|
+
* Log a network request for API call timeline tracking
|
|
931
|
+
*
|
|
932
|
+
* This is a low-priority, efficient way to track API calls during session replay.
|
|
933
|
+
* Network requests are stored separately and displayed in a collapsible timeline
|
|
934
|
+
* in the dashboard for easy correlation with user actions.
|
|
935
|
+
*
|
|
936
|
+
* @param request - Network request parameters
|
|
937
|
+
*
|
|
938
|
+
* @example
|
|
939
|
+
* ```typescript
|
|
940
|
+
* // After a fetch completes
|
|
941
|
+
* const startTime = Date.now();
|
|
942
|
+
* const response = await fetch('https://api.example.com/users', {
|
|
943
|
+
* method: 'POST',
|
|
944
|
+
* body: JSON.stringify(userData),
|
|
945
|
+
* });
|
|
946
|
+
*
|
|
947
|
+
* Rejourney.logNetworkRequest({
|
|
948
|
+
* method: 'POST',
|
|
949
|
+
* url: 'https://api.example.com/users',
|
|
950
|
+
* statusCode: response.status,
|
|
951
|
+
* duration: Date.now() - startTime,
|
|
952
|
+
* requestBodySize: JSON.stringify(userData).length,
|
|
953
|
+
* responseBodySize: (await response.text()).length,
|
|
954
|
+
* });
|
|
955
|
+
* ```
|
|
956
|
+
*/
|
|
957
|
+
logNetworkRequest(request) {
|
|
958
|
+
safeNativeCallSync('logNetworkRequest', () => {
|
|
959
|
+
// Parse URL for efficient storage and grouping
|
|
960
|
+
let urlPath = request.url;
|
|
961
|
+
let urlHost = '';
|
|
962
|
+
try {
|
|
963
|
+
const parsedUrl = new URL(request.url);
|
|
964
|
+
urlHost = parsedUrl.host;
|
|
965
|
+
urlPath = parsedUrl.pathname + parsedUrl.search;
|
|
966
|
+
} catch {
|
|
967
|
+
// If URL parsing fails, use the full URL as path
|
|
968
|
+
}
|
|
969
|
+
const endTimestamp = request.endTimestamp || Date.now();
|
|
970
|
+
const startTimestamp = request.startTimestamp || endTimestamp - request.duration;
|
|
971
|
+
const success = request.statusCode >= 200 && request.statusCode < 400;
|
|
972
|
+
|
|
973
|
+
// Create the network request event
|
|
974
|
+
const networkEvent = {
|
|
975
|
+
type: 'network_request',
|
|
976
|
+
requestId: request.requestId || `req_${startTimestamp}_${Math.random().toString(36).substr(2, 9)}`,
|
|
977
|
+
timestamp: startTimestamp,
|
|
978
|
+
method: request.method,
|
|
979
|
+
url: request.url.length > 500 ? request.url.substring(0, 500) : request.url,
|
|
980
|
+
// Truncate long URLs
|
|
981
|
+
urlPath,
|
|
982
|
+
urlHost,
|
|
983
|
+
statusCode: request.statusCode,
|
|
984
|
+
duration: request.duration,
|
|
985
|
+
endTimestamp,
|
|
986
|
+
success,
|
|
987
|
+
requestBodySize: request.requestBodySize,
|
|
988
|
+
responseBodySize: request.responseBodySize,
|
|
989
|
+
requestContentType: request.requestContentType,
|
|
990
|
+
responseContentType: request.responseContentType,
|
|
991
|
+
errorMessage: request.errorMessage,
|
|
992
|
+
cached: request.cached
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
// Fire and forget - don't await, this is low priority
|
|
996
|
+
getRejourneyNative().logEvent('network_request', networkEvent).catch(() => {});
|
|
997
|
+
}, undefined);
|
|
998
|
+
},
|
|
999
|
+
// ========================================================================
|
|
1000
|
+
// SDK Telemetry / Observability
|
|
1001
|
+
// ========================================================================
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Get SDK telemetry metrics for observability
|
|
1005
|
+
*
|
|
1006
|
+
* Returns metrics about SDK health including upload success rates,
|
|
1007
|
+
* retry attempts, circuit breaker events, and memory pressure.
|
|
1008
|
+
*
|
|
1009
|
+
* @returns SDK telemetry metrics
|
|
1010
|
+
*
|
|
1011
|
+
* @example
|
|
1012
|
+
* ```typescript
|
|
1013
|
+
* const metrics = await Rejourney.getSDKMetrics();
|
|
1014
|
+
* console.log(`Upload success rate: ${(metrics.uploadSuccessRate * 100).toFixed(1)}%`);
|
|
1015
|
+
* console.log(`Circuit breaker opens: ${metrics.circuitBreakerOpenCount}`);
|
|
1016
|
+
* ```
|
|
1017
|
+
*/
|
|
1018
|
+
async getSDKMetrics() {
|
|
1019
|
+
return safeNativeCall('getSDKMetrics', () => getRejourneyNative().getSDKMetrics(), {
|
|
1020
|
+
uploadSuccessCount: 0,
|
|
1021
|
+
uploadFailureCount: 0,
|
|
1022
|
+
retryAttemptCount: 0,
|
|
1023
|
+
circuitBreakerOpenCount: 0,
|
|
1024
|
+
memoryEvictionCount: 0,
|
|
1025
|
+
offlinePersistCount: 0,
|
|
1026
|
+
sessionStartCount: 0,
|
|
1027
|
+
crashCount: 0,
|
|
1028
|
+
uploadSuccessRate: 1.0,
|
|
1029
|
+
avgUploadDurationMs: 0,
|
|
1030
|
+
currentQueueDepth: 0,
|
|
1031
|
+
lastUploadTime: null,
|
|
1032
|
+
lastRetryTime: null,
|
|
1033
|
+
totalBytesUploaded: 0,
|
|
1034
|
+
totalBytesEvicted: 0
|
|
1035
|
+
});
|
|
1036
|
+
},
|
|
1037
|
+
/**
|
|
1038
|
+
* Trigger a debug ANR (Dev only)
|
|
1039
|
+
* Blocks the main thread for the specified duration
|
|
1040
|
+
*/
|
|
1041
|
+
debugTriggerANR(durationMs) {
|
|
1042
|
+
if (__DEV__) {
|
|
1043
|
+
safeNativeCallSync('debugTriggerANR', () => {
|
|
1044
|
+
getRejourneyNative().debugTriggerANR(durationMs);
|
|
1045
|
+
}, undefined);
|
|
1046
|
+
} else {
|
|
1047
|
+
getLogger().warn('debugTriggerANR is only available in development mode');
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
// ========================================================================
|
|
1051
|
+
// Privacy / View Masking
|
|
1052
|
+
// ========================================================================
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Mask a view by its nativeID prop (will be occluded in recordings)
|
|
1056
|
+
*
|
|
1057
|
+
* Use this to mask any sensitive content that isn't a text input.
|
|
1058
|
+
* The view must have a `nativeID` prop set.
|
|
1059
|
+
*
|
|
1060
|
+
* @param nativeID - The nativeID prop of the view to mask
|
|
1061
|
+
* @example
|
|
1062
|
+
* ```tsx
|
|
1063
|
+
* // In your component
|
|
1064
|
+
* <View nativeID="sensitiveCard">...</View>
|
|
1065
|
+
*
|
|
1066
|
+
* // To mask it
|
|
1067
|
+
* Rejourney.maskView('sensitiveCard');
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
maskView(nativeID) {
|
|
1071
|
+
safeNativeCallSync('maskView', () => {
|
|
1072
|
+
getRejourneyNative().maskViewByNativeID(nativeID).catch(() => {});
|
|
1073
|
+
}, undefined);
|
|
1074
|
+
},
|
|
1075
|
+
/**
|
|
1076
|
+
* Unmask a view by its nativeID prop
|
|
1077
|
+
*
|
|
1078
|
+
* Removes the mask from a view that was previously masked with maskView().
|
|
1079
|
+
*
|
|
1080
|
+
* @param nativeID - The nativeID prop of the view to unmask
|
|
1081
|
+
*/
|
|
1082
|
+
unmaskView(nativeID) {
|
|
1083
|
+
safeNativeCallSync('unmaskView', () => {
|
|
1084
|
+
getRejourneyNative().unmaskViewByNativeID(nativeID).catch(() => {});
|
|
1085
|
+
}, undefined);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
// =============================================================================
|
|
1090
|
+
// Automatic Lifecycle Management
|
|
1091
|
+
// =============================================================================
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Handle app state changes for automatic session management
|
|
1095
|
+
* - Pauses recording when app goes to background
|
|
1096
|
+
* - Resumes recording when app comes back to foreground
|
|
1097
|
+
* - Cleans up properly when app is terminated
|
|
1098
|
+
*/
|
|
1099
|
+
function handleAppStateChange(nextAppState) {
|
|
1100
|
+
if (!_isInitialized || _initializationFailed) return;
|
|
1101
|
+
try {
|
|
1102
|
+
if (_currentAppState.match(/active/) && nextAppState === 'background') {
|
|
1103
|
+
// App going to background - native module handles this automatically
|
|
1104
|
+
getLogger().debug('App moving to background');
|
|
1105
|
+
} else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
1106
|
+
// App coming back to foreground
|
|
1107
|
+
getLogger().debug('App returning to foreground');
|
|
1108
|
+
}
|
|
1109
|
+
_currentAppState = nextAppState;
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
getLogger().warn('Error handling app state change:', error);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Setup automatic lifecycle management
|
|
1117
|
+
* Handles cleanup when the app unmounts or goes to background
|
|
1118
|
+
*/
|
|
1119
|
+
function setupLifecycleManagement() {
|
|
1120
|
+
if (_sdkDisabled) return;
|
|
1121
|
+
const RN = getReactNative();
|
|
1122
|
+
if (!RN) return;
|
|
1123
|
+
|
|
1124
|
+
// Remove any existing subscription
|
|
1125
|
+
if (_appStateSubscription) {
|
|
1126
|
+
_appStateSubscription.remove();
|
|
1127
|
+
_appStateSubscription = null;
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
// Get current app state
|
|
1131
|
+
_currentAppState = RN.AppState.currentState || 'active';
|
|
1132
|
+
|
|
1133
|
+
// Subscribe to app state changes
|
|
1134
|
+
_appStateSubscription = RN.AppState.addEventListener('change', handleAppStateChange);
|
|
1135
|
+
|
|
1136
|
+
// Setup auth error listener from native module
|
|
1137
|
+
setupAuthErrorListener();
|
|
1138
|
+
getLogger().debug('Lifecycle management enabled');
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
getLogger().warn('Failed to setup lifecycle management:', error);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Setup listener for authentication errors from native module
|
|
1146
|
+
* This handles security errors like bundle ID mismatch
|
|
1147
|
+
*/
|
|
1148
|
+
function setupAuthErrorListener() {
|
|
1149
|
+
if (_sdkDisabled) return;
|
|
1150
|
+
const RN = getReactNative();
|
|
1151
|
+
if (!RN) return;
|
|
1152
|
+
if (_authErrorSubscription) {
|
|
1153
|
+
_authErrorSubscription.remove();
|
|
1154
|
+
_authErrorSubscription = null;
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
const nativeModule = getRejourneyNative();
|
|
1158
|
+
if (nativeModule) {
|
|
1159
|
+
// RN warns if a non-null module is passed without addListener/removeListeners.
|
|
1160
|
+
// Our native module may not implement these no-op methods yet, so only pass
|
|
1161
|
+
// the module when those hooks exist; otherwise use the global emitter.
|
|
1162
|
+
const maybeAny = nativeModule;
|
|
1163
|
+
const hasEventEmitterHooks = typeof maybeAny?.addListener === 'function' && typeof maybeAny?.removeListeners === 'function';
|
|
1164
|
+
const eventEmitter = hasEventEmitterHooks ? new RN.NativeEventEmitter(maybeAny) : new RN.NativeEventEmitter();
|
|
1165
|
+
_authErrorSubscription = eventEmitter.addListener('rejourneyAuthError', error => {
|
|
1166
|
+
getLogger().error('Authentication error from native:', error);
|
|
1167
|
+
if (error?.code === 403) {
|
|
1168
|
+
getLogger().logPackageMismatch();
|
|
1169
|
+
} else if (error?.code === 404) {
|
|
1170
|
+
getLogger().logInvalidProjectKey();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Update SDK state - recording has been stopped by native
|
|
1174
|
+
_isRecording = false;
|
|
1175
|
+
|
|
1176
|
+
// Call user's error handler if provided
|
|
1177
|
+
if (_storedConfig?.onAuthError) {
|
|
1178
|
+
try {
|
|
1179
|
+
_storedConfig.onAuthError(error);
|
|
1180
|
+
} catch (callbackError) {
|
|
1181
|
+
getLogger().warn('Error in onAuthError callback:', callbackError);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
// Event emitter not available on this platform - that's OK
|
|
1188
|
+
getLogger().debug('Auth error listener not available:', error);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Cleanup lifecycle management
|
|
1194
|
+
*/
|
|
1195
|
+
function cleanupLifecycleManagement() {
|
|
1196
|
+
if (_appStateSubscription) {
|
|
1197
|
+
_appStateSubscription.remove();
|
|
1198
|
+
_appStateSubscription = null;
|
|
1199
|
+
}
|
|
1200
|
+
if (_authErrorSubscription) {
|
|
1201
|
+
_authErrorSubscription.remove();
|
|
1202
|
+
_authErrorSubscription = null;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// =============================================================================
|
|
1207
|
+
// Simple Initialization API
|
|
1208
|
+
// =============================================================================
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Initialize Rejourney SDK - STEP 1 of 3
|
|
1212
|
+
*
|
|
1213
|
+
* This sets up the SDK, handles attestation, and prepares for recording,
|
|
1214
|
+
* but does NOT start recording automatically. Call startRejourney() after
|
|
1215
|
+
* obtaining user consent to begin recording.
|
|
1216
|
+
*
|
|
1217
|
+
* @param publicRouteKey - Your public route key from the Rejourney dashboard
|
|
1218
|
+
* @param options - Optional configuration options
|
|
1219
|
+
*
|
|
1220
|
+
* @example
|
|
1221
|
+
* ```typescript
|
|
1222
|
+
* import { initRejourney, startRejourney } from 'rejourney';
|
|
1223
|
+
*
|
|
1224
|
+
* // Step 1: Initialize SDK (safe to call on app start)
|
|
1225
|
+
* initRejourney('pk_live_xxxxxxxxxxxx');
|
|
1226
|
+
*
|
|
1227
|
+
* // Step 2: After obtaining user consent
|
|
1228
|
+
* startRejourney();
|
|
1229
|
+
*
|
|
1230
|
+
* // With options
|
|
1231
|
+
* initRejourney('pk_live_xxxxxxxxxxxx', {
|
|
1232
|
+
* debug: true,
|
|
1233
|
+
* apiUrl: 'https://api.yourdomain.com',
|
|
1234
|
+
* projectId: 'your-project-id',
|
|
1235
|
+
* });
|
|
1236
|
+
* ```
|
|
1237
|
+
*/
|
|
1238
|
+
function initRejourney(publicRouteKey, options) {
|
|
1239
|
+
// Validate public route key
|
|
1240
|
+
if (!publicRouteKey || typeof publicRouteKey !== 'string') {
|
|
1241
|
+
getLogger().warn('Rejourney: Invalid public route key provided. SDK will be disabled.');
|
|
1242
|
+
_initializationFailed = true;
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Store config for later use
|
|
1247
|
+
_storedConfig = {
|
|
1248
|
+
...options,
|
|
1249
|
+
publicRouteKey
|
|
1250
|
+
};
|
|
1251
|
+
if (options?.debug) {
|
|
1252
|
+
getLogger().setDebugMode(true);
|
|
1253
|
+
const nativeModule = getRejourneyNative();
|
|
1254
|
+
if (nativeModule) {
|
|
1255
|
+
nativeModule.setDebugMode(true).catch(() => {});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
_isInitialized = true;
|
|
1259
|
+
(async () => {
|
|
1260
|
+
try {
|
|
1261
|
+
setupLifecycleManagement();
|
|
1262
|
+
getLogger().logObservabilityStart();
|
|
1263
|
+
getLogger().logInitSuccess(_constants.SDK_VERSION);
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1266
|
+
getLogger().logInitFailure(reason);
|
|
1267
|
+
_initializationFailed = true;
|
|
1268
|
+
_isInitialized = false;
|
|
1269
|
+
}
|
|
1270
|
+
})();
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Start recording - STEP 2 of 3 (call after user consent)
|
|
1275
|
+
*
|
|
1276
|
+
* Begins session recording. Call this after obtaining user consent for recording.
|
|
1277
|
+
*
|
|
1278
|
+
* @example
|
|
1279
|
+
* ```typescript
|
|
1280
|
+
* import { initRejourney, startRejourney } from 'rejourney';
|
|
1281
|
+
*
|
|
1282
|
+
* initRejourney('pk_live_xxxxxxxxxxxx');
|
|
1283
|
+
*
|
|
1284
|
+
* // After user accepts consent dialog
|
|
1285
|
+
* startRejourney();
|
|
1286
|
+
* ```
|
|
1287
|
+
*/
|
|
1288
|
+
function startRejourney() {
|
|
1289
|
+
getLogger().debug('startRejourney() called');
|
|
1290
|
+
if (!_isInitialized) {
|
|
1291
|
+
getLogger().warn('Not initialized - call initRejourney() first');
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (_initializationFailed) {
|
|
1295
|
+
getLogger().warn('Initialization failed - cannot start recording');
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
getLogger().logRecordingStart();
|
|
1299
|
+
getLogger().debug('Starting session...');
|
|
1300
|
+
|
|
1301
|
+
// Fire and forget - don't block the caller
|
|
1302
|
+
(async () => {
|
|
1303
|
+
try {
|
|
1304
|
+
const started = await Rejourney._startSession();
|
|
1305
|
+
if (started) {
|
|
1306
|
+
getLogger().debug('✅ Recording started successfully');
|
|
1307
|
+
} else {
|
|
1308
|
+
getLogger().warn('Recording not started (native module unavailable or already recording)');
|
|
1309
|
+
}
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
getLogger().error('Failed to start recording:', error);
|
|
1312
|
+
}
|
|
1313
|
+
})();
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Stop recording and cleanup all resources.
|
|
1318
|
+
*
|
|
1319
|
+
* Note: This is usually not needed as the SDK handles cleanup automatically.
|
|
1320
|
+
* Only call this if you want to explicitly stop recording.
|
|
1321
|
+
*/
|
|
1322
|
+
function stopRejourney() {
|
|
1323
|
+
try {
|
|
1324
|
+
cleanupLifecycleManagement();
|
|
1325
|
+
Rejourney._stopSession();
|
|
1326
|
+
_isRecording = false;
|
|
1327
|
+
getLogger().debug('Rejourney stopped');
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
getLogger().warn('Error stopping Rejourney:', error);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
var _default = exports.default = Rejourney; // Export types
|
|
1333
|
+
// Export auto tracking utilities for advanced usage
|
|
1334
|
+
// These are used internally but can be called manually if needed
|
|
1335
|
+
// Navigation
|
|
1336
|
+
// Re-export LogLevel enum from utils
|
|
1337
|
+
// Note: This is safe because the enum itself doesn't trigger react-native imports
|
|
1338
|
+
/**
|
|
1339
|
+
* Configure SDK log verbosity.
|
|
1340
|
+
*
|
|
1341
|
+
* By default, the SDK logs minimally to avoid polluting your app's console:
|
|
1342
|
+
* - Production/Release: SILENT (no logs at all)
|
|
1343
|
+
* - Development/Debug: Only critical errors shown
|
|
1344
|
+
*
|
|
1345
|
+
* Essential lifecycle events (init success, session start/end) are automatically
|
|
1346
|
+
* logged in debug builds only - you don't need to configure anything.
|
|
1347
|
+
*
|
|
1348
|
+
* Use this function only if you need to troubleshoot SDK behavior.
|
|
1349
|
+
*
|
|
1350
|
+
* @param level - Minimum log level to display
|
|
1351
|
+
*
|
|
1352
|
+
* @example
|
|
1353
|
+
* ```typescript
|
|
1354
|
+
* import { setLogLevel, LogLevel } from 'rejourney';
|
|
1355
|
+
*
|
|
1356
|
+
* // Enable verbose logging for SDK debugging (not recommended for regular use)
|
|
1357
|
+
* setLogLevel(LogLevel.DEBUG);
|
|
1358
|
+
*
|
|
1359
|
+
* // Show warnings and errors (for troubleshooting)
|
|
1360
|
+
* setLogLevel(LogLevel.WARNING);
|
|
1361
|
+
*
|
|
1362
|
+
* // Silence all logs (default behavior in production)
|
|
1363
|
+
* setLogLevel(LogLevel.SILENT);
|
|
1364
|
+
* ```
|
|
1365
|
+
*/
|
|
1366
|
+
function setLogLevel(level) {
|
|
1367
|
+
getLogger().setLogLevel(level);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Note: Components and hooks removed in new engine
|
|
1371
|
+
// Session replay now handled by dashboard web UI
|
|
1372
|
+
// export { RejourneyReplay } from './components/replay/RejourneyReplay';
|
|
1373
|
+
// export { GestureTracker } from './components/GestureTracker';
|
|
1374
|
+
// export { SessionList } from './components/SessionList';
|
|
1375
|
+
// export { useRejourney } from './hooks/useRejourney';
|
|
1376
|
+
// export { useReplay } from './hooks/useReplay';
|
|
1377
|
+
|
|
1378
|
+
// Note: SDK managers removed in new engine - all functionality handled by native module
|
|
1379
|
+
|
|
1380
|
+
// Export Mask component
|
|
1381
|
+
//# sourceMappingURL=index.js.map
|