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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/SessionRecorderNative.podspec +29 -0
  2. package/android/build.gradle +32 -0
  3. package/app.plugin.js +42 -0
  4. package/copy-react-native-dist.sh +4 -10
  5. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
  6. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
  7. package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
  8. package/dist/components/GestureCaptureWrapper/index.js +1 -0
  9. package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
  10. package/dist/components/MaskableComponent.d.ts +22 -0
  11. package/dist/components/MaskableComponent.js +1 -0
  12. package/dist/components/MaskableComponent.js.map +1 -0
  13. package/dist/components/MaskableTextInput.d.ts +14 -0
  14. package/dist/components/MaskableTextInput.js +1 -0
  15. package/dist/components/MaskableTextInput.js.map +1 -0
  16. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
  17. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
  18. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  19. package/dist/components/ScreenRecorderView/index.d.ts +1 -0
  20. package/dist/components/ScreenRecorderView/index.js +1 -0
  21. package/dist/components/ScreenRecorderView/index.js.map +1 -0
  22. package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
  23. package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
  24. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  25. package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
  26. package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
  27. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  28. package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
  29. package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
  30. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  31. package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
  32. package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
  33. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  34. package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
  35. package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
  36. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  37. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
  38. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
  39. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  40. package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
  41. package/dist/components/SessionRecorderWidget/icons.js +1 -0
  42. package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
  43. package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
  44. package/dist/components/SessionRecorderWidget/index.js +1 -0
  45. package/dist/components/SessionRecorderWidget/index.js.map +1 -0
  46. package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
  47. package/dist/components/SessionRecorderWidget/styles.js +1 -0
  48. package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
  49. package/dist/components/index.d.ts +3 -0
  50. package/dist/components/index.js +1 -0
  51. package/dist/components/index.js.map +1 -0
  52. package/dist/config/defaults.js +1 -1
  53. package/dist/config/defaults.js.map +1 -1
  54. package/dist/config/masking.js +1 -1
  55. package/dist/config/masking.js.map +1 -1
  56. package/dist/context/SessionRecorderContext.d.ts +5 -3
  57. package/dist/context/SessionRecorderContext.js +1 -1
  58. package/dist/context/SessionRecorderContext.js.map +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/native/ScreenMasking.d.ts +21 -0
  61. package/dist/native/ScreenMasking.js +1 -0
  62. package/dist/native/ScreenMasking.js.map +1 -0
  63. package/dist/native/SessionRecorderNative.d.ts +21 -0
  64. package/dist/native/SessionRecorderNative.js +1 -0
  65. package/dist/native/SessionRecorderNative.js.map +1 -0
  66. package/dist/otel/index.d.ts +0 -2
  67. package/dist/otel/index.js.map +1 -1
  68. package/dist/otel/instrumentations/index.d.ts +0 -3
  69. package/dist/otel/instrumentations/index.js +1 -1
  70. package/dist/otel/instrumentations/index.js.map +1 -1
  71. package/dist/patch/xhr.js +1 -1
  72. package/dist/patch/xhr.js.map +1 -1
  73. package/dist/recorder/gestureRecorder.d.ts +0 -9
  74. package/dist/recorder/gestureRecorder.js +1 -1
  75. package/dist/recorder/gestureRecorder.js.map +1 -1
  76. package/dist/recorder/index.d.ts +4 -3
  77. package/dist/recorder/index.js.map +1 -1
  78. package/dist/recorder/screenRecorder.d.ts +2 -6
  79. package/dist/recorder/screenRecorder.js +1 -1
  80. package/dist/recorder/screenRecorder.js.map +1 -1
  81. package/dist/recorder/screenshotManager.d.ts +10 -0
  82. package/dist/recorder/screenshotManager.js +1 -0
  83. package/dist/recorder/screenshotManager.js.map +1 -0
  84. package/dist/services/screenMaskingService.d.ts +39 -0
  85. package/dist/services/screenMaskingService.js +1 -0
  86. package/dist/services/screenMaskingService.js.map +1 -0
  87. package/dist/services/storage.service.d.ts +18 -2
  88. package/dist/services/storage.service.js +1 -1
  89. package/dist/services/storage.service.js.map +1 -1
  90. package/dist/session-recorder.d.ts +4 -2
  91. package/dist/session-recorder.js +1 -1
  92. package/dist/session-recorder.js.map +1 -1
  93. package/dist/types/index.d.ts +2 -16
  94. package/dist/types/index.js +1 -1
  95. package/dist/types/index.js.map +1 -1
  96. package/dist/types/session-recorder.d.ts +6 -0
  97. package/dist/types/session-recorder.js.map +1 -1
  98. package/dist/utils/app-metadata.d.ts +16 -0
  99. package/dist/utils/app-metadata.js +1 -0
  100. package/dist/utils/app-metadata.js.map +1 -0
  101. package/dist/utils/componentRegistry.d.ts +64 -0
  102. package/dist/utils/componentRegistry.js +1 -0
  103. package/dist/utils/componentRegistry.js.map +1 -0
  104. package/dist/utils/nativeModuleTest.d.ts +8 -0
  105. package/dist/utils/nativeModuleTest.js +1 -0
  106. package/dist/utils/nativeModuleTest.js.map +1 -0
  107. package/dist/utils/platform.d.ts +35 -0
  108. package/dist/utils/platform.js +1 -1
  109. package/dist/utils/platform.js.map +1 -1
  110. package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
  111. package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
  112. package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
  113. package/dist/utils/rrweb-events.d.ts +1 -1
  114. package/dist/utils/rrweb-events.js +1 -1
  115. package/dist/utils/rrweb-events.js.map +1 -1
  116. package/dist/utils/screenshotMasker.d.ts +96 -0
  117. package/dist/utils/screenshotMasker.js +1 -0
  118. package/dist/utils/screenshotMasker.js.map +1 -0
  119. package/dist/utils/viewHierarchyTracker.d.ts +89 -0
  120. package/dist/utils/viewHierarchyTracker.js +1 -0
  121. package/dist/utils/viewHierarchyTracker.js.map +1 -0
  122. package/dist/version.d.ts +1 -1
  123. package/dist/version.js +1 -1
  124. package/dist/version.js.map +1 -1
  125. package/docs/NATIVE_MODULE_SETUP.md +177 -0
  126. package/ios/SessionRecorderNative.m +17 -0
  127. package/ios/SessionRecorderNative.podspec +26 -0
  128. package/ios/SessionRecorderNative.swift +205 -0
  129. package/package.json +25 -14
  130. package/plugin/package.json +20 -0
  131. package/plugin/src/index.js +42 -0
  132. package/react-native.config.js +15 -0
  133. package/RRWEB_INTEGRATION.md +0 -336
  134. package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
  135. package/babel.config.js +0 -13
  136. package/dist/components/GestureCaptureWrapper.js +0 -1
  137. package/dist/components/GestureCaptureWrapper.js.map +0 -1
  138. package/dist/expo.d.ts +0 -7
  139. package/dist/expo.js +0 -1
  140. package/dist/expo.js.map +0 -1
  141. package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
  142. package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
  143. package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
  144. package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
  145. package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
  146. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
  147. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
  148. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
  149. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
  150. package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
  151. package/dist/recorder/gestureHandlerRecorder.js +0 -1
  152. package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
  153. package/dist/types/rrweb.d.ts +0 -118
  154. package/dist/types/rrweb.js +0 -1
  155. package/dist/types/rrweb.js.map +0 -1
  156. package/src/components/GestureCaptureWrapper.tsx +0 -110
  157. package/src/config/constants.ts +0 -60
  158. package/src/config/defaults.ts +0 -82
  159. package/src/config/index.ts +0 -6
  160. package/src/config/masking.ts +0 -27
  161. package/src/config/session-recorder.ts +0 -55
  162. package/src/config/validators.ts +0 -31
  163. package/src/context/SessionRecorderContext.tsx +0 -143
  164. package/src/expo.ts +0 -11
  165. package/src/index.ts +0 -9
  166. package/src/otel/helpers.ts +0 -275
  167. package/src/otel/index.ts +0 -149
  168. package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
  169. package/src/otel/instrumentations/index.ts +0 -120
  170. package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -77
  171. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -119
  172. package/src/patch/index.ts +0 -1
  173. package/src/patch/xhr.ts +0 -142
  174. package/src/recorder/eventExporter.ts +0 -141
  175. package/src/recorder/gestureHandlerRecorder.ts +0 -157
  176. package/src/recorder/gestureRecorder.ts +0 -622
  177. package/src/recorder/index.ts +0 -178
  178. package/src/recorder/navigationTracker.ts +0 -449
  179. package/src/recorder/screenRecorder.ts +0 -506
  180. package/src/services/api.service.ts +0 -203
  181. package/src/services/storage.service.ts +0 -158
  182. package/src/session-recorder.ts +0 -601
  183. package/src/types/expo.d.ts +0 -23
  184. package/src/types/index.ts +0 -46
  185. package/src/types/session-recorder.ts +0 -423
  186. package/src/types/session.ts +0 -65
  187. package/src/utils/index.ts +0 -8
  188. package/src/utils/logger.ts +0 -225
  189. package/src/utils/platform.ts +0 -87
  190. package/src/utils/request-utils.ts +0 -61
  191. package/src/utils/rrweb-events.ts +0 -311
  192. package/src/utils/session.ts +0 -18
  193. package/src/utils/time.ts +0 -17
  194. package/src/utils/type-utils.ts +0 -75
  195. package/src/version.ts +0 -1
  196. package/tsconfig.json +0 -24
  197. /package/dist/components/{GestureCaptureWrapper.d.ts → GestureCaptureWrapper/GestureCaptureWrapper.d.ts} +0 -0
