@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1

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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/babel.config.js +13 -0
  4. package/dist/config/masking.d.ts +30 -0
  5. package/dist/config/masking.js +1 -0
  6. package/dist/config/masking.js.map +1 -0
  7. package/dist/expo.d.ts +11 -0
  8. package/dist/expo.js +1 -0
  9. package/dist/expo.js.map +1 -0
  10. package/dist/index.d.ts +11 -0
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/otel/helpers.d.ts +3 -0
  14. package/dist/otel/helpers.js +1 -0
  15. package/dist/otel/helpers.js.map +1 -0
  16. package/dist/otel/index.d.ts +40 -0
  17. package/dist/otel/index.js +1 -0
  18. package/dist/otel/index.js.map +1 -0
  19. package/dist/otel/instrumentations/gestureInstrumentation.d.ts +15 -0
  20. package/dist/otel/instrumentations/gestureInstrumentation.js +1 -0
  21. package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -0
  22. package/dist/otel/instrumentations/index.d.ts +5 -0
  23. package/dist/otel/instrumentations/index.js +1 -0
  24. package/dist/otel/instrumentations/index.js.map +1 -0
  25. package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +8 -0
  26. package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -0
  27. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -0
  28. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +12 -0
  29. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -0
  30. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -0
  31. package/dist/recorder/gestureRecorder.d.ts +42 -0
  32. package/dist/recorder/gestureRecorder.js +1 -0
  33. package/dist/recorder/gestureRecorder.js.map +1 -0
  34. package/dist/recorder/index.d.ts +16 -0
  35. package/dist/recorder/index.js +1 -0
  36. package/dist/recorder/index.js.map +1 -0
  37. package/dist/recorder/navigationTracker.d.ts +43 -0
  38. package/dist/recorder/navigationTracker.js +1 -0
  39. package/dist/recorder/navigationTracker.js.map +1 -0
  40. package/dist/recorder/screenRecorder.d.ts +46 -0
  41. package/dist/recorder/screenRecorder.js +1 -0
  42. package/dist/recorder/screenRecorder.js.map +1 -0
  43. package/dist/services/api.service.d.ts +20 -0
  44. package/dist/services/api.service.js +1 -0
  45. package/dist/services/api.service.js.map +1 -0
  46. package/dist/services/storage.service.d.ts +23 -0
  47. package/dist/services/storage.service.js +1 -0
  48. package/dist/services/storage.service.js.map +1 -0
  49. package/dist/sessionRecorder.d.ts +54 -0
  50. package/dist/sessionRecorder.js +1 -0
  51. package/dist/sessionRecorder.js.map +1 -0
  52. package/dist/types/index.d.ts +81 -0
  53. package/dist/types/index.js +1 -0
  54. package/dist/types/index.js.map +1 -0
  55. package/dist/utils/platform.d.ts +9 -0
  56. package/dist/utils/platform.js +1 -0
  57. package/dist/utils/platform.js.map +1 -0
  58. package/dist/version.d.ts +1 -0
  59. package/dist/version.js +1 -0
  60. package/dist/version.js.map +1 -0
  61. package/examples/sample-expo-app/README.md +142 -0
  62. package/examples/sample-expo-app/app/(tabs)/_layout.tsx +60 -0
  63. package/examples/sample-expo-app/app/(tabs)/explore.tsx +110 -0
  64. package/examples/sample-expo-app/app/(tabs)/index.tsx +125 -0
  65. package/examples/sample-expo-app/app/(tabs)/posts.tsx +96 -0
  66. package/examples/sample-expo-app/app/(tabs)/users.tsx +131 -0
  67. package/examples/sample-expo-app/app/+not-found.tsx +32 -0
  68. package/examples/sample-expo-app/app/_layout.tsx +53 -0
  69. package/examples/sample-expo-app/app/post/[id].tsx +199 -0
  70. package/examples/sample-expo-app/app/user/[id].tsx +270 -0
  71. package/examples/sample-expo-app/app.json +42 -0
  72. package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
  73. package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
  74. package/examples/sample-expo-app/assets/images/favicon.png +0 -0
  75. package/examples/sample-expo-app/assets/images/icon.png +0 -0
  76. package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
  77. package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
  78. package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
  79. package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
  80. package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
  81. package/examples/sample-expo-app/components/Collapsible.tsx +45 -0
  82. package/examples/sample-expo-app/components/ErrorView.tsx +52 -0
  83. package/examples/sample-expo-app/components/ExternalLink.tsx +24 -0
  84. package/examples/sample-expo-app/components/HapticTab.tsx +18 -0
  85. package/examples/sample-expo-app/components/HelloWave.tsx +40 -0
  86. package/examples/sample-expo-app/components/LoadingSpinner.tsx +34 -0
  87. package/examples/sample-expo-app/components/ParallaxScrollView.tsx +82 -0
  88. package/examples/sample-expo-app/components/ThemedText.tsx +60 -0
  89. package/examples/sample-expo-app/components/ThemedView.tsx +14 -0
  90. package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +32 -0
  91. package/examples/sample-expo-app/components/ui/IconSymbol.tsx +41 -0
  92. package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +19 -0
  93. package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +6 -0
  94. package/examples/sample-expo-app/constants/Colors.ts +26 -0
  95. package/examples/sample-expo-app/eslint.config.js +10 -0
  96. package/examples/sample-expo-app/hooks/useApi.ts +41 -0
  97. package/examples/sample-expo-app/hooks/useColorScheme.ts +1 -0
  98. package/examples/sample-expo-app/hooks/useColorScheme.web.ts +21 -0
  99. package/examples/sample-expo-app/hooks/useThemeColor.ts +21 -0
  100. package/examples/sample-expo-app/metro.config.js +26 -0
  101. package/examples/sample-expo-app/package-lock.json +26296 -0
  102. package/examples/sample-expo-app/package.json +59 -0
  103. package/examples/sample-expo-app/scripts/reset-project.js +112 -0
  104. package/examples/sample-expo-app/services/api.ts +98 -0
  105. package/examples/sample-expo-app/tsconfig.json +17 -0
  106. package/examples/sample-expo-app/utils/navigation.ts +19 -0
  107. package/package.json +98 -0
  108. package/src/config/masking.ts +78 -0
  109. package/src/expo.ts +41 -0
  110. package/src/index.ts +20 -0
  111. package/src/otel/helpers.ts +21 -0
  112. package/src/otel/index.ts +348 -0
  113. package/src/otel/instrumentations/gestureInstrumentation.ts +141 -0
  114. package/src/otel/instrumentations/index.ts +86 -0
  115. package/src/otel/instrumentations/reactNativeInstrumentation.ts +164 -0
  116. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +114 -0
  117. package/src/recorder/gestureRecorder.ts +429 -0
  118. package/src/recorder/index.ts +71 -0
  119. package/src/recorder/navigationTracker.ts +447 -0
  120. package/src/recorder/screenRecorder.ts +411 -0
  121. package/src/services/api.service.ts +78 -0
  122. package/src/services/storage.service.ts +130 -0
  123. package/src/sessionRecorder.ts +367 -0
  124. package/src/types/expo.d.ts +23 -0
  125. package/src/types/index.ts +88 -0
  126. package/src/utils/platform.ts +75 -0
  127. package/src/version.ts +1 -0
  128. package/tsconfig.json +24 -0
