@multiplayer-app/session-recorder-react-native 0.0.1-alpha.7 → 0.0.1-alpha.9
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/dist/components/GestureCaptureWrapper.d.ts +6 -0
- package/dist/components/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper.js.map +1 -0
- package/dist/context/SessionRecorderContext.d.ts +1 -2
- package/dist/context/SessionRecorderContext.js +1 -1
- package/dist/context/SessionRecorderContext.js.map +1 -1
- package/dist/otel/helpers.js +1 -1
- package/dist/otel/helpers.js.map +1 -1
- package/dist/otel/instrumentations/index.js +1 -1
- package/dist/otel/instrumentations/index.js.map +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
- package/dist/patch/xhr.js +1 -1
- package/dist/patch/xhr.js.map +1 -1
- package/dist/recorder/eventExporter.js +1 -1
- package/dist/recorder/eventExporter.js.map +1 -1
- package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
- package/dist/recorder/gestureHandlerRecorder.js +1 -0
- package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
- package/dist/recorder/gestureRecorder.d.ts +13 -1
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +1 -2
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.js.map +1 -1
- package/dist/recorder/navigationTracker.js +1 -1
- package/dist/recorder/navigationTracker.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +31 -6
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/session-recorder.d.ts +3 -1
- package/dist/session-recorder.js +1 -1
- package/dist/session-recorder.js.map +1 -1
- package/dist/types/index.d.ts +32 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/rrweb.d.ts +10 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +112 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rrweb-events.d.ts +65 -0
- package/dist/utils/rrweb-events.js +1 -0
- package/dist/utils/rrweb-events.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/src/components/GestureCaptureWrapper.tsx +110 -0
- package/src/context/SessionRecorderContext.tsx +76 -81
- package/src/otel/helpers.ts +2 -1
- package/src/otel/instrumentations/index.ts +5 -4
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +2 -1
- package/src/patch/xhr.ts +2 -2
- package/src/recorder/eventExporter.ts +4 -1
- package/src/recorder/gestureHandlerRecorder.ts +157 -0
- package/src/recorder/gestureRecorder.ts +93 -19
- package/src/recorder/index.ts +16 -18
- package/src/recorder/navigationTracker.ts +2 -0
- package/src/recorder/screenRecorder.ts +125 -82
- package/src/session-recorder.ts +12 -5
- package/src/types/index.ts +44 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/rrweb-events.ts +311 -0
- package/src/version.ts +1 -1
- package/example-usage.tsx +0 -174
- package/src/types/rrweb.ts +0 -122
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native'
|
|
2
|
+
import { EventType, eventWithTime, NodeType, serializedNodeWithId, IncrementalSource, mutationData } from '@rrweb/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a meta event to mark the start of recording
|
|
6
|
+
* @param sessionId - The session ID
|
|
7
|
+
* @param sessionType - The type of session (PLAIN or CONTINUOUS)
|
|
8
|
+
* @param additionalData - Additional data to include in the meta event
|
|
9
|
+
* @returns MetaEvent object
|
|
10
|
+
*/
|
|
11
|
+
export function createRecordingMetaEvent(): eventWithTime {
|
|
12
|
+
const screenDimensions = Dimensions.get('window')
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
type: EventType.Meta,
|
|
16
|
+
data: {
|
|
17
|
+
href: 'https://go.multiplayer.app/session-recorder-react-native',
|
|
18
|
+
width: screenDimensions.width,
|
|
19
|
+
height: screenDimensions.height,
|
|
20
|
+
},
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a full snapshot event with the given base64 image
|
|
27
|
+
* @param base64Image - Base64 encoded image data
|
|
28
|
+
* @param width - Screen width
|
|
29
|
+
* @param height - Screen height
|
|
30
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
31
|
+
* @param nodeIdCounter - Starting node ID counter (will be modified)
|
|
32
|
+
* @returns Full snapshot event
|
|
33
|
+
*/
|
|
34
|
+
export function createFullSnapshotEvent(
|
|
35
|
+
base64Image: string,
|
|
36
|
+
width: number,
|
|
37
|
+
height: number,
|
|
38
|
+
captureFormat: string = 'jpg',
|
|
39
|
+
nodeIdCounter: { current: number },
|
|
40
|
+
): eventWithTime {
|
|
41
|
+
// Create a virtual DOM node representing the screen as an image
|
|
42
|
+
const imageNodeId = nodeIdCounter.current++
|
|
43
|
+
const imageNode: serializedNodeWithId = {
|
|
44
|
+
type: NodeType.Element,
|
|
45
|
+
id: imageNodeId,
|
|
46
|
+
tagName: 'img',
|
|
47
|
+
attributes: {
|
|
48
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
49
|
+
width: width.toString(),
|
|
50
|
+
height: height.toString(),
|
|
51
|
+
style: `width: ${width}px; height: ${height}px;`,
|
|
52
|
+
},
|
|
53
|
+
childNodes: [],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create the root container
|
|
57
|
+
const rootNode: serializedNodeWithId = {
|
|
58
|
+
type: NodeType.Element,
|
|
59
|
+
id: nodeIdCounter.current++,
|
|
60
|
+
tagName: 'div',
|
|
61
|
+
attributes: {
|
|
62
|
+
style: `width: ${width}px; height: ${height}px; position: relative;`,
|
|
63
|
+
},
|
|
64
|
+
childNodes: [imageNode],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const domNode: serializedNodeWithId = {
|
|
68
|
+
type: NodeType.Document,
|
|
69
|
+
childNodes: [
|
|
70
|
+
{
|
|
71
|
+
type: NodeType.DocumentType,
|
|
72
|
+
name: 'html',
|
|
73
|
+
publicId: '',
|
|
74
|
+
systemId: '',
|
|
75
|
+
id: nodeIdCounter.current++,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: NodeType.Element,
|
|
79
|
+
tagName: 'html',
|
|
80
|
+
attributes: {},
|
|
81
|
+
childNodes: [
|
|
82
|
+
{
|
|
83
|
+
type: NodeType.Element,
|
|
84
|
+
tagName: 'head',
|
|
85
|
+
attributes: {},
|
|
86
|
+
childNodes: [
|
|
87
|
+
{
|
|
88
|
+
type: NodeType.Element,
|
|
89
|
+
tagName: 'meta',
|
|
90
|
+
attributes: { charset: 'utf-8' },
|
|
91
|
+
childNodes: [],
|
|
92
|
+
id: nodeIdCounter.current++,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: NodeType.Element,
|
|
96
|
+
tagName: 'meta',
|
|
97
|
+
attributes: {
|
|
98
|
+
name: 'viewport',
|
|
99
|
+
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
|
|
100
|
+
},
|
|
101
|
+
childNodes: [],
|
|
102
|
+
id: nodeIdCounter.current++,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
id: nodeIdCounter.current++,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: NodeType.Element,
|
|
109
|
+
tagName: 'body',
|
|
110
|
+
attributes: {},
|
|
111
|
+
childNodes: [rootNode],
|
|
112
|
+
id: nodeIdCounter.current++,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
id: nodeIdCounter.current++,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
id: nodeIdCounter.current++,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
type: EventType.FullSnapshot,
|
|
123
|
+
data: {
|
|
124
|
+
node: domNode,
|
|
125
|
+
initialOffset: { left: 0, top: 0 },
|
|
126
|
+
},
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create an incremental snapshot event with mutation data to update image src
|
|
133
|
+
* @param base64Image - New base64 encoded image data
|
|
134
|
+
* @param imageNodeId - ID of the image node to update
|
|
135
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
136
|
+
* @returns Incremental snapshot event with mutation data
|
|
137
|
+
*/
|
|
138
|
+
export function createIncrementalSnapshotWithImageUpdate(
|
|
139
|
+
base64Image: string,
|
|
140
|
+
imageNodeId: number,
|
|
141
|
+
captureFormat: string = 'jpg',
|
|
142
|
+
): eventWithTime {
|
|
143
|
+
const mutationData: mutationData = {
|
|
144
|
+
source: IncrementalSource.Mutation,
|
|
145
|
+
texts: [],
|
|
146
|
+
attributes: [
|
|
147
|
+
{
|
|
148
|
+
id: imageNodeId,
|
|
149
|
+
attributes: {
|
|
150
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
removes: [],
|
|
155
|
+
adds: [],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
type: EventType.IncrementalSnapshot,
|
|
160
|
+
data: mutationData,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Create a simple image node for React Native screen capture
|
|
167
|
+
* @param base64Image - Base64 encoded image data
|
|
168
|
+
* @param width - Image width
|
|
169
|
+
* @param height - Image height
|
|
170
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
171
|
+
* @param nodeId - Node ID for the image
|
|
172
|
+
* @returns Serialized node with ID
|
|
173
|
+
*/
|
|
174
|
+
export function createImageNode(
|
|
175
|
+
base64Image: string,
|
|
176
|
+
width: number,
|
|
177
|
+
height: number,
|
|
178
|
+
captureFormat: string = 'jpg',
|
|
179
|
+
nodeId: number,
|
|
180
|
+
): serializedNodeWithId {
|
|
181
|
+
return {
|
|
182
|
+
type: NodeType.Element,
|
|
183
|
+
id: nodeId,
|
|
184
|
+
tagName: 'img',
|
|
185
|
+
attributes: {
|
|
186
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
187
|
+
width: width.toString(),
|
|
188
|
+
height: height.toString(),
|
|
189
|
+
style: `width: ${width}px; height: ${height}px;`,
|
|
190
|
+
},
|
|
191
|
+
childNodes: [],
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create a document node for React Native screen capture
|
|
197
|
+
* @param imageNode - The image node to include
|
|
198
|
+
* @param width - Screen width
|
|
199
|
+
* @param height - Screen height
|
|
200
|
+
* @param nodeIdCounter - Node ID counter (will be modified)
|
|
201
|
+
* @returns Document node
|
|
202
|
+
*/
|
|
203
|
+
export function createDocumentNode(
|
|
204
|
+
imageNode: serializedNodeWithId,
|
|
205
|
+
width: number,
|
|
206
|
+
height: number,
|
|
207
|
+
nodeIdCounter: { current: number },
|
|
208
|
+
): serializedNodeWithId {
|
|
209
|
+
// Create the root container
|
|
210
|
+
const rootNode: serializedNodeWithId = {
|
|
211
|
+
type: NodeType.Element,
|
|
212
|
+
id: nodeIdCounter.current++,
|
|
213
|
+
tagName: 'div',
|
|
214
|
+
attributes: {
|
|
215
|
+
style: `width: ${width}px; height: ${height}px; position: relative;`,
|
|
216
|
+
},
|
|
217
|
+
childNodes: [imageNode],
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
type: NodeType.Document,
|
|
222
|
+
childNodes: [
|
|
223
|
+
{
|
|
224
|
+
type: NodeType.DocumentType,
|
|
225
|
+
name: 'html',
|
|
226
|
+
publicId: '',
|
|
227
|
+
systemId: '',
|
|
228
|
+
id: nodeIdCounter.current++,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
type: NodeType.Element,
|
|
232
|
+
tagName: 'html',
|
|
233
|
+
attributes: {},
|
|
234
|
+
childNodes: [
|
|
235
|
+
{
|
|
236
|
+
type: NodeType.Element,
|
|
237
|
+
tagName: 'head',
|
|
238
|
+
attributes: {},
|
|
239
|
+
childNodes: [
|
|
240
|
+
{
|
|
241
|
+
type: NodeType.Element,
|
|
242
|
+
tagName: 'meta',
|
|
243
|
+
attributes: { charset: 'utf-8' },
|
|
244
|
+
childNodes: [],
|
|
245
|
+
id: nodeIdCounter.current++,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: NodeType.Element,
|
|
249
|
+
tagName: 'meta',
|
|
250
|
+
attributes: {
|
|
251
|
+
name: 'viewport',
|
|
252
|
+
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
|
|
253
|
+
},
|
|
254
|
+
childNodes: [],
|
|
255
|
+
id: nodeIdCounter.current++,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
id: nodeIdCounter.current++,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: NodeType.Element,
|
|
262
|
+
tagName: 'body',
|
|
263
|
+
attributes: {},
|
|
264
|
+
childNodes: [rootNode],
|
|
265
|
+
id: nodeIdCounter.current++,
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
id: nodeIdCounter.current++,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
id: nodeIdCounter.current++,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Generate a simple hash for screen comparison
|
|
277
|
+
* This is a lightweight hash that focuses on the beginning and end of the base64 string
|
|
278
|
+
* to detect changes without doing a full comparison
|
|
279
|
+
* @param base64Image - Base64 encoded image
|
|
280
|
+
* @param sampleSize - Number of characters to sample from each part
|
|
281
|
+
* @returns Hash string for comparison
|
|
282
|
+
*/
|
|
283
|
+
export function generateScreenHash(base64Image: string, sampleSize: number = 100): string {
|
|
284
|
+
// Use a simple hash that samples the beginning, middle, and end of the base64 string
|
|
285
|
+
// This is much faster than comparing the entire string
|
|
286
|
+
const start = base64Image.substring(0, sampleSize)
|
|
287
|
+
const middle = base64Image.substring(
|
|
288
|
+
Math.floor(base64Image.length / 2) - sampleSize / 2,
|
|
289
|
+
Math.floor(base64Image.length / 2) + sampleSize / 2,
|
|
290
|
+
)
|
|
291
|
+
const end = base64Image.substring(base64Image.length - sampleSize)
|
|
292
|
+
|
|
293
|
+
// Combine samples and create a simple hash
|
|
294
|
+
const combined = start + middle + end
|
|
295
|
+
return simpleHash(combined)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Simple hash function for string comparison
|
|
300
|
+
* @param str - String to hash
|
|
301
|
+
* @returns Hash value as string
|
|
302
|
+
*/
|
|
303
|
+
export function simpleHash(str: string): string {
|
|
304
|
+
let hash = 0
|
|
305
|
+
for (let i = 0; i < str.length; i++) {
|
|
306
|
+
const char = str.charCodeAt(i)
|
|
307
|
+
hash = (hash << 5) - hash + char
|
|
308
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
309
|
+
}
|
|
310
|
+
return Math.abs(hash).toString(36)
|
|
311
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.0.1-alpha.
|
|
1
|
+
export const version = "0.0.1-alpha.9"
|
package/example-usage.tsx
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example usage of the React Native Session Recorder with rrweb integration
|
|
3
|
-
* This file demonstrates how to use the updated session recorder system
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from 'react'
|
|
7
|
-
import { View, Text, Button, StyleSheet, Alert } from 'react-native'
|
|
8
|
-
import { SessionRecorderProvider, useSessionRecorder } from './src/context/SessionRecorderContext'
|
|
9
|
-
import { EventType } from './src/types'
|
|
10
|
-
|
|
11
|
-
// Example app component
|
|
12
|
-
function App() {
|
|
13
|
-
return (
|
|
14
|
-
<SessionRecorderProvider
|
|
15
|
-
options={{
|
|
16
|
-
apiKey: 'your-api-key-here',
|
|
17
|
-
version: '1.0.0',
|
|
18
|
-
application: 'ExampleApp',
|
|
19
|
-
environment: 'development',
|
|
20
|
-
recordScreen: true,
|
|
21
|
-
recordGestures: true,
|
|
22
|
-
recordNavigation: true
|
|
23
|
-
}}
|
|
24
|
-
>
|
|
25
|
-
<MainContent />
|
|
26
|
-
</SessionRecorderProvider>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Main content component that will be wrapped by TouchEventCapture
|
|
31
|
-
function MainContent() {
|
|
32
|
-
const { client } = useSessionRecorder()
|
|
33
|
-
|
|
34
|
-
const handleStartSession = () => {
|
|
35
|
-
try {
|
|
36
|
-
client.start()
|
|
37
|
-
Alert.alert('Session Started', 'Recording has begun!')
|
|
38
|
-
} catch (error) {
|
|
39
|
-
Alert.alert('Error', `Failed to start session: ${error}`)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const handleStopSession = () => {
|
|
44
|
-
try {
|
|
45
|
-
client.stop()
|
|
46
|
-
Alert.alert('Session Stopped', 'Recording has ended!')
|
|
47
|
-
} catch (error) {
|
|
48
|
-
Alert.alert('Error', `Failed to stop session: ${error}`)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const handleRecordCustomEvent = () => {
|
|
53
|
-
// Example of recording a custom rrweb event
|
|
54
|
-
const customEvent = {
|
|
55
|
-
type: EventType.Custom,
|
|
56
|
-
data: {
|
|
57
|
-
customType: 'button_click',
|
|
58
|
-
buttonId: 'example_button',
|
|
59
|
-
timestamp: Date.now()
|
|
60
|
-
},
|
|
61
|
-
timestamp: Date.now()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
client.recordEvent(customEvent)
|
|
65
|
-
Alert.alert('Custom Event', 'Custom event recorded!')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const handleGetRecordingStats = () => {
|
|
69
|
-
// This would need to be implemented in the SessionRecorder
|
|
70
|
-
// const stats = client.getRecordingStats()
|
|
71
|
-
// Alert.alert('Recording Stats', `Events recorded: ${stats.totalEvents}`)
|
|
72
|
-
Alert.alert('Recording Stats', 'Feature coming soon!')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<View style={styles.container}>
|
|
77
|
-
<Text style={styles.title}>Session Recorder Example</Text>
|
|
78
|
-
<Text style={styles.subtitle}>This app demonstrates rrweb-compatible session recording for React Native</Text>
|
|
79
|
-
|
|
80
|
-
<View style={styles.buttonContainer}>
|
|
81
|
-
<Button title='Start Recording' onPress={handleStartSession} color='#4CAF50' />
|
|
82
|
-
</View>
|
|
83
|
-
|
|
84
|
-
<View style={styles.buttonContainer}>
|
|
85
|
-
<Button title='Stop Recording' onPress={handleStopSession} color='#F44336' />
|
|
86
|
-
</View>
|
|
87
|
-
|
|
88
|
-
<View style={styles.buttonContainer}>
|
|
89
|
-
<Button title='Record Custom Event' onPress={handleRecordCustomEvent} color='#2196F3' />
|
|
90
|
-
</View>
|
|
91
|
-
|
|
92
|
-
<View style={styles.buttonContainer}>
|
|
93
|
-
<Button title='Get Recording Stats' onPress={handleGetRecordingStats} color='#FF9800' />
|
|
94
|
-
</View>
|
|
95
|
-
|
|
96
|
-
<Text style={styles.instructions}>
|
|
97
|
-
Recording is now AUTOMATIC! When you start a session, the system will automatically:
|
|
98
|
-
{'\n'}• Capture screen snapshots periodically
|
|
99
|
-
{'\n'}• Record all touch interactions (start, move, end)
|
|
100
|
-
{'\n'}• Generate rrweb-compatible events
|
|
101
|
-
{'\n'}• No manual setup required!
|
|
102
|
-
</Text>
|
|
103
|
-
</View>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const styles = StyleSheet.create({
|
|
108
|
-
container: {
|
|
109
|
-
flex: 1,
|
|
110
|
-
padding: 20,
|
|
111
|
-
backgroundColor: '#f5f5f5',
|
|
112
|
-
justifyContent: 'center'
|
|
113
|
-
},
|
|
114
|
-
title: {
|
|
115
|
-
fontSize: 24,
|
|
116
|
-
fontWeight: 'bold',
|
|
117
|
-
textAlign: 'center',
|
|
118
|
-
marginBottom: 10,
|
|
119
|
-
color: '#333'
|
|
120
|
-
},
|
|
121
|
-
subtitle: {
|
|
122
|
-
fontSize: 16,
|
|
123
|
-
textAlign: 'center',
|
|
124
|
-
marginBottom: 30,
|
|
125
|
-
color: '#666'
|
|
126
|
-
},
|
|
127
|
-
buttonContainer: {
|
|
128
|
-
marginVertical: 10
|
|
129
|
-
},
|
|
130
|
-
instructions: {
|
|
131
|
-
fontSize: 14,
|
|
132
|
-
textAlign: 'center',
|
|
133
|
-
marginTop: 30,
|
|
134
|
-
color: '#888',
|
|
135
|
-
fontStyle: 'italic'
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
export default App
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* AUTOMATIC RECORDING INTEGRATION:
|
|
143
|
-
*
|
|
144
|
-
* 1. Screen Capture (AUTOMATIC with react-native-view-shot):
|
|
145
|
-
* - Install: npm install react-native-view-shot
|
|
146
|
-
* - iOS: Add to Podfile and run pod install
|
|
147
|
-
* - Android: No additional setup needed
|
|
148
|
-
* - Screen capture happens automatically when session starts
|
|
149
|
-
* - Captures the same View element that handles touch events
|
|
150
|
-
*
|
|
151
|
-
* 2. Touch Events (AUTOMATIC):
|
|
152
|
-
* - TouchEventCapture automatically wraps your app content
|
|
153
|
-
* - Touch events are automatically converted to rrweb MouseInteraction events
|
|
154
|
-
* - Coordinates are automatically mapped from React Native to rrweb format
|
|
155
|
-
* - No manual setup required!
|
|
156
|
-
*
|
|
157
|
-
* 3. Event Recording (AUTOMATIC):
|
|
158
|
-
* - All events are automatically stored in the RecorderReactNativeSDK
|
|
159
|
-
* - Events can be exported using getRecordedEvents()
|
|
160
|
-
* - Events are compatible with standard rrweb players
|
|
161
|
-
* - Recording starts/stops automatically with session
|
|
162
|
-
*
|
|
163
|
-
* 4. ViewShot Integration (AUTOMATIC):
|
|
164
|
-
* - The TouchEventCapture View is automatically used for screen capture
|
|
165
|
-
* - No need to manually set up viewshot refs
|
|
166
|
-
* - Screen captures include all touch interactions
|
|
167
|
-
* - Perfect synchronization between touch events and screen captures
|
|
168
|
-
*
|
|
169
|
-
* 5. Customization (Optional):
|
|
170
|
-
* - Modify capture intervals in ScreenRecorder
|
|
171
|
-
* - Adjust touch event throttling in GestureRecorder
|
|
172
|
-
* - Add custom event types as needed
|
|
173
|
-
* - All core functionality works automatically out of the box
|
|
174
|
-
*/
|
package/src/types/rrweb.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RRWeb event types for React Native session recording
|
|
3
|
-
* Based on rrweb specification: https://github.com/rrweb-io/rrweb
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export enum EventType {
|
|
7
|
-
DomContentLoaded = 0,
|
|
8
|
-
Load = 1,
|
|
9
|
-
FullSnapshot = 2,
|
|
10
|
-
IncrementalSnapshot = 3,
|
|
11
|
-
Meta = 4,
|
|
12
|
-
Custom = 5,
|
|
13
|
-
Plugin = 6,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export enum IncrementalSource {
|
|
17
|
-
Mutation = 0,
|
|
18
|
-
MouseMove = 1,
|
|
19
|
-
MouseInteraction = 2,
|
|
20
|
-
Scroll = 3,
|
|
21
|
-
ViewportResize = 4,
|
|
22
|
-
Input = 5,
|
|
23
|
-
TouchMove = 6,
|
|
24
|
-
MediaInteraction = 7,
|
|
25
|
-
StyleSheetRule = 8,
|
|
26
|
-
CanvasMutation = 9,
|
|
27
|
-
Font = 10,
|
|
28
|
-
Selection = 11,
|
|
29
|
-
AdoptedStyleSheet = 12,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export enum MouseInteractionType {
|
|
33
|
-
MouseUp = 0,
|
|
34
|
-
MouseDown = 1,
|
|
35
|
-
Click = 2,
|
|
36
|
-
ContextMenu = 3,
|
|
37
|
-
DblClick = 4,
|
|
38
|
-
Focus = 5,
|
|
39
|
-
Blur = 6,
|
|
40
|
-
TouchStart = 7,
|
|
41
|
-
TouchMove = 8,
|
|
42
|
-
TouchEnd = 9,
|
|
43
|
-
TouchCancel = 10,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface MouseInteractionData {
|
|
47
|
-
type: MouseInteractionType
|
|
48
|
-
id: number
|
|
49
|
-
x: number
|
|
50
|
-
y: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface TouchInteractionData {
|
|
54
|
-
type: MouseInteractionType
|
|
55
|
-
id: number
|
|
56
|
-
x: number
|
|
57
|
-
y: number
|
|
58
|
-
pressure?: number
|
|
59
|
-
target?: string
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface FullSnapshotEvent {
|
|
63
|
-
type: EventType.FullSnapshot
|
|
64
|
-
data: {
|
|
65
|
-
node: SerializedNodeWithId
|
|
66
|
-
initialOffset: {
|
|
67
|
-
left: number
|
|
68
|
-
top: number
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
timestamp: number
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface IncrementalSnapshotEvent {
|
|
75
|
-
type: EventType.IncrementalSnapshot
|
|
76
|
-
data: {
|
|
77
|
-
source: IncrementalSource
|
|
78
|
-
id?: number
|
|
79
|
-
x?: number
|
|
80
|
-
y?: number
|
|
81
|
-
type?: MouseInteractionType
|
|
82
|
-
} & Partial<MouseInteractionData> & Partial<TouchInteractionData>
|
|
83
|
-
timestamp: number
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface SerializedNodeWithId {
|
|
87
|
-
type: number
|
|
88
|
-
id: number
|
|
89
|
-
tagName?: string
|
|
90
|
-
attributes?: Record<string, string>
|
|
91
|
-
childNodes?: SerializedNodeWithId[]
|
|
92
|
-
textContent?: string
|
|
93
|
-
style?: Record<string, string>
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface RRWebEvent {
|
|
97
|
-
type: EventType
|
|
98
|
-
data: any
|
|
99
|
-
timestamp: number
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// React Native specific types
|
|
103
|
-
export interface ReactNativeScreenData {
|
|
104
|
-
width: number
|
|
105
|
-
height: number
|
|
106
|
-
base64Image: string
|
|
107
|
-
timestamp: number
|
|
108
|
-
screenName?: string
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface ReactNativeTouchData {
|
|
112
|
-
pageX: number
|
|
113
|
-
pageY: number
|
|
114
|
-
target?: string
|
|
115
|
-
pressure?: number
|
|
116
|
-
timestamp: number
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Event recording interface
|
|
120
|
-
export interface EventRecorder {
|
|
121
|
-
recordEvent(event: RRWebEvent): void
|
|
122
|
-
}
|