@multiplayer-app/session-recorder-react-native 0.0.1-beta.1 → 0.0.1-beta.11

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 (162) hide show
  1. package/SessionRecorderNative.podspec +29 -0
  2. package/android/build.gradle +32 -0
  3. package/copy-react-native-dist.sh +4 -10
  4. package/dist/components/MaskableComponent.d.ts +22 -0
  5. package/dist/components/MaskableComponent.js +1 -0
  6. package/dist/components/MaskableComponent.js.map +1 -0
  7. package/dist/components/MaskableTextInput.d.ts +14 -0
  8. package/dist/components/MaskableTextInput.js +1 -0
  9. package/dist/components/MaskableTextInput.js.map +1 -0
  10. package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
  11. package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
  12. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  13. package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
  14. package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
  15. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  16. package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
  17. package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
  18. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  19. package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
  20. package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
  21. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  22. package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
  23. package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
  24. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  25. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
  26. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
  27. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  28. package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
  29. package/dist/components/SessionRecorderWidget/icons.js +1 -0
  30. package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
  31. package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
  32. package/dist/components/SessionRecorderWidget/index.js +1 -0
  33. package/dist/components/SessionRecorderWidget/index.js.map +1 -0
  34. package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
  35. package/dist/components/SessionRecorderWidget/styles.js +1 -0
  36. package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
  37. package/dist/components/index.d.ts +2 -0
  38. package/dist/components/index.js +1 -1
  39. package/dist/components/index.js.map +1 -1
  40. package/dist/config/defaults.js +1 -1
  41. package/dist/config/defaults.js.map +1 -1
  42. package/dist/config/masking.js +1 -1
  43. package/dist/config/masking.js.map +1 -1
  44. package/dist/context/SessionRecorderContext.d.ts +5 -3
  45. package/dist/context/SessionRecorderContext.js +1 -1
  46. package/dist/context/SessionRecorderContext.js.map +1 -1
  47. package/dist/index.d.ts +0 -1
  48. package/dist/index.js +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/native/ScreenMasking.d.ts +21 -0
  51. package/dist/native/ScreenMasking.js +1 -0
  52. package/dist/native/ScreenMasking.js.map +1 -0
  53. package/dist/native/SessionRecorderNative.d.ts +21 -0
  54. package/dist/native/SessionRecorderNative.js +1 -0
  55. package/dist/native/SessionRecorderNative.js.map +1 -0
  56. package/dist/patch/xhr.js +1 -1
  57. package/dist/patch/xhr.js.map +1 -1
  58. package/dist/recorder/screenRecorder.d.ts +1 -0
  59. package/dist/recorder/screenRecorder.js +1 -1
  60. package/dist/recorder/screenRecorder.js.map +1 -1
  61. package/dist/recorder/screenshotManager.d.ts +10 -0
  62. package/dist/recorder/screenshotManager.js +1 -0
  63. package/dist/recorder/screenshotManager.js.map +1 -0
  64. package/dist/services/screenMaskingService.d.ts +39 -0
  65. package/dist/services/screenMaskingService.js +1 -0
  66. package/dist/services/screenMaskingService.js.map +1 -0
  67. package/dist/services/storage.service.d.ts +18 -2
  68. package/dist/services/storage.service.js +1 -1
  69. package/dist/services/storage.service.js.map +1 -1
  70. package/dist/session-recorder.d.ts +2 -1
  71. package/dist/session-recorder.js +1 -1
  72. package/dist/session-recorder.js.map +1 -1
  73. package/dist/types/session-recorder.d.ts +6 -0
  74. package/dist/types/session-recorder.js.map +1 -1
  75. package/dist/utils/componentRegistry.d.ts +64 -0
  76. package/dist/utils/componentRegistry.js +1 -0
  77. package/dist/utils/componentRegistry.js.map +1 -0
  78. package/dist/utils/nativeModuleTest.d.ts +8 -0
  79. package/dist/utils/nativeModuleTest.js +1 -0
  80. package/dist/utils/nativeModuleTest.js.map +1 -0
  81. package/dist/utils/platform.d.ts +3 -0
  82. package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
  83. package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
  84. package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
  85. package/dist/utils/screenshotMasker.d.ts +96 -0
  86. package/dist/utils/screenshotMasker.js +1 -0
  87. package/dist/utils/screenshotMasker.js.map +1 -0
  88. package/dist/utils/viewHierarchyTracker.d.ts +89 -0
  89. package/dist/utils/viewHierarchyTracker.js +1 -0
  90. package/dist/utils/viewHierarchyTracker.js.map +1 -0
  91. package/ios/SessionRecorderNative.m +17 -0
  92. package/ios/SessionRecorderNative.podspec +26 -0
  93. package/ios/SessionRecorderNative.swift +205 -0
  94. package/package.json +22 -7
  95. package/react-native.config.js +15 -0
  96. package/RRWEB_INTEGRATION.md +0 -336
  97. package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
  98. package/babel.config.js +0 -13
  99. package/dist/components/GestureCaptureWrapper.d.ts +0 -6
  100. package/dist/components/GestureCaptureWrapper.js +0 -1
  101. package/dist/components/GestureCaptureWrapper.js.map +0 -1
  102. package/dist/expo.d.ts +0 -7
  103. package/dist/expo.js +0 -1
  104. package/dist/expo.js.map +0 -1
  105. package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
  106. package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
  107. package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
  108. package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
  109. package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
  110. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
  111. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
  112. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
  113. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
  114. package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
  115. package/dist/recorder/gestureHandlerRecorder.js +0 -1
  116. package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
  117. package/dist/types/rrweb.d.ts +0 -118
  118. package/dist/types/rrweb.js +0 -1
  119. package/dist/types/rrweb.js.map +0 -1
  120. package/scripts/generate-app-metadata.js +0 -173
  121. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
  122. package/src/components/GestureCaptureWrapper/index.ts +0 -1
  123. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
  124. package/src/components/ScreenRecorderView/index.ts +0 -1
  125. package/src/components/index.ts +0 -1
  126. package/src/config/constants.ts +0 -60
  127. package/src/config/defaults.ts +0 -82
  128. package/src/config/index.ts +0 -6
  129. package/src/config/masking.ts +0 -27
  130. package/src/config/session-recorder.ts +0 -55
  131. package/src/config/validators.ts +0 -31
  132. package/src/context/SessionRecorderContext.tsx +0 -75
  133. package/src/expo.ts +0 -11
  134. package/src/index.ts +0 -17
  135. package/src/otel/helpers.ts +0 -275
  136. package/src/otel/index.ts +0 -138
  137. package/src/otel/instrumentations/index.ts +0 -115
  138. package/src/patch/index.ts +0 -1
  139. package/src/patch/xhr.ts +0 -142
  140. package/src/recorder/eventExporter.ts +0 -141
  141. package/src/recorder/gestureRecorder.ts +0 -498
  142. package/src/recorder/index.ts +0 -179
  143. package/src/recorder/navigationTracker.ts +0 -449
  144. package/src/recorder/screenRecorder.ts +0 -498
  145. package/src/services/api.service.ts +0 -203
  146. package/src/services/storage.service.ts +0 -158
  147. package/src/session-recorder.ts +0 -600
  148. package/src/types/expo.d.ts +0 -23
  149. package/src/types/index.ts +0 -28
  150. package/src/types/session-recorder.ts +0 -423
  151. package/src/types/session.ts +0 -65
  152. package/src/utils/app-metadata.ts +0 -31
  153. package/src/utils/index.ts +0 -8
  154. package/src/utils/logger.ts +0 -225
  155. package/src/utils/platform.ts +0 -384
  156. package/src/utils/request-utils.ts +0 -61
  157. package/src/utils/rrweb-events.ts +0 -309
  158. package/src/utils/session.ts +0 -18
  159. package/src/utils/time.ts +0 -17
  160. package/src/utils/type-utils.ts +0 -75
  161. package/src/version.ts +0 -1
  162. package/tsconfig.json +0 -24