@@ -0,0 +1,348 @@
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
+ } from '@multiplayer-app/session-recorder-common'
12
+ import { TracerReactNativeConfig } from '../types'
13
+ import { getInstrumentations } from './instrumentations'
14
+ 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
+
21
+ // Create a custom TracerProvider for React Native
22
+ class ReactNativeTracerProvider {
23
+ private resource: any
24
+ private idGenerator: any
25
+ private sampler: any
26
+ private spanProcessors: any[]
27
+ private propagator: any
28
+
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
+
37
+ register(options: any) {
38
+ // Register the provider with OpenTelemetry
39
+ console.log('ReactNativeTracerProvider registered')
40
+ }
41
+
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
+
55
+ export class TracerReactNativeSDK {
56
+ private tracerProvider?: ReactNativeTracerProvider
57
+ private config?: TracerReactNativeConfig
58
+ private httpMaskingConfig: any
59
+ private sessionId = ''
60
+ private idGenerator: SessionRecorderIdGenerator
61
+ private exporter?: any
62
+ private navigationInstrumentation: ReactNavigationInstrumentation
63
+ private gestureInstrumentation: GestureInstrumentation
64
+ private isInitialized = false
65
+
66
+ constructor() {
67
+ this.idGenerator = new SessionRecorderIdGenerator()
68
+ this.navigationInstrumentation = new ReactNavigationInstrumentation()
69
+ this.gestureInstrumentation = new GestureInstrumentation()
70
+ }
71
+
72
+ private _setSessionId(
73
+ sessionId: string,
74
+ sessionType: SessionType = SessionType.PLAIN,
75
+ ) {
76
+ this.sessionId = sessionId
77
+ this.idGenerator.setSessionId(sessionId, sessionType)
78
+ }
79
+
80
+ init(options: TracerReactNativeConfig): void {
81
+ if (this.isInitialized) {
82
+ console.warn('TracerReactNativeSDK already initialized')
83
+ return
84
+ }
85
+
86
+ this.config = options
87
+ this.httpMaskingConfig = getHttpMaskingConfig(options.httpMasking)
88
+ const { application, version, environment } = this.config
89
+
90
+ // Create OTLP exporter
91
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
92
+ this.exporter = new OTLPTraceExporter({
93
+ url: getExporterEndpoint(options.exporterEndpoint),
94
+ headers: {
95
+ 'Authorization': `Bearer ${options.apiKey}`,
96
+ },
97
+ })
98
+
99
+ this.tracerProvider = new ReactNativeTracerProvider({
100
+ resource: resourceFromAttributes({
101
+ [SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
102
+ [SemanticAttributes.SEMRESATTRS_SERVICE_VERSION]: version,
103
+ [SemanticAttributes.SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: environment,
104
+ ...getPlatformAttributes(),
105
+ }),
106
+ idGenerator: this.idGenerator,
107
+ sampler: new SessionRecorderTraceIdRatioBasedSampler(this.config.sampleTraceRatio || 0.15),
108
+ spanProcessors: [
109
+ this._getSpanSessionIdProcessor(),
110
+ new BatchSpanProcessor(this.exporter),
111
+ ],
112
+ })
113
+
114
+ this.tracerProvider.register({
115
+ propagator: new W3CTraceContextPropagator(),
116
+ })
117
+
118
+ // Register instrumentations
119
+ registerInstrumentations({
120
+ tracerProvider: this.tracerProvider,
121
+ instrumentations: getInstrumentations(this.config),
122
+ })
123
+
124
+ this.isInitialized = true
125
+ console.log('TracerReactNativeSDK initialized successfully')
126
+ }
127
+
128
+ private _getSpanSessionIdProcessor() {
129
+ return {
130
+ onStart: (span: any) => {
131
+ if (this.sessionId) {
132
+ span.setAttribute(ATTR_MULTIPLAYER_SESSION_ID, this.sessionId)
133
+ }
134
+ // Add React Native specific attributes
135
+ span.setAttribute('platform', 'react-native')
136
+ span.setAttribute('timestamp', Date.now())
137
+ },
138
+ onEnd: () => { },
139
+ shutdown: () => Promise.resolve(),
140
+ forceFlush: () => Promise.resolve(),
141
+ }
142
+ }
143
+
144
+ setSessionId(sessionId: string, sessionType: SessionType): void {
145
+ this._setSessionId(sessionId, sessionType)
146
+ }
147
+
148
+ // Navigation instrumentation methods
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
203
+ }
204
+
205
+ const { trace } = require('@opentelemetry/api')
206
+ const span = trace.startSpan(name, {
207
+ attributes: {
208
+ 'trace.type': 'performance',
209
+ 'trace.name': name,
210
+ ...attributes,
211
+ },
212
+ })
213
+
214
+ return span
215
+ }
216
+
217
+ endTrace(span: any, status?: any): void {
218
+ if (span) {
219
+ if (status) {
220
+ span.setStatus(status)
221
+ }
222
+ span.end()
223
+ }
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
+
277
+ span.end()
278
+ }
279
+
280
+ // Memory monitoring
281
+ recordMemoryUsage(usedMemory: number, totalMemory: number): void {
282
+ if (!this.isInitialized) return
283
+
284
+ const { trace } = require('@opentelemetry/api')
285
+ const span = trace.startSpan('memory.usage', {
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
+ })
322
+
323
+ span.end()
324
+ }
325
+
326
+ // Custom event tracking
327
+ recordCustomEvent(eventName: string, attributes?: Record<string, any>): void {
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()
340
+ }
341
+
342
+ // Shutdown
343
+ shutdown(): Promise<void> {
344
+ this.isInitialized = false
345
+ console.log('TracerReactNativeSDK shutdown')
346
+ return Promise.resolve()
347
+ }
348
+ }
@@ -0,0 +1,141 @@
1
+ import { InstrumentationBase } from '@opentelemetry/instrumentation'
2
+ import { trace, SpanStatusCode } from '@opentelemetry/api'
3
+
4
+ export class GestureInstrumentation extends InstrumentationBase {
5
+ private _isEnabled = false
6
+
7
+ constructor() {
8
+ super('react-native-gesture', '1.0.0', {})
9
+ }
10
+
11
+ init(): void {
12
+ // Initialize the instrumentation
13
+ }
14
+
15
+ enable(): void {
16
+ this._isEnabled = true
17
+ }
18
+
19
+ disable(): void {
20
+ this._isEnabled = false
21
+ }
22
+
23
+ isEnabled(): boolean {
24
+ return this._isEnabled
25
+ }
26
+
27
+ // Manual gesture tracking methods
28
+ recordTap(x: number, y: number, target?: string) {
29
+ if (!this._isEnabled) return
30
+
31
+ const span = trace.getTracer('gesture').startSpan('Gesture.tap', {
32
+ attributes: {
33
+ 'gesture.type': 'tap',
34
+ 'gesture.coordinates.x': x,
35
+ 'gesture.coordinates.y': y,
36
+ 'gesture.timestamp': Date.now(),
37
+ },
38
+ })
39
+
40
+ if (target) {
41
+ span.setAttribute('gesture.target', target)
42
+ }
43
+
44
+ span.setStatus({ code: SpanStatusCode.OK })
45
+ span.end()
46
+ }
47
+
48
+ recordSwipe(direction: string, target?: string) {
49
+ if (!this._isEnabled) return
50
+
51
+ const span = trace.getTracer('gesture').startSpan('Gesture.swipe', {
52
+ attributes: {
53
+ 'gesture.type': 'swipe',
54
+ 'gesture.direction': direction,
55
+ 'gesture.timestamp': Date.now(),
56
+ },
57
+ })
58
+
59
+ if (target) {
60
+ span.setAttribute('gesture.target', target)
61
+ }
62
+
63
+ span.setStatus({ code: SpanStatusCode.OK })
64
+ span.end()
65
+ }
66
+
67
+ recordPinch(scale: number, target?: string) {
68
+ if (!this._isEnabled) return
69
+
70
+ const span = trace.getTracer('gesture').startSpan('Gesture.pinch', {
71
+ attributes: {
72
+ 'gesture.type': 'pinch',
73
+ 'gesture.scale': scale,
74
+ 'gesture.timestamp': Date.now(),
75
+ },
76
+ })
77
+
78
+ if (target) {
79
+ span.setAttribute('gesture.target', target)
80
+ }
81
+
82
+ span.setStatus({ code: SpanStatusCode.OK })
83
+ span.end()
84
+ }
85
+
86
+ recordPan(deltaX: number, deltaY: number, target?: string) {
87
+ if (!this._isEnabled) return
88
+
89
+ const span = trace.getTracer('gesture').startSpan('Gesture.pan', {
90
+ attributes: {
91
+ 'gesture.type': 'pan',
92
+ 'gesture.delta_x': deltaX,
93
+ 'gesture.delta_y': deltaY,
94
+ 'gesture.timestamp': Date.now(),
95
+ },
96
+ })
97
+
98
+ if (target) {
99
+ span.setAttribute('gesture.target', target)
100
+ }
101
+
102
+ span.setStatus({ code: SpanStatusCode.OK })
103
+ span.end()
104
+ }
105
+
106
+ recordLongPress(duration: number, target?: string) {
107
+ if (!this._isEnabled) return
108
+
109
+ const span = trace.getTracer('gesture').startSpan('Gesture.longPress', {
110
+ attributes: {
111
+ 'gesture.type': 'longPress',
112
+ 'gesture.duration': duration,
113
+ 'gesture.timestamp': Date.now(),
114
+ },
115
+ })
116
+
117
+ if (target) {
118
+ span.setAttribute('gesture.target', target)
119
+ }
120
+
121
+ span.setStatus({ code: SpanStatusCode.OK })
122
+ span.end()
123
+ }
124
+
125
+ // Error tracking for gesture failures
126
+ recordGestureError(error: Error, gestureType: string) {
127
+ if (!this._isEnabled) return
128
+
129
+ const span = trace.getTracer('gesture').startSpan(`Gesture.${gestureType}.error`, {
130
+ attributes: {
131
+ 'gesture.type': gestureType,
132
+ 'gesture.error': true,
133
+ 'gesture.timestamp': Date.now(),
134
+ },
135
+ })
136
+
137
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
138
+ span.recordException(error)
139
+ span.end()
140
+ }
141
+ }
@@ -0,0 +1,86 @@
1
+ import { TracerReactNativeConfig } from '../../types'
2
+ import { ReactNativeInstrumentation } from './reactNativeInstrumentation'
3
+ import { getHttpMaskingConfig } from '../../config/masking'
4
+
5
+ export function getInstrumentations(config: TracerReactNativeConfig) {
6
+ const httpMaskingConfig = getHttpMaskingConfig(config.httpMasking)
7
+ const instrumentations = []
8
+
9
+ // Fetch instrumentation
10
+ try {
11
+ const { FetchInstrumentation } = require('@opentelemetry/instrumentation-fetch')
12
+ instrumentations.push(
13
+ new FetchInstrumentation({
14
+ ignoreUrls: config.ignoreUrls || [],
15
+ propagateTraceHeaderCorsUrls: config.propagateTraceHeaderCorsUrls || [],
16
+ applyCustomAttributesOnSpan: (span: any, request: any) => {
17
+ if (config.captureHeaders) {
18
+ const headers = request.headers
19
+ if (headers && httpMaskingConfig.maskHeaders) {
20
+ const maskedHeaders = httpMaskingConfig.maskHeaders(headers, span)
21
+ Object.keys(maskedHeaders).forEach(key => {
22
+ span.setAttribute(`http.request.header.${key}`, maskedHeaders[key])
23
+ })
24
+ } else if (headers) {
25
+ Object.keys(headers).forEach(key => {
26
+ span.setAttribute(`http.request.header.${key}`, headers[key])
27
+ })
28
+ }
29
+ }
30
+ },
31
+ applyCustomAttributesOnSpanResponse: (span: any, response: any) => {
32
+ if (config.captureBody && httpMaskingConfig.maskBody) {
33
+ const maskedBody = httpMaskingConfig.maskBody(response.body, span)
34
+ span.setAttribute('http.response.body', JSON.stringify(maskedBody))
35
+ } else if (config.captureBody) {
36
+ span.setAttribute('http.response.body', JSON.stringify(response.body))
37
+ }
38
+ },
39
+ })
40
+ )
41
+ } catch (error) {
42
+ console.warn('Fetch instrumentation not available:', error)
43
+ }
44
+
45
+ // XMLHttpRequest instrumentation
46
+ try {
47
+ const { XMLHttpRequestInstrumentation } = require('@opentelemetry/instrumentation-xml-http-request')
48
+ instrumentations.push(
49
+ new XMLHttpRequestInstrumentation({
50
+ ignoreUrls: config.ignoreUrls || [],
51
+ propagateTraceHeaderCorsUrls: config.propagateTraceHeaderCorsUrls || [],
52
+ applyCustomAttributesOnSpan: (span: any, xhr: any) => {
53
+ if (config.captureHeaders) {
54
+ const headers = xhr.getAllResponseHeaders()
55
+ if (headers && httpMaskingConfig.maskHeaders) {
56
+ const maskedHeaders = httpMaskingConfig.maskHeaders(headers, span)
57
+ Object.keys(maskedHeaders).forEach(key => {
58
+ span.setAttribute(`http.response.header.${key}`, maskedHeaders[key])
59
+ })
60
+ } else if (headers) {
61
+ Object.keys(headers).forEach(key => {
62
+ span.setAttribute(`http.response.header.${key}`, headers[key])
63
+ })
64
+ }
65
+ }
66
+ },
67
+ })
68
+ )
69
+ } catch (error) {
70
+ console.warn('XMLHttpRequest instrumentation not available:', error)
71
+ }
72
+
73
+ // Custom React Native instrumentations
74
+ try {
75
+ instrumentations.push(new ReactNativeInstrumentation())
76
+ } catch (error) {
77
+ console.warn('React Native instrumentation not available:', error)
78
+ }
79
+
80
+ return instrumentations
81
+ }
82
+
83
+ // Export custom instrumentations for manual use
84
+ export { ReactNativeInstrumentation } from './reactNativeInstrumentation'
85
+ export { ReactNavigationInstrumentation } from './reactNavigationInstrumentation'
86
+ export { GestureInstrumentation } from './gestureInstrumentation'