@multiplayer-app/session-recorder-react-native 1.0.1-beta.4 → 1.0.1-beta.5
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/src/main/java/com/multiplayer/sessionrecordernative/SessionRecorderNativeModule.kt +2 -2
- package/lib/module/components/SessionRecorderWidget/ErrorBanner.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/ModalHeader.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/icons.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/styles.js.map +1 -1
- package/lib/module/config/constants.js.map +1 -1
- package/lib/module/config/defaults.js.map +1 -1
- package/lib/module/config/masking.js.map +1 -1
- package/lib/module/config/session-recorder.js.map +1 -1
- package/lib/module/config/validators.js.map +1 -1
- package/lib/module/config/widget.js.map +1 -1
- package/lib/module/context/SessionRecorderStore.js.map +1 -1
- package/lib/module/context/useSessionRecorderStore.js.map +1 -1
- package/lib/module/context/useStoreSelector.js.map +1 -1
- package/lib/module/native/SessionRecorderNative.js.map +1 -1
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/otel/helpers.js +1 -1
- package/lib/module/otel/helpers.js.map +1 -1
- package/lib/module/otel/index.js.map +1 -1
- package/lib/module/otel/instrumentations/index.js.map +1 -1
- package/lib/module/patch/xhr.js.map +1 -1
- package/lib/module/recorder/eventExporter.js.map +1 -1
- package/lib/module/recorder/gestureRecorder.js.map +1 -1
- package/lib/module/recorder/index.js.map +1 -1
- package/lib/module/recorder/navigationTracker.js.map +1 -1
- package/lib/module/recorder/screenRecorder.js.map +1 -1
- package/lib/module/services/api.service.js.map +1 -1
- package/lib/module/services/network.service.js.map +1 -1
- package/lib/module/services/screenMaskingService.js.map +1 -1
- package/lib/module/services/storage.service.js.map +1 -1
- package/lib/module/session-recorder.js.map +1 -1
- package/lib/module/types/index.js.map +1 -1
- package/lib/module/types/session-recorder.js.map +1 -1
- package/lib/module/utils/app-metadata.js +2 -2
- package/lib/module/utils/constants.optional.js.map +1 -1
- package/lib/module/utils/createStore.js.map +1 -1
- package/lib/module/utils/logger.js +0 -8
- package/lib/module/utils/logger.js.map +1 -1
- package/lib/module/utils/platform.js +1 -1
- package/lib/module/utils/platform.js.map +1 -1
- package/lib/module/utils/rrweb-events.js.map +1 -1
- package/lib/module/utils/session.js.map +1 -1
- package/lib/module/utils/shallowEqual.js.map +1 -1
- package/lib/module/utils/time.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/components/ScreenRecorderView/index.d.ts +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/ErrorBanner.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/SessionRecorderWidget.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/icons.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/index.d.ts +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/config/constants.d.ts.map +1 -1
- package/lib/typescript/src/config/defaults.d.ts.map +1 -1
- package/lib/typescript/src/config/index.d.ts.map +1 -1
- package/lib/typescript/src/config/masking.d.ts.map +1 -1
- package/lib/typescript/src/config/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/config/validators.d.ts.map +1 -1
- package/lib/typescript/src/config/widget.d.ts +1 -1
- package/lib/typescript/src/config/widget.d.ts.map +1 -1
- package/lib/typescript/src/context/SessionRecorderStore.d.ts.map +1 -1
- package/lib/typescript/src/context/useSessionRecorderStore.d.ts +3 -3
- package/lib/typescript/src/context/useSessionRecorderStore.d.ts.map +1 -1
- package/lib/typescript/src/context/useStoreSelector.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/SessionRecorderNative.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +1 -1
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/otel/helpers.d.ts.map +1 -1
- package/lib/typescript/src/otel/index.d.ts.map +1 -1
- package/lib/typescript/src/otel/instrumentations/index.d.ts.map +1 -1
- package/lib/typescript/src/patch/index.d.ts.map +1 -1
- package/lib/typescript/src/patch/xhr.d.ts.map +1 -1
- package/lib/typescript/src/recorder/eventExporter.d.ts.map +1 -1
- package/lib/typescript/src/recorder/gestureRecorder.d.ts.map +1 -1
- package/lib/typescript/src/recorder/index.d.ts.map +1 -1
- package/lib/typescript/src/recorder/navigationTracker.d.ts.map +1 -1
- package/lib/typescript/src/recorder/screenRecorder.d.ts.map +1 -1
- package/lib/typescript/src/services/api.service.d.ts.map +1 -1
- package/lib/typescript/src/services/network.service.d.ts.map +1 -1
- package/lib/typescript/src/services/screenMaskingService.d.ts.map +1 -1
- package/lib/typescript/src/services/storage.service.d.ts.map +1 -1
- package/lib/typescript/src/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/configs.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/types/session-recorder.d.ts +4 -4
- package/lib/typescript/src/types/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/session.d.ts.map +1 -1
- package/lib/typescript/src/utils/app-metadata.d.ts.map +1 -1
- package/lib/typescript/src/utils/constants.optional.d.ts.map +1 -1
- package/lib/typescript/src/utils/constants.optional.expo.d.ts.map +1 -1
- package/lib/typescript/src/utils/createStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/logger.d.ts +1 -1
- package/lib/typescript/src/utils/logger.d.ts.map +1 -1
- package/lib/typescript/src/utils/platform.d.ts.map +1 -1
- package/lib/typescript/src/utils/request-utils.d.ts.map +1 -1
- package/lib/typescript/src/utils/rrweb-events.d.ts.map +1 -1
- package/lib/typescript/src/utils/session.d.ts.map +1 -1
- package/lib/typescript/src/utils/shallowEqual.d.ts.map +1 -1
- package/lib/typescript/src/utils/time.d.ts.map +1 -1
- package/lib/typescript/src/utils/type-utils.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ScreenRecorderView/index.ts +1 -1
- package/src/components/SessionRecorderWidget/ErrorBanner.tsx +14 -14
- package/src/components/SessionRecorderWidget/ModalHeader.tsx +11 -9
- package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +70 -56
- package/src/components/SessionRecorderWidget/icons.tsx +58 -30
- package/src/components/SessionRecorderWidget/index.ts +1 -1
- package/src/components/SessionRecorderWidget/styles.ts +17 -18
- package/src/components/index.ts +2 -2
- package/src/config/constants.ts +19 -20
- package/src/config/defaults.ts +35 -31
- package/src/config/index.ts +5 -5
- package/src/config/masking.ts +44 -18
- package/src/config/session-recorder.ts +54 -26
- package/src/config/validators.ts +43 -20
- package/src/config/widget.ts +24 -15
- package/src/context/SessionRecorderStore.ts +19 -18
- package/src/context/useSessionRecorderStore.ts +17 -10
- package/src/context/useStoreSelector.ts +20 -18
- package/src/index.ts +7 -7
- package/src/native/SessionRecorderNative.ts +83 -67
- package/src/native/index.ts +5 -1
- package/src/otel/helpers.ts +109 -93
- package/src/otel/index.ts +46 -49
- package/src/otel/instrumentations/index.ts +44 -41
- package/src/patch/index.ts +1 -1
- package/src/patch/xhr.ts +77 -78
- package/src/recorder/eventExporter.ts +63 -68
- package/src/recorder/gestureRecorder.ts +359 -212
- package/src/recorder/index.ts +75 -62
- package/src/recorder/navigationTracker.ts +120 -97
- package/src/recorder/screenRecorder.ts +214 -163
- package/src/services/api.service.ts +49 -48
- package/src/services/network.service.ts +67 -58
- package/src/services/screenMaskingService.ts +81 -50
- package/src/services/storage.service.ts +99 -70
- package/src/session-recorder.ts +270 -214
- package/src/types/configs.ts +53 -31
- package/src/types/expo-constants.d.ts +2 -2
- package/src/types/index.ts +16 -18
- package/src/types/session-recorder.ts +106 -111
- package/src/types/session.ts +45 -45
- package/src/utils/app-metadata.ts +9 -9
- package/src/utils/constants.optional.expo.ts +3 -3
- package/src/utils/constants.optional.ts +14 -12
- package/src/utils/createStore.ts +23 -20
- package/src/utils/index.ts +7 -7
- package/src/utils/logger.ts +87 -58
- package/src/utils/platform.ts +149 -118
- package/src/utils/request-utils.ts +15 -15
- package/src/utils/rrweb-events.ts +47 -34
- package/src/utils/session.ts +15 -12
- package/src/utils/shallowEqual.ts +16 -10
- package/src/utils/time.ts +7 -4
- package/src/utils/type-utils.ts +36 -36
- package/src/version.ts +1 -1
- package/android/src/main/java/com/multiplayer/sessionrecordernative/SessionRecorderNativeModuleSpec.kt +0 -51
- package/android/src/main/java/com/xxx/XxxModule.kt +0 -23
- package/ios/Xxx.h +0 -5
- package/ios/Xxx.mm +0 -21
|
@@ -1,148 +1,159 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
type ScreenEvent,
|
|
3
|
+
type RecorderConfig,
|
|
4
|
+
type EventRecorder,
|
|
5
|
+
} from '../types';
|
|
6
|
+
import { type eventWithTime } from '@rrweb/types';
|
|
7
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
|
8
|
+
import { Dimensions, Platform } from 'react-native';
|
|
5
9
|
import {
|
|
6
10
|
createRecordingMetaEvent,
|
|
7
11
|
createFullSnapshotEvent,
|
|
8
12
|
createIncrementalSnapshotWithImageUpdate as createIncrementalSnapshotUtil,
|
|
9
13
|
generateScreenHash,
|
|
10
14
|
logger,
|
|
11
|
-
} from '../utils'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
} from '../utils';
|
|
16
|
+
import {
|
|
17
|
+
screenMaskingService,
|
|
18
|
+
type ScreenMaskingConfig,
|
|
19
|
+
} from '../services/screenMaskingService';
|
|
20
|
+
import { captureRef } from 'react-native-view-shot';
|
|
21
|
+
const isWeb = Platform.OS === 'web';
|
|
16
22
|
|
|
17
23
|
export class ScreenRecorder implements EventRecorder {
|
|
18
|
-
private config?: RecorderConfig
|
|
19
|
-
private isRecording = false
|
|
20
|
-
private events: ScreenEvent[] = []
|
|
21
|
-
private captureInterval?:
|
|
22
|
-
private captureCount: number = 0
|
|
23
|
-
private maxCaptures: number = 100 // Limit captures to prevent memory issues
|
|
24
|
-
private captureQuality: number = 0.2
|
|
25
|
-
private captureScale: number = 0.66
|
|
26
|
-
private captureFormat: 'png' | 'jpg' = 'jpg'
|
|
27
|
-
private screenDimensions: { width: number; height: number } | null = null
|
|
28
|
-
private eventRecorder?: EventRecorder
|
|
29
|
-
private nodeIdCounter: number = 1
|
|
30
|
-
private viewShotRef: any = null
|
|
31
|
-
private lastScreenCapture: string | null = null
|
|
32
|
-
private lastScreenHash: string | null = null
|
|
33
|
-
private enableChangeDetection: boolean = true
|
|
34
|
-
private hashSampleSize: number = 100
|
|
35
|
-
private currentImageNodeId: number | null = null
|
|
36
|
-
private maskingConfig?: ScreenMaskingConfig
|
|
24
|
+
private config?: RecorderConfig;
|
|
25
|
+
private isRecording = false;
|
|
26
|
+
private events: ScreenEvent[] = [];
|
|
27
|
+
private captureInterval?: any;
|
|
28
|
+
private captureCount: number = 0;
|
|
29
|
+
private maxCaptures: number = 100; // Limit captures to prevent memory issues
|
|
30
|
+
private captureQuality: number = 0.2;
|
|
31
|
+
private captureScale: number = 0.66;
|
|
32
|
+
private captureFormat: 'png' | 'jpg' = 'jpg';
|
|
33
|
+
private screenDimensions: { width: number; height: number } | null = null;
|
|
34
|
+
private eventRecorder?: EventRecorder;
|
|
35
|
+
private nodeIdCounter: number = 1;
|
|
36
|
+
private viewShotRef: any = null;
|
|
37
|
+
private lastScreenCapture: string | null = null;
|
|
38
|
+
private lastScreenHash: string | null = null;
|
|
39
|
+
private enableChangeDetection: boolean = true;
|
|
40
|
+
private hashSampleSize: number = 100;
|
|
41
|
+
private currentImageNodeId: number | null = null;
|
|
42
|
+
private maskingConfig?: ScreenMaskingConfig;
|
|
37
43
|
|
|
38
44
|
init(config: RecorderConfig, eventRecorder?: EventRecorder): void {
|
|
39
|
-
this.config = config
|
|
40
|
-
this.eventRecorder = eventRecorder
|
|
41
|
-
this._getScreenDimensions()
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.eventRecorder = eventRecorder;
|
|
47
|
+
this._getScreenDimensions();
|
|
42
48
|
|
|
43
49
|
// Initialize masking configuration
|
|
44
50
|
this.maskingConfig = {
|
|
45
51
|
enabled: true,
|
|
46
52
|
...this.config.masking,
|
|
47
|
-
}
|
|
53
|
+
};
|
|
48
54
|
|
|
49
55
|
// Update the masking service configuration
|
|
50
|
-
screenMaskingService.updateConfig(this.maskingConfig)
|
|
56
|
+
screenMaskingService.updateConfig(this.maskingConfig);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
start(): void {
|
|
54
|
-
this.isRecording = true
|
|
55
|
-
this.events = []
|
|
56
|
-
this.captureCount = 0
|
|
57
|
-
this.lastScreenCapture = null
|
|
58
|
-
this.lastScreenHash = null
|
|
59
|
-
this.currentImageNodeId = null // Reset image node ID for new session
|
|
60
|
-
logger.info('ScreenRecorder', 'Screen recording started')
|
|
60
|
+
this.isRecording = true;
|
|
61
|
+
this.events = [];
|
|
62
|
+
this.captureCount = 0;
|
|
63
|
+
this.lastScreenCapture = null;
|
|
64
|
+
this.lastScreenHash = null;
|
|
65
|
+
this.currentImageNodeId = null; // Reset image node ID for new session
|
|
66
|
+
logger.info('ScreenRecorder', 'Screen recording started');
|
|
61
67
|
// Emit screen recording started meta event
|
|
62
68
|
|
|
63
|
-
this.recordEvent(createRecordingMetaEvent())
|
|
69
|
+
this.recordEvent(createRecordingMetaEvent());
|
|
64
70
|
|
|
65
|
-
this._startPeriodicCapture()
|
|
71
|
+
this._startPeriodicCapture();
|
|
66
72
|
|
|
67
73
|
// Capture initial screen immediately
|
|
68
|
-
this._captureScreen()
|
|
74
|
+
this._captureScreen();
|
|
69
75
|
|
|
70
76
|
// Screen recording started
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
stop(): void {
|
|
74
|
-
this.isRecording = false
|
|
75
|
-
this._stopPeriodicCapture()
|
|
80
|
+
this.isRecording = false;
|
|
81
|
+
this._stopPeriodicCapture();
|
|
76
82
|
// Screen recording stopped
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
pause(): void {
|
|
80
|
-
this.isRecording = false
|
|
81
|
-
this._stopPeriodicCapture()
|
|
86
|
+
this.isRecording = false;
|
|
87
|
+
this._stopPeriodicCapture();
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
resume(): void {
|
|
85
|
-
this.isRecording = true
|
|
91
|
+
this.isRecording = true;
|
|
86
92
|
// this._startPeriodicCapture()
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
private _getScreenDimensions(): void {
|
|
90
96
|
try {
|
|
91
|
-
this.screenDimensions = Dimensions.get('window')
|
|
97
|
+
this.screenDimensions = Dimensions.get('window');
|
|
92
98
|
} catch (error) {
|
|
93
99
|
// Failed to get screen dimensions - silently continue
|
|
94
|
-
this.screenDimensions = { width: 375, height: 667 } // Default fallback
|
|
100
|
+
this.screenDimensions = { width: 375, height: 667 }; // Default fallback
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
private _startPeriodicCapture(): void {
|
|
99
105
|
if (this.captureInterval) {
|
|
100
|
-
clearInterval(this.captureInterval)
|
|
106
|
+
clearInterval(this.captureInterval);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
// Capture screen every 5 seconds (reduced frequency)
|
|
104
110
|
this.captureInterval = setInterval(() => {
|
|
105
|
-
this._captureScreen()
|
|
106
|
-
}, 5000)
|
|
111
|
+
this._captureScreen();
|
|
112
|
+
}, 5000);
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
private _stopPeriodicCapture(): void {
|
|
110
116
|
if (this.captureInterval) {
|
|
111
|
-
clearInterval(this.captureInterval)
|
|
112
|
-
this.captureInterval = undefined
|
|
117
|
+
clearInterval(this.captureInterval);
|
|
118
|
+
this.captureInterval = undefined;
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
private async _captureScreen(timestamp?: number): Promise<void> {
|
|
117
|
-
if (!this.isRecording || this.captureCount >= this.maxCaptures) return
|
|
123
|
+
if (!this.isRecording || this.captureCount >= this.maxCaptures) return;
|
|
118
124
|
|
|
119
125
|
try {
|
|
120
|
-
const base64Image = await this._captureScreenBase64()
|
|
126
|
+
const base64Image = await this._captureScreenBase64();
|
|
121
127
|
|
|
122
128
|
if (base64Image) {
|
|
123
129
|
// Check if screen has changed by comparing with previous capture
|
|
124
|
-
const hasChanged = this.enableChangeDetection
|
|
130
|
+
const hasChanged = this.enableChangeDetection
|
|
131
|
+
? this._hasScreenChanged(base64Image)
|
|
132
|
+
: true;
|
|
125
133
|
|
|
126
134
|
if (hasChanged) {
|
|
127
135
|
// Use incremental snapshot if we have an existing image node, otherwise create full snapshot
|
|
128
136
|
if (this.currentImageNodeId !== null && this.lastScreenCapture) {
|
|
129
|
-
const success = this.updateScreenWithIncrementalSnapshot(
|
|
137
|
+
const success = this.updateScreenWithIncrementalSnapshot(
|
|
138
|
+
base64Image,
|
|
139
|
+
timestamp
|
|
140
|
+
);
|
|
130
141
|
if (!success) {
|
|
131
142
|
// Fallback to full snapshot if incremental update fails
|
|
132
|
-
this._createAndEmitFullSnapshotEvent(base64Image, timestamp)
|
|
143
|
+
this._createAndEmitFullSnapshotEvent(base64Image, timestamp);
|
|
133
144
|
}
|
|
134
145
|
} else {
|
|
135
146
|
// First capture or no existing image node - create full snapshot
|
|
136
|
-
this._createAndEmitFullSnapshotEvent(base64Image, timestamp)
|
|
147
|
+
this._createAndEmitFullSnapshotEvent(base64Image, timestamp);
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
this.lastScreenCapture = base64Image
|
|
140
|
-
this.lastScreenHash = this._generateScreenHash(base64Image)
|
|
141
|
-
this.captureCount
|
|
150
|
+
this.lastScreenCapture = base64Image;
|
|
151
|
+
this.lastScreenHash = this._generateScreenHash(base64Image);
|
|
152
|
+
this.captureCount++;
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
} catch (error) {
|
|
145
|
-
this._recordScreenCaptureError(error as Error)
|
|
156
|
+
this._recordScreenCaptureError(error as Error);
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -150,35 +161,47 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
150
161
|
try {
|
|
151
162
|
// Check if we're on web platform
|
|
152
163
|
if (isWeb) {
|
|
153
|
-
logger.warn(
|
|
154
|
-
|
|
164
|
+
logger.warn(
|
|
165
|
+
'ScreenRecorder',
|
|
166
|
+
'Screen capture not available on web platform'
|
|
167
|
+
);
|
|
168
|
+
return null;
|
|
155
169
|
}
|
|
156
170
|
|
|
157
171
|
// Try native masking first if available
|
|
158
172
|
if (screenMaskingService.isScreenMaskingAvailable()) {
|
|
159
|
-
logger.info(
|
|
173
|
+
logger.info(
|
|
174
|
+
'ScreenRecorder',
|
|
175
|
+
'Using native masking for screen capture'
|
|
176
|
+
);
|
|
160
177
|
const maskedImage = await screenMaskingService.captureMaskedScreen({
|
|
161
178
|
quality: this.captureQuality,
|
|
162
179
|
scale: this.captureScale,
|
|
163
|
-
})
|
|
180
|
+
});
|
|
164
181
|
|
|
165
182
|
if (maskedImage) {
|
|
166
|
-
return maskedImage
|
|
183
|
+
return maskedImage;
|
|
167
184
|
}
|
|
168
185
|
|
|
169
|
-
logger.warn(
|
|
186
|
+
logger.warn(
|
|
187
|
+
'ScreenRecorder',
|
|
188
|
+
'Native masking failed, falling back to view-shot'
|
|
189
|
+
);
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
// Fallback to react-native-view-shot
|
|
173
193
|
if (!this.viewShotRef) {
|
|
174
|
-
logger.warn(
|
|
175
|
-
|
|
194
|
+
logger.warn(
|
|
195
|
+
'ScreenRecorder',
|
|
196
|
+
'ViewShot ref not available for screen capture'
|
|
197
|
+
);
|
|
198
|
+
return null;
|
|
176
199
|
}
|
|
177
200
|
|
|
178
201
|
// Check if captureRef is available
|
|
179
202
|
if (!captureRef) {
|
|
180
|
-
logger.warn('ScreenRecorder', 'react-native-view-shot not available')
|
|
181
|
-
return null
|
|
203
|
+
logger.warn('ScreenRecorder', 'react-native-view-shot not available');
|
|
204
|
+
return null;
|
|
182
205
|
}
|
|
183
206
|
|
|
184
207
|
// Capture the screen using react-native-view-shot
|
|
@@ -186,21 +209,28 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
186
209
|
format: this.captureFormat,
|
|
187
210
|
quality: this.captureQuality,
|
|
188
211
|
result: 'base64',
|
|
189
|
-
})
|
|
212
|
+
});
|
|
190
213
|
|
|
191
|
-
return result
|
|
214
|
+
return result;
|
|
192
215
|
} catch (error) {
|
|
193
|
-
logger.error(
|
|
194
|
-
|
|
216
|
+
logger.error(
|
|
217
|
+
'ScreenRecorder',
|
|
218
|
+
'Failed to capture screen. Make sure react-native-view-shot is properly installed and linked:',
|
|
219
|
+
error
|
|
220
|
+
);
|
|
221
|
+
return null;
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
private _createAndEmitFullSnapshotEvent(
|
|
199
|
-
|
|
225
|
+
private _createAndEmitFullSnapshotEvent(
|
|
226
|
+
base64Image: string,
|
|
227
|
+
timestamp?: number
|
|
228
|
+
): void {
|
|
229
|
+
if (!this.screenDimensions) return;
|
|
200
230
|
|
|
201
231
|
// Use the new createFullSnapshot method
|
|
202
|
-
const fullSnapshotEvent = this.createFullSnapshot(base64Image, timestamp)
|
|
203
|
-
this.recordEvent(fullSnapshotEvent)
|
|
232
|
+
const fullSnapshotEvent = this.createFullSnapshot(base64Image, timestamp);
|
|
233
|
+
this.recordEvent(fullSnapshotEvent);
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
/**
|
|
@@ -211,11 +241,11 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
211
241
|
*/
|
|
212
242
|
createFullSnapshot(base64Image: string, timestamp?: number): eventWithTime {
|
|
213
243
|
if (!this.screenDimensions) {
|
|
214
|
-
throw new Error('Screen dimensions not available')
|
|
244
|
+
throw new Error('Screen dimensions not available');
|
|
215
245
|
}
|
|
216
246
|
|
|
217
|
-
const { width, height } = this.screenDimensions
|
|
218
|
-
this.nodeIdCounter = 1
|
|
247
|
+
const { width, height } = this.screenDimensions;
|
|
248
|
+
this.nodeIdCounter = 1;
|
|
219
249
|
|
|
220
250
|
// Use utility function to create full snapshot event
|
|
221
251
|
const fullSnapshotEvent = createFullSnapshotEvent(
|
|
@@ -224,14 +254,14 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
224
254
|
height,
|
|
225
255
|
this.captureFormat,
|
|
226
256
|
{ current: this.nodeIdCounter },
|
|
227
|
-
timestamp
|
|
228
|
-
)
|
|
257
|
+
timestamp
|
|
258
|
+
);
|
|
229
259
|
|
|
230
260
|
// Store the image node ID for future incremental updates
|
|
231
261
|
// The image node ID is the first node created (after the document)
|
|
232
|
-
this.currentImageNodeId = 0 // First element node is the image
|
|
262
|
+
this.currentImageNodeId = 0; // First element node is the image
|
|
233
263
|
|
|
234
|
-
return fullSnapshotEvent
|
|
264
|
+
return fullSnapshotEvent;
|
|
235
265
|
}
|
|
236
266
|
|
|
237
267
|
/**
|
|
@@ -241,30 +271,43 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
241
271
|
* @param timestamp - Optional timestamp to use for the event
|
|
242
272
|
* @returns Incremental snapshot event with mutation data
|
|
243
273
|
*/
|
|
244
|
-
createIncrementalSnapshotWithImageUpdate(
|
|
274
|
+
createIncrementalSnapshotWithImageUpdate(
|
|
275
|
+
base64Image: string,
|
|
276
|
+
captureFormat?: string,
|
|
277
|
+
timestamp?: number
|
|
278
|
+
): eventWithTime {
|
|
245
279
|
return createIncrementalSnapshotUtil(
|
|
246
280
|
base64Image,
|
|
247
281
|
captureFormat || this.captureFormat,
|
|
248
|
-
timestamp
|
|
249
|
-
)
|
|
282
|
+
timestamp
|
|
283
|
+
);
|
|
250
284
|
}
|
|
251
285
|
|
|
252
|
-
|
|
253
286
|
/**
|
|
254
287
|
* Update the screen with a new image using incremental snapshot
|
|
255
288
|
* @param base64Image - New base64 encoded image data
|
|
256
289
|
* @param timestamp - Optional timestamp to use for the event
|
|
257
290
|
* @returns true if update was successful, false otherwise
|
|
258
291
|
*/
|
|
259
|
-
updateScreenWithIncrementalSnapshot(
|
|
292
|
+
updateScreenWithIncrementalSnapshot(
|
|
293
|
+
base64Image: string,
|
|
294
|
+
timestamp?: number
|
|
295
|
+
): boolean {
|
|
260
296
|
if (this.currentImageNodeId === null) {
|
|
261
|
-
logger.warn(
|
|
262
|
-
|
|
297
|
+
logger.warn(
|
|
298
|
+
'ScreenRecorder',
|
|
299
|
+
'No image node ID available for incremental update'
|
|
300
|
+
);
|
|
301
|
+
return false;
|
|
263
302
|
}
|
|
264
303
|
|
|
265
|
-
const incrementalEvent = this.createIncrementalSnapshotWithImageUpdate(
|
|
266
|
-
|
|
267
|
-
|
|
304
|
+
const incrementalEvent = this.createIncrementalSnapshotWithImageUpdate(
|
|
305
|
+
base64Image,
|
|
306
|
+
'jpg',
|
|
307
|
+
timestamp
|
|
308
|
+
);
|
|
309
|
+
this.recordEvent(incrementalEvent);
|
|
310
|
+
return true;
|
|
268
311
|
}
|
|
269
312
|
|
|
270
313
|
/**
|
|
@@ -272,10 +315,10 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
272
315
|
* @param base64Image - Base64 encoded image data
|
|
273
316
|
*/
|
|
274
317
|
forceFullSnapshot(base64Image: string): void {
|
|
275
|
-
this._createAndEmitFullSnapshotEvent(base64Image)
|
|
276
|
-
this.lastScreenCapture = base64Image
|
|
277
|
-
this.lastScreenHash = this._generateScreenHash(base64Image)
|
|
278
|
-
this.captureCount
|
|
318
|
+
this._createAndEmitFullSnapshotEvent(base64Image);
|
|
319
|
+
this.lastScreenCapture = base64Image;
|
|
320
|
+
this.lastScreenHash = this._generateScreenHash(base64Image);
|
|
321
|
+
this.captureCount++;
|
|
279
322
|
}
|
|
280
323
|
|
|
281
324
|
/**
|
|
@@ -286,14 +329,14 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
286
329
|
private _hasScreenChanged(currentBase64: string): boolean {
|
|
287
330
|
// If this is the first capture, consider it changed
|
|
288
331
|
if (!this.lastScreenCapture) {
|
|
289
|
-
return true
|
|
332
|
+
return true;
|
|
290
333
|
}
|
|
291
334
|
|
|
292
335
|
// Generate hash for current capture
|
|
293
|
-
const currentHash = this._generateScreenHash(currentBase64)
|
|
336
|
+
const currentHash = this._generateScreenHash(currentBase64);
|
|
294
337
|
|
|
295
338
|
// Compare with previous hash
|
|
296
|
-
return currentHash !== this.lastScreenHash
|
|
339
|
+
return currentHash !== this.lastScreenHash;
|
|
297
340
|
}
|
|
298
341
|
|
|
299
342
|
/**
|
|
@@ -304,7 +347,7 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
304
347
|
* @returns Hash string for comparison
|
|
305
348
|
*/
|
|
306
349
|
private _generateScreenHash(base64Image: string): string {
|
|
307
|
-
return generateScreenHash(base64Image, this.hashSampleSize)
|
|
350
|
+
return generateScreenHash(base64Image, this.hashSampleSize);
|
|
308
351
|
}
|
|
309
352
|
|
|
310
353
|
private _sendEvent(_event: ScreenEvent): void {
|
|
@@ -320,16 +363,16 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
320
363
|
'screen.timestamp': event.timestamp,
|
|
321
364
|
'screen.platform': 'react-native',
|
|
322
365
|
},
|
|
323
|
-
})
|
|
366
|
+
});
|
|
324
367
|
|
|
325
368
|
if (event.metadata) {
|
|
326
369
|
Object.entries(event.metadata).forEach(([key, value]) => {
|
|
327
|
-
span.setAttribute(`screen.metadata.${key}`, String(value))
|
|
328
|
-
})
|
|
370
|
+
span.setAttribute(`screen.metadata.${key}`, String(value));
|
|
371
|
+
});
|
|
329
372
|
}
|
|
330
373
|
|
|
331
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
332
|
-
span.end()
|
|
374
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
375
|
+
span.end();
|
|
333
376
|
} catch (error) {
|
|
334
377
|
// Failed to record OpenTelemetry span for screen - silently continue
|
|
335
378
|
}
|
|
@@ -344,11 +387,11 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
344
387
|
'screen.error.message': error.message,
|
|
345
388
|
'screen.timestamp': Date.now(),
|
|
346
389
|
},
|
|
347
|
-
})
|
|
390
|
+
});
|
|
348
391
|
|
|
349
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
|
|
350
|
-
span.recordException(error)
|
|
351
|
-
span.end()
|
|
392
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
393
|
+
span.recordException(error);
|
|
394
|
+
span.end();
|
|
352
395
|
} catch (spanError) {
|
|
353
396
|
// Failed to record error span - silently continue
|
|
354
397
|
}
|
|
@@ -357,45 +400,48 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
357
400
|
async captureSpecificElement(
|
|
358
401
|
elementRef: any,
|
|
359
402
|
_options?: {
|
|
360
|
-
format?: 'png' | 'jpg' | 'webp'
|
|
361
|
-
quality?: number
|
|
362
|
-
}
|
|
403
|
+
format?: 'png' | 'jpg' | 'webp';
|
|
404
|
+
quality?: number;
|
|
405
|
+
}
|
|
363
406
|
): Promise<string | null> {
|
|
364
407
|
try {
|
|
365
408
|
if (isWeb || !captureRef) {
|
|
366
|
-
logger.warn(
|
|
367
|
-
|
|
409
|
+
logger.warn(
|
|
410
|
+
'ScreenRecorder',
|
|
411
|
+
'Element capture not available on web platform'
|
|
412
|
+
);
|
|
413
|
+
return null;
|
|
368
414
|
}
|
|
369
|
-
return await captureRef(elementRef)
|
|
415
|
+
return await captureRef(elementRef);
|
|
370
416
|
} catch (error) {
|
|
371
417
|
// Failed to capture specific element - silently continue
|
|
372
|
-
return null
|
|
418
|
+
return null;
|
|
373
419
|
}
|
|
374
420
|
}
|
|
375
421
|
|
|
376
422
|
// Configuration methods
|
|
377
423
|
setCaptureInterval(intervalMs: number): void {
|
|
378
424
|
if (this.captureInterval) {
|
|
379
|
-
clearInterval(this.captureInterval)
|
|
425
|
+
clearInterval(this.captureInterval);
|
|
380
426
|
}
|
|
381
427
|
|
|
382
428
|
if (this.isRecording) {
|
|
383
429
|
this.captureInterval = setInterval(() => {
|
|
384
|
-
this._captureScreen()
|
|
385
|
-
}, intervalMs)
|
|
430
|
+
this._captureScreen();
|
|
431
|
+
}, intervalMs);
|
|
386
432
|
}
|
|
387
433
|
}
|
|
388
434
|
|
|
389
435
|
setCaptureQuality(quality: number): void {
|
|
390
|
-
this.captureQuality = Math.max(0.1, Math.min(1.0, quality))
|
|
436
|
+
this.captureQuality = Math.max(0.1, Math.min(1.0, quality));
|
|
391
437
|
}
|
|
392
438
|
|
|
393
439
|
setCaptureFormat(format: 'png' | 'jpg'): void {
|
|
394
|
-
this.captureFormat = format
|
|
440
|
+
this.captureFormat = format;
|
|
395
441
|
}
|
|
396
442
|
|
|
397
443
|
setMaxCaptures(max: number): void {
|
|
398
|
-
this.maxCaptures = Math.max(1, max)
|
|
444
|
+
this.maxCaptures = Math.max(1, max);
|
|
399
445
|
}
|
|
400
446
|
|
|
401
447
|
/**
|
|
@@ -403,7 +449,7 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
403
449
|
* @param enabled - Whether to enable change detection
|
|
404
450
|
*/
|
|
405
451
|
setChangeDetection(enabled: boolean): void {
|
|
406
|
-
this.enableChangeDetection = enabled
|
|
452
|
+
this.enableChangeDetection = enabled;
|
|
407
453
|
}
|
|
408
454
|
|
|
409
455
|
/**
|
|
@@ -411,7 +457,7 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
411
457
|
* @param size - Number of characters to sample from each part of the image
|
|
412
458
|
*/
|
|
413
459
|
setHashSampleSize(size: number): void {
|
|
414
|
-
this.hashSampleSize = Math.max(10, Math.min(1000, size))
|
|
460
|
+
this.hashSampleSize = Math.max(10, Math.min(1000, size));
|
|
415
461
|
}
|
|
416
462
|
|
|
417
463
|
// Performance monitoring
|
|
@@ -426,14 +472,14 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
426
472
|
performance: 'monitoring',
|
|
427
473
|
captureCount: this.captureCount,
|
|
428
474
|
},
|
|
429
|
-
}
|
|
475
|
+
};
|
|
430
476
|
|
|
431
|
-
this.events.push(event)
|
|
432
|
-
this._sendEvent(event)
|
|
433
|
-
this._recordOpenTelemetrySpan(event)
|
|
434
|
-
this.events.push(event)
|
|
435
|
-
this._sendEvent(event)
|
|
436
|
-
this._recordOpenTelemetrySpan(event)
|
|
477
|
+
this.events.push(event);
|
|
478
|
+
this._sendEvent(event);
|
|
479
|
+
this._recordOpenTelemetrySpan(event);
|
|
480
|
+
this.events.push(event);
|
|
481
|
+
this._sendEvent(event);
|
|
482
|
+
this._recordOpenTelemetrySpan(event);
|
|
437
483
|
}
|
|
438
484
|
|
|
439
485
|
// Error tracking
|
|
@@ -449,25 +495,25 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
449
495
|
screenName,
|
|
450
496
|
captureCount: this.captureCount,
|
|
451
497
|
},
|
|
452
|
-
}
|
|
498
|
+
};
|
|
453
499
|
|
|
454
|
-
this.events.push(event)
|
|
455
|
-
this._sendEvent(event)
|
|
456
|
-
this._recordOpenTelemetrySpan(event)
|
|
457
|
-
this.events.push(event)
|
|
458
|
-
this._sendEvent(event)
|
|
459
|
-
this._recordScreenCaptureError(error)
|
|
500
|
+
this.events.push(event);
|
|
501
|
+
this._sendEvent(event);
|
|
502
|
+
this._recordOpenTelemetrySpan(event);
|
|
503
|
+
this.events.push(event);
|
|
504
|
+
this._sendEvent(event);
|
|
505
|
+
this._recordScreenCaptureError(error);
|
|
460
506
|
}
|
|
461
507
|
|
|
462
508
|
// Get recorded events
|
|
463
509
|
getEvents(): ScreenEvent[] {
|
|
464
|
-
return [...this.events]
|
|
510
|
+
return [...this.events];
|
|
465
511
|
}
|
|
466
512
|
|
|
467
513
|
// Clear events
|
|
468
514
|
clearEvents(): void {
|
|
469
|
-
this.events = []
|
|
470
|
-
this.captureCount = 0
|
|
515
|
+
this.events = [];
|
|
516
|
+
this.captureCount = 0;
|
|
471
517
|
}
|
|
472
518
|
|
|
473
519
|
// Get screen capture statistics
|
|
@@ -477,25 +523,30 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
477
523
|
totalEvents: this.events.length,
|
|
478
524
|
averageCaptureTime: 0,
|
|
479
525
|
successRate: 0,
|
|
480
|
-
}
|
|
526
|
+
};
|
|
481
527
|
|
|
482
528
|
if (this.events.length > 0) {
|
|
483
|
-
const captureTimes = this.events
|
|
529
|
+
const captureTimes = this.events
|
|
530
|
+
.map((event) => event.metadata?.captureTime || 0)
|
|
531
|
+
.filter((time) => time > 0);
|
|
484
532
|
|
|
485
533
|
if (captureTimes.length > 0) {
|
|
486
|
-
stats.averageCaptureTime =
|
|
534
|
+
stats.averageCaptureTime =
|
|
535
|
+
captureTimes.reduce((a, b) => a + b, 0) / captureTimes.length;
|
|
487
536
|
}
|
|
488
537
|
|
|
489
|
-
const successfulCaptures = this.events.filter(
|
|
490
|
-
|
|
538
|
+
const successfulCaptures = this.events.filter(
|
|
539
|
+
(event) => event.dataUrl
|
|
540
|
+
).length;
|
|
541
|
+
stats.successRate = (successfulCaptures / this.events.length) * 100;
|
|
491
542
|
}
|
|
492
543
|
|
|
493
|
-
return stats
|
|
544
|
+
return stats;
|
|
494
545
|
}
|
|
495
546
|
|
|
496
547
|
// Get recording status
|
|
497
548
|
isRecordingEnabled(): boolean {
|
|
498
|
-
return this.isRecording
|
|
549
|
+
return this.isRecording;
|
|
499
550
|
}
|
|
500
551
|
|
|
501
552
|
// Get current configuration
|
|
@@ -506,13 +557,13 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
506
557
|
captureFormat: this.captureFormat,
|
|
507
558
|
maxCaptures: this.maxCaptures,
|
|
508
559
|
screenDimensions: this.screenDimensions,
|
|
509
|
-
}
|
|
560
|
+
};
|
|
510
561
|
}
|
|
511
562
|
|
|
512
563
|
// Shutdown
|
|
513
564
|
shutdown(): void {
|
|
514
|
-
this.stop()
|
|
515
|
-
this.clearEvents()
|
|
565
|
+
this.stop();
|
|
566
|
+
this.clearEvents();
|
|
516
567
|
// Screen recorder shutdown
|
|
517
568
|
}
|
|
518
569
|
|
|
@@ -521,7 +572,7 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
521
572
|
* @param ref - React Native View ref for screen capture
|
|
522
573
|
*/
|
|
523
574
|
setViewShotRef(ref: any): void {
|
|
524
|
-
this.viewShotRef = ref
|
|
575
|
+
this.viewShotRef = ref;
|
|
525
576
|
}
|
|
526
577
|
|
|
527
578
|
/**
|
|
@@ -531,10 +582,10 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
531
582
|
*/
|
|
532
583
|
forceCapture(timestamp?: number): void {
|
|
533
584
|
if (!this.isRecording) {
|
|
534
|
-
return
|
|
585
|
+
return;
|
|
535
586
|
}
|
|
536
587
|
|
|
537
|
-
this._captureScreen(timestamp)
|
|
588
|
+
this._captureScreen(timestamp);
|
|
538
589
|
}
|
|
539
590
|
|
|
540
591
|
/**
|
|
@@ -543,7 +594,7 @@ export class ScreenRecorder implements EventRecorder {
|
|
|
543
594
|
*/
|
|
544
595
|
recordEvent(event: any): void {
|
|
545
596
|
if (this.eventRecorder) {
|
|
546
|
-
this.eventRecorder.recordEvent(event)
|
|
597
|
+
this.eventRecorder.recordEvent(event);
|
|
547
598
|
}
|
|
548
599
|
}
|
|
549
600
|
}
|