@multiplayer-app/session-recorder-react-native 0.0.1-alpha.5 → 0.0.1-alpha.7

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 (70) hide show
  1. package/RRWEB_INTEGRATION.md +336 -0
  2. package/VIEWSHOT_INTEGRATION_TEST.md +123 -0
  3. package/copy-react-native-dist.sh +38 -0
  4. package/dist/config/constants.d.ts +0 -1
  5. package/dist/config/constants.js +1 -1
  6. package/dist/config/constants.js.map +1 -1
  7. package/dist/context/SessionRecorderContext.js +1 -1
  8. package/dist/context/SessionRecorderContext.js.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/otel/helpers.d.ts +4 -4
  11. package/dist/otel/helpers.js +1 -1
  12. package/dist/otel/helpers.js.map +1 -1
  13. package/dist/otel/index.js +1 -1
  14. package/dist/otel/index.js.map +1 -1
  15. package/dist/otel/instrumentations/index.d.ts +2 -3
  16. package/dist/otel/instrumentations/index.js +1 -1
  17. package/dist/otel/instrumentations/index.js.map +1 -1
  18. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
  19. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
  20. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
  21. package/dist/recorder/eventExporter.d.ts +21 -0
  22. package/dist/recorder/eventExporter.js +1 -0
  23. package/dist/recorder/eventExporter.js.map +1 -0
  24. package/dist/recorder/gestureRecorder.d.ts +57 -3
  25. package/dist/recorder/gestureRecorder.js +1 -1
  26. package/dist/recorder/gestureRecorder.js.map +1 -1
  27. package/dist/recorder/index.d.ts +61 -7
  28. package/dist/recorder/index.js +1 -1
  29. package/dist/recorder/index.js.map +1 -1
  30. package/dist/recorder/screenRecorder.d.ts +58 -10
  31. package/dist/recorder/screenRecorder.js +1 -1
  32. package/dist/recorder/screenRecorder.js.map +1 -1
  33. package/dist/services/api.service.js.map +1 -1
  34. package/dist/services/storage.service.js.map +1 -1
  35. package/dist/session-recorder.d.ts +40 -2
  36. package/dist/session-recorder.js +1 -1
  37. package/dist/session-recorder.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/index.js +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/dist/types/rrweb.d.ts +108 -0
  42. package/dist/types/rrweb.js +1 -0
  43. package/dist/types/rrweb.js.map +1 -0
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/example-usage.tsx +174 -0
  47. package/package.json +4 -3
  48. package/src/config/constants.ts +3 -3
  49. package/src/context/SessionRecorderContext.tsx +93 -16
  50. package/src/index.ts +1 -0
  51. package/src/otel/helpers.ts +37 -20
  52. package/src/otel/index.ts +7 -3
  53. package/src/otel/instrumentations/index.ts +79 -38
  54. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +5 -0
  55. package/src/recorder/eventExporter.ts +138 -0
  56. package/src/recorder/gestureRecorder.ts +124 -3
  57. package/src/recorder/index.ts +130 -21
  58. package/src/recorder/screenRecorder.ts +184 -110
  59. package/src/services/api.service.ts +1 -8
  60. package/src/services/storage.service.ts +1 -0
  61. package/src/session-recorder.ts +91 -12
  62. package/src/types/index.ts +2 -1
  63. package/src/types/rrweb.ts +122 -0
  64. package/src/version.ts +1 -1
  65. package/dist/exporters.d.ts +0 -3
  66. package/dist/exporters.js +0 -1
  67. package/dist/exporters.js.map +0 -1
  68. package/dist/sessionRecorder.d.ts +0 -133
  69. package/dist/sessionRecorder.js +0 -1
  70. package/dist/sessionRecorder.js.map +0 -1
@@ -1,22 +1,30 @@
1
- import { ScreenEvent, RecorderConfig } from '../types'
1
+ import { ScreenEvent, RecorderConfig, EventType, FullSnapshotEvent, SerializedNodeWithId, EventRecorder } from '../types'
2
2
  import { trace, SpanStatusCode } from '@opentelemetry/api'
3
- import { Dimensions, AppRegistry } from 'react-native'
4
- import { captureRef, captureScreen } from 'react-native-view-shot'
3
+ import { Dimensions } from 'react-native'
4
+ import { captureRef } from 'react-native-view-shot'
5
5
 
