@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1 → 0.0.1-alpha.10

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 (234) 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/components/GestureCaptureWrapper/GestureCaptureWrapper.d.ts +6 -0
  5. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
  6. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
  7. package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
  8. package/dist/components/GestureCaptureWrapper/index.js +1 -0
  9. package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
  10. package/dist/components/GestureCaptureWrapper.d.ts +6 -0
  11. package/dist/components/GestureCaptureWrapper.js +1 -0
  12. package/dist/components/GestureCaptureWrapper.js.map +1 -0
  13. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
  14. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
  15. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  16. package/dist/components/ScreenRecorderView/index.d.ts +1 -0
  17. package/dist/components/ScreenRecorderView/index.js +1 -0
  18. package/dist/components/ScreenRecorderView/index.js.map +1 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.js +1 -0
  21. package/dist/components/index.js.map +1 -0
  22. package/dist/config/constants.d.ts +18 -0
  23. package/dist/config/constants.js +1 -0
  24. package/dist/config/constants.js.map +1 -0
  25. package/dist/config/defaults.d.ts +4 -0
  26. package/dist/config/defaults.js +1 -0
  27. package/dist/config/defaults.js.map +1 -0
  28. package/dist/config/index.d.ts +5 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/config/masking.d.ts +2 -30
  32. package/dist/config/masking.js +1 -1
  33. package/dist/config/masking.js.map +1 -1
  34. package/dist/config/session-recorder.d.ts +2 -0
  35. package/dist/config/session-recorder.js +1 -0
  36. package/dist/config/session-recorder.js.map +1 -0
  37. package/dist/config/validators.d.ts +10 -0
  38. package/dist/config/validators.js +1 -0
  39. package/dist/config/validators.js.map +1 -0
  40. package/dist/context/SessionRecorderContext.d.ts +12 -0
  41. package/dist/context/SessionRecorderContext.js +1 -0
  42. package/dist/context/SessionRecorderContext.js.map +1 -0
  43. package/dist/expo.d.ts +5 -9
  44. package/dist/expo.js +1 -1
  45. package/dist/expo.js.map +1 -1
  46. package/dist/index.d.ts +6 -10
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/otel/helpers.d.ts +45 -3
  50. package/dist/otel/helpers.js +1 -1
  51. package/dist/otel/helpers.js.map +1 -1
  52. package/dist/otel/index.d.ts +4 -25
  53. package/dist/otel/index.js +1 -1
  54. package/dist/otel/index.js.map +1 -1
  55. package/dist/otel/instrumentations/gestureInstrumentation.js +1 -1
  56. package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -1
  57. package/dist/otel/instrumentations/index.d.ts +3 -4
  58. package/dist/otel/instrumentations/index.js +1 -1
  59. package/dist/otel/instrumentations/index.js.map +1 -1
  60. package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
  61. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
  62. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
  63. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
  64. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
  65. package/dist/patch/index.d.ts +1 -0
  66. package/dist/patch/index.js +1 -0
  67. package/dist/patch/index.js.map +1 -0
  68. package/dist/patch/xhr.d.ts +2 -0
  69. package/dist/patch/xhr.js +1 -0
  70. package/dist/patch/xhr.js.map +1 -0
  71. package/dist/recorder/eventExporter.d.ts +21 -0
  72. package/dist/recorder/eventExporter.js +1 -0
  73. package/dist/recorder/eventExporter.js.map +1 -0
  74. package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
  75. package/dist/recorder/gestureHandlerRecorder.js +1 -0
  76. package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
  77. package/dist/recorder/gestureRecorder.d.ts +68 -11
  78. package/dist/recorder/gestureRecorder.js +1 -1
  79. package/dist/recorder/gestureRecorder.js.map +1 -1
  80. package/dist/recorder/index.d.ts +60 -6
  81. package/dist/recorder/index.js +1 -1
  82. package/dist/recorder/index.js.map +1 -1
  83. package/dist/recorder/navigationTracker.js +1 -1
  84. package/dist/recorder/navigationTracker.js.map +1 -1
  85. package/dist/recorder/screenRecorder.d.ts +79 -10
  86. package/dist/recorder/screenRecorder.js +1 -1
  87. package/dist/recorder/screenRecorder.js.map +1 -1
  88. package/dist/services/api.service.d.ts +62 -10
  89. package/dist/services/api.service.js +1 -1
  90. package/dist/services/api.service.js.map +1 -1
  91. package/dist/services/storage.service.d.ts +23 -16
  92. package/dist/services/storage.service.js +1 -1
  93. package/dist/services/storage.service.js.map +1 -1
  94. package/dist/session-recorder.d.ts +166 -0
  95. package/dist/session-recorder.js +1 -0
  96. package/dist/session-recorder.js.map +1 -0
  97. package/dist/types/index.d.ts +15 -76
  98. package/dist/types/index.js +1 -1
  99. package/dist/types/index.js.map +1 -1
  100. package/dist/types/rrweb.d.ts +118 -0
  101. package/dist/types/rrweb.js +1 -0
  102. package/dist/types/rrweb.js.map +1 -0
  103. package/dist/types/session-recorder.d.ts +366 -0
  104. package/dist/types/session-recorder.js +1 -0
  105. package/dist/types/session-recorder.js.map +1 -0
  106. package/dist/types/session.d.ts +59 -0
  107. package/dist/types/session.js +1 -0
  108. package/dist/types/session.js.map +1 -0
  109. package/dist/utils/app-metadata.d.ts +16 -0
  110. package/dist/utils/app-metadata.js +1 -0
  111. package/dist/utils/app-metadata.js.map +1 -0
  112. package/dist/utils/index.d.ts +7 -0
  113. package/dist/utils/index.js +1 -0
  114. package/dist/utils/index.js.map +1 -0
  115. package/dist/utils/logger.d.ts +112 -0
  116. package/dist/utils/logger.js +1 -0
  117. package/dist/utils/logger.js.map +1 -0
  118. package/dist/utils/platform.d.ts +37 -0
  119. package/dist/utils/platform.js +1 -1
  120. package/dist/utils/platform.js.map +1 -1
  121. package/dist/utils/request-utils.d.ts +21 -0
  122. package/dist/utils/request-utils.js +1 -0
  123. package/dist/utils/request-utils.js.map +1 -0
  124. package/dist/utils/rrweb-events.d.ts +65 -0
  125. package/dist/utils/rrweb-events.js +1 -0
  126. package/dist/utils/rrweb-events.js.map +1 -0
  127. package/dist/utils/session.d.ts +5 -0
  128. package/dist/utils/session.js +1 -0
  129. package/dist/utils/session.js.map +1 -0
  130. package/dist/utils/time.d.ts +4 -0
  131. package/dist/utils/time.js +1 -0
  132. package/dist/utils/time.js.map +1 -0
  133. package/dist/utils/type-utils.d.ts +16 -0
  134. package/dist/utils/type-utils.js +1 -0
  135. package/dist/utils/type-utils.js.map +1 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/docs/AUTO_METADATA_DETECTION.md +108 -0
  140. package/package.json +10 -9
  141. package/scripts/generate-app-metadata.js +173 -0
  142. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +86 -0
  143. package/src/components/GestureCaptureWrapper/index.ts +1 -0
  144. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +72 -0
  145. package/src/components/ScreenRecorderView/index.ts +1 -0
  146. package/src/components/index.ts +1 -0
  147. package/src/config/constants.ts +60 -0
  148. package/src/config/defaults.ts +82 -0
  149. package/src/config/index.ts +6 -0
  150. package/src/config/masking.ts +10 -61
  151. package/src/config/session-recorder.ts +55 -0
  152. package/src/config/validators.ts +31 -0
  153. package/src/context/SessionRecorderContext.tsx +75 -0
  154. package/src/expo.ts +7 -37
  155. package/src/index.ts +14 -17
  156. package/src/otel/helpers.ts +265 -11
  157. package/src/otel/index.ts +37 -247
  158. package/src/otel/instrumentations/index.ts +82 -53
  159. package/src/patch/index.ts +1 -0
  160. package/src/patch/xhr.ts +142 -0
  161. package/src/recorder/eventExporter.ts +141 -0
  162. package/src/recorder/gestureRecorder.ts +194 -125
  163. package/src/recorder/index.ts +132 -24
  164. package/src/recorder/navigationTracker.ts +12 -10
  165. package/src/recorder/screenRecorder.ts +242 -155
  166. package/src/services/api.service.ts +170 -45
  167. package/src/services/storage.service.ts +102 -74
  168. package/src/session-recorder.ts +600 -0
  169. package/src/types/index.ts +19 -79
  170. package/src/types/session-recorder.ts +423 -0
  171. package/src/types/session.ts +65 -0
  172. package/src/utils/app-metadata.ts +31 -0
  173. package/src/utils/index.ts +8 -0
  174. package/src/utils/logger.ts +225 -0
  175. package/src/utils/platform.ts +321 -6
  176. package/src/utils/request-utils.ts +61 -0
  177. package/src/utils/rrweb-events.ts +309 -0
  178. package/src/utils/session.ts +18 -0
  179. package/src/utils/time.ts +17 -0
  180. package/src/utils/type-utils.ts +75 -0
  181. package/src/version.ts +1 -1
  182. package/dist/sessionRecorder.d.ts +0 -54
  183. package/dist/sessionRecorder.js +0 -1
  184. package/dist/sessionRecorder.js.map +0 -1
  185. package/examples/sample-expo-app/README.md +0 -142
  186. package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
  187. package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
  188. package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
  189. package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
  190. package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
  191. package/examples/sample-expo-app/app/+not-found.tsx +0 -32
  192. package/examples/sample-expo-app/app/_layout.tsx +0 -53
  193. package/examples/sample-expo-app/app/post/[id].tsx +0 -199
  194. package/examples/sample-expo-app/app/user/[id].tsx +0 -270
  195. package/examples/sample-expo-app/app.json +0 -42
  196. package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
  197. package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
  198. package/examples/sample-expo-app/assets/images/favicon.png +0 -0
  199. package/examples/sample-expo-app/assets/images/icon.png +0 -0
  200. package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
  201. package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
  202. package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
  203. package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
  204. package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
  205. package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
  206. package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
  207. package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
  208. package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
  209. package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
  210. package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
  211. package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
  212. package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
  213. package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
  214. package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
  215. package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
  216. package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
  217. package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
  218. package/examples/sample-expo-app/constants/Colors.ts +0 -26
  219. package/examples/sample-expo-app/eslint.config.js +0 -10
  220. package/examples/sample-expo-app/hooks/useApi.ts +0 -41
  221. package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
  222. package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
  223. package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
  224. package/examples/sample-expo-app/metro.config.js +0 -26
  225. package/examples/sample-expo-app/package-lock.json +0 -26296
  226. package/examples/sample-expo-app/package.json +0 -59
  227. package/examples/sample-expo-app/scripts/reset-project.js +0 -112
  228. package/examples/sample-expo-app/services/api.ts +0 -98
  229. package/examples/sample-expo-app/tsconfig.json +0 -17
  230. package/examples/sample-expo-app/utils/navigation.ts +0 -19
  231. package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
  232. package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -164
  233. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -114
  234. package/src/sessionRecorder.ts +0 -367
