@rejourneyco/react-native 1.0.7 → 1.0.8
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/README.md +1 -1
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
- package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
- package/ios/Engine/RejourneyImpl.swift +3 -18
- package/ios/Recording/InteractionRecorder.swift +28 -0
- package/ios/Recording/ReplayOrchestrator.swift +50 -17
- package/ios/Recording/SegmentDispatcher.swift +147 -13
- package/ios/Recording/SpecialCases.swift +614 -0
- package/ios/Recording/StabilityMonitor.swift +2 -2
- package/ios/Recording/TelemetryPipeline.swift +21 -3
- package/ios/Recording/VisualCapture.swift +50 -20
- package/lib/commonjs/index.js +4 -5
- package/lib/commonjs/sdk/constants.js +2 -2
- package/lib/commonjs/sdk/utils.js +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/sdk/constants.js +2 -2
- package/lib/module/sdk/utils.js +1 -1
- package/lib/typescript/sdk/constants.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +1 -6
- package/package.json +2 -2
- package/src/index.ts +9 -10
- package/src/sdk/constants.ts +2 -2
- package/src/sdk/utils.ts +1 -1
- package/src/types/index.ts +1 -6
|
@@ -25,8 +25,10 @@ public final class VisualCapture: NSObject {
|
|
|
25
25
|
|
|
26
26
|
@objc public static let shared = VisualCapture()
|
|
27
27
|
|
|
28
|
-
@objc public var snapshotInterval: Double = 0
|
|
28
|
+
@objc public var snapshotInterval: Double = 1.0
|
|
29
29
|
@objc public var quality: CGFloat = 0.5
|
|
30
|
+
/// Capture scale (e.g. 1.25 = capture at 80% linear size). Matches Android for parity; reduces JPEG size.
|
|
31
|
+
@objc public var captureScale: CGFloat = 1.25
|
|
30
32
|
|
|
31
33
|
@objc public var isCapturing: Bool {
|
|
32
34
|
_stateMachine.currentState == .capturing
|
|
@@ -58,6 +60,7 @@ public final class VisualCapture: NSObject {
|
|
|
58
60
|
|
|
59
61
|
// Industry standard batch size (20 frames per batch, not 5)
|
|
60
62
|
private let _batchSize = 20
|
|
63
|
+
|
|
61
64
|
|
|
62
65
|
private override init() {
|
|
63
66
|
_redactionMask = RedactionMask()
|
|
@@ -135,6 +138,13 @@ public final class VisualCapture: NSObject {
|
|
|
135
138
|
_flushBufferToDisk()
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
/// Submit any buffered frames to the upload pipeline immediately
|
|
142
|
+
/// (regardless of batch size threshold). Packages synchronously to
|
|
143
|
+
/// avoid race conditions during backgrounding.
|
|
144
|
+
@objc public func flushBufferToNetwork() {
|
|
145
|
+
_flushBuffer()
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
@objc public func activateDeferredMode() {
|
|
139
149
|
_deferredUntilCommit = true
|
|
140
150
|
}
|
|
@@ -152,9 +162,10 @@ public final class VisualCapture: NSObject {
|
|
|
152
162
|
_redactionMask.remove(view)
|
|
153
163
|
}
|
|
154
164
|
|
|
155
|
-
@objc public func configure(snapshotInterval: Double, jpegQuality: Double) {
|
|
165
|
+
@objc public func configure(snapshotInterval: Double, jpegQuality: Double, captureScale: CGFloat = 1.25) {
|
|
156
166
|
self.snapshotInterval = snapshotInterval
|
|
157
167
|
self.quality = CGFloat(jpegQuality)
|
|
168
|
+
self.captureScale = max(1.0, captureScale)
|
|
158
169
|
if _stateMachine.currentState == .capturing {
|
|
159
170
|
_stopCaptureTimer()
|
|
160
171
|
_startCaptureTimer()
|
|
@@ -163,15 +174,12 @@ public final class VisualCapture: NSObject {
|
|
|
163
174
|
|
|
164
175
|
@objc public func snapshotNow() {
|
|
165
176
|
DispatchQueue.main.async { [weak self] in
|
|
166
|
-
self?._captureFrame()
|
|
177
|
+
self?._captureFrame(forced: true)
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
private func _startCaptureTimer() {
|
|
171
182
|
_stopCaptureTimer()
|
|
172
|
-
// Industry standard: Use default run loop mode (NOT .common)
|
|
173
|
-
// This lets the timer pause during scrolling/tracking which prevents stutter
|
|
174
|
-
// The capture will resume when scrolling stops
|
|
175
183
|
_captureTimer = Timer.scheduledTimer(withTimeInterval: snapshotInterval, repeats: true) { [weak self] _ in
|
|
176
184
|
self?._captureFrame()
|
|
177
185
|
}
|
|
@@ -182,7 +190,7 @@ public final class VisualCapture: NSObject {
|
|
|
182
190
|
_captureTimer = nil
|
|
183
191
|
}
|
|
184
192
|
|
|
185
|
-
private func _captureFrame() {
|
|
193
|
+
private func _captureFrame(forced: Bool = false) {
|
|
186
194
|
guard _stateMachine.currentState == .capturing else { return }
|
|
187
195
|
|
|
188
196
|
// Skip capture if app is not in foreground (prevents "not in visible window" warnings)
|
|
@@ -190,10 +198,31 @@ public final class VisualCapture: NSObject {
|
|
|
190
198
|
|
|
191
199
|
let frameStart = CFAbsoluteTimeGetCurrent()
|
|
192
200
|
|
|
201
|
+
// Refresh map detection state (very cheap shallow walk)
|
|
202
|
+
SpecialCases.shared.refreshMapState()
|
|
203
|
+
|
|
204
|
+
// Debug-only: confirm capture is running and map state
|
|
205
|
+
if _frameCounter < 5 || _frameCounter % 30 == 0 {
|
|
206
|
+
DiagnosticLog.trace("[VisualCapture] frame#\(_frameCounter) mapVisible=\(SpecialCases.shared.mapVisible) mapIdle=\(SpecialCases.shared.mapIdle) forced=\(forced)")
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Map stutter prevention: when a map view is visible and its camera
|
|
210
|
+
// is still moving (user gesture or animation), skip drawHierarchy
|
|
211
|
+
// entirely — this is the call that causes GPU readback stutter on
|
|
212
|
+
// Metal/OpenGL-backed map tiles. We resume capture at 1 FPS once
|
|
213
|
+
// the map SDK reports idle.
|
|
214
|
+
if !forced && SpecialCases.shared.mapVisible && !SpecialCases.shared.mapIdle {
|
|
215
|
+
DiagnosticLog.trace("[VisualCapture] SKIPPING frame (map moving)")
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
193
219
|
// Capture the pixel buffer on the main thread (required by UIKit),
|
|
194
220
|
// then move JPEG compression to the encode queue to reduce main-thread blocking.
|
|
195
221
|
autoreleasepool {
|
|
196
|
-
guard let window = UIApplication.shared.
|
|
222
|
+
guard let window = UIApplication.shared.connectedScenes
|
|
223
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
224
|
+
.flatMap({ $0.windows })
|
|
225
|
+
.first(where: { $0.isKeyWindow }) else { return }
|
|
197
226
|
let bounds = window.bounds
|
|
198
227
|
// Guard against NaN and invalid bounds that cause CoreGraphics errors
|
|
199
228
|
guard bounds.width > 0, bounds.height > 0 else { return }
|
|
@@ -201,15 +230,18 @@ public final class VisualCapture: NSObject {
|
|
|
201
230
|
guard bounds.width.isFinite && bounds.height.isFinite else { return }
|
|
202
231
|
|
|
203
232
|
let redactRects = _redactionMask.computeRects()
|
|
233
|
+
let scale = max(1.0, captureScale)
|
|
234
|
+
let scaledSize = CGSize(width: bounds.width / scale, height: bounds.height / scale)
|
|
235
|
+
guard scaledSize.width >= 1, scaledSize.height >= 1 else {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
204
238
|
|
|
205
|
-
|
|
206
|
-
let screenScale: CGFloat = 1.25 // Lower scale reduces encoding time significantly
|
|
207
|
-
UIGraphicsBeginImageContextWithOptions(bounds.size, false, screenScale)
|
|
239
|
+
UIGraphicsBeginImageContextWithOptions(scaledSize, false, 1.0)
|
|
208
240
|
guard let context = UIGraphicsGetCurrentContext() else {
|
|
209
241
|
UIGraphicsEndImageContext()
|
|
210
242
|
return
|
|
211
243
|
}
|
|
212
|
-
|
|
244
|
+
context.scaleBy(x: 1.0 / scale, y: 1.0 / scale)
|
|
213
245
|
window.drawHierarchy(in: bounds, afterScreenUpdates: false)
|
|
214
246
|
|
|
215
247
|
// Apply redactions inline while context is open
|
|
@@ -263,6 +295,8 @@ public final class VisualCapture: NSObject {
|
|
|
263
295
|
}
|
|
264
296
|
}
|
|
265
297
|
|
|
298
|
+
|
|
299
|
+
|
|
266
300
|
/// Enforce memory caps to prevent unbounded growth (industry standard backpressure)
|
|
267
301
|
private func _enforceScreenshotCaps() {
|
|
268
302
|
// Called with lock held
|
|
@@ -594,14 +628,10 @@ private final class RedactionMask {
|
|
|
594
628
|
}
|
|
595
629
|
|
|
596
630
|
private func _keyWindow() -> UIWindow? {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
.first { $0.isKeyWindow }
|
|
602
|
-
} else {
|
|
603
|
-
return UIApplication.shared.windows.first { $0.isKeyWindow }
|
|
604
|
-
}
|
|
631
|
+
return UIApplication.shared.connectedScenes
|
|
632
|
+
.compactMap { $0 as? UIWindowScene }
|
|
633
|
+
.flatMap { $0.windows }
|
|
634
|
+
.first { $0.isKeyWindow }
|
|
605
635
|
}
|
|
606
636
|
|
|
607
637
|
private func _scanForSensitiveViews(in view: UIView, rects: inout [CGRect], depth: Int = 0) {
|
package/lib/commonjs/index.js
CHANGED
|
@@ -322,7 +322,7 @@ let _storedConfig = null;
|
|
|
322
322
|
// Abort recording (fail-closed)
|
|
323
323
|
|
|
324
324
|
let _remoteConfig = null;
|
|
325
|
-
let _sessionSampledOut = false; // True = telemetry only, no replay
|
|
325
|
+
let _sessionSampledOut = false; // True = telemetry only, no visual replay capture
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Fetch project configuration from backend
|
|
@@ -618,7 +618,7 @@ const Rejourney = {
|
|
|
618
618
|
// =========================================================
|
|
619
619
|
_sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
|
|
620
620
|
if (_sessionSampledOut) {
|
|
621
|
-
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay
|
|
621
|
+
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
// =========================================================
|
|
@@ -865,7 +865,6 @@ const Rejourney = {
|
|
|
865
865
|
pixelRatio: 1
|
|
866
866
|
},
|
|
867
867
|
eventCount: 0,
|
|
868
|
-
videoSegmentCount: 0,
|
|
869
868
|
storageSize: 0,
|
|
870
869
|
sdkVersion: _constants.SDK_VERSION,
|
|
871
870
|
isComplete: false
|
|
@@ -941,10 +940,10 @@ const Rejourney = {
|
|
|
941
940
|
}, false);
|
|
942
941
|
},
|
|
943
942
|
/**
|
|
944
|
-
* Report a scroll event for
|
|
943
|
+
* Report a scroll event for visual replay timing
|
|
945
944
|
*
|
|
946
945
|
* Call this from your ScrollView's onScroll handler to improve scroll capture.
|
|
947
|
-
* The SDK captures
|
|
946
|
+
* The SDK captures visual replay frames continuously, and this helps log scroll events
|
|
948
947
|
* for timeline correlation during replay.
|
|
949
948
|
*
|
|
950
949
|
* @param scrollOffset - Current scroll offset (vertical or horizontal)
|
|
@@ -37,7 +37,7 @@ var _version = require("./version");
|
|
|
37
37
|
/** Default configuration values */
|
|
38
38
|
const DEFAULT_CONFIG = exports.DEFAULT_CONFIG = {
|
|
39
39
|
enabled: true,
|
|
40
|
-
captureFPS: 0
|
|
40
|
+
captureFPS: 1.0,
|
|
41
41
|
captureOnEvents: true,
|
|
42
42
|
maxSessionDuration: 10 * 60 * 1000,
|
|
43
43
|
maxStorageSize: 50 * 1024 * 1024,
|
|
@@ -90,7 +90,7 @@ const PLAYBACK_SPEEDS = exports.PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
|
|
|
90
90
|
|
|
91
91
|
/** Capture settings */
|
|
92
92
|
const CAPTURE_SETTINGS = exports.CAPTURE_SETTINGS = {
|
|
93
|
-
DEFAULT_FPS: 0
|
|
93
|
+
DEFAULT_FPS: 1.0,
|
|
94
94
|
MIN_FPS: 0.1,
|
|
95
95
|
MAX_FPS: 2,
|
|
96
96
|
CAPTURE_SCALE: 0.25,
|
package/lib/module/index.js
CHANGED
|
@@ -214,7 +214,7 @@ let _storedConfig = null;
|
|
|
214
214
|
// Abort recording (fail-closed)
|
|
215
215
|
|
|
216
216
|
let _remoteConfig = null;
|
|
217
|
-
let _sessionSampledOut = false; // True = telemetry only, no replay
|
|
217
|
+
let _sessionSampledOut = false; // True = telemetry only, no visual replay capture
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
220
|
* Fetch project configuration from backend
|
|
@@ -510,7 +510,7 @@ const Rejourney = {
|
|
|
510
510
|
// =========================================================
|
|
511
511
|
_sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
|
|
512
512
|
if (_sessionSampledOut) {
|
|
513
|
-
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay
|
|
513
|
+
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
// =========================================================
|
|
@@ -757,7 +757,6 @@ const Rejourney = {
|
|
|
757
757
|
pixelRatio: 1
|
|
758
758
|
},
|
|
759
759
|
eventCount: 0,
|
|
760
|
-
videoSegmentCount: 0,
|
|
761
760
|
storageSize: 0,
|
|
762
761
|
sdkVersion: SDK_VERSION,
|
|
763
762
|
isComplete: false
|
|
@@ -833,10 +832,10 @@ const Rejourney = {
|
|
|
833
832
|
}, false);
|
|
834
833
|
},
|
|
835
834
|
/**
|
|
836
|
-
* Report a scroll event for
|
|
835
|
+
* Report a scroll event for visual replay timing
|
|
837
836
|
*
|
|
838
837
|
* Call this from your ScrollView's onScroll handler to improve scroll capture.
|
|
839
|
-
* The SDK captures
|
|
838
|
+
* The SDK captures visual replay frames continuously, and this helps log scroll events
|
|
840
839
|
* for timeline correlation during replay.
|
|
841
840
|
*
|
|
842
841
|
* @param scrollOffset - Current scroll offset (vertical or horizontal)
|
|
@@ -25,7 +25,7 @@ export { SDK_VERSION };
|
|
|
25
25
|
/** Default configuration values */
|
|
26
26
|
export const DEFAULT_CONFIG = {
|
|
27
27
|
enabled: true,
|
|
28
|
-
captureFPS: 0
|
|
28
|
+
captureFPS: 1.0,
|
|
29
29
|
captureOnEvents: true,
|
|
30
30
|
maxSessionDuration: 10 * 60 * 1000,
|
|
31
31
|
maxStorageSize: 50 * 1024 * 1024,
|
|
@@ -78,7 +78,7 @@ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
|
|
|
78
78
|
|
|
79
79
|
/** Capture settings */
|
|
80
80
|
export const CAPTURE_SETTINGS = {
|
|
81
|
-
DEFAULT_FPS: 0
|
|
81
|
+
DEFAULT_FPS: 1.0,
|
|
82
82
|
MIN_FPS: 0.1,
|
|
83
83
|
MAX_FPS: 2,
|
|
84
84
|
CAPTURE_SCALE: 0.25,
|
package/lib/module/sdk/utils.js
CHANGED
|
@@ -21,7 +21,7 @@ export { SDK_VERSION };
|
|
|
21
21
|
/** Default configuration values */
|
|
22
22
|
export declare const DEFAULT_CONFIG: {
|
|
23
23
|
readonly enabled: true;
|
|
24
|
-
readonly captureFPS:
|
|
24
|
+
readonly captureFPS: 1;
|
|
25
25
|
readonly captureOnEvents: true;
|
|
26
26
|
readonly maxSessionDuration: number;
|
|
27
27
|
readonly maxStorageSize: number;
|
|
@@ -70,7 +70,7 @@ export declare const GESTURE_TYPES: {
|
|
|
70
70
|
export declare const PLAYBACK_SPEEDS: readonly [0.5, 1, 2, 4];
|
|
71
71
|
/** Capture settings */
|
|
72
72
|
export declare const CAPTURE_SETTINGS: {
|
|
73
|
-
readonly DEFAULT_FPS:
|
|
73
|
+
readonly DEFAULT_FPS: 1;
|
|
74
74
|
readonly MIN_FPS: 0.1;
|
|
75
75
|
readonly MAX_FPS: 2;
|
|
76
76
|
readonly CAPTURE_SCALE: 0.25;
|
|
@@ -17,7 +17,7 @@ export interface RejourneyConfig {
|
|
|
17
17
|
packageName?: string;
|
|
18
18
|
/** Enable or disable recording (default: true) */
|
|
19
19
|
enabled?: boolean;
|
|
20
|
-
/**
|
|
20
|
+
/** Visual capture FPS (default: 1 = capture every 1000ms) */
|
|
21
21
|
captureFPS?: number;
|
|
22
22
|
/** Maximum session duration in milliseconds (default: 30 minutes) */
|
|
23
23
|
maxSessionDuration?: number;
|
|
@@ -325,8 +325,6 @@ export interface SessionMetadata {
|
|
|
325
325
|
geoLocation?: GeoLocation;
|
|
326
326
|
/** Number of events in session */
|
|
327
327
|
eventCount: number;
|
|
328
|
-
/** Number of video segments */
|
|
329
|
-
videoSegmentCount?: number;
|
|
330
328
|
/** Total storage size in bytes */
|
|
331
329
|
storageSize: number;
|
|
332
330
|
/** Session tags */
|
|
@@ -349,7 +347,6 @@ export interface SessionSummary {
|
|
|
349
347
|
endTime?: number;
|
|
350
348
|
duration?: number;
|
|
351
349
|
eventCount: number;
|
|
352
|
-
videoSegmentCount?: number;
|
|
353
350
|
storageSize: number;
|
|
354
351
|
isComplete: boolean;
|
|
355
352
|
filePath: string;
|
|
@@ -363,8 +360,6 @@ export interface ReplayState {
|
|
|
363
360
|
speed: 0.5 | 1 | 2 | 4;
|
|
364
361
|
/** Session duration */
|
|
365
362
|
duration: number;
|
|
366
|
-
/** Current video segment time position */
|
|
367
|
-
currentVideoTime?: number;
|
|
368
363
|
/** Events at or near current time */
|
|
369
364
|
activeEvents: SessionEvent[];
|
|
370
365
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rejourneyco/react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Rejourney Session Recording SDK for React Native",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-native": "*",
|
|
85
85
|
"react-native-builder-bob": "^0.23.0",
|
|
86
86
|
"typescript": "^5.0.0",
|
|
87
|
-
"vitest": "^2.
|
|
87
|
+
"vitest": "^3.2.4"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
90
|
"react": "*",
|
package/src/index.ts
CHANGED
|
@@ -247,13 +247,13 @@ interface RemoteConfig {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Result type for fetchRemoteConfig - distinguishes network errors from access denial
|
|
250
|
-
type ConfigFetchResult =
|
|
250
|
+
type ConfigFetchResult =
|
|
251
251
|
| { status: 'success'; config: RemoteConfig }
|
|
252
252
|
| { status: 'network_error' } // Proceed with defaults (fail-open)
|
|
253
253
|
| { status: 'access_denied'; httpStatus: number }; // Abort recording (fail-closed)
|
|
254
254
|
|
|
255
255
|
let _remoteConfig: RemoteConfig | null = null;
|
|
256
|
-
let _sessionSampledOut: boolean = false; // True = telemetry only, no replay
|
|
256
|
+
let _sessionSampledOut: boolean = false; // True = telemetry only, no visual replay capture
|
|
257
257
|
|
|
258
258
|
/**
|
|
259
259
|
* Fetch project configuration from backend
|
|
@@ -323,7 +323,7 @@ function shouldRecordSession(sampleRate: number): boolean {
|
|
|
323
323
|
// sampleRate is 0-100 (percentage)
|
|
324
324
|
if (sampleRate >= 100) return true;
|
|
325
325
|
if (sampleRate <= 0) return false;
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
const randomValue = Math.random() * 100;
|
|
328
328
|
return randomValue < sampleRate;
|
|
329
329
|
}
|
|
@@ -522,7 +522,7 @@ const Rejourney: RejourneyAPI = {
|
|
|
522
522
|
// This determines if recording is enabled and at what rate
|
|
523
523
|
// =========================================================
|
|
524
524
|
const configResult = await fetchRemoteConfig(apiUrl, publicKey);
|
|
525
|
-
|
|
525
|
+
|
|
526
526
|
// =========================================================
|
|
527
527
|
// CASE 0: Access denied (401/403) - abort immediately
|
|
528
528
|
// This means project disabled, invalid key, etc - HARD STOP
|
|
@@ -531,10 +531,10 @@ const Rejourney: RejourneyAPI = {
|
|
|
531
531
|
getLogger().info(`Recording disabled - access denied (${configResult.httpStatus})`);
|
|
532
532
|
return false;
|
|
533
533
|
}
|
|
534
|
-
|
|
534
|
+
|
|
535
535
|
// For success, extract the config; for network_error, proceed with null
|
|
536
536
|
_remoteConfig = configResult.status === 'success' ? configResult.config : null;
|
|
537
|
-
|
|
537
|
+
|
|
538
538
|
if (_remoteConfig) {
|
|
539
539
|
// =========================================================
|
|
540
540
|
// CASE 1: Rejourney completely disabled - abort early, nothing captured
|
|
@@ -560,7 +560,7 @@ const Rejourney: RejourneyAPI = {
|
|
|
560
560
|
// =========================================================
|
|
561
561
|
_sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
|
|
562
562
|
if (_sessionSampledOut) {
|
|
563
|
-
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay
|
|
563
|
+
getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
// =========================================================
|
|
@@ -868,7 +868,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
868
868
|
duration: 0,
|
|
869
869
|
deviceInfo: { model: '', os: 'ios', osVersion: '', screenWidth: 0, screenHeight: 0, pixelRatio: 1 },
|
|
870
870
|
eventCount: 0,
|
|
871
|
-
videoSegmentCount: 0,
|
|
872
871
|
storageSize: 0,
|
|
873
872
|
sdkVersion: SDK_VERSION,
|
|
874
873
|
isComplete: false,
|
|
@@ -957,10 +956,10 @@ const Rejourney: RejourneyAPI = {
|
|
|
957
956
|
},
|
|
958
957
|
|
|
959
958
|
/**
|
|
960
|
-
* Report a scroll event for
|
|
959
|
+
* Report a scroll event for visual replay timing
|
|
961
960
|
*
|
|
962
961
|
* Call this from your ScrollView's onScroll handler to improve scroll capture.
|
|
963
|
-
* The SDK captures
|
|
962
|
+
* The SDK captures visual replay frames continuously, and this helps log scroll events
|
|
964
963
|
* for timeline correlation during replay.
|
|
965
964
|
*
|
|
966
965
|
* @param scrollOffset - Current scroll offset (vertical or horizontal)
|
package/src/sdk/constants.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { SDK_VERSION };
|
|
|
26
26
|
/** Default configuration values */
|
|
27
27
|
export const DEFAULT_CONFIG = {
|
|
28
28
|
enabled: true,
|
|
29
|
-
captureFPS: 0
|
|
29
|
+
captureFPS: 1.0,
|
|
30
30
|
captureOnEvents: true,
|
|
31
31
|
maxSessionDuration: 10 * 60 * 1000,
|
|
32
32
|
maxStorageSize: 50 * 1024 * 1024,
|
|
@@ -79,7 +79,7 @@ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4] as const;
|
|
|
79
79
|
|
|
80
80
|
/** Capture settings */
|
|
81
81
|
export const CAPTURE_SETTINGS = {
|
|
82
|
-
DEFAULT_FPS: 0
|
|
82
|
+
DEFAULT_FPS: 1.0,
|
|
83
83
|
MIN_FPS: 0.1,
|
|
84
84
|
MAX_FPS: 2,
|
|
85
85
|
CAPTURE_SCALE: 0.25,
|
package/src/sdk/utils.ts
CHANGED
package/src/types/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface RejourneyConfig {
|
|
|
23
23
|
|
|
24
24
|
/** Enable or disable recording (default: true) */
|
|
25
25
|
enabled?: boolean;
|
|
26
|
-
/**
|
|
26
|
+
/** Visual capture FPS (default: 1 = capture every 1000ms) */
|
|
27
27
|
captureFPS?: number;
|
|
28
28
|
/** Maximum session duration in milliseconds (default: 30 minutes) */
|
|
29
29
|
maxSessionDuration?: number;
|
|
@@ -410,8 +410,6 @@ export interface SessionMetadata {
|
|
|
410
410
|
geoLocation?: GeoLocation;
|
|
411
411
|
/** Number of events in session */
|
|
412
412
|
eventCount: number;
|
|
413
|
-
/** Number of video segments */
|
|
414
|
-
videoSegmentCount?: number;
|
|
415
413
|
/** Total storage size in bytes */
|
|
416
414
|
storageSize: number;
|
|
417
415
|
/** Session tags */
|
|
@@ -436,7 +434,6 @@ export interface SessionSummary {
|
|
|
436
434
|
endTime?: number;
|
|
437
435
|
duration?: number;
|
|
438
436
|
eventCount: number;
|
|
439
|
-
videoSegmentCount?: number;
|
|
440
437
|
storageSize: number;
|
|
441
438
|
isComplete: boolean;
|
|
442
439
|
filePath: string;
|
|
@@ -451,8 +448,6 @@ export interface ReplayState {
|
|
|
451
448
|
speed: 0.5 | 1 | 2 | 4;
|
|
452
449
|
/** Session duration */
|
|
453
450
|
duration: number;
|
|
454
|
-
/** Current video segment time position */
|
|
455
|
-
currentVideoTime?: number;
|
|
456
451
|
/** Events at or near current time */
|
|
457
452
|
activeEvents: SessionEvent[];
|
|
458
453
|
}
|