@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.
Files changed (69) hide show
  1. package/dist/components/GestureCaptureWrapper.d.ts +6 -0
  2. package/dist/components/GestureCaptureWrapper.js +1 -0
  3. package/dist/components/GestureCaptureWrapper.js.map +1 -0
  4. package/dist/context/SessionRecorderContext.d.ts +1 -2
  5. package/dist/context/SessionRecorderContext.js +1 -1
  6. package/dist/context/SessionRecorderContext.js.map +1 -1
  7. package/dist/otel/helpers.js +1 -1
  8. package/dist/otel/helpers.js.map +1 -1
  9. package/dist/otel/instrumentations/index.js +1 -1
  10. package/dist/otel/instrumentations/index.js.map +1 -1
  11. package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
  12. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
  13. package/dist/patch/xhr.js +1 -1
  14. package/dist/patch/xhr.js.map +1 -1
  15. package/dist/recorder/eventExporter.js +1 -1
  16. package/dist/recorder/eventExporter.js.map +1 -1
  17. package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
  18. package/dist/recorder/gestureHandlerRecorder.js +1 -0
  19. package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
  20. package/dist/recorder/gestureRecorder.d.ts +13 -1
  21. package/dist/recorder/gestureRecorder.js +1 -1
  22. package/dist/recorder/gestureRecorder.js.map +1 -1
  23. package/dist/recorder/index.d.ts +1 -2
  24. package/dist/recorder/index.js +1 -1
  25. package/dist/recorder/index.js.map +1 -1
  26. package/dist/recorder/navigationTracker.js +1 -1
  27. package/dist/recorder/navigationTracker.js.map +1 -1
  28. package/dist/recorder/screenRecorder.d.ts +31 -6
  29. package/dist/recorder/screenRecorder.js +1 -1
  30. package/dist/recorder/screenRecorder.js.map +1 -1
  31. package/dist/session-recorder.d.ts +3 -1
  32. package/dist/session-recorder.js +1 -1
  33. package/dist/session-recorder.js.map +1 -1
  34. package/dist/types/index.d.ts +32 -1
  35. package/dist/types/index.js +1 -1
  36. package/dist/types/index.js.map +1 -1
  37. package/dist/types/rrweb.d.ts +10 -0
  38. package/dist/utils/index.d.ts +2 -0
  39. package/dist/utils/index.js +1 -1
  40. package/dist/utils/index.js.map +1 -1
  41. package/dist/utils/logger.d.ts +112 -0
  42. package/dist/utils/logger.js +1 -0
  43. package/dist/utils/logger.js.map +1 -0
  44. package/dist/utils/rrweb-events.d.ts +65 -0
  45. package/dist/utils/rrweb-events.js +1 -0
  46. package/dist/utils/rrweb-events.js.map +1 -0
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +3 -1
  50. package/src/components/GestureCaptureWrapper.tsx +110 -0
  51. package/src/context/SessionRecorderContext.tsx +76 -81
  52. package/src/otel/helpers.ts +2 -1
  53. package/src/otel/instrumentations/index.ts +5 -4
  54. package/src/otel/instrumentations/reactNativeInstrumentation.ts +2 -1
  55. package/src/patch/xhr.ts +2 -2
  56. package/src/recorder/eventExporter.ts +4 -1
  57. package/src/recorder/gestureHandlerRecorder.ts +157 -0
  58. package/src/recorder/gestureRecorder.ts +93 -19
  59. package/src/recorder/index.ts +16 -18
  60. package/src/recorder/navigationTracker.ts +2 -0
  61. package/src/recorder/screenRecorder.ts +125 -82
  62. package/src/session-recorder.ts +12 -5
  63. package/src/types/index.ts +44 -1
  64. package/src/utils/index.ts +2 -0
  65. package/src/utils/logger.ts +225 -0
  66. package/src/utils/rrweb-events.ts +311 -0
  67. package/src/version.ts +1 -1
  68. package/example-usage.tsx +0 -174
  69. 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.7"
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
- */
@@ -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
- }