@@ -1,143 +0,0 @@
1
- import React, { createContext, useContext, ReactNode, PropsWithChildren, useState, useEffect, useRef, useCallback } from 'react'
2
- import { Pressable, Text, View } from 'react-native'
3
- import { SessionRecorderOptions, SessionState } from '../types'
4
- import SessionRecorder from '../session-recorder'
5
- import { GestureCaptureWrapper } from '../components/GestureCaptureWrapper'
6
- import sessionRecorder from '../session-recorder'
7
- import { logger } from '../utils'
8
-
9
- interface SessionRecorderContextType {
10
- instance: typeof SessionRecorder
11
- }
12
-
13
- const SessionRecorderContext = createContext<SessionRecorderContextType | null>(null)
14
-
15
- export interface SessionRecorderProviderProps extends PropsWithChildren {
16
- options: SessionRecorderOptions
17
- }
18
-
19
- export const SessionRecorderProvider: React.FC<SessionRecorderProviderProps> = ({ children, options }) => {
20
- const [sessionState, setSessionState] = useState<SessionState | null>(null)
21
- const optionsRef = useRef<string>()
22
-
23
- useEffect(() => {
24
- const newOptions = JSON.stringify(options)
25
- if (optionsRef.current === JSON.stringify(options)) return
26
- optionsRef.current = newOptions
27
- SessionRecorder.init(options)
28
- }, [options])
29
-
30
- useEffect(() => {
31
- setSessionState(SessionRecorder.sessionState)
32
- SessionRecorder.on('state-change', (state: SessionState) => {
33
- setSessionState(state)
34
- })
35
- }, [])
36
-
37
- const onToggleSession = () => {
38
- if (SessionRecorder.sessionState === SessionState.started) {
39
- SessionRecorder.stop()
40
- } else {
41
- SessionRecorder.start()
42
- }
43
- }
44
-
45
- return (
46
- <SessionRecorderContext.Provider value={{ instance: sessionRecorder }}>
47
- <GestureEventCapture>
48
- {children}
49
- <Pressable onPress={onToggleSession}>
50
- <View
51
- style={{
52
- position: 'absolute',
53
- right: 0,
54
- bottom: 100,
55
- width: 48,
56
- height: 48,
57
- paddingTop: 16,
58
- paddingLeft: 10,
59
- backgroundColor: 'red',
60
- borderTopLeftRadius: 24,
61
- borderBottomLeftRadius: 24
62
- }}
63
- >
64
- <Text style={{ color: 'white' }}>{sessionState === SessionState.started ? 'Stop' : 'Start'}</Text>
65
- </View>
66
- </Pressable>
67
- </GestureEventCapture>
68
- </SessionRecorderContext.Provider>
69
- )
70
- }
71
-
72
- // Gesture-based event capture component
73
- const GestureEventCapture: React.FC<{ children: ReactNode }> = ({ children }) => {
74
- // Set up gesture recording callback
75
- const handleGestureRecord = useCallback((gestureType: string, data: any) => {
76
- if (SessionRecorder.sessionState !== SessionState.started) {
77
- logger.debug('SessionRecorderContext', 'Gesture recording skipped', {
78
- client: !!SessionRecorder.sessionState,
79
- sessionState: SessionRecorder.sessionState
80
- })
81
- return
82
- }
83
- logger.debug('SessionRecorderContext', 'Gesture recorded', { gestureType, data })
84
- try {
85
- // Record gesture as appropriate touch events
86
- switch (gestureType) {
87
- case 'tap':
88
- // For tap, record both touch start and end
89
- logger.debug('SessionRecorderContext', 'Recording tap as touch start + end')
90
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
91
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
92
- break
93
-
94
- case 'pan_start':
95
- logger.debug('SessionRecorderContext', 'Recording pan_start as touch start')
96
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
97
- break
98
-
99
- case 'pan_update':
100
- logger.debug('SessionRecorderContext', 'Recording pan_update as touch move')
101
- SessionRecorder.recordTouchMove?.(data.x, data.y, undefined, 1.0)
102
- break
103
-
104
- case 'pan_end':
105
- logger.debug('SessionRecorderContext', 'Recording pan_end as touch end')
106
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
107
- break
108
-
109
- case 'long_press':
110
- logger.debug('SessionRecorderContext', 'Recording long_press as touch start + end')
111
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
112
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
113
- break
114
- default:
115
- }
116
- } catch (error) {
117
- logger.error('SessionRecorderContext', 'Failed to record gesture event', error)
118
- }
119
- }, [])
120
-
121
- // Callback ref to set the viewshot ref immediately when available
122
- const setViewShotRef = (ref: View | null) => {
123
- if (ref) {
124
- SessionRecorder.setViewShotRef?.(ref)
125
- }
126
- }
127
-
128
- return (
129
- <GestureCaptureWrapper onGestureRecord={handleGestureRecord}>
130
- <View ref={setViewShotRef} style={{ flex: 1 }}>
131
- {children}
132
- </View>
133
- </GestureCaptureWrapper>
134
- )
135
- }
136
-
137
- export const useSessionRecorder = (): SessionRecorderContextType => {
138
- const context = useContext(SessionRecorderContext)
139
- if (!context) {
140
- throw new Error('useSessionRecorder must be used within a SessionRecorderProvider')
141
- }
142
- return context
143
- }
package/src/expo.ts DELETED
@@ -1,11 +0,0 @@
1
- import './patch'
2
- import SessionRecorder from './session-recorder'
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
9
-
10
- // Export Expo-specific utilities
11
- export { isExpoEnvironment, getPlatformAttributes } from './utils/platform'
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- import './patch'
2
- import SessionRecorder from './session-recorder'
3
- export * from '@multiplayer-app/session-recorder-common'
4
- export * from './context/SessionRecorderContext'
5
-
6
-
7
- export { SessionRecorder }
8
- // Export the instance as default
9
- export default SessionRecorder
@@ -1,275 +0,0 @@
1
- import { Span } from '@opentelemetry/api'
2
- import {
3
- MULTIPLAYER_TRACE_DEBUG_PREFIX,
4
- MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX,
5
- ATTR_MULTIPLAYER_HTTP_REQUEST_BODY,
6
- ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS,
7
- ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY,
8
- ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS,
9
- } from '@multiplayer-app/session-recorder-common'
10
- import { logger } from '../utils'
11
- import { SessionRecorderSdk } from '@multiplayer-app/session-recorder-common'
12
- import { TracerReactNativeConfig } from '../types'
13
-
14
- const { schemify } = SessionRecorderSdk
15
-
16
- export interface HttpPayloadData {
17
- requestBody?: any
18
- responseBody?: any
19
- requestHeaders?: Record<string, string>
20
- responseHeaders?: Record<string, string>
21
- }
22
-
23
- export interface ProcessedHttpPayload {
24
- requestBody?: string
25
- responseBody?: string
26
- requestHeaders?: string
27
- responseHeaders?: string
28
- }
29
-
30
- /**
31
- * Checks if the trace should be processed based on trace ID prefixes
32
- */
33
- export function shouldProcessTrace(traceId: string): boolean {
34
- return (
35
- traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) ||
36
- traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)
37
- )
38
- }
39
-
40
- /**
41
- * Processes request and response body based on trace type and configuration
42
- */
43
- export function processBody(
44
- payload: HttpPayloadData,
45
- config: TracerReactNativeConfig,
46
- span: Span,
47
- ): { requestBody?: string; responseBody?: string } {
48
- const { captureBody, masking } = config
49
- const traceId = span.spanContext().traceId
50
-
51
- if (!captureBody) {
52
- return {}
53
- }
54
-
55
- let { requestBody, responseBody } = payload
56
-
57
- if (requestBody !== undefined && requestBody !== null) {
58
- requestBody = JSON.parse(JSON.stringify(requestBody))
59
- }
60
- if (responseBody !== undefined && responseBody !== null) {
61
- responseBody = JSON.parse(JSON.stringify(responseBody))
62
- }
63
-
64
- // Apply masking for debug traces
65
- if (
66
- traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) ||
67
- traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)
68
- ) {
69
- if (masking.isContentMaskingEnabled) {
70
- requestBody = requestBody && masking.maskBody?.(requestBody, span)
71
- responseBody = responseBody && masking.maskBody?.(responseBody, span)
72
- }
73
- }
74
-
75
- // Convert to string if needed
76
- if (typeof requestBody !== 'string') {
77
- requestBody = JSON.stringify(requestBody)
78
- }
79
-
80
- if (typeof responseBody !== 'string') {
81
- responseBody = JSON.stringify(responseBody)
82
- }
83
-
84
- return {
85
- requestBody: requestBody?.length ? requestBody : undefined,
86
- responseBody: responseBody?.length ? responseBody : undefined,
87
- }
88
- }
89
-
90
- /**
91
- * Processes request and response headers based on configuration
92
- */
93
- export function processHeaders(
94
- payload: HttpPayloadData,
95
- config: TracerReactNativeConfig,
96
- span: Span,
97
- ): { requestHeaders?: string; responseHeaders?: string } {
98
- const { captureHeaders, masking } = config
99
-
100
- if (!captureHeaders) {
101
- return {}
102
- }
103
-
104
- let { requestHeaders = {}, responseHeaders = {} } = payload
105
-
106
- // Handle header filtering
107
- if (
108
- !masking.headersToInclude?.length &&
109
- !masking.headersToExclude?.length
110
- ) {
111
- // Add null checks to prevent JSON.parse error when headers is undefined
112
- if (requestHeaders !== undefined && requestHeaders !== null) {
113
- requestHeaders = JSON.parse(JSON.stringify(requestHeaders))
114
- }
115
- if (responseHeaders !== undefined && responseHeaders !== null) {
116
- responseHeaders = JSON.parse(JSON.stringify(responseHeaders))
117
- }
118
- } else {
119
- if (masking.headersToInclude) {
120
- const _requestHeaders: Record<string, string> = {}
121
- const _responseHeaders: Record<string, string> = {}
122
-
123
- for (const headerName of masking.headersToInclude) {
124
- if (requestHeaders[headerName]) {
125
- _requestHeaders[headerName] = requestHeaders[headerName]
126
- }
127
- if (responseHeaders[headerName]) {
128
- _responseHeaders[headerName] = responseHeaders[headerName]
129
- }
130
- }
131
-
132
- requestHeaders = _requestHeaders
133
- responseHeaders = _responseHeaders
134
- }
135
-
136
- if (masking.headersToExclude?.length) {
137
- for (const headerName of masking.headersToExclude) {
138
- delete requestHeaders[headerName]
139
- delete responseHeaders[headerName]
140
- }
141
- }
142
- }
143
-
144
- // Apply masking
145
- const maskedRequestHeaders = masking.maskHeaders?.(requestHeaders, span) || requestHeaders
146
- const maskedResponseHeaders = masking.maskHeaders?.(responseHeaders, span) || responseHeaders
147
-
148
- // Convert to string
149
- const requestHeadersStr = typeof maskedRequestHeaders === 'string'
150
- ? maskedRequestHeaders
151
- : JSON.stringify(maskedRequestHeaders)
152
-
153
- const responseHeadersStr = typeof maskedResponseHeaders === 'string'
154
- ? maskedResponseHeaders
155
- : JSON.stringify(maskedResponseHeaders)
156
-
157
- return {
158
- requestHeaders: requestHeadersStr?.length ? requestHeadersStr : undefined,
159
- responseHeaders: responseHeadersStr?.length ? responseHeadersStr : undefined,
160
- }
161
- }
162
-
163
- /**
164
- * Processes HTTP payload (body and headers) and sets span attributes
165
- */
166
- export function processHttpPayload(
167
- payload: HttpPayloadData,
168
- config: TracerReactNativeConfig,
169
- span: Span,
170
- ): void {
171
- const traceId = span.spanContext().traceId
172
-
173
- if (!shouldProcessTrace(traceId)) {
174
- return
175
- }
176
-
177
- const { requestBody, responseBody } = processBody(payload, config, span)
178
- const { requestHeaders, responseHeaders } = processHeaders(payload, config, span)
179
-
180
- // Set span attributes
181
- if (requestBody) {
182
- span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_BODY, requestBody)
183
- }
184
-
185
- if (responseBody) {
186
- span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY, responseBody)
187
- }
188
-
189
- if (requestHeaders) {
190
- span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS, requestHeaders)
191
- }
192
-
193
- if (responseHeaders) {
194
- span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, responseHeaders)
195
- }
196
- }
197
-
198
- /**
199
- * Converts Headers object to plain object
200
- */
201
- export function headersToObject(headers: Headers | Record<string, string> | Record<string, string | string[]> | string[][] | undefined): Record<string, string> {
202
- const result: Record<string, string> = {}
203
-
204
- if (!headers) {
205
- return result
206
- }
207
-
208
- if (headers instanceof Headers) {
209
- headers.forEach((value: string, key: string) => {
210
- result[key] = value
211
- })
212
- } else if (Array.isArray(headers)) {
213
- // Handle array of [key, value] pairs
214
- for (const [key, value] of headers) {
215
- if (typeof key === 'string' && typeof value === 'string') {
216
- result[key] = value
217
- }
218
- }
219
- } else if (typeof headers === 'object' && !Array.isArray(headers)) {
220
- for (const [key, value] of Object.entries(headers)) {
221
- if (typeof key === 'string' && typeof value === 'string') {
222
- result[key] = value
223
- }
224
- }
225
- }
226
-
227
- return result
228
- }
229
-
230
- /**
231
- * Extracts response body as string from Response object
232
- */
233
- export async function extractResponseBody(response: Response): Promise<string | null> {
234
- if (!response.body) {
235
- return null
236
- }
237
-
238
- try {
239
- if (response.body instanceof ReadableStream) {
240
- // Check if response body is already consumed
241
- if (response.bodyUsed) {
242
- return null
243
- }
244
-
245
- const responseClone = response.clone()
246
- return responseClone.text()
247
- } else {
248
- return JSON.stringify(response.body)
249
- }
250
- } catch (error) {
251
- // If cloning fails (body already consumed), return null
252
- // eslint-disable-next-line no-console
253
- logger.warn('DEBUGGER_LIB', 'Failed to extract response body', error)
254
- return null
255
- }
256
- }
257
-
258
- export const getExporterEndpoint = (exporterEndpoint: string): string => {
259
- const hasPath = exporterEndpoint && (() => {
260
- try {
261
- const url = new URL(exporterEndpoint)
262
- return url.pathname !== '/' && url.pathname !== ''
263
- } catch {
264
- return false
265
- }
266
- })()
267
-
268
- if (hasPath) {
269
- return exporterEndpoint
270
- }
271
-
272
- const trimmedExporterEndpoint = new URL(exporterEndpoint).origin
273
-
274
- return `${trimmedExporterEndpoint}/v1/traces`
275
- }
package/src/otel/index.ts DELETED
@@ -1,149 +0,0 @@
1
- import { resourceFromAttributes } from '@opentelemetry/resources'
2
- import { W3CTraceContextPropagator } from '@opentelemetry/core'
3
- import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
- import * as SemanticAttributes from '@opentelemetry/semantic-conventions'
5
- import { registerInstrumentations } from '@opentelemetry/instrumentation'
6
- import {
7
- SessionType,
8
- ATTR_MULTIPLAYER_SESSION_ID,
9
- SessionRecorderIdGenerator,
10
- SessionRecorderTraceIdRatioBasedSampler,
11
- SessionRecorderBrowserTraceExporter,
12
- } from '@multiplayer-app/session-recorder-common'
13
- import { TracerReactNativeConfig } from '../types'
14
- import { getInstrumentations } from './instrumentations'
15
- import { getExporterEndpoint } from './helpers'
16
- import { ReactNavigationInstrumentation } from './instrumentations/reactNavigationInstrumentation'
17
- import { GestureInstrumentation } from './instrumentations/gestureInstrumentation'
18
- import { getPlatformAttributes } from '../utils/platform'
19
- import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
20
-
21
-
22
-
23
-
24
- export class TracerReactNativeSDK {
25
- private tracerProvider?: WebTracerProvider
26
- private config?: TracerReactNativeConfig
27
-
28
- private sessionId = ''
29
- private idGenerator?: SessionRecorderIdGenerator
30
- private exporter?: any
31
- private navigationInstrumentation?: ReactNavigationInstrumentation
32
- private gestureInstrumentation?: GestureInstrumentation
33
- private isInitialized = false
34
-
35
- constructor() { }
36
-
37
- private _setSessionId(
38
- sessionId: string,
39
- sessionType: SessionType = SessionType.PLAIN,
40
- ) {
41
- this.sessionId = sessionId
42
- this.idGenerator?.setSessionId(sessionId, sessionType)
43
- }
44
-
45
- init(options: TracerReactNativeConfig): void {
46
- this.config = options
47
-
48
- const { application, version, environment } = this.config
49
-
50
- this.idGenerator = new SessionRecorderIdGenerator()
51
-
52
- this.exporter = new SessionRecorderBrowserTraceExporter({
53
- apiKey: options.apiKey,
54
- url: getExporterEndpoint(options.exporterEndpoint),
55
- usePostMessageFallback: options.usePostMessageFallback,
56
- })
57
-
58
- this.tracerProvider = new WebTracerProvider({
59
- resource: resourceFromAttributes({
60
- [SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
61
- [SemanticAttributes.SEMRESATTRS_SERVICE_VERSION]: version,
62
- [SemanticAttributes.SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: environment,
63
- ...getPlatformAttributes(),
64
- }),
65
- idGenerator: this.idGenerator,
66
- sampler: new SessionRecorderTraceIdRatioBasedSampler(this.config.sampleTraceRatio || 0.15),
67
- spanProcessors: [
68
- this._getSpanSessionIdProcessor(),
69
- new BatchSpanProcessor(this.exporter),
70
- ],
71
- })
72
-
73
- this.tracerProvider.register({
74
- propagator: new W3CTraceContextPropagator(),
75
- })
76
-
77
- // Register instrumentations
78
- registerInstrumentations({
79
- tracerProvider: this.tracerProvider,
80
- instrumentations: getInstrumentations(this.config),
81
- })
82
-
83
- // // Initialize React Native specific instrumentations
84
- // this.navigationInstrumentation = new ReactNavigationInstrumentation()
85
- // this.gestureInstrumentation = new GestureInstrumentation()
86
-
87
- // // Enable the custom instrumentations
88
- // this.navigationInstrumentation.enable()
89
- // this.gestureInstrumentation.enable()
90
-
91
- this.isInitialized = true
92
- }
93
-
94
- private _getSpanSessionIdProcessor() {
95
- return {
96
- onStart: (span: any) => {
97
- if (this.sessionId) {
98
- span.setAttribute(ATTR_MULTIPLAYER_SESSION_ID, this.sessionId)
99
- }
100
- // Add React Native specific attributes
101
- span.setAttribute('platform', 'react-native')
102
- span.setAttribute('timestamp', Date.now())
103
- },
104
- onEnd: () => { },
105
- shutdown: () => Promise.resolve(),
106
- forceFlush: () => Promise.resolve(),
107
- }
108
- }
109
-
110
- start(sessionId: string, sessionType: SessionType): void {
111
- if (!this.tracerProvider) {
112
- throw new Error(
113
- 'Configuration not initialized. Call init() before start().',
114
- )
115
- }
116
-
117
- this._setSessionId(sessionId, sessionType)
118
- }
119
-
120
- stop(): void {
121
- if (!this.tracerProvider) {
122
- throw new Error(
123
- 'Configuration not initialized. Call init() before start().',
124
- )
125
- }
126
-
127
- this._setSessionId('')
128
- }
129
-
130
- setApiKey(apiKey: string): void {
131
- if (!this.exporter) {
132
- throw new Error(
133
- 'Configuration not initialized. Call init() before setApiKey().',
134
- )
135
- }
136
-
137
- this.exporter.setApiKey?.(apiKey)
138
- }
139
-
140
- setSessionId(sessionId: string, sessionType: SessionType): void {
141
- this._setSessionId(sessionId, sessionType)
142
- }
143
-
144
- // Shutdown (React Native specific)
145
- shutdown(): Promise<void> {
146
- this.isInitialized = false
147
- return Promise.resolve()
148
- }
149
- }