@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.
- package/RRWEB_INTEGRATION.md +336 -0
- package/VIEWSHOT_INTEGRATION_TEST.md +123 -0
- package/copy-react-native-dist.sh +38 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.d.ts +6 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
- package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
- package/dist/components/GestureCaptureWrapper/index.js +1 -0
- package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
- 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/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
- package/dist/components/ScreenRecorderView/index.d.ts +1 -0
- package/dist/components/ScreenRecorderView/index.js +1 -0
- package/dist/components/ScreenRecorderView/index.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -0
- package/dist/config/constants.d.ts +18 -0
- package/dist/config/constants.js +1 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/defaults.d.ts +4 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/masking.d.ts +2 -30
- package/dist/config/masking.js +1 -1
- package/dist/config/masking.js.map +1 -1
- package/dist/config/session-recorder.d.ts +2 -0
- package/dist/config/session-recorder.js +1 -0
- package/dist/config/session-recorder.js.map +1 -0
- package/dist/config/validators.d.ts +10 -0
- package/dist/config/validators.js +1 -0
- package/dist/config/validators.js.map +1 -0
- package/dist/context/SessionRecorderContext.d.ts +12 -0
- package/dist/context/SessionRecorderContext.js +1 -0
- package/dist/context/SessionRecorderContext.js.map +1 -0
- package/dist/expo.d.ts +5 -9
- package/dist/expo.js +1 -1
- package/dist/expo.js.map +1 -1
- package/dist/index.d.ts +6 -10
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/helpers.d.ts +45 -3
- package/dist/otel/helpers.js +1 -1
- package/dist/otel/helpers.js.map +1 -1
- package/dist/otel/index.d.ts +4 -25
- package/dist/otel/index.js +1 -1
- package/dist/otel/index.js.map +1 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js +1 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -1
- package/dist/otel/instrumentations/index.d.ts +3 -4
- 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/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
- package/dist/patch/index.d.ts +1 -0
- package/dist/patch/index.js +1 -0
- package/dist/patch/index.js.map +1 -0
- package/dist/patch/xhr.d.ts +2 -0
- package/dist/patch/xhr.js +1 -0
- package/dist/patch/xhr.js.map +1 -0
- package/dist/recorder/eventExporter.d.ts +21 -0
- package/dist/recorder/eventExporter.js +1 -0
- package/dist/recorder/eventExporter.js.map +1 -0
- 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 +68 -11
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +60 -6
- 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 +79 -10
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/services/api.service.d.ts +62 -10
- package/dist/services/api.service.js +1 -1
- package/dist/services/api.service.js.map +1 -1
- package/dist/services/storage.service.d.ts +23 -16
- package/dist/services/storage.service.js +1 -1
- package/dist/services/storage.service.js.map +1 -1
- package/dist/session-recorder.d.ts +166 -0
- package/dist/session-recorder.js +1 -0
- package/dist/session-recorder.js.map +1 -0
- package/dist/types/index.d.ts +15 -76
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/rrweb.d.ts +118 -0
- package/dist/types/rrweb.js +1 -0
- package/dist/types/rrweb.js.map +1 -0
- package/dist/types/session-recorder.d.ts +366 -0
- package/dist/types/session-recorder.js +1 -0
- package/dist/types/session-recorder.js.map +1 -0
- package/dist/types/session.d.ts +59 -0
- package/dist/types/session.js +1 -0
- package/dist/types/session.js.map +1 -0
- package/dist/utils/app-metadata.d.ts +16 -0
- package/dist/utils/app-metadata.js +1 -0
- package/dist/utils/app-metadata.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -0
- 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/platform.d.ts +37 -0
- package/dist/utils/platform.js +1 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/request-utils.d.ts +21 -0
- package/dist/utils/request-utils.js +1 -0
- package/dist/utils/request-utils.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/utils/session.d.ts +5 -0
- package/dist/utils/session.js +1 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/time.d.ts +4 -0
- package/dist/utils/time.js +1 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/utils/type-utils.d.ts +16 -0
- package/dist/utils/type-utils.js +1 -0
- package/dist/utils/type-utils.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/AUTO_METADATA_DETECTION.md +108 -0
- package/package.json +10 -9
- package/scripts/generate-app-metadata.js +173 -0
- package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +86 -0
- package/src/components/GestureCaptureWrapper/index.ts +1 -0
- package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +72 -0
- package/src/components/ScreenRecorderView/index.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/config/constants.ts +60 -0
- package/src/config/defaults.ts +82 -0
- package/src/config/index.ts +6 -0
- package/src/config/masking.ts +10 -61
- package/src/config/session-recorder.ts +55 -0
- package/src/config/validators.ts +31 -0
- package/src/context/SessionRecorderContext.tsx +75 -0
- package/src/expo.ts +7 -37
- package/src/index.ts +14 -17
- package/src/otel/helpers.ts +265 -11
- package/src/otel/index.ts +37 -247
- package/src/otel/instrumentations/index.ts +82 -53
- package/src/patch/index.ts +1 -0
- package/src/patch/xhr.ts +142 -0
- package/src/recorder/eventExporter.ts +141 -0
- package/src/recorder/gestureRecorder.ts +194 -125
- package/src/recorder/index.ts +132 -24
- package/src/recorder/navigationTracker.ts +12 -10
- package/src/recorder/screenRecorder.ts +242 -155
- package/src/services/api.service.ts +170 -45
- package/src/services/storage.service.ts +102 -74
- package/src/session-recorder.ts +600 -0
- package/src/types/index.ts +19 -79
- package/src/types/session-recorder.ts +423 -0
- package/src/types/session.ts +65 -0
- package/src/utils/app-metadata.ts +31 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/platform.ts +321 -6
- package/src/utils/request-utils.ts +61 -0
- package/src/utils/rrweb-events.ts +309 -0
- package/src/utils/session.ts +18 -0
- package/src/utils/time.ts +17 -0
- package/src/utils/type-utils.ts +75 -0
- package/src/version.ts +1 -1
- package/dist/sessionRecorder.d.ts +0 -54
- package/dist/sessionRecorder.js +0 -1
- package/dist/sessionRecorder.js.map +0 -1
- package/examples/sample-expo-app/README.md +0 -142
- package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
- package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
- package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
- package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
- package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
- package/examples/sample-expo-app/app/+not-found.tsx +0 -32
- package/examples/sample-expo-app/app/_layout.tsx +0 -53
- package/examples/sample-expo-app/app/post/[id].tsx +0 -199
- package/examples/sample-expo-app/app/user/[id].tsx +0 -270
- package/examples/sample-expo-app/app.json +0 -42
- package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
- package/examples/sample-expo-app/assets/images/favicon.png +0 -0
- package/examples/sample-expo-app/assets/images/icon.png +0 -0
- package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
- package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
- package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
- package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
- package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
- package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
- package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
- package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
- package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
- package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
- package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
- package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
- package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
- package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
- package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
- package/examples/sample-expo-app/constants/Colors.ts +0 -26
- package/examples/sample-expo-app/eslint.config.js +0 -10
- package/examples/sample-expo-app/hooks/useApi.ts +0 -41
- package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
- package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
- package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
- package/examples/sample-expo-app/metro.config.js +0 -26
- package/examples/sample-expo-app/package-lock.json +0 -26296
- package/examples/sample-expo-app/package.json +0 -59
- package/examples/sample-expo-app/scripts/reset-project.js +0 -112
- package/examples/sample-expo-app/services/api.ts +0 -98
- package/examples/sample-expo-app/tsconfig.json +0 -17
- package/examples/sample-expo-app/utils/navigation.ts +0 -19
- package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -164
- package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -114
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
// Gesture event recorded
|
|
107
82
|
}
|
|
108
83
|
|
|
109
84
|
private _recordOpenTelemetrySpan(event: GestureEvent): void {
|
|
110
85
|
try {
|
|
111
|
-
const span = trace.getTracer('
|
|
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
|
-
|
|
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
|
}
|
package/src/recorder/index.ts
CHANGED
|
@@ -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 {
|
|
4
|
-
import {
|
|
5
|
-
|
|
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
|
|
12
|
-
private
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
this.navigationTracker.
|
|
57
|
-
this.screenRecorder.pause()
|
|
73
|
+
|
|
74
|
+
setNavigationRef(ref: any): void {
|
|
75
|
+
this.navigationTracker.setNavigationRef(ref)
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
33
|
+
// Navigation tracking started
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
stop(): void {
|
|
35
37
|
this.isRecording = false
|
|
36
38
|
this._removeNavigationListener()
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
// Navigation listeners setup complete
|
|
83
85
|
} catch (error) {
|
|
84
|
-
|
|
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
|
-
|
|
99
|
+
// Navigation listeners removed
|
|
98
100
|
} catch (error) {
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
404
|
+
// Failed to record error span - silently continue
|
|
403
405
|
}
|
|
404
406
|
}
|
|
405
407
|
|