@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
package/src/otel/helpers.ts
CHANGED
|
@@ -1,21 +1,275 @@
|
|
|
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'
|
|
1
13
|
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
}
|
|
4
161
|
}
|
|
5
162
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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)
|
|
9
191
|
}
|
|
10
192
|
|
|
11
|
-
|
|
12
|
-
|
|
193
|
+
if (responseHeaders) {
|
|
194
|
+
span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, responseHeaders)
|
|
195
|
+
}
|
|
13
196
|
}
|
|
14
197
|
|
|
15
|
-
|
|
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> {
|
|
16
202
|
const result: Record<string, string> = {}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
|
|
20
227
|
return result
|
|
21
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
CHANGED
|
@@ -8,95 +8,51 @@ import {
|
|
|
8
8
|
ATTR_MULTIPLAYER_SESSION_ID,
|
|
9
9
|
SessionRecorderIdGenerator,
|
|
10
10
|
SessionRecorderTraceIdRatioBasedSampler,
|
|
11
|
+
SessionRecorderBrowserTraceExporter,
|
|
11
12
|
} from '@multiplayer-app/session-recorder-common'
|
|
12
13
|
import { TracerReactNativeConfig } from '../types'
|
|
13
14
|
import { getInstrumentations } from './instrumentations'
|
|
14
15
|
import { getExporterEndpoint } from './helpers'
|
|
15
|
-
import { ReactNavigationInstrumentation } from './instrumentations/reactNavigationInstrumentation'
|
|
16
|
-
import { GestureInstrumentation } from './instrumentations/gestureInstrumentation'
|
|
17
|
-
import { getHttpMaskingConfig } from '../config/masking'
|
|
18
|
-
import { getPlatformAttributes } from '../utils/platform'
|
|
19
|
-
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
private resource: any
|
|
24
|
-
private idGenerator: any
|
|
25
|
-
private sampler: any
|
|
26
|
-
private spanProcessors: any[]
|
|
27
|
-
private propagator: any
|
|
17
|
+
import { getPlatformAttributes } from '../utils/platform'
|
|
18
|
+
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
|
|
28
19
|
|
|
29
|
-
constructor(options: any) {
|
|
30
|
-
this.resource = options.resource
|
|
31
|
-
this.idGenerator = options.idGenerator
|
|
32
|
-
this.sampler = options.sampler
|
|
33
|
-
this.spanProcessors = options.spanProcessors
|
|
34
|
-
this.propagator = options.propagator
|
|
35
|
-
}
|
|
36
20
|
|
|
37
|
-
register(options: any) {
|
|
38
|
-
// Register the provider with OpenTelemetry
|
|
39
|
-
console.log('ReactNativeTracerProvider registered')
|
|
40
|
-
}
|
|
41
21
|
|
|
42
|
-
getTracer(name: string, version?: string, options?: any): any {
|
|
43
|
-
// Return a mock tracer for now
|
|
44
|
-
return {
|
|
45
|
-
startSpan: (name: string, options?: any) => ({
|
|
46
|
-
setAttribute: (key: string, value: any) => { },
|
|
47
|
-
setStatus: (status: any) => { },
|
|
48
|
-
end: () => { },
|
|
49
|
-
recordException: (error: Error) => { },
|
|
50
|
-
}),
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
22
|
|
|
55
23
|
export class TracerReactNativeSDK {
|
|
56
|
-
private tracerProvider?:
|
|
24
|
+
private tracerProvider?: WebTracerProvider
|
|
57
25
|
private config?: TracerReactNativeConfig
|
|
58
|
-
|
|
26
|
+
|
|
59
27
|
private sessionId = ''
|
|
60
|
-
private idGenerator
|
|
28
|
+
private idGenerator?: SessionRecorderIdGenerator
|
|
61
29
|
private exporter?: any
|
|
62
|
-
private navigationInstrumentation: ReactNavigationInstrumentation
|
|
63
|
-
private gestureInstrumentation: GestureInstrumentation
|
|
64
30
|
private isInitialized = false
|
|
65
31
|
|
|
66
|
-
constructor() {
|
|
67
|
-
this.idGenerator = new SessionRecorderIdGenerator()
|
|
68
|
-
this.navigationInstrumentation = new ReactNavigationInstrumentation()
|
|
69
|
-
this.gestureInstrumentation = new GestureInstrumentation()
|
|
70
|
-
}
|
|
32
|
+
constructor() { }
|
|
71
33
|
|
|
72
34
|
private _setSessionId(
|
|
73
35
|
sessionId: string,
|
|
74
36
|
sessionType: SessionType = SessionType.PLAIN,
|
|
75
37
|
) {
|
|
76
38
|
this.sessionId = sessionId
|
|
77
|
-
this.idGenerator
|
|
39
|
+
this.idGenerator?.setSessionId(sessionId, sessionType)
|
|
78
40
|
}
|
|
79
41
|
|
|
80
42
|
init(options: TracerReactNativeConfig): void {
|
|
81
|
-
if (this.isInitialized) {
|
|
82
|
-
console.warn('TracerReactNativeSDK already initialized')
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
43
|
this.config = options
|
|
87
|
-
|
|
44
|
+
|
|
88
45
|
const { application, version, environment } = this.config
|
|
89
46
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.exporter = new
|
|
47
|
+
this.idGenerator = new SessionRecorderIdGenerator()
|
|
48
|
+
|
|
49
|
+
this.exporter = new SessionRecorderBrowserTraceExporter({
|
|
50
|
+
apiKey: options.apiKey,
|
|
93
51
|
url: getExporterEndpoint(options.exporterEndpoint),
|
|
94
|
-
|
|
95
|
-
'Authorization': `Bearer ${options.apiKey}`,
|
|
96
|
-
},
|
|
52
|
+
usePostMessageFallback: options.usePostMessageFallback,
|
|
97
53
|
})
|
|
98
54
|
|
|
99
|
-
this.tracerProvider = new
|
|
55
|
+
this.tracerProvider = new WebTracerProvider({
|
|
100
56
|
resource: resourceFromAttributes({
|
|
101
57
|
[SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
|
|
102
58
|
[SemanticAttributes.SEMRESATTRS_SERVICE_VERSION]: version,
|
|
@@ -122,7 +78,6 @@ export class TracerReactNativeSDK {
|
|
|
122
78
|
})
|
|
123
79
|
|
|
124
80
|
this.isInitialized = true
|
|
125
|
-
console.log('TracerReactNativeSDK initialized successfully')
|
|
126
81
|
}
|
|
127
82
|
|
|
128
83
|
private _getSpanSessionIdProcessor() {
|
|
@@ -141,208 +96,43 @@ export class TracerReactNativeSDK {
|
|
|
141
96
|
}
|
|
142
97
|
}
|
|
143
98
|
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
setNavigationRef(ref: any): void {
|
|
150
|
-
this.navigationInstrumentation.setNavigationRef(ref)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
recordNavigate(routeName: string, params?: Record<string, any>): void {
|
|
154
|
-
this.navigationInstrumentation.recordNavigate(routeName, params)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
recordGoBack(): void {
|
|
158
|
-
this.navigationInstrumentation.recordGoBack()
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
recordReset(routes: any[]): void {
|
|
162
|
-
this.navigationInstrumentation.recordReset(routes)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Gesture instrumentation methods
|
|
166
|
-
enableGestureTracking(): void {
|
|
167
|
-
this.gestureInstrumentation.enable()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
disableGestureTracking(): void {
|
|
171
|
-
this.gestureInstrumentation.disable()
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
recordTap(x: number, y: number, target?: string): void {
|
|
175
|
-
this.gestureInstrumentation.recordTap(x, y, target)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
recordSwipe(direction: string, target?: string): void {
|
|
179
|
-
this.gestureInstrumentation.recordSwipe(direction, target)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
recordPinch(scale: number, target?: string): void {
|
|
183
|
-
this.gestureInstrumentation.recordPinch(scale, target)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
recordPan(deltaX: number, deltaY: number, target?: string): void {
|
|
187
|
-
this.gestureInstrumentation.recordPan(deltaX, deltaY, target)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
recordLongPress(duration: number, target?: string): void {
|
|
191
|
-
this.gestureInstrumentation.recordLongPress(duration, target)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
recordGestureError(error: Error, gestureType: string): void {
|
|
195
|
-
this.gestureInstrumentation.recordGestureError(error, gestureType)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Performance monitoring methods
|
|
199
|
-
startTrace(name: string, attributes?: Record<string, any>): any {
|
|
200
|
-
if (!this.isInitialized) {
|
|
201
|
-
console.warn('TracerReactNativeSDK not initialized')
|
|
202
|
-
return null
|
|
99
|
+
start(sessionId: string, sessionType: SessionType): void {
|
|
100
|
+
if (!this.tracerProvider) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
'Configuration not initialized. Call init() before start().',
|
|
103
|
+
)
|
|
203
104
|
}
|
|
204
105
|
|
|
205
|
-
|
|
206
|
-
const span = trace.startSpan(name, {
|
|
207
|
-
attributes: {
|
|
208
|
-
'trace.type': 'performance',
|
|
209
|
-
'trace.name': name,
|
|
210
|
-
...attributes,
|
|
211
|
-
},
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
return span
|
|
106
|
+
this._setSessionId(sessionId, sessionType)
|
|
215
107
|
}
|
|
216
108
|
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
span.end()
|
|
109
|
+
stop(): void {
|
|
110
|
+
if (!this.tracerProvider) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'Configuration not initialized. Call init() before start().',
|
|
113
|
+
)
|
|
223
114
|
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Error tracking
|
|
227
|
-
captureException(error: Error, context?: Record<string, any>): void {
|
|
228
|
-
if (!this.isInitialized) return
|
|
229
|
-
|
|
230
|
-
const { trace, SpanStatusCode } = require('@opentelemetry/api')
|
|
231
|
-
const span = trace.startSpan('error.capture', {
|
|
232
|
-
attributes: {
|
|
233
|
-
'error.type': error.name,
|
|
234
|
-
'error.message': error.message,
|
|
235
|
-
'error.stack': error.stack,
|
|
236
|
-
...context,
|
|
237
|
-
},
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
|
|
241
|
-
span.recordException(error)
|
|
242
|
-
span.end()
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Network monitoring
|
|
246
|
-
recordNetworkRequest(url: string, method: string, statusCode: number, duration: number): void {
|
|
247
|
-
if (!this.isInitialized) return
|
|
248
|
-
|
|
249
|
-
const { trace } = require('@opentelemetry/api')
|
|
250
|
-
const span = trace.startSpan('network.request', {
|
|
251
|
-
attributes: {
|
|
252
|
-
'http.url': url,
|
|
253
|
-
'http.method': method,
|
|
254
|
-
'http.status_code': statusCode,
|
|
255
|
-
'http.duration': duration,
|
|
256
|
-
'network.type': 'fetch',
|
|
257
|
-
},
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
span.setStatus({ code: statusCode >= 400 ? 2 : 1 })
|
|
261
|
-
span.end()
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Screen performance
|
|
265
|
-
recordScreenLoad(screenName: string, loadTime: number): void {
|
|
266
|
-
if (!this.isInitialized) return
|
|
267
|
-
|
|
268
|
-
const { trace } = require('@opentelemetry/api')
|
|
269
|
-
const span = trace.startSpan('screen.load', {
|
|
270
|
-
attributes: {
|
|
271
|
-
'screen.name': screenName,
|
|
272
|
-
'screen.load_time': loadTime,
|
|
273
|
-
'screen.type': 'react_native',
|
|
274
|
-
},
|
|
275
|
-
})
|
|
276
115
|
|
|
277
|
-
|
|
116
|
+
this._setSessionId('')
|
|
278
117
|
}
|
|
279
118
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
attributes: {
|
|
287
|
-
'memory.used': usedMemory,
|
|
288
|
-
'memory.total': totalMemory,
|
|
289
|
-
'memory.percentage': (usedMemory / totalMemory) * 100,
|
|
290
|
-
},
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
span.end()
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Battery monitoring
|
|
297
|
-
recordBatteryLevel(level: number, isCharging: boolean): void {
|
|
298
|
-
if (!this.isInitialized) return
|
|
299
|
-
|
|
300
|
-
const { trace } = require('@opentelemetry/api')
|
|
301
|
-
const span = trace.startSpan('battery.status', {
|
|
302
|
-
attributes: {
|
|
303
|
-
'battery.level': level,
|
|
304
|
-
'battery.charging': isCharging,
|
|
305
|
-
},
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
span.end()
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// App lifecycle events
|
|
312
|
-
recordAppStateChange(state: string): void {
|
|
313
|
-
if (!this.isInitialized) return
|
|
314
|
-
|
|
315
|
-
const { trace } = require('@opentelemetry/api')
|
|
316
|
-
const span = trace.startSpan('app.state_change', {
|
|
317
|
-
attributes: {
|
|
318
|
-
'app.state': state,
|
|
319
|
-
'app.timestamp': Date.now(),
|
|
320
|
-
},
|
|
321
|
-
})
|
|
119
|
+
setApiKey(apiKey: string): void {
|
|
120
|
+
if (!this.exporter) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'Configuration not initialized. Call init() before setApiKey().',
|
|
123
|
+
)
|
|
124
|
+
}
|
|
322
125
|
|
|
323
|
-
|
|
126
|
+
this.exporter.setApiKey?.(apiKey)
|
|
324
127
|
}
|
|
325
128
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (!this.isInitialized) return
|
|
329
|
-
|
|
330
|
-
const { trace } = require('@opentelemetry/api')
|
|
331
|
-
const span = trace.startSpan(`custom.${eventName}`, {
|
|
332
|
-
attributes: {
|
|
333
|
-
'event.name': eventName,
|
|
334
|
-
'event.type': 'custom',
|
|
335
|
-
...attributes,
|
|
336
|
-
},
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
span.end()
|
|
129
|
+
setSessionId(sessionId: string, sessionType: SessionType): void {
|
|
130
|
+
this._setSessionId(sessionId, sessionType)
|
|
340
131
|
}
|
|
341
132
|
|
|
342
|
-
// Shutdown
|
|
133
|
+
// Shutdown (React Native specific)
|
|
343
134
|
shutdown(): Promise<void> {
|
|
344
135
|
this.isInitialized = false
|
|
345
|
-
console.log('TracerReactNativeSDK shutdown')
|
|
346
136
|
return Promise.resolve()
|
|
347
137
|
}
|
|
348
138
|
}
|