@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { ReactNode, useCallback, useMemo } from 'react'
|
|
2
|
+
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
3
|
+
|
|
4
|
+
export interface GestureCaptureWrapperProps {
|
|
5
|
+
children: ReactNode
|
|
6
|
+
onGestureRecord: (gestureType: string, data: any) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const GestureCaptureWrapper: React.FC<GestureCaptureWrapperProps> = ({ children, onGestureRecord }) => {
|
|
10
|
+
const recordGesture = useCallback(
|
|
11
|
+
(gestureType: string, data: any) => {
|
|
12
|
+
// Record with session recorder
|
|
13
|
+
onGestureRecord(gestureType, data)
|
|
14
|
+
},
|
|
15
|
+
[onGestureRecord]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// Create tap gesture
|
|
19
|
+
const tapGesture = useMemo(() => {
|
|
20
|
+
return Gesture.Tap()
|
|
21
|
+
.runOnJS(true)
|
|
22
|
+
.onStart((event) => {
|
|
23
|
+
recordGesture('tap', {
|
|
24
|
+
x: event.x,
|
|
25
|
+
y: event.y,
|
|
26
|
+
timestamp: Date.now()
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
}, [recordGesture])
|
|
30
|
+
|
|
31
|
+
// Create pan gesture (for swipes and drags)
|
|
32
|
+
const panGesture = useMemo(() => {
|
|
33
|
+
return Gesture.Pan()
|
|
34
|
+
.runOnJS(true)
|
|
35
|
+
.onStart((event) => {
|
|
36
|
+
recordGesture('pan_start', {
|
|
37
|
+
x: event.x,
|
|
38
|
+
y: event.y,
|
|
39
|
+
timestamp: Date.now()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
.onUpdate((event) => {
|
|
43
|
+
recordGesture('pan_update', {
|
|
44
|
+
x: event.x,
|
|
45
|
+
y: event.y,
|
|
46
|
+
translationX: event.translationX,
|
|
47
|
+
translationY: event.translationY,
|
|
48
|
+
velocityX: event.velocityX,
|
|
49
|
+
velocityY: event.velocityY,
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
.onEnd((event) => {
|
|
54
|
+
recordGesture('pan_end', {
|
|
55
|
+
x: event.x,
|
|
56
|
+
y: event.y,
|
|
57
|
+
translationX: event.translationX,
|
|
58
|
+
translationY: event.translationY,
|
|
59
|
+
velocityX: event.velocityX,
|
|
60
|
+
velocityY: event.velocityY,
|
|
61
|
+
timestamp: Date.now()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}, [recordGesture])
|
|
65
|
+
|
|
66
|
+
// Create long press gesture
|
|
67
|
+
const longPressGesture = useMemo(() => {
|
|
68
|
+
return Gesture.LongPress()
|
|
69
|
+
.runOnJS(true)
|
|
70
|
+
.minDuration(500)
|
|
71
|
+
.onStart((event) => {
|
|
72
|
+
recordGesture('long_press', {
|
|
73
|
+
x: event.x,
|
|
74
|
+
y: event.y,
|
|
75
|
+
duration: 500,
|
|
76
|
+
timestamp: Date.now()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}, [recordGesture])
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
83
|
+
<GestureDetector gesture={Gesture.Simultaneous(tapGesture, panGesture, longPressGesture)}>{children}</GestureDetector>
|
|
84
|
+
</GestureHandlerRootView>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./GestureCaptureWrapper";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import SessionRecorder from '@multiplayer-app/session-recorder-react-native'
|
|
2
|
+
import React, { PropsWithChildren, useCallback } from 'react'
|
|
3
|
+
import { View } from 'react-native'
|
|
4
|
+
import { SessionState } from '../../types'
|
|
5
|
+
import { logger } from '../../utils'
|
|
6
|
+
import { GestureCaptureWrapper } from '../GestureCaptureWrapper'
|
|
7
|
+
|
|
8
|
+
interface ScreenRecorderViewProps extends PropsWithChildren {}
|
|
9
|
+
|
|
10
|
+
export const ScreenRecorderView = ({ children }: ScreenRecorderViewProps) => {
|
|
11
|
+
// Set up gesture recording callback
|
|
12
|
+
const handleGestureRecord = useCallback((gestureType: string, data: any) => {
|
|
13
|
+
if (SessionRecorder.sessionState !== SessionState.started) {
|
|
14
|
+
logger.debug('SessionRecorderContext', 'Gesture recording skipped', {
|
|
15
|
+
client: !!SessionRecorder.sessionState,
|
|
16
|
+
sessionState: SessionRecorder.sessionState
|
|
17
|
+
})
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
logger.debug('SessionRecorderContext', 'Gesture recorded', { gestureType, data })
|
|
21
|
+
try {
|
|
22
|
+
// Record gesture as appropriate touch events
|
|
23
|
+
switch (gestureType) {
|
|
24
|
+
case 'tap':
|
|
25
|
+
// For tap, record both touch start and end
|
|
26
|
+
logger.debug('SessionRecorderContext', 'Recording tap as touch start + end')
|
|
27
|
+
SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
|
|
28
|
+
SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
|
|
29
|
+
break
|
|
30
|
+
|
|
31
|
+
case 'pan_start':
|
|
32
|
+
logger.debug('SessionRecorderContext', 'Recording pan_start as touch start')
|
|
33
|
+
SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
|
|
34
|
+
break
|
|
35
|
+
|
|
36
|
+
case 'pan_update':
|
|
37
|
+
logger.debug('SessionRecorderContext', 'Recording pan_update as touch move')
|
|
38
|
+
SessionRecorder.recordTouchMove?.(data.x, data.y, undefined, 1.0)
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
case 'pan_end':
|
|
42
|
+
logger.debug('SessionRecorderContext', 'Recording pan_end as touch end')
|
|
43
|
+
SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
case 'long_press':
|
|
47
|
+
logger.debug('SessionRecorderContext', 'Recording long_press as touch start + end')
|
|
48
|
+
SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
|
|
49
|
+
SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
|
|
50
|
+
break
|
|
51
|
+
default:
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error('SessionRecorderContext', 'Failed to record gesture event', error)
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
// Callback ref to set the viewshot ref immediately when available
|
|
59
|
+
const setViewShotRef = (ref: View | null) => {
|
|
60
|
+
if (ref) {
|
|
61
|
+
SessionRecorder.setViewShotRef?.(ref)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<GestureCaptureWrapper onGestureRecord={handleGestureRecord}>
|
|
67
|
+
<View ref={setViewShotRef} style={{ flex: 1 }}>
|
|
68
|
+
{children}
|
|
69
|
+
</View>
|
|
70
|
+
</GestureCaptureWrapper>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ScreenRecorderView";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './GestureCaptureWrapper'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
export const OTEL_MP_SAMPLE_TRACE_RATIO = 0.15
|
|
3
|
+
|
|
4
|
+
export const SESSION_ID_PROP_NAME = 'multiplayer-session-id'
|
|
5
|
+
|
|
6
|
+
export const SESSION_SHORT_ID_PROP_NAME = 'multiplayer-session-short-id'
|
|
7
|
+
|
|
8
|
+
export const SESSION_CONTINUOUS_DEBUGGING_PROP_NAME = 'multiplayer-session-continuous-debugging'
|
|
9
|
+
|
|
10
|
+
export const SESSION_STATE_PROP_NAME = 'multiplayer-session-state'
|
|
11
|
+
|
|
12
|
+
export const SESSION_TYPE_PROP_NAME = 'multiplayer-session-type'
|
|
13
|
+
|
|
14
|
+
export const SESSION_PROP_NAME = 'multiplayer-session-data'
|
|
15
|
+
|
|
16
|
+
export const SESSION_STARTED_EVENT = 'debug-session:started'
|
|
17
|
+
|
|
18
|
+
export const SESSION_STOPPED_EVENT = 'debug-session:stopped'
|
|
19
|
+
|
|
20
|
+
export const SESSION_SUBSCRIBE_EVENT = 'debug-session:subscribe'
|
|
21
|
+
|
|
22
|
+
export const SESSION_UNSUBSCRIBE_EVENT = 'debug-session:unsubscribe'
|
|
23
|
+
|
|
24
|
+
export const SESSION_AUTO_CREATED = 'debug-session:auto-created'
|
|
25
|
+
|
|
26
|
+
export const SESSION_ADD_EVENT = 'debug-session:rrweb:add-event'
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000
|
|
29
|
+
|
|
30
|
+
export const SESSION_RESPONSE = 'multiplayer-debug-session-response'
|
|
31
|
+
|
|
32
|
+
export const CONTINUOUS_DEBUGGING_TIMEOUT = 60000 // 1 minutes
|
|
33
|
+
|
|
34
|
+
export const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30 // TODO: move to shared config otel core
|
|
35
|
+
|
|
36
|
+
// // Package version - injected by webpack during build
|
|
37
|
+
// declare const PACKAGE_VERSION: string
|
|
38
|
+
// export const PACKAGE_VERSION_EXPORT = PACKAGE_VERSION || '1.0.0'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
// Regex patterns for OpenTelemetry ignore URLs
|
|
42
|
+
export const OTEL_IGNORE_URLS = [
|
|
43
|
+
// Traces endpoint
|
|
44
|
+
/.*\/v1\/traces/,
|
|
45
|
+
// Debug sessions endpoints
|
|
46
|
+
/.*\/v0\/radar\/debug-sessions\/start$/,
|
|
47
|
+
/.*\/v0\/radar\/debug-sessions\/[^/]+\/stop$/,
|
|
48
|
+
/.*\/v0\/radar\/debug-sessions\/[^/]+\/cancel$/,
|
|
49
|
+
|
|
50
|
+
// Continuous debug sessions endpoints
|
|
51
|
+
/.*\/v0\/radar\/continuous-debug-sessions\/start$/,
|
|
52
|
+
/.*\/v0\/radar\/continuous-debug-sessions\/[^/]+\/save$/,
|
|
53
|
+
/.*\/v0\/radar\/continuous-debug-sessions\/[^/]+\/cancel$/,
|
|
54
|
+
|
|
55
|
+
// Remote debug session endpoint
|
|
56
|
+
/.*\/v0\/radar\/remote-debug-session\/check$/,
|
|
57
|
+
|
|
58
|
+
// Or use a more general pattern to catch all radar API endpoints
|
|
59
|
+
// /.*\/v0\/radar\/.*/
|
|
60
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SessionRecorderSdk,
|
|
3
|
+
MULTIPLAYER_BASE_API_URL,
|
|
4
|
+
MULTIPLAYER_OTEL_DEFAULT_TRACES_EXPORTER_HTTP_URL,
|
|
5
|
+
} from '@multiplayer-app/session-recorder-common'
|
|
6
|
+
import {
|
|
7
|
+
MaskingConfig,
|
|
8
|
+
SessionRecorderConfigs,
|
|
9
|
+
WidgetButtonPlacement,
|
|
10
|
+
WidgetTextOverridesConfig,
|
|
11
|
+
} from '../types'
|
|
12
|
+
import {
|
|
13
|
+
OTEL_MP_SAMPLE_TRACE_RATIO,
|
|
14
|
+
DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE,
|
|
15
|
+
} from './constants'
|
|
16
|
+
const { mask, sensitiveFields, sensitiveHeaders } = SessionRecorderSdk
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_MASKING_CONFIG: MaskingConfig = {
|
|
19
|
+
isContentMaskingEnabled: true,
|
|
20
|
+
maskBody: mask(sensitiveFields),
|
|
21
|
+
maskHeaders: mask(sensitiveHeaders),
|
|
22
|
+
maskBodyFieldsList: sensitiveFields,
|
|
23
|
+
maskHeadersList: sensitiveHeaders,
|
|
24
|
+
headersToInclude: [],
|
|
25
|
+
headersToExclude: [],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_WIDGET_TEXT_CONFIG: WidgetTextOverridesConfig = {
|
|
29
|
+
initialTitleWithContinuous: 'Encountered an issue?',
|
|
30
|
+
initialTitleWithoutContinuous: 'Encountered an issue?',
|
|
31
|
+
initialDescriptionWithContinuous: 'Record your session so we can see the problem and fix it faster.',
|
|
32
|
+
initialDescriptionWithoutContinuous: 'Record your session so we can see the problem and fix it faster.',
|
|
33
|
+
continuousRecordingLabel: 'Continuous recording',
|
|
34
|
+
startRecordingButtonText: 'Start recording',
|
|
35
|
+
finalTitle: 'Done recording?',
|
|
36
|
+
finalDescription: 'You can also add a quick note with extra context, expectations, or questions. Thank you!',
|
|
37
|
+
commentPlaceholder: 'Add a message...',
|
|
38
|
+
saveButtonText: 'Submit recording',
|
|
39
|
+
cancelButtonText: 'Cancel recording',
|
|
40
|
+
continuousOverlayTitle: 'Save time, skip the reproductions',
|
|
41
|
+
continuousOverlayDescription: 'We keep a rolling record of your recent activity. If something doesn’t work as expected, just save the recording and continue working. No need to worry about exceptions and errors - we automatically save recordings for those!',
|
|
42
|
+
saveLastSnapshotButtonText: 'Save recording',
|
|
43
|
+
submitDialogTitle: 'Save recording',
|
|
44
|
+
submitDialogSubtitle: 'This full-stack session recording will be saved directly to your selected Multiplayer project. All data is automatically correlated end-to-end.',
|
|
45
|
+
submitDialogCommentLabel: 'You can also add context, comments, or notes.',
|
|
46
|
+
submitDialogCommentPlaceholder: 'Add a message...',
|
|
47
|
+
submitDialogSubmitText: 'Save',
|
|
48
|
+
submitDialogCancelText: 'Cancel',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const BASE_CONFIG: Required<SessionRecorderConfigs> = {
|
|
52
|
+
apiKey: '',
|
|
53
|
+
|
|
54
|
+
version: '',
|
|
55
|
+
application: '',
|
|
56
|
+
environment: '',
|
|
57
|
+
|
|
58
|
+
showWidget: true,
|
|
59
|
+
showContinuousRecording: true,
|
|
60
|
+
widgetButtonPlacement: WidgetButtonPlacement.bottomRight,
|
|
61
|
+
|
|
62
|
+
usePostMessageFallback: false,
|
|
63
|
+
apiBaseUrl: MULTIPLAYER_BASE_API_URL,
|
|
64
|
+
exporterEndpoint: MULTIPLAYER_OTEL_DEFAULT_TRACES_EXPORTER_HTTP_URL,
|
|
65
|
+
|
|
66
|
+
schemifyDocSpanPayload: true,
|
|
67
|
+
|
|
68
|
+
ignoreUrls: [],
|
|
69
|
+
propagateTraceHeaderCorsUrls: [],
|
|
70
|
+
|
|
71
|
+
sampleTraceRatio: OTEL_MP_SAMPLE_TRACE_RATIO,
|
|
72
|
+
maxCapturingHttpPayloadSize: DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE,
|
|
73
|
+
|
|
74
|
+
captureBody: true,
|
|
75
|
+
captureHeaders: true,
|
|
76
|
+
masking: DEFAULT_MASKING_CONFIG,
|
|
77
|
+
widgetTextOverrides: DEFAULT_WIDGET_TEXT_CONFIG,
|
|
78
|
+
|
|
79
|
+
recordScreen: true,
|
|
80
|
+
recordGestures: true,
|
|
81
|
+
recordNavigation: true,
|
|
82
|
+
}
|
package/src/config/masking.ts
CHANGED
|
@@ -1,63 +1,12 @@
|
|
|
1
|
+
import { MaskingConfig } from '../types'
|
|
2
|
+
import { DEFAULT_MASKING_CONFIG } from './defaults'
|
|
3
|
+
import { isValidArray, isValidBoolean, isValidFunction } from './validators'
|
|
1
4
|
import { SessionRecorderSdk } from '@multiplayer-app/session-recorder-common'
|
|
2
5
|
|
|
3
6
|
const { mask, sensitiveFields, sensitiveHeaders } = SessionRecorderSdk
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Focused only on HTTP request/response masking for OpenTelemetry traces
|
|
8
|
-
*/
|
|
9
|
-
export interface HttpMaskingConfig {
|
|
10
|
-
/** If true, enables masking for debug span payload in traces */
|
|
11
|
-
isContentMaskingEnabled?: boolean
|
|
12
|
-
/** Custom function for masking body in traces */
|
|
13
|
-
maskBody?: (payload: any, span: any) => any
|
|
14
|
-
/** Custom function for masking headers in traces */
|
|
15
|
-
maskHeaders?: (headers: any, span: any) => any
|
|
16
|
-
/** List of body fields to mask in traces */
|
|
17
|
-
maskBodyFieldsList?: string[]
|
|
18
|
-
/** List of headers to mask in traces */
|
|
19
|
-
maskHeadersList?: string[]
|
|
20
|
-
/** List of headers to include in traces (if specified, only these headers will be captured) */
|
|
21
|
-
headersToInclude?: string[]
|
|
22
|
-
/** List of headers to exclude from traces */
|
|
23
|
-
headersToExclude?: string[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Default HTTP masking configuration
|
|
28
|
-
*/
|
|
29
|
-
export const DEFAULT_HTTP_MASKING_CONFIG: HttpMaskingConfig = {
|
|
30
|
-
isContentMaskingEnabled: true,
|
|
31
|
-
maskBody: mask(sensitiveFields),
|
|
32
|
-
maskHeaders: mask(sensitiveHeaders),
|
|
33
|
-
maskBodyFieldsList: sensitiveFields,
|
|
34
|
-
maskHeadersList: sensitiveHeaders,
|
|
35
|
-
headersToInclude: [],
|
|
36
|
-
headersToExclude: [],
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Validation helper functions
|
|
41
|
-
*/
|
|
42
|
-
const isValidArray = (value: any[] | undefined, defaultValue: any[]): any[] => {
|
|
43
|
-
return Array.isArray(value) ? value : defaultValue
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const isValidBoolean = (value: boolean | undefined, defaultValue: boolean): boolean => {
|
|
47
|
-
return typeof value === 'boolean' ? value : defaultValue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const isValidFunction = (value: any, defaultValue: any): any => {
|
|
51
|
-
return typeof value === 'function' ? value : defaultValue
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get HTTP masking configuration
|
|
56
|
-
* @param masking Optional masking configuration
|
|
57
|
-
* @returns Processed HTTP masking configuration
|
|
58
|
-
*/
|
|
59
|
-
export const getHttpMaskingConfig = (masking?: HttpMaskingConfig): HttpMaskingConfig => {
|
|
60
|
-
const baseMasking = DEFAULT_HTTP_MASKING_CONFIG
|
|
8
|
+
export const getMaskingConfig = (masking?: MaskingConfig): MaskingConfig => {
|
|
9
|
+
const baseMasking = DEFAULT_MASKING_CONFIG
|
|
61
10
|
|
|
62
11
|
if (typeof masking !== 'object') {
|
|
63
12
|
return baseMasking
|
|
@@ -67,12 +16,12 @@ export const getHttpMaskingConfig = (masking?: HttpMaskingConfig): HttpMaskingCo
|
|
|
67
16
|
const maskBodyFieldsList = isValidArray(masking.maskBodyFieldsList, sensitiveFields)
|
|
68
17
|
|
|
69
18
|
return {
|
|
70
|
-
isContentMaskingEnabled: isValidBoolean(masking.isContentMaskingEnabled, baseMasking.isContentMaskingEnabled ?? true),
|
|
71
|
-
maskBody: isValidFunction(masking.maskBody, mask(maskBodyFieldsList)),
|
|
72
|
-
maskHeaders: isValidFunction(masking.maskHeaders, mask(maskHeadersList)),
|
|
73
|
-
maskBodyFieldsList,
|
|
74
19
|
maskHeadersList,
|
|
20
|
+
maskBodyFieldsList,
|
|
75
21
|
headersToInclude: isValidArray(masking.headersToInclude, baseMasking.headersToInclude ?? []),
|
|
76
22
|
headersToExclude: isValidArray(masking.headersToExclude, baseMasking.headersToExclude ?? []),
|
|
23
|
+
isContentMaskingEnabled: isValidBoolean(masking.isContentMaskingEnabled, baseMasking.isContentMaskingEnabled ?? true),
|
|
24
|
+
maskBody: isValidFunction(masking.maskBody, mask(maskBodyFieldsList)),
|
|
25
|
+
maskHeaders: isValidFunction(masking.maskHeaders, mask(maskHeadersList)),
|
|
77
26
|
}
|
|
78
|
-
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SessionRecorderConfigs, SessionRecorderOptions, WidgetButtonPlacement } from '../types'
|
|
2
|
+
import { BASE_CONFIG } from './defaults'
|
|
3
|
+
import { getMaskingConfig } from './masking'
|
|
4
|
+
import {
|
|
5
|
+
isValidString,
|
|
6
|
+
isValidNumber,
|
|
7
|
+
isValidBoolean,
|
|
8
|
+
isValidArray,
|
|
9
|
+
isValidEnum,
|
|
10
|
+
} from './validators'
|
|
11
|
+
|
|
12
|
+
const getWidgetTextOverridesConfig = (config: any, defaultConfig: any) => {
|
|
13
|
+
if (!config || typeof config !== 'object') {
|
|
14
|
+
return defaultConfig
|
|
15
|
+
}
|
|
16
|
+
return Object.keys(defaultConfig).reduce((acc, key) => {
|
|
17
|
+
acc[key] = isValidString(config[key], defaultConfig[key])
|
|
18
|
+
return acc
|
|
19
|
+
}, {} as Record<string, any>)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const getSessionRecorderConfig = (c: SessionRecorderOptions): SessionRecorderConfigs => {
|
|
23
|
+
if (!c) {
|
|
24
|
+
return BASE_CONFIG
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
apiKey: isValidString(c.apiKey, BASE_CONFIG.apiKey),
|
|
29
|
+
version: isValidString(c.version, BASE_CONFIG.version),
|
|
30
|
+
application: isValidString(c.application, BASE_CONFIG.application),
|
|
31
|
+
environment: isValidString(c.environment, BASE_CONFIG.environment),
|
|
32
|
+
|
|
33
|
+
exporterEndpoint: isValidString(c.exporterEndpoint, BASE_CONFIG.exporterEndpoint),
|
|
34
|
+
apiBaseUrl: isValidString(c.apiBaseUrl, BASE_CONFIG.apiBaseUrl),
|
|
35
|
+
usePostMessageFallback: isValidBoolean(c.usePostMessageFallback, BASE_CONFIG.usePostMessageFallback),
|
|
36
|
+
|
|
37
|
+
showWidget: isValidBoolean(c.showWidget, BASE_CONFIG.showWidget),
|
|
38
|
+
showContinuousRecording: isValidBoolean(c.showContinuousRecording, BASE_CONFIG.showContinuousRecording),
|
|
39
|
+
widgetButtonPlacement: isValidEnum<WidgetButtonPlacement>(c.widgetButtonPlacement, BASE_CONFIG.widgetButtonPlacement, Object.values(WidgetButtonPlacement) as WidgetButtonPlacement[]),
|
|
40
|
+
ignoreUrls: isValidArray(c.ignoreUrls, BASE_CONFIG.ignoreUrls),
|
|
41
|
+
sampleTraceRatio: isValidNumber(c.sampleTraceRatio, BASE_CONFIG.sampleTraceRatio),
|
|
42
|
+
propagateTraceHeaderCorsUrls: c.propagateTraceHeaderCorsUrls || BASE_CONFIG.propagateTraceHeaderCorsUrls,
|
|
43
|
+
schemifyDocSpanPayload: isValidBoolean(c.schemifyDocSpanPayload, BASE_CONFIG.schemifyDocSpanPayload),
|
|
44
|
+
maxCapturingHttpPayloadSize: isValidNumber(c.maxCapturingHttpPayloadSize, BASE_CONFIG.maxCapturingHttpPayloadSize),
|
|
45
|
+
|
|
46
|
+
masking: getMaskingConfig(c.masking),
|
|
47
|
+
captureBody: isValidBoolean(c.captureBody, BASE_CONFIG.captureBody),
|
|
48
|
+
captureHeaders: isValidBoolean(c.captureHeaders, BASE_CONFIG.captureHeaders),
|
|
49
|
+
widgetTextOverrides: getWidgetTextOverridesConfig(c.widgetTextOverrides, BASE_CONFIG.widgetTextOverrides),
|
|
50
|
+
|
|
51
|
+
recordScreen: isValidBoolean(c.recordScreen, BASE_CONFIG.recordScreen),
|
|
52
|
+
recordGestures: isValidBoolean(c.recordGestures, BASE_CONFIG.recordGestures),
|
|
53
|
+
recordNavigation: isValidBoolean(c.recordNavigation, BASE_CONFIG.recordNavigation),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation helper functions for configuration objects
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const isValidStringOrRegExp = (value: string | RegExp | undefined, defaultValue: string | RegExp) => {
|
|
6
|
+
return typeof value === 'string' || value instanceof RegExp ? value : defaultValue
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const isValidString = <T extends string>(value: string | undefined | T, defaultValue: string) => {
|
|
10
|
+
return typeof value === 'string' ? value.trim() : defaultValue
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const isValidNumber = (value: number | undefined, defaultValue: number) => {
|
|
14
|
+
return typeof value === 'number' ? value : defaultValue
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const isValidBoolean = (value: boolean | undefined, defaultValue: boolean) => {
|
|
18
|
+
return typeof value === 'boolean' ? value : defaultValue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const isValidArray = (value: any[] | undefined, defaultValue: any[]) => {
|
|
22
|
+
return Array.isArray(value) ? value : defaultValue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const isValidEnum = <T>(value: any | T, defaultValue: T, enumValues: T[]): T => {
|
|
26
|
+
return enumValues.includes(value as T) ? value as T : defaultValue
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const isValidFunction = (value: any, defaultValue: any) => {
|
|
30
|
+
return typeof value === 'function' ? value : defaultValue
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { createContext, useContext, PropsWithChildren, useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { Pressable, Text, View } from 'react-native'
|
|
3
|
+
import { SessionRecorderOptions, SessionState } from '../types'
|
|
4
|
+
import SessionRecorder from '../session-recorder'
|
|
5
|
+
import sessionRecorder from '../session-recorder'
|
|
6
|
+
import { ScreenRecorderView } from '../components/ScreenRecorderView'
|
|
7
|
+
|
|
8
|
+
interface SessionRecorderContextType {
|
|
9
|
+
instance: typeof SessionRecorder
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SessionRecorderContext = createContext<SessionRecorderContextType | null>(null)
|
|
13
|
+
|
|
14
|
+
export interface SessionRecorderProviderProps extends PropsWithChildren {
|
|
15
|
+
options: SessionRecorderOptions
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SessionRecorderProvider: React.FC<SessionRecorderProviderProps> = ({ children, options }) => {
|
|
19
|
+
const [sessionState, setSessionState] = useState<SessionState | null>(null)
|
|
20
|
+
const optionsRef = useRef<string>()
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const newOptions = JSON.stringify(options)
|
|
24
|
+
if (optionsRef.current === JSON.stringify(options)) return
|
|
25
|
+
optionsRef.current = newOptions
|
|
26
|
+
SessionRecorder.init(options)
|
|
27
|
+
}, [options])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setSessionState(SessionRecorder.sessionState)
|
|
31
|
+
SessionRecorder.on('state-change', (state: SessionState) => {
|
|
32
|
+
setSessionState(state)
|
|
33
|
+
})
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
const onToggleSession = () => {
|
|
37
|
+
if (SessionRecorder.sessionState === SessionState.started) {
|
|
38
|
+
SessionRecorder.stop()
|
|
39
|
+
} else {
|
|
40
|
+
SessionRecorder.start()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<SessionRecorderContext.Provider value={{ instance: sessionRecorder }}>
|
|
46
|
+
<ScreenRecorderView>{children}</ScreenRecorderView>
|
|
47
|
+
<Pressable onPress={onToggleSession}>
|
|
48
|
+
<View
|
|
49
|
+
style={{
|
|
50
|
+
position: 'absolute',
|
|
51
|
+
right: 0,
|
|
52
|
+
bottom: 100,
|
|
53
|
+
width: 48,
|
|
54
|
+
height: 48,
|
|
55
|
+
paddingTop: 16,
|
|
56
|
+
paddingLeft: 10,
|
|
57
|
+
backgroundColor: 'red',
|
|
58
|
+
borderTopLeftRadius: 24,
|
|
59
|
+
borderBottomLeftRadius: 24
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Text style={{ color: 'white' }}>{sessionState === SessionState.started ? 'Stop' : 'Start'}</Text>
|
|
63
|
+
</View>
|
|
64
|
+
</Pressable>
|
|
65
|
+
</SessionRecorderContext.Provider>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const useSessionRecorder = (): SessionRecorderContextType => {
|
|
70
|
+
const context = useContext(SessionRecorderContext)
|
|
71
|
+
if (!context) {
|
|
72
|
+
throw new Error('useSessionRecorder must be used within a SessionRecorderProvider')
|
|
73
|
+
}
|
|
74
|
+
return context
|
|
75
|
+
}
|
package/src/expo.ts
CHANGED
|
@@ -1,41 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
// Create a specialized instance for Expo
|
|
5
|
-
const SessionRecorderExpoInstance = new SessionRecorder()
|
|
6
|
-
|
|
7
|
-
// Add Expo-specific initialization
|
|
8
|
-
const originalInit = SessionRecorderExpoInstance.init.bind(SessionRecorderExpoInstance)
|
|
9
|
-
|
|
10
|
-
SessionRecorderExpoInstance.init = async function(options: any) {
|
|
11
|
-
// Add Expo-specific configuration
|
|
12
|
-
const expoOptions = {
|
|
13
|
-
...options,
|
|
14
|
-
// Add Expo-specific defaults
|
|
15
|
-
platform: 'expo',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Log Expo environment detection
|
|
19
|
-
if (isExpoEnvironment()) {
|
|
20
|
-
console.log('Multiplayer Session Recorder: Running in Expo environment')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return originalInit(expoOptions)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Export the Expo instance as default
|
|
27
|
-
export default SessionRecorderExpoInstance
|
|
28
|
-
|
|
29
|
-
// Export types and classes
|
|
30
|
-
export * from './types'
|
|
31
|
-
export { SessionRecorder } from './sessionRecorder'
|
|
32
|
-
export { TracerReactNativeSDK } from './otel'
|
|
33
|
-
export { RecorderReactNativeSDK } from './recorder'
|
|
34
|
-
export { ApiService } from './services/api.service'
|
|
35
|
-
export { StorageService } from './services/storage.service'
|
|
36
|
-
|
|
37
|
-
// Export common types
|
|
1
|
+
import './patch'
|
|
2
|
+
import SessionRecorder from './session-recorder'
|
|
38
3
|
export * from '@multiplayer-app/session-recorder-common'
|
|
4
|
+
export * from './context/SessionRecorderContext'
|
|
5
|
+
|
|
6
|
+
export { SessionRecorder }
|
|
7
|
+
// Export the instance as default
|
|
8
|
+
export default SessionRecorder
|
|
39
9
|
|
|
40
10
|
// Export Expo-specific utilities
|
|
41
11
|
export { isExpoEnvironment, getPlatformAttributes } from './utils/platform'
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import './patch'
|
|
2
|
+
import SessionRecorder from './session-recorder'
|
|
3
|
+
export * from '@multiplayer-app/session-recorder-common'
|
|
4
|
+
export * from './context/SessionRecorderContext'
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
// Export platform utilities including app metadata configuration
|
|
7
|
+
export {
|
|
8
|
+
detectPlatform,
|
|
9
|
+
isExpoEnvironment,
|
|
10
|
+
configureAppMetadata,
|
|
11
|
+
getPlatformAttributes,
|
|
12
|
+
getConfiguredAppMetadata,
|
|
13
|
+
} from './utils/platform'
|
|
4
14
|
|
|
15
|
+
export { SessionRecorder }
|
|
5
16
|
// Export the instance as default
|
|
6
|
-
export default
|
|
7
|
-
|
|
8
|
-
// Export types and classes
|
|
9
|
-
export * from './types'
|
|
10
|
-
export { SessionRecorder } from './sessionRecorder'
|
|
11
|
-
export { TracerReactNativeSDK } from './otel'
|
|
12
|
-
export { RecorderReactNativeSDK } from './recorder'
|
|
13
|
-
export { ApiService } from './services/api.service'
|
|
14
|
-
export { StorageService } from './services/storage.service'
|
|
15
|
-
|
|
16
|
-
// Export common types
|
|
17
|
-
export * from '@multiplayer-app/session-recorder-common'
|
|
18
|
-
|
|
19
|
-
// Export platform utilities
|
|
20
|
-
export { isExpoEnvironment, getPlatformAttributes, detectPlatform } from './utils/platform'
|
|
17
|
+
export default SessionRecorder
|