@@ -1,7 +1,10 @@
1
- import { GestureEvent, RecorderConfig } from '../types'
1
+ import { GestureEvent, RecorderConfig, EventRecorder } from '../types'
2
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'
3
6
 
4
- export class GestureRecorder {
7
+ export class GestureRecorder implements EventRecorder {
5
8
  private config?: RecorderConfig
6
9
  private isRecording = false
7
10
  private events: GestureEvent[] = []
@@ -9,79 +12,51 @@ export class GestureRecorder {
9
12
  private screenDimensions: { width: number; height: number } | null = null
10
13
  private lastGestureTime: number = 0
11
14
  private gestureThrottleMs: number = 50 // Throttle gestures to avoid spam
12
- init(config: RecorderConfig): void {
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 {
13
27
  this.config = config
28
+ this.eventRecorder = eventRecorder
29
+ this.screenRecorder = screenRecorder
14
30
  this._getScreenDimensions()
15
31
  }
16
32
 
17
33
  start(): void {
34
+ logger.info('GestureRecorder', 'Gesture recording started')
18
35
  this.isRecording = true
19
36
  this.events = []
20
- this._setupGestureHandlers()
21
- console.log('Gesture recording started')
37
+ // Gesture recording started
22
38
  }
23
39
 
24
40
  stop(): void {
25
41
  this.isRecording = false
26
42
  this._removeGestureHandlers()
27
- console.log('Gesture recording stopped')
43
+ // Gesture recording stopped
28
44
  }
29
45
 
30
- pause(): void {
31
- this.isRecording = false
32
- }
33
-
34
- resume(): void {
35
- this.isRecording = true
36
- }
37
-
38
- // Input component registration temporarily disabled
39
46
 
40
47
  private _getScreenDimensions(): void {
41
48
  try {
42
- const { Dimensions } = require('react-native')
43
49
  this.screenDimensions = Dimensions.get('window')
44
50
  } catch (error) {
45
- console.warn('Failed to get screen dimensions:', error)
51
+ // Failed to get screen dimensions - silently continue
46
52
  this.screenDimensions = { width: 375, height: 667 } // Default fallback
47
53
  }
48
54
  }
49
55
 
50
- private _setupInputTracking(): void {
51
- // Set up React Native input component tracking
52
- try {
53
- // This would integrate with React Native's component tracking
54
- // For now, we'll provide methods for manual registration
55
- console.log('Input tracking setup complete')
56
- } catch (error) {
57
- console.warn('Failed to setup input tracking:', error)
58
- }
59
- }
60
-
61
- private _setupGestureHandlers(): void {
62
- // This would integrate with react-native-gesture-handler
63
- // For now, we'll create a comprehensive implementation that can be easily integrated
64
- console.log('Setting up gesture handlers')
65
-
66
- // Set up global gesture listener
67
- this._setupGlobalGestureListener()
68
- }
69
-
70
- private _setupGlobalGestureListener(): void {
71
- try {
72
- // Listen for touch events at the app level
73
- const { TouchableWithoutFeedback } = require('react-native')
74
-
75
- // This is a simplified implementation - in production you'd use react-native-gesture-handler
76
- console.log('Global gesture listener setup complete')
77
- } catch (error) {
78
- console.warn('Failed to setup global gesture listener:', error)
79
- }
80
- }
81
56
 
82
57
  private _removeGestureHandlers(): void {
83
58
  this.gestureHandlers.clear()
84
- console.log('Gesture handlers removed')
59
+ // Gesture handlers removed
85
60
  }
86
61
 
87
62
  private _recordEvent(event: GestureEvent): void {
@@ -103,12 +78,12 @@ export class GestureRecorder {
103
78
 
104
79
  private _sendEvent(event: GestureEvent): void {
105
80
  // Send event to backend or store locally
106
- console.log('Gesture event recorded:', event)
81
+ // Gesture event recorded
107
82
  }
108
83
 
109
84
  private _recordOpenTelemetrySpan(event: GestureEvent): void {
110
85
  try {
111
- const span = trace.getTracer('gesture').startSpan(`Gesture.${event.type}`, {
86
+ const span = trace.getTracer('@opentelemetry/instrumentation-user-interaction').startSpan(`Gesture.${event.type}`, {
112
87
  attributes: {
113
88
  'gesture.type': event.type,
114
89
  'gesture.timestamp': event.timestamp,
@@ -142,7 +117,7 @@ export class GestureRecorder {
142
117
  span.setStatus({ code: SpanStatusCode.OK })
143
118
  span.end()
144
119
  } catch (error) {
145
- console.warn('Failed to record OpenTelemetry span for gesture:', error)
120
+ // Failed to record OpenTelemetry span for gesture - silently continue
146
121
  }
147
122
  }
148
123
 
@@ -325,79 +300,6 @@ export class GestureRecorder {
325
300
  this._recordEvent(event)
326
301
  }
327
302
 
328
- // Gesture sequence tracking
329
- recordGestureSequence(gestures: string[], duration: number, target?: string): void {
330
- const event: GestureEvent = {
331
- type: 'gestureSequence',
332
- timestamp: Date.now(),
333
- target,
334
- metadata: {
335
- gestures: gestures.join(','),
336
- duration,
337
- gestureCount: gestures.length,
338
- screenWidth: this.screenDimensions?.width,
339
- screenHeight: this.screenDimensions?.height,
340
- },
341
- }
342
-
343
- this._recordEvent(event)
344
- }
345
-
346
- // Error tracking for gesture failures
347
- recordGestureError(error: Error, gestureType: string, target?: string): void {
348
- const event: GestureEvent = {
349
- type: 'gestureError',
350
- timestamp: Date.now(),
351
- target,
352
- metadata: {
353
- errorType: error.name,
354
- errorMessage: error.message,
355
- gestureType,
356
- screenWidth: this.screenDimensions?.width,
357
- screenHeight: this.screenDimensions?.height,
358
- },
359
- }
360
-
361
- this._recordEvent(event)
362
-
363
- // Also record as OpenTelemetry error span
364
- try {
365
- const span = trace.getTracer('gesture').startSpan(`Gesture.${gestureType}.error`, {
366
- attributes: {
367
- 'gesture.type': gestureType,
368
- 'gesture.error': true,
369
- 'gesture.error.type': error.name,
370
- 'gesture.error.message': error.message,
371
- 'gesture.timestamp': Date.now(),
372
- },
373
- })
374
-
375
- span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
376
- span.recordException(error)
377
- span.end()
378
- } catch (spanError) {
379
- console.warn('Failed to record error span:', spanError)
380
- }
381
- }
382
-
383
- // Performance monitoring
384
- recordGesturePerformance(gestureType: string, duration: number, target?: string): void {
385
- const event: GestureEvent = {
386
- type: 'gesturePerformance',
387
- timestamp: Date.now(),
388
- target,
389
- metadata: {
390
- gestureType,
391
- duration,
392
- performance: 'monitoring',
393
- screenWidth: this.screenDimensions?.width,
394
- screenHeight: this.screenDimensions?.height,
395
- },
396
- }
397
-
398
- this._recordEvent(event)
399
- }
400
-
401
303
  // Get recorded events
402
304
  getEvents(): GestureEvent[] {
403
305
  return [...this.events]
@@ -426,4 +328,171 @@ export class GestureRecorder {
426
328
  isRecordingEnabled(): boolean {
427
329
  return this.isRecording
428
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
+ }
429
498
  }
@@ -1,27 +1,39 @@
1
+ import { SessionType } from '@multiplayer-app/session-recorder-common'
2
+ // import { pack } from '@rrweb/packer' // Removed to avoid blob creation issues in Hermes
3
+ import { EventExporter } from './eventExporter'
4
+ import { logger } from '../utils'
5
+ import { ScreenRecorder } from './screenRecorder'
1
6
  import { GestureRecorder } from './gestureRecorder'
2
7
  import { NavigationTracker } from './navigationTracker'
3
- import { ScreenRecorder } from './screenRecorder'
4
- import { SessionType } from '@multiplayer-app/session-recorder-common'
5
- import { RecorderConfig } from '../types'
6
-
7
- export class RecorderReactNativeSDK {
8
+ import { RecorderConfig, EventRecorder } from '../types'
9
+ import { eventWithTime } from '@rrweb/types'
10
+ export class RecorderReactNativeSDK implements EventRecorder {
11
+ private isRecording = false
8
12
  private config?: RecorderConfig
13
+ private screenRecorder: ScreenRecorder
9
14
  private gestureRecorder: GestureRecorder
10
15
  private navigationTracker: NavigationTracker
11
- private screenRecorder: ScreenRecorder
12
- private isRecording = false
16
+ private recordedEvents: eventWithTime[] = []
17
+ private exporter: EventExporter | undefined
18
+ private sessionId: string | null = null
19
+ private sessionType: SessionType = SessionType.PLAIN
20
+
13
21
 
14
22
  constructor() {
23
+ this.screenRecorder = new ScreenRecorder()
15
24
  this.gestureRecorder = new GestureRecorder()
16
25
  this.navigationTracker = new NavigationTracker()
17
- this.screenRecorder = new ScreenRecorder()
18
26
  }
19
27
 
20
28
  init(config: RecorderConfig): void {
21
29
  this.config = config
22
- this.gestureRecorder.init(config)
30
+ this.gestureRecorder.init(config, this, this.screenRecorder)
23
31
  this.navigationTracker.init(config)
24
- this.screenRecorder.init(config)
32
+ this.screenRecorder.init(config, this)
33
+ this.exporter = new EventExporter({
34
+ socketUrl: config.apiBaseUrl || '',
35
+ apiKey: config.apiKey,
36
+ })
25
37
  }
26
38
 
27
39
  start(sessionId: string | null, sessionType: SessionType): void {
@@ -29,8 +41,16 @@ export class RecorderReactNativeSDK {
29
41
  throw new Error('Configuration not initialized. Call init() before start().')
30
42
  }
31
43
 
44
+ this.sessionId = sessionId
45
+ this.sessionType = sessionType
32
46
  this.isRecording = true
33
47
 
48
+ // Emit recording started meta event
49
+
50
+ if (this.config.recordScreen) {
51
+ this.screenRecorder.start()
52
+ }
53
+
34
54
  if (this.config.recordGestures) {
35
55
  this.gestureRecorder.start()
36
56
  }
@@ -39,9 +59,7 @@ export class RecorderReactNativeSDK {
39
59
  this.navigationTracker.start()
40
60
  }
41
61
 
42
- if (this.config.recordScreen) {
43
- this.screenRecorder.start()
44
- }
62
+
45
63
  }
46
64
 
47
65
  stop(): void {
@@ -49,23 +67,113 @@ export class RecorderReactNativeSDK {
49
67
  this.gestureRecorder.stop()
50
68
  this.navigationTracker.stop()
51
69
  this.screenRecorder.stop()
70
+ this.exporter?.close()
52
71
  }
53
72
 
54
- pause(): void {
55
- this.gestureRecorder.pause()
56
- this.navigationTracker.pause()
57
- this.screenRecorder.pause()
73
+
74
+ setNavigationRef(ref: any): void {
75
+ this.navigationTracker.setNavigationRef(ref)
58
76
  }
59
77
 
60
- resume(): void {
61
- if (this.isRecording) {
62
- this.gestureRecorder.resume()
63
- this.navigationTracker.resume()
64
- this.screenRecorder.resume()
78
+ /**
79
+ * Set the viewshot ref for screen capture
80
+ * @param ref - React Native View ref for screen capture
81
+ */
82
+ setViewShotRef(ref: any): void {
83
+ this.screenRecorder.setViewShotRef(ref)
84
+ }
85
+
86
+ /**
87
+ * Record an rrweb event
88
+ * @param event - The rrweb event to record
89
+ */
90
+ recordEvent(event: eventWithTime): void {
91
+ if (!this.isRecording) {
92
+ return
93
+ }
94
+
95
+ if (this.exporter) {
96
+ logger.debug('RecorderReactNativeSDK', 'Sending to exporter', event)
97
+ // Skip packing to avoid blob creation issues in Hermes
98
+ // const packedEvent = pack(event)
99
+ this.exporter.send({
100
+ event: event, // Send raw event instead of packed
101
+ eventType: event.type,
102
+ timestamp: event.timestamp,
103
+ debugSessionId: this.sessionId,
104
+ debugSessionType: this.sessionType,
105
+ })
65
106
  }
66
107
  }
67
108
 
68
- setNavigationRef(ref: any): void {
69
- this.navigationTracker.setNavigationRef(ref)
109
+ /**
110
+ * Record touch start event
111
+ * @param x - X coordinate
112
+ * @param y - Y coordinate
113
+ * @param target - Target element identifier
114
+ * @param pressure - Touch pressure
115
+ */
116
+ recordTouchStart(x: number, y: number, target?: string, pressure?: number): void {
117
+ if (!this.isRecording) {
118
+ return
119
+ }
120
+
121
+ this.gestureRecorder.recordTouchStart(x, y, target, pressure)
122
+ }
123
+
124
+ /**
125
+ * Record touch move event
126
+ * @param x - X coordinate
127
+ * @param y - Y coordinate
128
+ * @param target - Target element identifier
129
+ * @param pressure - Touch pressure
130
+ */
131
+ recordTouchMove(x: number, y: number, target?: string, pressure?: number): void {
132
+ if (!this.isRecording) {
133
+ return
134
+ }
135
+
136
+ this.gestureRecorder.recordTouchMove(x, y, target, pressure)
137
+ }
138
+
139
+ /**
140
+ * Record touch end event
141
+ * @param x - X coordinate
142
+ * @param y - Y coordinate
143
+ * @param target - Target element identifier
144
+ * @param pressure - Touch pressure
145
+ */
146
+ recordTouchEnd(x: number, y: number, target?: string, pressure?: number): void {
147
+ if (!this.isRecording) {
148
+ return
149
+ }
150
+
151
+ this.gestureRecorder.recordTouchEnd(x, y, target, pressure)
152
+ }
153
+
154
+ /**
155
+ * Get all recorded events
156
+ * @returns Array of recorded rrweb events
157
+ */
158
+ getRecordedEvents(): eventWithTime[] {
159
+ return [...this.recordedEvents]
160
+ }
161
+
162
+ /**
163
+ * Clear all recorded events
164
+ */
165
+ clearRecordedEvents(): void {
166
+ this.recordedEvents = []
167
+ }
168
+
169
+ /**
170
+ * Get recording statistics
171
+ * @returns Recording statistics
172
+ */
173
+ getRecordingStats(): { totalEvents: number; isRecording: boolean } {
174
+ return {
175
+ totalEvents: this.recordedEvents.length,
176
+ isRecording: this.isRecording,
177
+ }
70
178
  }
71
179
  }
@@ -1,5 +1,6 @@
1
1
  import { NavigationEvent, RecorderConfig } from '../types'
2
2
  import { trace, SpanStatusCode } from '@opentelemetry/api'
3
+ import { logger } from '../utils'
3
4
 
4
5
  export class NavigationTracker {
5
6
  private config?: RecorderConfig
@@ -23,18 +24,19 @@ export class NavigationTracker {
23
24
  }
24
25
 
25
26
  start(): void {
27
+ logger.info('NavigationTracker', 'Navigation tracking started')
26
28
  this.isRecording = true
27
29
  this.events = []
28
30
  this.navigationStack = []
29
31
  this.navigationStartTime = Date.now()
30
32
  this._setupNavigationListener()
31
- console.log('Navigation tracking started')
33
+ // Navigation tracking started
32
34
  }
33
35
 
34
36
  stop(): void {
35
37
  this.isRecording = false
36
38
  this._removeNavigationListener()
37
- console.log('Navigation tracking stopped')
39
+ // Navigation tracking stopped
38
40
  }
39
41
 
40
42
  pause(): void {
@@ -48,7 +50,7 @@ export class NavigationTracker {
48
50
 
49
51
  private _setupNavigationListener(): void {
50
52
  if (!this.navigationRef) {
51
- console.warn('Navigation ref not set')
53
+ // Navigation ref not set - silently continue
52
54
  return
53
55
  }
54
56
 
@@ -79,9 +81,9 @@ export class NavigationTracker {
79
81
  this.navigationListeners.set('blur', blurListener)
80
82
  this.navigationListeners.set('beforeRemove', beforeRemoveListener)
81
83
 
82
- console.log('Navigation listeners setup complete')
84
+ // Navigation listeners setup complete
83
85
  } catch (error) {
84
- console.warn('Failed to setup navigation listeners:', error)
86
+ // Failed to setup navigation listeners - silently continue
85
87
  }
86
88
  }
87
89
 
@@ -94,9 +96,9 @@ export class NavigationTracker {
94
96
  }
95
97
  })
96
98
  this.navigationListeners.clear()
97
- console.log('Navigation listeners removed')
99
+ // Navigation listeners removed
98
100
  } catch (error) {
99
- console.warn('Failed to remove navigation listeners:', error)
101
+ // Failed to remove navigation listeners - silently continue
100
102
  }
101
103
  }
102
104
 
@@ -148,7 +150,7 @@ export class NavigationTracker {
148
150
  }
149
151
 
150
152
  private _sendEvent(event: NavigationEvent): void {
151
- console.log('Navigation event recorded:', event)
153
+ // Navigation event recorded
152
154
  }
153
155
 
154
156
  private _recordOpenTelemetrySpan(event: NavigationEvent): void {
@@ -178,7 +180,7 @@ export class NavigationTracker {
178
180
  span.setStatus({ code: SpanStatusCode.OK })
179
181
  span.end()
180
182
  } catch (error) {
181
- console.warn('Failed to record OpenTelemetry span for navigation:', error)
183
+ // Failed to record OpenTelemetry span for navigation - silently continue
182
184
  }
183
185
  }
184
186
 
@@ -399,7 +401,7 @@ export class NavigationTracker {
399
401
  span.recordException(error)
400
402
  span.end()
401
403
  } catch (spanError) {
402
- console.warn('Failed to record error span:', spanError)
404
+ // Failed to record error span - silently continue
403
405
  }
404
406
  }
405
407