6
- export class ScreenRecorder {
6
+ export class ScreenRecorder implements EventRecorder {
7
7
  private config?: RecorderConfig
8
8
  private isRecording = false
9
9
  private events: ScreenEvent[] = []
10
10
  private captureInterval?: NodeJS.Timeout
11
11
  private captureCount: number = 0
12
12
  private maxCaptures: number = 100 // Limit captures to prevent memory issues
13
- private captureQuality: number = 0.8
14
- private captureFormat: 'png' | 'jpg' | 'webp' = 'png'
13
+ private captureQuality: number = 0.3
14
+ private captureFormat: 'png' | 'jpg' = 'jpg'
15
15
  private screenDimensions: { width: number; height: number } | null = null
16
16
  private currentScreen: string | null = null
17
-
18
- init(config: RecorderConfig): void {
17
+ private eventRecorder?: EventRecorder
18
+ private nodeIdCounter: number = 1
19
+ private viewShotRef: any = null
20
+ private lastScreenCapture: string | null = null
21
+ private lastScreenHash: string | null = null
22
+ private enableChangeDetection: boolean = true
23
+ private hashSampleSize: number = 100
24
+
25
+ init(config: RecorderConfig, eventRecorder?: EventRecorder): void {
19
26
  this.config = config
27
+ this.eventRecorder = eventRecorder
20
28
  this._getScreenDimensions()
21
29
  }
22
30
 
@@ -24,7 +32,13 @@ export class ScreenRecorder {
24
32
  this.isRecording = true
25
33
  this.events = []
26
34
  this.captureCount = 0
35
+ this.lastScreenCapture = null
36
+ this.lastScreenHash = null
27
37
  this._startPeriodicCapture()
38
+
39
+ // Capture initial screen immediately
40
+ this._captureScreen()
41
+
28
42
  // Screen recording started
29
43
  }
30
44
 
@@ -41,7 +55,7 @@ export class ScreenRecorder {
41
55
 
42
56
  resume(): void {
43
57
  this.isRecording = true
44
- this._startPeriodicCapture()
58
+ // this._startPeriodicCapture()
45
59
  }
46
60
 
47
61
  private _getScreenDimensions(): void {
@@ -75,121 +89,146 @@ export class ScreenRecorder {
75
89
  if (!this.isRecording || this.captureCount >= this.maxCaptures) return
76
90
 
77
91
  try {
78
- const startTime = Date.now()
79
- const screenData = await this._performScreenCapture()
80
- const captureTime = Date.now() - startTime
81
-
82
- if (screenData) {
83
- const screenEvent: ScreenEvent = {
84
- screenName: this.currentScreen || 'unknown',
85
- type: 'screenCapture',
86
- timestamp: Date.now(),
87
- dataUrl: screenData,
88
- metadata: {
89
- captureTime,
90
- captureCount: this.captureCount + 1,
91
- quality: this.captureQuality,
92
- format: this.captureFormat,
93
- screenWidth: this.screenDimensions?.width,
94
- screenHeight: this.screenDimensions?.height,
95
- },
96
- }
92
+ const base64Image = await this._captureScreenBase64()
93
+
94
+ if (base64Image) {
95
+ // Check if screen has changed by comparing with previous capture
96
+ const hasChanged = this.enableChangeDetection ? this._hasScreenChanged(base64Image) : true
97
97
 
98
- this.events.push(screenEvent)
99
- this.captureCount++
100
- this._sendEvent(screenEvent)
101
- this._recordOpenTelemetrySpan(screenEvent)
98
+ if (hasChanged) {
99
+ this._createAndEmitFullSnapshotEvent(base64Image)
100
+ this.lastScreenCapture = base64Image
101
+ this.lastScreenHash = this._generateScreenHash(base64Image)
102
+ this.captureCount++
103
+ }
102
104
  }
103
105
  } catch (error) {
104
- // Failed to capture screen - silently continue
105
- this._recordScreenCaptureError(error instanceof Error ? error : new Error(String(error)))
106
+ this._recordScreenCaptureError(error as Error)
106
107
  }
107
108
  }
108
109
 
110
+ private async _captureScreenBase64(): Promise<string | null> {
111
+ try {
112
+ if (!this.viewShotRef) {
113
+ // console.warn('ViewShot ref not available for screen capture')
114
+ return null
115
+ }
109
116
 
117
+ // Capture the screen using react-native-view-shot
118
+ const result = await captureRef(this.viewShotRef, {
119
+ format: this.captureFormat,
120
+ quality: this.captureQuality,
121
+ result: 'base64',
122
+ })
110
123
 
111
- private async _performScreenCapture(): Promise<string | null> {
112
- try {
113
- // Try react-native-view-shot screen capture first
114
- return await this._captureWithViewShot()
124
+ return result
115
125
  } catch (error) {
116
- // react-native-view-shot not available - silently continue
126
+ // console.error('Failed to capture screen:', error)
127
+ return null
117
128
  }
129
+ }
118
130
 
119
- try {
120
- // Try react-native-screenshot
121
- const Screenshot = require('react-native-screenshot')
122
- if (Screenshot && Screenshot.takeScreenshot) {
123
- return await this._captureWithScreenshot()
124
- }
125
- } catch (error) {
126
- // react-native-screenshot not available - silently continue
131
+ private _createAndEmitFullSnapshotEvent(base64Image: string): void {
132
+ if (!this.screenDimensions) return
133
+
134
+ const { width, height } = this.screenDimensions
135
+
136
+ // Create a virtual DOM node representing the screen as an image
137
+ const imageNode: SerializedNodeWithId = {
138
+ type: 1, // Element node
139
+ id: this.nodeIdCounter++,
140
+ tagName: 'img',
141
+ attributes: {
142
+ src: `data:image/${this.captureFormat};base64,${base64Image}`,
143
+ width: width.toString(),
144
+ height: height.toString(),
145
+ style: `width: ${width}px; height: ${height}px;`,
146
+ },
127
147
  }
128
148
 
129
- // Fallback to placeholder
130
- return this._createPlaceholderImage()
131
- }
132
-
133
- private async _captureWithViewShot(): Promise<string | null> {
134
- try {
135
- // Use captureScreen for full screen capture
149
+ // Create the root container
150
+ const rootNode: SerializedNodeWithId = {
151
+ type: 1, // Element node
152
+ id: this.nodeIdCounter++,
153
+ tagName: 'div',
154
+ attributes: {
155
+ style: `width: ${width}px; height: ${height}px; position: relative;`,
156
+ },
157
+ childNodes: [imageNode],
158
+ }
136
159
 
137
- return await captureScreen()
138
- } catch (error) {
139
- // Failed to capture with ViewShot - silently continue
160
+ const fullSnapshotEvent: FullSnapshotEvent = {
161
+ type: EventType.FullSnapshot,
162
+ data: {
163
+ node: rootNode,
164
+ initialOffset: {
165
+ left: 0,
166
+ top: 0,
167
+ },
168
+ },
169
+ timestamp: Date.now(),
140
170
  }
141
- return null
142
- }
143
171
 
144
- private async _captureWithScreenshot(): Promise<string | null> {
145
- try {
146
- const Screenshot = require('react-native-screenshot')
147
- const options = {
148
- format: this.captureFormat,
149
- quality: this.captureQuality,
150
- }
172
+ this.recordEvent(fullSnapshotEvent)
173
+ }
151
174
 
152
- return await Screenshot.takeScreenshot(options)
153
- } catch (error) {
154
- // Failed to capture with Screenshot - silently continue
175
+ /**
176
+ * Check if the screen has changed by comparing with the previous capture
177
+ * @param currentBase64 - Current screen capture as base64
178
+ * @returns true if screen has changed, false otherwise
179
+ */
180
+ private _hasScreenChanged(currentBase64: string): boolean {
181
+ // If this is the first capture, consider it changed
182
+ if (!this.lastScreenCapture) {
183
+ return true
155
184
  }
156
- return null
185
+
186
+ // Generate hash for current capture
187
+ const currentHash = this._generateScreenHash(currentBase64)
188
+
189
+ // Compare with previous hash
190
+ return currentHash !== this.lastScreenHash
157
191
  }
158
192
 
159
- private _getRootViewRef(): any {
160
- try {
161
- // Try to get the root view reference
162
- const appName = AppRegistry.getAppKeys()[0]
163
- return appName ? { current: null } : null
164
- } catch (error) {
165
- // Failed to get root view ref - silently continue
166
- return null
167
- }
193
+ /**
194
+ * Generate a simple hash for screen comparison
195
+ * This is a lightweight hash that focuses on the beginning and end of the base64 string
196
+ * to detect changes without doing a full comparison
197
+ * @param base64Image - Base64 encoded image
198
+ * @returns Hash string for comparison
199
+ */
200
+ private _generateScreenHash(base64Image: string): string {
201
+ // Use a simple hash that samples the beginning, middle, and end of the base64 string
202
+ // This is much faster than comparing the entire string
203
+ const sampleSize = this.hashSampleSize
204
+ const start = base64Image.substring(0, sampleSize)
205
+ const middle = base64Image.substring(
206
+ Math.floor(base64Image.length / 2) - sampleSize / 2,
207
+ Math.floor(base64Image.length / 2) + sampleSize / 2,
208
+ )
209
+ const end = base64Image.substring(base64Image.length - sampleSize)
210
+
211
+ // Combine samples and create a simple hash
212
+ const combined = start + middle + end
213
+ return this._simpleHash(combined)
168
214
  }
169
215
 
170
- private _createPlaceholderImage(): string {
171
- // Create a simple placeholder image data URL
172
- // Note: This won't work in React Native environment, but provides a fallback
173
- const width = this.screenDimensions?.width || 375
174
- const height = this.screenDimensions?.height || 667
216
+ /**
217
+ * Simple hash function for string comparison
218
+ * @param str - String to hash
219
+ * @returns Hash value as string
220
+ */
221
+ private _simpleHash(str: string): string {
222
+ let hash = 0
223
+ for (let i = 0; i < str.length; i++) {
224
+ const char = str.charCodeAt(i)
225
+ hash = ((hash << 5) - hash) + char
226
+ hash = hash & hash // Convert to 32-bit integer
227
+ }
228
+ return Math.abs(hash).toString(36)
229
+ }
175
230
 
176
- // Return a simple data URL for placeholder
177
- const svgContent = `
178
- <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
179
- <rect width="100%" height="100%" fill="#f0f0f0"/>
180
- <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666" font-family="Arial" font-size="16">
181
- Screen Capture Placeholder
182
- </text>
183
- <text x="50%" y="60%" text-anchor="middle" dy=".3em" fill="#666" font-family="Arial" font-size="14">
184
- Captured at ${new Date().toLocaleTimeString()}
185
- </text>
186
- </svg>
187
- `
188
231
 
189
- // Use a simple base64 encoding alternative for React Native
190
- const base64Content = Buffer.from(svgContent, 'utf8').toString('base64')
191
- return `data:image/svg+xml;base64,${base64Content}`
192
- }
193
232
 
194
233
  private _sendEvent(event: ScreenEvent): void {
195
234
  // Screen event recorded
@@ -238,15 +277,6 @@ export class ScreenRecorder {
238
277
  }
239
278
  }
240
279
 
241
- // Manual screen capture methods
242
- async captureScreenNow(): Promise<string | null> {
243
- if (!this.isRecording) {
244
- // Screen recording not active - silently continue
245
- return null
246
- }
247
-
248
- return await this._performScreenCapture()
249
- }
250
280
 
251
281
  async captureSpecificElement(elementRef: any, options?: {
252
282
  format?: 'png' | 'jpg' | 'webp'
@@ -279,7 +309,7 @@ export class ScreenRecorder {
279
309
  this.captureQuality = Math.max(0.1, Math.min(1.0, quality))
280
310
  }
281
311
 
282
- setCaptureFormat(format: 'png' | 'jpg' | 'webp'): void {
312
+ setCaptureFormat(format: 'png' | 'jpg'): void {
283
313
  this.captureFormat = format
284
314
  }
285
315
 
@@ -287,6 +317,22 @@ export class ScreenRecorder {
287
317
  this.maxCaptures = Math.max(1, max)
288
318
  }
289
319
 
320
+ /**
321
+ * Enable or disable change detection
322
+ * @param enabled - Whether to enable change detection
323
+ */
324
+ setChangeDetection(enabled: boolean): void {
325
+ this.enableChangeDetection = enabled
326
+ }
327
+
328
+ /**
329
+ * Set the hash sample size for change detection
330
+ * @param size - Number of characters to sample from each part of the image
331
+ */
332
+ setHashSampleSize(size: number): void {
333
+ this.hashSampleSize = Math.max(10, Math.min(1000, size))
334
+ }
335
+
290
336
  // Performance monitoring
291
337
  recordScreenPerformance(screenName: string, loadTime: number): void {
292
338
  const event: ScreenEvent = {
@@ -386,4 +432,32 @@ export class ScreenRecorder {
386
432
  this.clearEvents()
387
433
  // Screen recorder shutdown
388
434
  }
435
+
436
+ /**
437
+ * Set the viewshot ref for screen capture
438
+ * @param ref - React Native View ref for screen capture
439
+ */
440
+ setViewShotRef(ref: any): void {
441
+ this.viewShotRef = ref
442
+ }
443
+
444
+ /**
445
+ * Force capture screen (useful after touch interactions)
446
+ * This bypasses the change detection and always captures
447
+ */
448
+ forceCapture(): void {
449
+ if (!this.isRecording) return
450
+
451
+ this._captureScreen()
452
+ }
453
+
454
+ /**
455
+ * Record an rrweb event
456
+ * @param event - The rrweb event to record
457
+ */
458
+ recordEvent(event: any): void {
459
+ if (this.eventRecorder) {
460
+ this.eventRecorder.recordEvent(event)
461
+ }
462
+ }
389
463
  }
@@ -1,13 +1,5 @@
1
1
  import { SessionRecorderOptions, IResourceAttributes, ISessionAttributes } from '../types'
2
2
 
3
- // Type definitions for fetch API
4
- type RequestInit = {
5
- method?: string
6
- headers?: Record<string, string>
7
- body?: string
8
- signal?: AbortSignal
9
- }
10
-
11
3
  export interface StartSessionRequest {
12
4
  name?: string
13
5
  stoppedAt?: string | number
@@ -37,6 +29,7 @@ export class ApiService {
37
29
  }
38
30
 
39
31
  init(config: SessionRecorderOptions): void {
32
+
40
33
  this.config = {
41
34
  ...this.config,
42
35
  ...config,
@@ -87,6 +87,7 @@ export class StorageService {
87
87
  saveSessionState(state: SessionState): void {
88
88
  try {
89
89
  StorageService.cache.sessionState = state
90
+
90
91
  AsyncStorage.setItem(StorageService.SESSION_STATE_KEY, state).catch(error => {
91
92
  // Failed to persist session state - silently continue
92
93
  })
@@ -9,7 +9,9 @@ import {
9
9
  SessionState,
10
10
  ISessionRecorder,
11
11
  SessionRecorderConfigs,
12
- SessionRecorderOptions
12
+ SessionRecorderOptions,
13
+ RRWebEvent,
14
+ EventRecorder
13
15
  } from './types'
14
16
  import { getFormattedDate, isSessionActive, getNavigatorInfo } from './utils'
15
17
  import { setMaxCapturingHttpPayloadSize, setShouldRecordHttpData } from './patch/xhr'
@@ -21,7 +23,7 @@ import { ApiService, StartSessionRequest, StopSessionRequest } from './services/
21
23
  // Utility functions for React Native
22
24
 
23
25
 
24
- class SessionRecorder implements ISessionRecorder {
26
+ class SessionRecorder implements ISessionRecorder, EventRecorder {
25
27
  private _isInitialized = false
26
28
  private _configs: SessionRecorderConfigs | null = null
27
29
  private _apiService = new ApiService()
@@ -109,13 +111,13 @@ class SessionRecorder implements ISessionRecorder {
109
111
  */
110
112
  constructor() {
111
113
  // Initialize with stored session data if available
112
- this._loadStoredSessionData()
114
+ StorageService.initialize()
113
115
  }
114
116
 
115
117
  private async _loadStoredSessionData(): Promise<void> {
116
118
  try {
119
+ await StorageService.initialize()
117
120
  const storedData = await this._storageService.getAllSessionData()
118
-
119
121
  if (isSessionActive(storedData.sessionObject, storedData.sessionType === SessionType.CONTINUOUS)) {
120
122
  this.session = storedData.sessionObject
121
123
  this.sessionId = storedData.sessionId
@@ -142,20 +144,22 @@ class SessionRecorder implements ISessionRecorder {
142
144
  */
143
145
  public async init(configs: SessionRecorderOptions): Promise<void> {
144
146
  this._configs = getSessionRecorderConfig({ ...this._configs, ...configs })
145
-
146
147
  this._isInitialized = true
147
148
  this._checkOperation('init')
148
-
149
- await StorageService.initialize()
150
-
149
+ await this._loadStoredSessionData()
151
150
  setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize || DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE)
152
151
  setShouldRecordHttpData(!this._configs.captureBody, this._configs.captureHeaders)
153
152
 
154
- this._tracer.init(this._configs)
155
- this._apiService.init(this._configs)
156
153
 
154
+ try {
155
+ this._apiService.init(this._configs)
156
+ this._tracer.init(this._configs)
157
+
158
+ } catch (error) {
159
+ console.error('Failed to initialize API service:', error)
160
+ }
157
161
  if (this._configs.apiKey) {
158
- this._recorder.init(this._configs)
162
+ this._recorder.init(this._configs, this)
159
163
  }
160
164
 
161
165
  if (this.sessionId && (this.sessionState === SessionState.started || this.sessionState === SessionState.paused)) {
@@ -411,7 +415,7 @@ class SessionRecorder implements ISessionRecorder {
411
415
  private _setupSessionAndStart(session: ISession, configureExporters: boolean = true): void {
412
416
  if (configureExporters && session.tempApiKey) {
413
417
  this._configs!.apiKey = session.tempApiKey
414
- this._recorder.init(this._configs!)
418
+ this._recorder.init(this._configs!, this)
415
419
  this._tracer.init(this._configs!)
416
420
  this._apiService.updateConfigs({ apiKey: this._configs!.apiKey })
417
421
  }
@@ -510,6 +514,81 @@ class SessionRecorder implements ISessionRecorder {
510
514
  this._session.updatedAt = new Date().toISOString()
511
515
  }
512
516
  }
517
+
518
+ /**
519
+ * Record a custom rrweb event
520
+ * Note: Screen capture and touch events are recorded automatically when session is started
521
+ * @param event - The rrweb event to record
522
+ */
523
+ recordEvent(event: RRWebEvent): void {
524
+ if (!this._isInitialized || this.sessionState !== SessionState.started) {
525
+ return
526
+ }
527
+
528
+ // Forward the event to the recorder SDK
529
+ this._recorder.recordEvent(event)
530
+ }
531
+
532
+ /**
533
+ * Record touch start event (internal use - touch recording is automatic)
534
+ * @param x - X coordinate
535
+ * @param y - Y coordinate
536
+ * @param target - Target element identifier
537
+ * @param pressure - Touch pressure
538
+ * @internal
539
+ */
540
+ recordTouchStart(x: number, y: number, target?: string, pressure?: number): void {
541
+ if (!this._isInitialized || this.sessionState !== SessionState.started) {
542
+ return
543
+ }
544
+
545
+ // Forward to gesture recorder
546
+ this._recorder.recordTouchStart(x, y, target, pressure)
547
+ }
548
+
549
+ /**
550
+ * Record touch move event (internal use - touch recording is automatic)
551
+ * @param x - X coordinate
552
+ * @param y - Y coordinate
553
+ * @param target - Target element identifier
554
+ * @param pressure - Touch pressure
555
+ * @internal
556
+ */
557
+ recordTouchMove(x: number, y: number, target?: string, pressure?: number): void {
558
+ if (!this._isInitialized || this.sessionState !== SessionState.started) {
559
+ return
560
+ }
561
+
562
+ // Forward to gesture recorder
563
+ this._recorder.recordTouchMove(x, y, target, pressure)
564
+ }
565
+
566
+ /**
567
+ * Record touch end event (internal use - touch recording is automatic)
568
+ * @param x - X coordinate
569
+ * @param y - Y coordinate
570
+ * @param target - Target element identifier
571
+ * @param pressure - Touch pressure
572
+ * @internal
573
+ */
574
+ recordTouchEnd(x: number, y: number, target?: string, pressure?: number): void {
575
+ if (!this._isInitialized || this.sessionState !== SessionState.started) {
576
+ return
577
+ }
578
+
579
+ // Forward to gesture recorder
580
+ this._recorder.recordTouchEnd(x, y, target, pressure)
581
+ }
582
+
583
+ /**
584
+ * Set the viewshot ref for screen capture
585
+ * @param ref - React Native View ref for screen capture
586
+ */
587
+ setViewShotRef(ref: any): void {
588
+ if (this._recorder) {
589
+ this._recorder.setViewShotRef(ref)
590
+ }
591
+ }
513
592
  }
514
593
 
515
594
  export default new SessionRecorder()
@@ -1,2 +1,3 @@
1
1
  export * from './session-recorder'
2
- export * from './session'
2
+ export * from './session'
3
+ export * from './rrweb'
@@ -0,0 +1,122 @@
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
+ }