@@ -1,141 +0,0 @@
1
- import io, { Socket } from 'socket.io-client'
2
-
3
- import { ISession } from '../types'
4
- import { logger } from '../utils'
5
-
6
- import {
7
- SESSION_ADD_EVENT,
8
- SESSION_AUTO_CREATED,
9
- SESSION_STOPPED_EVENT,
10
- SESSION_SUBSCRIBE_EVENT,
11
- SESSION_UNSUBSCRIBE_EVENT,
12
- } from '../config'
13
-
14
- const MAX_RECONNECTION_ATTEMPTS = 2
15
-
16
- export class EventExporter {
17
- private socket: Socket | null = null
18
- private queue: any[] = []
19
- private isConnecting: boolean = false
20
- private isConnected: boolean = false
21
- private attempts: number = 0
22
- private sessionId: string | null = null
23
-
24
- constructor(private options: { socketUrl: string, apiKey: string }) { }
25
-
26
- private init(): void {
27
- if (this.isConnecting || this.isConnected) return
28
- this.attempts++
29
- this.isConnecting = true
30
- this.socket = io(this.options.socketUrl, {
31
- path: '/v0/radar/ws',
32
- auth: {
33
- 'x-api-key': this.options.apiKey,
34
- },
35
- reconnectionAttempts: 2,
36
- transports: ['websocket'],
37
- })
38
-
39
- // this.socket.on('connect', () => {
40
- // this.isConnecting = false
41
- // this.isConnected = true
42
- // this.usePostMessage = false
43
- // this.flushQueue()
44
- // })
45
-
46
- this.socket.on('ready', () => {
47
- this.isConnecting = false
48
- this.isConnected = true
49
- logger.info('EventExporter', 'Connected to server')
50
- this.flushQueue()
51
- })
52
-
53
- this.socket.on('disconnect', (err: any) => {
54
- this.isConnecting = false
55
- this.isConnected = false
56
- logger.info('EventExporter', 'Disconnected from server')
57
- })
58
-
59
- this.socket.on('connect_error', (err: any) => {
60
- this.isConnecting = false
61
- this.isConnected = false
62
- this.checkReconnectionAttempts()
63
- logger.error('EventExporter', 'Error connecting to server', err)
64
- })
65
-
66
- this.socket.on(SESSION_STOPPED_EVENT, (data: any) => {
67
-
68
- this.unsubscribeFromSession()
69
- })
70
-
71
- this.socket.on(SESSION_AUTO_CREATED, (data: any) => {
72
-
73
- })
74
- }
75
-
76
- private checkReconnectionAttempts(): void {
77
- if (this.attempts >= MAX_RECONNECTION_ATTEMPTS) {
78
-
79
- this.flushQueue()
80
- }
81
- }
82
-
83
-
84
- private flushQueue(): void {
85
- while (this.queue.length > 0 && (this.socket?.connected)) {
86
- const event = this.queue.shift()
87
- if (!event) continue
88
-
89
- if (this.socket?.connected) {
90
- this.socket.emit(event.name, event.data)
91
- }
92
- }
93
- }
94
-
95
- private unsubscribeFromSession() {
96
- const payload = {
97
- debugSessionId: this.sessionId,
98
- }
99
- if (this.socket?.connected) {
100
- this.socket.emit(SESSION_UNSUBSCRIBE_EVENT, payload)
101
- }
102
- }
103
-
104
- public send(event: any): void {
105
- if (this.socket?.connected) {
106
- this.socket.emit(SESSION_ADD_EVENT, event)
107
- } else {
108
- this.queue.push({ data: event, name: SESSION_ADD_EVENT })
109
- this.init()
110
- }
111
- }
112
-
113
- public subscribeToSession(session: ISession): void {
114
- this.sessionId = session.shortId || session._id
115
- const payload = {
116
- projectId: session.project,
117
- workspaceId: session.workspace,
118
- debugSessionId: this.sessionId,
119
- sessionType: session.creationType,
120
- }
121
- if (this.socket?.connected) {
122
- this.socket.emit(SESSION_SUBSCRIBE_EVENT, payload)
123
- } else {
124
- this.queue.push({ data: payload, name: SESSION_SUBSCRIBE_EVENT })
125
- this.init()
126
- }
127
- }
128
-
129
- public close(): void {
130
- if (this.socket?.connected) {
131
- setTimeout(() => {
132
- this.unsubscribeFromSession()
133
- this.attempts = 0
134
- this.isConnected = false
135
- this.isConnecting = false
136
- this.socket?.disconnect()
137
- this.socket = null
138
- }, 500)
139
- }
140
- }
141
- }
@@ -1,498 +0,0 @@
1
- import { GestureEvent, RecorderConfig, EventRecorder } from '../types'
2
- import { trace, SpanStatusCode } from '@opentelemetry/api'
3
- import { Dimensions } from 'react-native'
4
- import { logger } from '../utils'
5
- import { MouseInteractions, eventWithTime, EventType, IncrementalSource } from '@rrweb/types'
6
-
7
- export class GestureRecorder implements EventRecorder {
8
- private config?: RecorderConfig
9
- private isRecording = false
10
- private events: GestureEvent[] = []
11
- private gestureHandlers: Map<string, any> = new Map()
12
- private screenDimensions: { width: number; height: number } | null = null
13
- private lastGestureTime: number = 0
14
- private gestureThrottleMs: number = 50 // Throttle gestures to avoid spam
15
- private lastTouchTime: number = 0
16
- private touchThrottleMs: number = 100 // Throttle touch events to max 10 per second
17
-
18
- // Cyclic call detection
19
- private isRecordingGesture = false
20
- private gestureCallStack: string[] = []
21
- private maxGestureCallDepth = 5
22
- private eventRecorder?: EventRecorder
23
- private imageNodeId: number = 1 // ID of the image node for touch interactions
24
- private screenRecorder?: any // Reference to screen recorder for force capture
25
-
26
- init(config: RecorderConfig, eventRecorder?: EventRecorder, screenRecorder?: any): void {
27
- this.config = config
28
- this.eventRecorder = eventRecorder
29
- this.screenRecorder = screenRecorder
30
- this._getScreenDimensions()
31
- }
32
-
33
- start(): void {
34
- logger.info('GestureRecorder', 'Gesture recording started')
35
- this.isRecording = true
36
- this.events = []
37
- // Gesture recording started
38
- }
39
-
40
- stop(): void {
41
- this.isRecording = false
42
- this._removeGestureHandlers()
43
- // Gesture recording stopped
44
- }
45
-
46
-
47
- private _getScreenDimensions(): void {
48
- try {
49
- this.screenDimensions = Dimensions.get('window')
50
- } catch (error) {
51
- // Failed to get screen dimensions - silently continue
52
- this.screenDimensions = { width: 375, height: 667 } // Default fallback
53
- }
54
- }
55
-
56
-
57
- private _removeGestureHandlers(): void {
58
- this.gestureHandlers.clear()
59
- // Gesture handlers removed
60
- }
61
-
62
- private _recordEvent(event: GestureEvent): void {
63
- if (!this.isRecording) return
64
-
65
- // Throttle gestures to avoid spam
66
- const now = Date.now()
67
- if (now - this.lastGestureTime < this.gestureThrottleMs) {
68
- return
69
- }
70
- this.lastGestureTime = now
71
-
72
- this.events.push(event)
73
- this._sendEvent(event)
74
- this._recordOpenTelemetrySpan(event)
75
- }
76
-
77
-
78
-
79
- private _sendEvent(event: GestureEvent): void {
80
- // Send event to backend or store locally
81
- // Gesture event recorded
82
- }
83
-
84
- private _recordOpenTelemetrySpan(event: GestureEvent): void {
85
- try {
86
- const span = trace.getTracer('@opentelemetry/instrumentation-user-interaction').startSpan(`Gesture.${event.type}`, {
87
- attributes: {
88
- 'gesture.type': event.type,
89
- 'gesture.timestamp': event.timestamp,
90
- 'gesture.platform': 'react-native',
91
- },
92
- })
93
-
94
- if (event.coordinates) {
95
- span.setAttribute('gesture.coordinates.x', event.coordinates.x)
96
- span.setAttribute('gesture.coordinates.y', event.coordinates.y)
97
-
98
- // Calculate relative position
99
- if (this.screenDimensions) {
100
- const relativeX = event.coordinates.x / this.screenDimensions.width
101
- const relativeY = event.coordinates.y / this.screenDimensions.height
102
- span.setAttribute('gesture.coordinates.relative_x', relativeX)
103
- span.setAttribute('gesture.coordinates.relative_y', relativeY)
104
- }
105
- }
106
-
107
- if (event.target) {
108
- span.setAttribute('gesture.target', event.target)
109
- }
110
-
111
- if (event.metadata) {
112
- Object.entries(event.metadata).forEach(([key, value]) => {
113
- span.setAttribute(`gesture.metadata.${key}`, String(value))
114
- })
115
- }
116
-
117
- span.setStatus({ code: SpanStatusCode.OK })
118
- span.end()
119
- } catch (error) {
120
- // Failed to record OpenTelemetry span for gesture - silently continue
121
- }
122
- }
123
-
124
- // Public methods for manual event recording
125
- recordTap(x: number, y: number, target?: string, pressure?: number): void {
126
- const event: GestureEvent = {
127
- type: 'tap',
128
- timestamp: Date.now(),
129
- coordinates: { x, y },
130
- target,
131
- metadata: {
132
- pressure: pressure || 1.0,
133
- screenWidth: this.screenDimensions?.width,
134
- screenHeight: this.screenDimensions?.height,
135
- },
136
- }
137
-
138
- this._recordEvent(event)
139
- }
140
-
141
- recordSwipe(direction: string, target?: string, velocity?: number, distance?: number): void {
142
- const event: GestureEvent = {
143
- type: 'swipe',
144
- timestamp: Date.now(),
145
- target,
146
- metadata: {
147
- direction,
148
- velocity: velocity || 0,
149
- distance: distance || 0,
150
- screenWidth: this.screenDimensions?.width,
151
- screenHeight: this.screenDimensions?.height,
152
- },
153
- }
154
-
155
- this._recordEvent(event)
156
- }
157
-
158
- recordPinch(scale: number, target?: string, velocity?: number): void {
159
- const event: GestureEvent = {
160
- type: 'pinch',
161
- timestamp: Date.now(),
162
- target,
163
- metadata: {
164
- scale,
165
- velocity: velocity || 0,
166
- screenWidth: this.screenDimensions?.width,
167
- screenHeight: this.screenDimensions?.height,
168
- },
169
- }
170
-
171
- this._recordEvent(event)
172
- }
173
-
174
- recordPan(deltaX: number, deltaY: number, target?: string, velocity?: number): void {
175
- const event: GestureEvent = {
176
- type: 'pan',
177
- timestamp: Date.now(),
178
- target,
179
- metadata: {
180
- deltaX,
181
- deltaY,
182
- velocity: velocity || 0,
183
- screenWidth: this.screenDimensions?.width,
184
- screenHeight: this.screenDimensions?.height,
185
- },
186
- }
187
-
188
- this._recordEvent(event)
189
- }
190
-
191
- recordLongPress(duration: number, target?: string, pressure?: number): void {
192
- const event: GestureEvent = {
193
- type: 'longPress',
194
- timestamp: Date.now(),
195
- target,
196
- metadata: {
197
- duration,
198
- pressure: pressure || 1.0,
199
- screenWidth: this.screenDimensions?.width,
200
- screenHeight: this.screenDimensions?.height,
201
- },
202
- }
203
-
204
- this._recordEvent(event)
205
- }
206
-
207
- recordDoubleTap(x: number, y: number, target?: string): void {
208
- const event: GestureEvent = {
209
- type: 'doubleTap',
210
- timestamp: Date.now(),
211
- coordinates: { x, y },
212
- target,
213
- metadata: {
214
- screenWidth: this.screenDimensions?.width,
215
- screenHeight: this.screenDimensions?.height,
216
- },
217
- }
218
-
219
- this._recordEvent(event)
220
- }
221
-
222
- recordRotate(rotation: number, target?: string, velocity?: number): void {
223
- const event: GestureEvent = {
224
- type: 'rotate',
225
- timestamp: Date.now(),
226
- target,
227
- metadata: {
228
- rotation,
229
- velocity: velocity || 0,
230
- screenWidth: this.screenDimensions?.width,
231
- screenHeight: this.screenDimensions?.height,
232
- },
233
- }
234
-
235
- this._recordEvent(event)
236
- }
237
-
238
- recordFling(direction: string, velocity: number, target?: string): void {
239
- const event: GestureEvent = {
240
- type: 'fling',
241
- timestamp: Date.now(),
242
- target,
243
- metadata: {
244
- direction,
245
- velocity,
246
- screenWidth: this.screenDimensions?.width,
247
- screenHeight: this.screenDimensions?.height,
248
- },
249
- }
250
-
251
- this._recordEvent(event)
252
- }
253
-
254
- // Advanced gesture tracking methods
255
- recordMultiTouch(touchCount: number, target?: string): void {
256
- const event: GestureEvent = {
257
- type: 'multiTouch',
258
- timestamp: Date.now(),
259
- target,
260
- metadata: {
261
- touchCount,
262
- screenWidth: this.screenDimensions?.width,
263
- screenHeight: this.screenDimensions?.height,
264
- },
265
- }
266
-
267
- this._recordEvent(event)
268
- }
269
-
270
- recordScroll(direction: string, distance: number, velocity: number, target?: string): void {
271
- const event: GestureEvent = {
272
- type: 'scroll',
273
- timestamp: Date.now(),
274
- target,
275
- metadata: {
276
- direction,
277
- distance,
278
- velocity,
279
- screenWidth: this.screenDimensions?.width,
280
- screenHeight: this.screenDimensions?.height,
281
- },
282
- }
283
-
284
- this._recordEvent(event)
285
- }
286
-
287
- recordZoom(scale: number, target?: string, velocity?: number): void {
288
- const event: GestureEvent = {
289
- type: 'zoom',
290
- timestamp: Date.now(),
291
- target,
292
- metadata: {
293
- scale,
294
- velocity: velocity || 0,
295
- screenWidth: this.screenDimensions?.width,
296
- screenHeight: this.screenDimensions?.height,
297
- },
298
- }
299
-
300
- this._recordEvent(event)
301
- }
302
-
303
- // Get recorded events
304
- getEvents(): GestureEvent[] {
305
- return [...this.events]
306
- }
307
-
308
- // Clear events
309
- clearEvents(): void {
310
- this.events = []
311
- }
312
-
313
- // Get event statistics
314
- getEventStats(): Record<string, number> {
315
- const stats: Record<string, number> = {}
316
- this.events.forEach(event => {
317
- stats[event.type] = (stats[event.type] || 0) + 1
318
- })
319
- return stats
320
- }
321
-
322
- // Set gesture throttle
323
- setGestureThrottle(throttleMs: number): void {
324
- this.gestureThrottleMs = throttleMs
325
- }
326
-
327
- // Get recording status
328
- isRecordingEnabled(): boolean {
329
- return this.isRecording
330
- }
331
-
332
- /**
333
- * Record an rrweb event
334
- * @param event - The rrweb event to record
335
- */
336
- recordEvent(event: any): void {
337
- if (this.eventRecorder) {
338
- this.eventRecorder.recordEvent(event)
339
- }
340
- }
341
-
342
- /**
343
- * Create and emit a rrweb MouseInteraction event for touch interactions
344
- * @param x - X coordinate
345
- * @param y - Y coordinate
346
- * @param interactionType - Type of interaction (TouchStart, TouchMove, TouchEnd, etc.)
347
- * @param target - Target element identifier
348
- */
349
- private _createMouseInteractionEvent(
350
- x: number,
351
- y: number,
352
- interactionType: MouseInteractions,
353
- target?: string,
354
- ): void {
355
- const incrementalSnapshotEvent: eventWithTime = {
356
- type: EventType.IncrementalSnapshot,
357
- data: {
358
- source: IncrementalSource.MouseInteraction,
359
- type: interactionType,
360
- id: this.imageNodeId, // Reference to the image node
361
- x: x, // Preserve decimal precision like web rrweb
362
- y: y, // Preserve decimal precision like web rrweb
363
- pointerType: 2, // 2 = Touch for React Native (0=Mouse, 1=Pen, 2=Touch)
364
- },
365
- timestamp: Date.now(),
366
- }
367
-
368
- this.recordEvent(incrementalSnapshotEvent)
369
- }
370
-
371
- /**
372
- * Create mouse move event with positions array (like web rrweb)
373
- * @param x - X coordinate
374
- * @param y - Y coordinate
375
- * @param target - Target element identifier
376
- */
377
- private _createMouseMoveEvent(x: number, y: number, target?: string): void {
378
- const incrementalSnapshotEvent: eventWithTime = {
379
- type: EventType.IncrementalSnapshot,
380
- data: {
381
- source: IncrementalSource.TouchMove, // Use MouseMove instead of MouseInteraction
382
- positions: [
383
- {
384
- x: x, // Preserve decimal precision like web rrweb
385
- y: y, // Preserve decimal precision like web rrweb
386
- id: this.imageNodeId, // Reference to the image node
387
- timeOffset: 0, // No time offset for single position
388
- },
389
- ],
390
- },
391
- timestamp: Date.now(),
392
- }
393
-
394
- this.recordEvent(incrementalSnapshotEvent)
395
- }
396
-
397
- /**
398
- * Record touch start event as rrweb MouseInteraction
399
- * @param x - X coordinate
400
- * @param y - Y coordinate
401
- * @param target - Target element identifier
402
- * @param pressure - Touch pressure (optional)
403
- */
404
- recordTouchStart(x: number, y: number, target?: string, pressure?: number): void {
405
- // Throttle touch events to prevent spam
406
- const now = Date.now()
407
- if (now - this.lastTouchTime < this.touchThrottleMs) {
408
- logger.debug('GestureRecorder', `Touch start throttled (${now - this.lastTouchTime}ms < ${this.touchThrottleMs}ms)`)
409
- return
410
- }
411
- this.lastTouchTime = now
412
-
413
- logger.debug('GestureRecorder', 'Touch start recorded', { x, y, target, pressure })
414
- // Record as MouseDown (type: 1) like web rrweb
415
- this._createMouseInteractionEvent(x, y, MouseInteractions.TouchStart, target)
416
- }
417
-
418
- /**
419
- * Record touch move event as rrweb MouseMove with positions array
420
- * @param x - X coordinate
421
- * @param y - Y coordinate
422
- * @param target - Target element identifier
423
- * @param pressure - Touch pressure (optional)
424
- */
425
- recordTouchMove(x: number, y: number, target?: string, pressure?: number): void {
426
- // Throttle touch move events more aggressively
427
- const now = Date.now()
428
- if (now - this.lastTouchTime < this.touchThrottleMs * 2) { // 200ms throttle for move events
429
- logger.debug('GestureRecorder', `Touch move throttled (${now - this.lastTouchTime}ms < ${this.touchThrottleMs * 2}ms)`)
430
- return
431
- }
432
- this.lastTouchTime = now
433
-
434
- logger.debug('GestureRecorder', 'Touch move recorded', { x, y, target, pressure })
435
- // Record as MouseMove with positions array (like web rrweb)
436
- this._createMouseMoveEvent(x, y, target)
437
- }
438
-
439
- /**
440
- * Record touch end event as rrweb MouseInteraction
441
- * @param x - X coordinate
442
- * @param y - Y coordinate
443
- * @param target - Target element identifier
444
- * @param pressure - Touch pressure (optional)
445
- */
446
- recordTouchEnd(x: number, y: number, target?: string, pressure?: number): void {
447
- // Cyclic call detection
448
- if (this.isRecordingGesture) {
449
- logger.error('GestureRecorder', 'CYCLIC CALL DETECTED! Already recording gesture', this.gestureCallStack)
450
- return
451
- }
452
-
453
- if (this.gestureCallStack.length >= this.maxGestureCallDepth) {
454
- logger.error('GestureRecorder', 'MAX GESTURE CALL DEPTH REACHED!', this.gestureCallStack)
455
- return
456
- }
457
-
458
- this.isRecordingGesture = true
459
- this.gestureCallStack.push('recordTouchEnd')
460
-
461
- try {
462
- logger.debug('GestureRecorder', 'Touch end recorded', { x, y, target, pressure })
463
- // Always record touch end (no throttling for completion)
464
- this.recordTap(x, y, target, pressure)
465
- // Record as MouseUp (type: 0) like web rrweb
466
- this._createMouseInteractionEvent(x, y, MouseInteractions.TouchEnd, target)
467
- // Also record Click (type: 2) like web rrweb
468
- // this._createMouseInteractionEvent(x, y, MouseInteractions.Click, target)
469
-
470
- // Only force screen capture on touch end (not on every touch event)
471
- logger.debug('GestureRecorder', 'Forcing screen capture after touch end')
472
- this.screenRecorder?.forceCapture()
473
- } finally {
474
- this.isRecordingGesture = false
475
- this.gestureCallStack.pop()
476
- }
477
- }
478
-
479
- /**
480
- * Record touch cancel event as rrweb MouseInteraction
481
- * @param x - X coordinate
482
- * @param y - Y coordinate
483
- * @param target - Target element identifier
484
- */
485
- recordTouchCancel(x: number, y: number, target?: string): void {
486
- // Record as MouseUp (type: 0) like web rrweb for touch cancel
487
- this._createMouseInteractionEvent(x, y, MouseInteractions.TouchCancel, target)
488
- }
489
-
490
- /**
491
- * Set the image node ID for touch interactions
492
- * This should be called when a new screen snapshot is created
493
- * @param nodeId - The ID of the image node in the current snapshot
494
- */
495
- setImageNodeId(nodeId: number): void {
496
- this.imageNodeId = nodeId
497
- }
498
- }