@multiplayer-app/session-recorder-react-native 0.0.1-alpha.7 → 0.0.1-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/GestureCaptureWrapper.d.ts +6 -0
- package/dist/components/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper.js.map +1 -0
- package/dist/context/SessionRecorderContext.d.ts +1 -2
- package/dist/context/SessionRecorderContext.js +1 -1
- package/dist/context/SessionRecorderContext.js.map +1 -1
- package/dist/otel/helpers.js +1 -1
- package/dist/otel/helpers.js.map +1 -1
- package/dist/otel/instrumentations/index.js +1 -1
- package/dist/otel/instrumentations/index.js.map +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
- package/dist/patch/xhr.js +1 -1
- package/dist/patch/xhr.js.map +1 -1
- package/dist/recorder/eventExporter.js +1 -1
- package/dist/recorder/eventExporter.js.map +1 -1
- package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
- package/dist/recorder/gestureHandlerRecorder.js +1 -0
- package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
- package/dist/recorder/gestureRecorder.d.ts +13 -1
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +1 -2
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.js.map +1 -1
- package/dist/recorder/navigationTracker.js +1 -1
- package/dist/recorder/navigationTracker.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +31 -6
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/session-recorder.d.ts +3 -1
- package/dist/session-recorder.js +1 -1
- package/dist/session-recorder.js.map +1 -1
- package/dist/types/index.d.ts +32 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/rrweb.d.ts +10 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +112 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rrweb-events.d.ts +65 -0
- package/dist/utils/rrweb-events.js +1 -0
- package/dist/utils/rrweb-events.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/src/components/GestureCaptureWrapper.tsx +110 -0
- package/src/context/SessionRecorderContext.tsx +76 -81
- package/src/otel/helpers.ts +2 -1
- package/src/otel/instrumentations/index.ts +5 -4
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +2 -1
- package/src/patch/xhr.ts +2 -2
- package/src/recorder/eventExporter.ts +4 -1
- package/src/recorder/gestureHandlerRecorder.ts +157 -0
- package/src/recorder/gestureRecorder.ts +93 -19
- package/src/recorder/index.ts +16 -18
- package/src/recorder/navigationTracker.ts +2 -0
- package/src/recorder/screenRecorder.ts +125 -82
- package/src/session-recorder.ts +12 -5
- package/src/types/index.ts +44 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/rrweb-events.ts +311 -0
- package/src/version.ts +1 -1
- package/example-usage.tsx +0 -174
- package/src/types/rrweb.ts +0 -122
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InstrumentationBase } from '@opentelemetry/instrumentation'
|
|
2
|
+
import { logger } from '../../utils'
|
|
2
3
|
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
3
4
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
4
5
|
|
|
@@ -18,7 +19,7 @@ export class ReactNativeInstrumentation extends InstrumentationBase {
|
|
|
18
19
|
this._wrap(AsyncStorage, 'setItem', this._wrapAsyncStorage)
|
|
19
20
|
}
|
|
20
21
|
} catch (error) {
|
|
21
|
-
|
|
22
|
+
logger.warn('DEBUGGER_LIB', '@react-native-async-storage/async-storage is not available. AsyncStorage instrumentation will be disabled.')
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
package/src/patch/xhr.ts
CHANGED
|
@@ -92,7 +92,7 @@ function _tryReadXHRBody({
|
|
|
92
92
|
|
|
93
93
|
if (
|
|
94
94
|
requestBody?.length
|
|
95
|
-
&&
|
|
95
|
+
&& requestBody.length <= maxCapturingHttpPayloadSize
|
|
96
96
|
) {
|
|
97
97
|
networkRequest.requestBody = requestBody
|
|
98
98
|
}
|
|
@@ -126,7 +126,7 @@ function _tryReadXHRBody({
|
|
|
126
126
|
|
|
127
127
|
if (
|
|
128
128
|
responseBody?.length
|
|
129
|
-
&&
|
|
129
|
+
&& responseBody.length <= maxCapturingHttpPayloadSize
|
|
130
130
|
) {
|
|
131
131
|
networkRequest.responseBody = responseBody
|
|
132
132
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import io, { Socket } from 'socket.io-client'
|
|
2
2
|
|
|
3
3
|
import { ISession } from '../types'
|
|
4
|
+
import { logger } from '../utils'
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
SESSION_ADD_EVENT,
|
|
@@ -45,19 +46,21 @@ export class EventExporter {
|
|
|
45
46
|
this.socket.on('ready', () => {
|
|
46
47
|
this.isConnecting = false
|
|
47
48
|
this.isConnected = true
|
|
48
|
-
|
|
49
|
+
logger.info('EventExporter', 'Connected to server')
|
|
49
50
|
this.flushQueue()
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
this.socket.on('disconnect', (err: any) => {
|
|
53
54
|
this.isConnecting = false
|
|
54
55
|
this.isConnected = false
|
|
56
|
+
logger.info('EventExporter', 'Disconnected from server')
|
|
55
57
|
})
|
|
56
58
|
|
|
57
59
|
this.socket.on('connect_error', (err: any) => {
|
|
58
60
|
this.isConnecting = false
|
|
59
61
|
this.isConnected = false
|
|
60
62
|
this.checkReconnectionAttempts()
|
|
63
|
+
logger.error('EventExporter', 'Error connecting to server', err)
|
|
61
64
|
})
|
|
62
65
|
|
|
63
66
|
this.socket.on(SESSION_STOPPED_EVENT, (data: any) => {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Gesture } from 'react-native-gesture-handler'
|
|
2
|
+
import { ReactNode } from 'react'
|
|
3
|
+
import { SessionState } from '../types'
|
|
4
|
+
import { GestureInstrumentation } from '../otel/instrumentations/gestureInstrumentation'
|
|
5
|
+
|
|
6
|
+
export interface GestureHandlerRecorderProps {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
sessionState: SessionState | null
|
|
9
|
+
onGestureRecord: (gestureType: string, data: any) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class GestureHandlerRecorder {
|
|
13
|
+
private gestureInstrumentation: GestureInstrumentation
|
|
14
|
+
private onGestureRecord?: (gestureType: string, data: any) => void
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.gestureInstrumentation = new GestureInstrumentation()
|
|
18
|
+
this.gestureInstrumentation.enable()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setGestureCallback(callback: (gestureType: string, data: any) => void) {
|
|
22
|
+
this.onGestureRecord = callback
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create tap gesture
|
|
26
|
+
createTapGesture() {
|
|
27
|
+
return Gesture.Tap()
|
|
28
|
+
.onStart((event) => {
|
|
29
|
+
this.recordGesture('tap', {
|
|
30
|
+
x: event.x,
|
|
31
|
+
y: event.y,
|
|
32
|
+
timestamp: Date.now(),
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create pan gesture (for swipes and drags)
|
|
38
|
+
createPanGesture() {
|
|
39
|
+
return Gesture.Pan()
|
|
40
|
+
.onStart((event) => {
|
|
41
|
+
this.recordGesture('pan_start', {
|
|
42
|
+
x: event.x,
|
|
43
|
+
y: event.y,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
.onUpdate((event) => {
|
|
48
|
+
this.recordGesture('pan_update', {
|
|
49
|
+
x: event.x,
|
|
50
|
+
y: event.y,
|
|
51
|
+
translationX: event.translationX,
|
|
52
|
+
translationY: event.translationY,
|
|
53
|
+
velocityX: event.velocityX,
|
|
54
|
+
velocityY: event.velocityY,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
.onEnd((event) => {
|
|
59
|
+
this.recordGesture('pan_end', {
|
|
60
|
+
x: event.x,
|
|
61
|
+
y: event.y,
|
|
62
|
+
translationX: event.translationX,
|
|
63
|
+
translationY: event.translationY,
|
|
64
|
+
velocityX: event.velocityX,
|
|
65
|
+
velocityY: event.velocityY,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Create pinch gesture
|
|
72
|
+
createPinchGesture() {
|
|
73
|
+
return Gesture.Pinch()
|
|
74
|
+
.onStart((event) => {
|
|
75
|
+
this.recordGesture('pinch_start', {
|
|
76
|
+
scale: event.scale,
|
|
77
|
+
focalX: event.focalX,
|
|
78
|
+
focalY: event.focalY,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
.onUpdate((event) => {
|
|
83
|
+
this.recordGesture('pinch_update', {
|
|
84
|
+
scale: event.scale,
|
|
85
|
+
focalX: event.focalX,
|
|
86
|
+
focalY: event.focalY,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
.onEnd((event) => {
|
|
91
|
+
this.recordGesture('pinch_end', {
|
|
92
|
+
scale: event.scale,
|
|
93
|
+
focalX: event.focalX,
|
|
94
|
+
focalY: event.focalY,
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create long press gesture
|
|
101
|
+
createLongPressGesture() {
|
|
102
|
+
return Gesture.LongPress()
|
|
103
|
+
.minDuration(500)
|
|
104
|
+
.onStart((event) => {
|
|
105
|
+
this.recordGesture('long_press', {
|
|
106
|
+
x: event.x,
|
|
107
|
+
y: event.y,
|
|
108
|
+
duration: 500,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private recordGesture(gestureType: string, data: any) {
|
|
115
|
+
// Record with OpenTelemetry
|
|
116
|
+
switch (gestureType) {
|
|
117
|
+
case 'tap':
|
|
118
|
+
this.gestureInstrumentation.recordTap(data.x, data.y)
|
|
119
|
+
break
|
|
120
|
+
case 'pan_start':
|
|
121
|
+
case 'pan_update':
|
|
122
|
+
case 'pan_end':
|
|
123
|
+
this.gestureInstrumentation.recordPan(data.translationX || 0, data.translationY || 0)
|
|
124
|
+
break
|
|
125
|
+
case 'pinch_start':
|
|
126
|
+
case 'pinch_update':
|
|
127
|
+
case 'pinch_end':
|
|
128
|
+
this.gestureInstrumentation.recordPinch(data.scale, undefined)
|
|
129
|
+
break
|
|
130
|
+
case 'long_press':
|
|
131
|
+
this.gestureInstrumentation.recordLongPress(data.duration, undefined)
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Record with session recorder
|
|
136
|
+
if (this.onGestureRecord) {
|
|
137
|
+
this.onGestureRecord(gestureType, data)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create a gesture detector component
|
|
142
|
+
createGestureDetector(children: ReactNode, sessionState: SessionState | null): ReactNode {
|
|
143
|
+
if (sessionState !== SessionState.started) {
|
|
144
|
+
return children
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const tapGesture = this.createTapGesture()
|
|
148
|
+
const panGesture = this.createPanGesture()
|
|
149
|
+
const pinchGesture = this.createPinchGesture()
|
|
150
|
+
const longPressGesture = this.createLongPressGesture()
|
|
151
|
+
|
|
152
|
+
// Note: This would need to be implemented as a proper React component
|
|
153
|
+
// For now, return children directly - the gesture detection would be handled
|
|
154
|
+
// at the app level by wrapping the entire app with GestureHandlerRootView
|
|
155
|
+
return children
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { GestureEvent, RecorderConfig, EventType, IncrementalSource,
|
|
1
|
+
import { GestureEvent, RecorderConfig, EventType, IncrementalSource, MouseInteractionType, EventRecorder, eventWithTime } from '../types'
|
|
2
2
|
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
3
3
|
import { Dimensions } from 'react-native'
|
|
4
|
+
import { logger } from '../utils'
|
|
4
5
|
|
|
5
6
|
export class GestureRecorder implements EventRecorder {
|
|
6
7
|
private config?: RecorderConfig
|
|
@@ -10,6 +11,13 @@ export class GestureRecorder implements EventRecorder {
|
|
|
10
11
|
private screenDimensions: { width: number; height: number } | null = null
|
|
11
12
|
private lastGestureTime: number = 0
|
|
12
13
|
private gestureThrottleMs: number = 50 // Throttle gestures to avoid spam
|
|
14
|
+
private lastTouchTime: number = 0
|
|
15
|
+
private touchThrottleMs: number = 100 // Throttle touch events to max 10 per second
|
|
16
|
+
|
|
17
|
+
// Cyclic call detection
|
|
18
|
+
private isRecordingGesture = false
|
|
19
|
+
private gestureCallStack: string[] = []
|
|
20
|
+
private maxGestureCallDepth = 5
|
|
13
21
|
private eventRecorder?: EventRecorder
|
|
14
22
|
private imageNodeId: number = 1 // ID of the image node for touch interactions
|
|
15
23
|
private screenRecorder?: any // Reference to screen recorder for force capture
|
|
@@ -21,6 +29,7 @@ export class GestureRecorder implements EventRecorder {
|
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
start(): void {
|
|
32
|
+
logger.info('GestureRecorder', 'Gesture recording started')
|
|
24
33
|
this.isRecording = true
|
|
25
34
|
this.events = []
|
|
26
35
|
this._setupGestureHandlers()
|
|
@@ -467,14 +476,41 @@ export class GestureRecorder implements EventRecorder {
|
|
|
467
476
|
interactionType: MouseInteractionType,
|
|
468
477
|
target?: string,
|
|
469
478
|
): void {
|
|
470
|
-
const incrementalSnapshotEvent:
|
|
479
|
+
const incrementalSnapshotEvent: eventWithTime = {
|
|
471
480
|
type: EventType.IncrementalSnapshot,
|
|
472
481
|
data: {
|
|
473
482
|
source: IncrementalSource.MouseInteraction,
|
|
474
483
|
type: interactionType,
|
|
475
484
|
id: this.imageNodeId, // Reference to the image node
|
|
476
|
-
x:
|
|
477
|
-
y:
|
|
485
|
+
x: x, // Preserve decimal precision like web rrweb
|
|
486
|
+
y: y, // Preserve decimal precision like web rrweb
|
|
487
|
+
pointerType: 2, // 2 = Touch for React Native (0=Mouse, 1=Pen, 2=Touch)
|
|
488
|
+
},
|
|
489
|
+
timestamp: Date.now(),
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this.recordEvent(incrementalSnapshotEvent)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Create mouse move event with positions array (like web rrweb)
|
|
497
|
+
* @param x - X coordinate
|
|
498
|
+
* @param y - Y coordinate
|
|
499
|
+
* @param target - Target element identifier
|
|
500
|
+
*/
|
|
501
|
+
private _createMouseMoveEvent(x: number, y: number, target?: string): void {
|
|
502
|
+
const incrementalSnapshotEvent: eventWithTime = {
|
|
503
|
+
type: EventType.IncrementalSnapshot,
|
|
504
|
+
data: {
|
|
505
|
+
source: IncrementalSource.MouseMove, // Use MouseMove instead of MouseInteraction
|
|
506
|
+
positions: [
|
|
507
|
+
{
|
|
508
|
+
x: x, // Preserve decimal precision like web rrweb
|
|
509
|
+
y: y, // Preserve decimal precision like web rrweb
|
|
510
|
+
id: this.imageNodeId, // Reference to the image node
|
|
511
|
+
timeOffset: 0, // No time offset for single position
|
|
512
|
+
},
|
|
513
|
+
],
|
|
478
514
|
},
|
|
479
515
|
timestamp: Date.now(),
|
|
480
516
|
}
|
|
@@ -490,25 +526,38 @@ export class GestureRecorder implements EventRecorder {
|
|
|
490
526
|
* @param pressure - Touch pressure (optional)
|
|
491
527
|
*/
|
|
492
528
|
recordTouchStart(x: number, y: number, target?: string, pressure?: number): void {
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
this.
|
|
529
|
+
// Throttle touch events to prevent spam
|
|
530
|
+
const now = Date.now()
|
|
531
|
+
if (now - this.lastTouchTime < this.touchThrottleMs) {
|
|
532
|
+
logger.debug('GestureRecorder', `Touch start throttled (${now - this.lastTouchTime}ms < ${this.touchThrottleMs}ms)`)
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
this.lastTouchTime = now
|
|
496
536
|
|
|
497
|
-
|
|
498
|
-
|
|
537
|
+
logger.debug('GestureRecorder', 'Touch start recorded', { x, y, target, pressure })
|
|
538
|
+
// Record as MouseDown (type: 1) like web rrweb
|
|
539
|
+
this._createMouseInteractionEvent(x, y, MouseInteractionType.MouseDown, target)
|
|
499
540
|
}
|
|
500
541
|
|
|
501
542
|
/**
|
|
502
|
-
* Record touch move event as rrweb
|
|
543
|
+
* Record touch move event as rrweb MouseMove with positions array
|
|
503
544
|
* @param x - X coordinate
|
|
504
545
|
* @param y - Y coordinate
|
|
505
546
|
* @param target - Target element identifier
|
|
506
547
|
* @param pressure - Touch pressure (optional)
|
|
507
548
|
*/
|
|
508
549
|
recordTouchMove(x: number, y: number, target?: string, pressure?: number): void {
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
this.
|
|
550
|
+
// Throttle touch move events more aggressively
|
|
551
|
+
const now = Date.now()
|
|
552
|
+
if (now - this.lastTouchTime < this.touchThrottleMs * 2) { // 200ms throttle for move events
|
|
553
|
+
logger.debug('GestureRecorder', `Touch move throttled (${now - this.lastTouchTime}ms < ${this.touchThrottleMs * 2}ms)`)
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
this.lastTouchTime = now
|
|
557
|
+
|
|
558
|
+
logger.debug('GestureRecorder', 'Touch move recorded', { x, y, target, pressure })
|
|
559
|
+
// Record as MouseMove with positions array (like web rrweb)
|
|
560
|
+
this._createMouseMoveEvent(x, y, target)
|
|
512
561
|
}
|
|
513
562
|
|
|
514
563
|
/**
|
|
@@ -519,12 +568,36 @@ export class GestureRecorder implements EventRecorder {
|
|
|
519
568
|
* @param pressure - Touch pressure (optional)
|
|
520
569
|
*/
|
|
521
570
|
recordTouchEnd(x: number, y: number, target?: string, pressure?: number): void {
|
|
522
|
-
//
|
|
523
|
-
this.
|
|
524
|
-
|
|
571
|
+
// Cyclic call detection
|
|
572
|
+
if (this.isRecordingGesture) {
|
|
573
|
+
logger.error('GestureRecorder', 'CYCLIC CALL DETECTED! Already recording gesture', this.gestureCallStack)
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (this.gestureCallStack.length >= this.maxGestureCallDepth) {
|
|
578
|
+
logger.error('GestureRecorder', 'MAX GESTURE CALL DEPTH REACHED!', this.gestureCallStack)
|
|
579
|
+
return
|
|
580
|
+
}
|
|
525
581
|
|
|
526
|
-
|
|
527
|
-
this.
|
|
582
|
+
this.isRecordingGesture = true
|
|
583
|
+
this.gestureCallStack.push('recordTouchEnd')
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
logger.debug('GestureRecorder', 'Touch end recorded', { x, y, target, pressure })
|
|
587
|
+
// Always record touch end (no throttling for completion)
|
|
588
|
+
this.recordTap(x, y, target, pressure)
|
|
589
|
+
// Record as MouseUp (type: 0) like web rrweb
|
|
590
|
+
this._createMouseInteractionEvent(x, y, MouseInteractionType.MouseUp, target)
|
|
591
|
+
// Also record Click (type: 2) like web rrweb
|
|
592
|
+
this._createMouseInteractionEvent(x, y, MouseInteractionType.Click, target)
|
|
593
|
+
|
|
594
|
+
// Only force screen capture on touch end (not on every touch event)
|
|
595
|
+
logger.debug('GestureRecorder', 'Forcing screen capture after touch end')
|
|
596
|
+
this.screenRecorder?.forceCapture()
|
|
597
|
+
} finally {
|
|
598
|
+
this.isRecordingGesture = false
|
|
599
|
+
this.gestureCallStack.pop()
|
|
600
|
+
}
|
|
528
601
|
}
|
|
529
602
|
|
|
530
603
|
/**
|
|
@@ -534,7 +607,8 @@ export class GestureRecorder implements EventRecorder {
|
|
|
534
607
|
* @param target - Target element identifier
|
|
535
608
|
*/
|
|
536
609
|
recordTouchCancel(x: number, y: number, target?: string): void {
|
|
537
|
-
|
|
610
|
+
// Record as MouseUp (type: 0) like web rrweb for touch cancel
|
|
611
|
+
this._createMouseInteractionEvent(x, y, MouseInteractionType.MouseUp, target)
|
|
538
612
|
}
|
|
539
613
|
|
|
540
614
|
/**
|
package/src/recorder/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { pack } from '@rrweb/packer'
|
|
2
1
|
import { SessionType } from '@multiplayer-app/session-recorder-common'
|
|
3
|
-
|
|
2
|
+
// import { pack } from '@rrweb/packer' // Removed to avoid blob creation issues in Hermes
|
|
4
3
|
import { EventExporter } from './eventExporter'
|
|
4
|
+
import { logger } from '../utils'
|
|
5
5
|
import { ScreenRecorder } from './screenRecorder'
|
|
6
6
|
import { GestureRecorder } from './gestureRecorder'
|
|
7
7
|
import { NavigationTracker } from './navigationTracker'
|
|
@@ -9,7 +9,6 @@ import { RecorderConfig, EventRecorder, RRWebEvent } from '../types'
|
|
|
9
9
|
export class RecorderReactNativeSDK implements EventRecorder {
|
|
10
10
|
private isRecording = false
|
|
11
11
|
private config?: RecorderConfig
|
|
12
|
-
private eventRecorder?: EventRecorder
|
|
13
12
|
private screenRecorder: ScreenRecorder
|
|
14
13
|
private gestureRecorder: GestureRecorder
|
|
15
14
|
private navigationTracker: NavigationTracker
|
|
@@ -18,15 +17,15 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
18
17
|
private sessionId: string | null = null
|
|
19
18
|
private sessionType: SessionType = SessionType.PLAIN
|
|
20
19
|
|
|
20
|
+
|
|
21
21
|
constructor() {
|
|
22
|
+
this.screenRecorder = new ScreenRecorder()
|
|
22
23
|
this.gestureRecorder = new GestureRecorder()
|
|
23
24
|
this.navigationTracker = new NavigationTracker()
|
|
24
|
-
this.screenRecorder = new ScreenRecorder()
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
init(config: RecorderConfig
|
|
27
|
+
init(config: RecorderConfig): void {
|
|
28
28
|
this.config = config
|
|
29
|
-
this.eventRecorder = eventRecorder
|
|
30
29
|
this.gestureRecorder.init(config, this, this.screenRecorder)
|
|
31
30
|
this.navigationTracker.init(config)
|
|
32
31
|
this.screenRecorder.init(config, this)
|
|
@@ -45,6 +44,12 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
45
44
|
this.sessionType = sessionType
|
|
46
45
|
this.isRecording = true
|
|
47
46
|
|
|
47
|
+
// Emit recording started meta event
|
|
48
|
+
|
|
49
|
+
if (this.config.recordScreen) {
|
|
50
|
+
this.screenRecorder.start()
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
if (this.config.recordGestures) {
|
|
49
54
|
this.gestureRecorder.start()
|
|
50
55
|
}
|
|
@@ -53,9 +58,7 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
53
58
|
this.navigationTracker.start()
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
this.screenRecorder.start()
|
|
58
|
-
}
|
|
61
|
+
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
stop(): void {
|
|
@@ -88,23 +91,18 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
88
91
|
return
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
// Store the event locally
|
|
92
|
-
this.recordedEvents.push(event)
|
|
93
94
|
if (this.exporter) {
|
|
94
|
-
|
|
95
|
+
logger.debug('RecorderReactNativeSDK', 'Sending to exporter', event)
|
|
96
|
+
// Skip packing to avoid blob creation issues in Hermes
|
|
97
|
+
// const packedEvent = pack(event)
|
|
95
98
|
this.exporter.send({
|
|
96
|
-
event:
|
|
99
|
+
event: event, // Send raw event instead of packed
|
|
97
100
|
eventType: event.type,
|
|
98
101
|
timestamp: event.timestamp,
|
|
99
102
|
debugSessionId: this.sessionId,
|
|
100
103
|
debugSessionType: this.sessionType,
|
|
101
104
|
})
|
|
102
105
|
}
|
|
103
|
-
|
|
104
|
-
// Forward to parent event recorder if available
|
|
105
|
-
if (this.eventRecorder) {
|
|
106
|
-
this.eventRecorder.recordEvent(event)
|
|
107
|
-
}
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
/**
|
|
@@ -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,6 +24,7 @@ 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 = []
|