@multiplayer-app/session-recorder-react-native 0.0.1-beta.6 → 0.0.1-beta.8

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 (76) hide show
  1. package/copy-react-native-dist.sh +3 -3
  2. package/docs/NATIVE_MODULE_SETUP.md +175 -0
  3. package/ios/SessionRecorderNative.podspec +5 -0
  4. package/package.json +11 -1
  5. package/plugin/package.json +20 -0
  6. package/plugin/src/index.js +42 -0
  7. package/react-native.config.js +1 -1
  8. package/android/src/main/AndroidManifest.xml +0 -2
  9. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingModule.kt +0 -202
  10. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingPackage.kt +0 -16
  11. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderModule.kt +0 -202
  12. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderPackage.kt +0 -16
  13. package/babel.config.js +0 -13
  14. package/docs/AUTO_METADATA_DETECTION.md +0 -108
  15. package/docs/TROUBLESHOOTING.md +0 -168
  16. package/ios/ScreenMasking.m +0 -12
  17. package/ios/ScreenMasking.podspec +0 -21
  18. package/ios/ScreenMasking.swift +0 -205
  19. package/ios/SessionRecorder.podspec +0 -21
  20. package/scripts/generate-app-metadata.js +0 -173
  21. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
  22. package/src/components/GestureCaptureWrapper/index.ts +0 -1
  23. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
  24. package/src/components/ScreenRecorderView/index.ts +0 -1
  25. package/src/components/SessionRecorderWidget/FinalPopover.tsx +0 -62
  26. package/src/components/SessionRecorderWidget/FloatingButton.tsx +0 -136
  27. package/src/components/SessionRecorderWidget/InitialPopover.tsx +0 -89
  28. package/src/components/SessionRecorderWidget/ModalContainer.tsx +0 -128
  29. package/src/components/SessionRecorderWidget/ModalHeader.tsx +0 -24
  30. package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +0 -109
  31. package/src/components/SessionRecorderWidget/icons.tsx +0 -52
  32. package/src/components/SessionRecorderWidget/index.ts +0 -3
  33. package/src/components/SessionRecorderWidget/styles.ts +0 -150
  34. package/src/components/index.ts +0 -3
  35. package/src/config/constants.ts +0 -60
  36. package/src/config/defaults.ts +0 -83
  37. package/src/config/index.ts +0 -6
  38. package/src/config/masking.ts +0 -28
  39. package/src/config/session-recorder.ts +0 -55
  40. package/src/config/validators.ts +0 -31
  41. package/src/context/SessionRecorderContext.tsx +0 -53
  42. package/src/index.ts +0 -9
  43. package/src/native/ScreenMasking.ts +0 -34
  44. package/src/native/SessionRecorderNative.ts +0 -34
  45. package/src/otel/helpers.ts +0 -275
  46. package/src/otel/index.ts +0 -138
  47. package/src/otel/instrumentations/index.ts +0 -115
  48. package/src/patch/index.ts +0 -1
  49. package/src/patch/xhr.ts +0 -141
  50. package/src/recorder/eventExporter.ts +0 -141
  51. package/src/recorder/gestureRecorder.ts +0 -498
  52. package/src/recorder/index.ts +0 -179
  53. package/src/recorder/navigationTracker.ts +0 -449
  54. package/src/recorder/screenRecorder.ts +0 -527
  55. package/src/services/api.service.ts +0 -203
  56. package/src/services/screenMaskingService.ts +0 -118
  57. package/src/services/storage.service.ts +0 -199
  58. package/src/session-recorder.ts +0 -606
  59. package/src/types/expo.d.ts +0 -23
  60. package/src/types/index.ts +0 -28
  61. package/src/types/session-recorder.ts +0 -429
  62. package/src/types/session.ts +0 -65
  63. package/src/utils/app-metadata.ts +0 -31
  64. package/src/utils/index.ts +0 -8
  65. package/src/utils/logger.ts +0 -225
  66. package/src/utils/nativeModuleTest.ts +0 -60
  67. package/src/utils/platform.ts +0 -384
  68. package/src/utils/request-utils.ts +0 -61
  69. package/src/utils/rrweb-events.ts +0 -309
  70. package/src/utils/session.ts +0 -18
  71. package/src/utils/time.ts +0 -17
  72. package/src/utils/type-utils.ts +0 -75
  73. package/src/version.ts +0 -1
  74. package/tsconfig.json +0 -24
  75. /package/ios/{SessionRecorder.m → SessionRecorderNative.m} +0 -0
  76. /package/ios/{SessionRecorder.swift → SessionRecorderNative.swift} +0 -0
package/src/otel/index.ts DELETED
@@ -1,138 +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
-
17
- import { getPlatformAttributes } from '../utils/platform'
18
- import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
19
-
20
-
21
-
22
-
23
- export class TracerReactNativeSDK {
24
- private tracerProvider?: WebTracerProvider
25
- private config?: TracerReactNativeConfig
26
-
27
- private sessionId = ''
28
- private idGenerator?: SessionRecorderIdGenerator
29
- private exporter?: any
30
- private isInitialized = false
31
-
32
- constructor() { }
33
-
34
- private _setSessionId(
35
- sessionId: string,
36
- sessionType: SessionType = SessionType.PLAIN,
37
- ) {
38
- this.sessionId = sessionId
39
- this.idGenerator?.setSessionId(sessionId, sessionType)
40
- }
41
-
42
- init(options: TracerReactNativeConfig): void {
43
- this.config = options
44
-
45
- const { application, version, environment } = this.config
46
-
47
- this.idGenerator = new SessionRecorderIdGenerator()
48
-
49
- this.exporter = new SessionRecorderBrowserTraceExporter({
50
- apiKey: options.apiKey,
51
- url: getExporterEndpoint(options.exporterEndpoint),
52
- usePostMessageFallback: options.usePostMessageFallback,
53
- })
54
-
55
- this.tracerProvider = new WebTracerProvider({
56
- resource: resourceFromAttributes({
57
- [SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
58
- [SemanticAttributes.SEMRESATTRS_SERVICE_VERSION]: version,
59
- [SemanticAttributes.SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: environment,
60
- ...getPlatformAttributes(),
61
- }),
62
- idGenerator: this.idGenerator,
63
- sampler: new SessionRecorderTraceIdRatioBasedSampler(this.config.sampleTraceRatio || 0.15),
64
- spanProcessors: [
65
- this._getSpanSessionIdProcessor(),
66
- new BatchSpanProcessor(this.exporter),
67
- ],
68
- })
69
-
70
- this.tracerProvider.register({
71
- propagator: new W3CTraceContextPropagator(),
72
- })
73
-
74
- // Register instrumentations
75
- registerInstrumentations({
76
- tracerProvider: this.tracerProvider,
77
- instrumentations: getInstrumentations(this.config),
78
- })
79
-
80
- this.isInitialized = true
81
- }
82
-
83
- private _getSpanSessionIdProcessor() {
84
- return {
85
- onStart: (span: any) => {
86
- if (this.sessionId) {
87
- span.setAttribute(ATTR_MULTIPLAYER_SESSION_ID, this.sessionId)
88
- }
89
- // Add React Native specific attributes
90
- span.setAttribute('platform', 'react-native')
91
- span.setAttribute('timestamp', Date.now())
92
- },
93
- onEnd: () => { },
94
- shutdown: () => Promise.resolve(),
95
- forceFlush: () => Promise.resolve(),
96
- }
97
- }
98
-
99
- start(sessionId: string, sessionType: SessionType): void {
100
- if (!this.tracerProvider) {
101
- throw new Error(
102
- 'Configuration not initialized. Call init() before start().',
103
- )
104
- }
105
-
106
- this._setSessionId(sessionId, sessionType)
107
- }
108
-
109
- stop(): void {
110
- if (!this.tracerProvider) {
111
- throw new Error(
112
- 'Configuration not initialized. Call init() before start().',
113
- )
114
- }
115
-
116
- this._setSessionId('')
117
- }
118
-
119
- setApiKey(apiKey: string): void {
120
- if (!this.exporter) {
121
- throw new Error(
122
- 'Configuration not initialized. Call init() before setApiKey().',
123
- )
124
- }
125
-
126
- this.exporter.setApiKey?.(apiKey)
127
- }
128
-
129
- setSessionId(sessionId: string, sessionType: SessionType): void {
130
- this._setSessionId(sessionId, sessionType)
131
- }
132
-
133
- // Shutdown (React Native specific)
134
- shutdown(): Promise<void> {
135
- this.isInitialized = false
136
- return Promise.resolve()
137
- }
138
- }
@@ -1,115 +0,0 @@
1
- import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
2
- import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'
3
-
4
- import { logger } from '../../utils'
5
- import { OTEL_IGNORE_URLS } from '../../config'
6
- import { TracerReactNativeConfig } from '../../types'
7
- import { extractResponseBody, headersToObject, processHttpPayload } from '../helpers'
8
-
9
- export function getInstrumentations(config: TracerReactNativeConfig) {
10
-
11
- const instrumentations = []
12
-
13
- // Fetch instrumentation
14
- try {
15
- instrumentations.push(
16
- new FetchInstrumentation({
17
- clearTimingResources: false,
18
- ignoreUrls: [
19
- ...OTEL_IGNORE_URLS,
20
- ...(config.ignoreUrls || []),
21
- ],
22
- propagateTraceHeaderCorsUrls: config.propagateTraceHeaderCorsUrls,
23
- applyCustomAttributesOnSpan: async (span, request, response) => {
24
- if (!config) return
25
-
26
- const { captureBody, captureHeaders } = config
27
-
28
- try {
29
- if (!captureBody && !captureHeaders) {
30
- return
31
- }
32
-
33
- const requestBody = request.body
34
- const requestHeaders = headersToObject(request.headers)
35
- const responseHeaders = headersToObject(response instanceof Response ? response.headers : undefined)
36
-
37
- let responseBody: string | null = null
38
- if (response instanceof Response && response.body) {
39
- responseBody = await extractResponseBody(response)
40
- }
41
-
42
- const payload = {
43
- requestBody,
44
- responseBody,
45
- requestHeaders,
46
- responseHeaders,
47
- }
48
- processHttpPayload(payload, config, span)
49
- } catch (error) {
50
- // eslint-disable-next-line
51
- logger.error('DEBUGGER_LIB', 'Failed to capture fetch payload', error)
52
- }
53
- },
54
- })
55
- )
56
- } catch (error) {
57
- logger.warn('DEBUGGER_LIB', 'Fetch instrumentation not available', error)
58
- }
59
-
60
- // XMLHttpRequest instrumentation
61
- try {
62
- instrumentations.push(
63
- new XMLHttpRequestInstrumentation({
64
- clearTimingResources: false,
65
- ignoreUrls: [
66
- ...OTEL_IGNORE_URLS,
67
- ...(config.ignoreUrls || []),
68
- ],
69
- propagateTraceHeaderCorsUrls: config.propagateTraceHeaderCorsUrls,
70
- applyCustomAttributesOnSpan: (span, xhr) => {
71
- if (!config) return
72
-
73
- const { captureBody, captureHeaders } = config
74
-
75
- try {
76
- if (!captureBody && !captureHeaders) {
77
- return
78
- }
79
-
80
- // @ts-ignore
81
- const requestBody = xhr.networkRequest.requestBody
82
- // @ts-ignore
83
- const responseBody = xhr.networkRequest.responseBody
84
- // @ts-ignore
85
- const requestHeaders = xhr.networkRequest.requestHeaders || {}
86
- // @ts-ignore
87
- const responseHeaders = xhr.networkRequest.responseHeaders || {}
88
-
89
- const payload = {
90
- requestBody,
91
- responseBody,
92
- requestHeaders,
93
- responseHeaders,
94
- }
95
- processHttpPayload(payload, config, span)
96
- } catch (error) {
97
- // eslint-disable-next-line
98
- logger.error('DEBUGGER_LIB', 'Failed to capture xml-http payload', error)
99
- }
100
- },
101
- })
102
- )
103
- } catch (error) {
104
- logger.warn('DEBUGGER_LIB', 'XMLHttpRequest instrumentation not available', error)
105
- }
106
-
107
- // Custom React Native instrumentations
108
- // try {
109
- // instrumentations.push(new ReactNativeInstrumentation())
110
- // } catch (error) {
111
- // console.warn('React Native instrumentation not available:', error)
112
- // }
113
-
114
- return instrumentations
115
- }
@@ -1 +0,0 @@
1
- import './xhr'
package/src/patch/xhr.ts DELETED
@@ -1,141 +0,0 @@
1
- import {
2
-
3
- isFormData,
4
- isNullish,
5
- isObject,
6
- isString,
7
- } from '../utils/type-utils'
8
- import { formDataToQuery } from '../utils/request-utils'
9
- import { DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE } from '../config'
10
-
11
- let recordRequestHeaders = true
12
- let recordResponseHeaders = true
13
- let shouldRecordBody = true
14
- let maxCapturingHttpPayloadSize = DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE
15
-
16
- export const setMaxCapturingHttpPayloadSize = (_maxCapturingHttpPayloadSize: number) => {
17
- maxCapturingHttpPayloadSize = _maxCapturingHttpPayloadSize
18
- }
19
-
20
- export const setShouldRecordHttpData = (shouldRecordBody: boolean, shouldRecordHeaders: boolean) => {
21
- recordRequestHeaders = shouldRecordHeaders
22
- recordResponseHeaders = shouldRecordHeaders
23
- shouldRecordBody = shouldRecordBody
24
- }
25
-
26
- function _tryReadXHRBody({
27
- body,
28
- url,
29
- }: {
30
- body: any | null | undefined
31
- url: string | URL | RequestInfo
32
- }): string | null {
33
-
34
- if (isNullish(body)) {
35
- return null
36
- }
37
-
38
- if (isString(body)) {
39
- return body
40
- }
41
-
42
- if (isFormData(body)) {
43
- return formDataToQuery(body)
44
- }
45
-
46
- if (isObject(body)) {
47
- try {
48
- return JSON.stringify({ ...body })
49
- } catch {
50
- return '[XHR] Failed to stringify response object'
51
- }
52
- }
53
-
54
- return `[XHR] Cannot read body of type ${Object.prototype.toString.call(body)}`
55
- }
56
-
57
- (function (xhr) {
58
- const originalOpen = XMLHttpRequest.prototype.open
59
-
60
- xhr.open = function (
61
- method: string,
62
- url: string | URL,
63
- async = true,
64
- username?: string | null,
65
- password?: string | null,
66
- ) {
67
- const xhr = this as XMLHttpRequest
68
- const networkRequest: {
69
- requestHeaders?: any,
70
- requestBody?: any,
71
- responseHeaders?: any,
72
- responseBody?: any,
73
- } = {}
74
-
75
-
76
- // @ts-ignore
77
- const requestHeaders: Record<string, string> = {}
78
- const originalSetRequestHeader = xhr.setRequestHeader.bind(xhr)
79
- xhr.setRequestHeader = (header: string, value: string) => {
80
- requestHeaders[header] = value
81
- return originalSetRequestHeader(header, value)
82
- }
83
- if (recordRequestHeaders) {
84
- networkRequest.requestHeaders = requestHeaders
85
- }
86
-
87
- const originalSend = xhr.send.bind(xhr)
88
- xhr.send = (body) => {
89
- if (shouldRecordBody) {
90
- const requestBody = _tryReadXHRBody({ body, url })
91
-
92
- if (
93
- requestBody?.length
94
- && requestBody.length <= maxCapturingHttpPayloadSize
95
- ) {
96
- networkRequest.requestBody = requestBody
97
- }
98
- }
99
- return originalSend(body)
100
- }
101
-
102
- xhr.addEventListener('readystatechange', () => {
103
- if (xhr.readyState !== xhr.DONE) {
104
- return
105
- }
106
-
107
- // @ts-ignore
108
- const responseHeaders: Record<string, string> = {}
109
- const rawHeaders = xhr.getAllResponseHeaders() || ''
110
- const headers = rawHeaders.trim().split(/[\r\n]+/).filter(Boolean)
111
-
112
- headers.forEach((line) => {
113
- const parts = line.split(': ')
114
- const header = parts.shift()
115
- const value = parts.join(': ')
116
- if (header) {
117
- responseHeaders[header] = value
118
- }
119
- })
120
- if (recordResponseHeaders) {
121
- networkRequest.responseHeaders = responseHeaders
122
- }
123
- if (shouldRecordBody) {
124
- const responseBody = _tryReadXHRBody({ body: xhr.response, url })
125
-
126
- if (
127
- responseBody?.length
128
- && responseBody.length <= maxCapturingHttpPayloadSize
129
- ) {
130
- networkRequest.responseBody = responseBody
131
- }
132
- }
133
- })
134
-
135
-
136
- // @ts-ignore
137
- xhr.networkRequest = networkRequest
138
-
139
- originalOpen.call(xhr, method, url as string, async, username, password)
140
- }
141
- })(XMLHttpRequest.prototype)
@@ -1,141 +0,0 @@
1
- import io, { Socket } from 'socket.io-client'
2
-
3
- import { ISession } from '../types'
4
- import { logger } from '../utils'
5
-
6
- import {
7
- SESSION_ADD_EVENT,
8
- SESSION_AUTO_CREATED,
9
- SESSION_STOPPED_EVENT,
10
- SESSION_SUBSCRIBE_EVENT,
11
- SESSION_UNSUBSCRIBE_EVENT,
12
- } from '../config'
13
-
14
- const MAX_RECONNECTION_ATTEMPTS = 2
15
-
16
- export class EventExporter {
17
- private socket: Socket | null = null
18
- private queue: any[] = []
19
- private isConnecting: boolean = false
20
- private isConnected: boolean = false
21
- private attempts: number = 0
22
- private sessionId: string | null = null
23
-
24
- constructor(private options: { socketUrl: string, apiKey: string }) { }
25
-
26
- private init(): void {
27
- if (this.isConnecting || this.isConnected) return
28
- this.attempts++
29
- this.isConnecting = true
30
- this.socket = io(this.options.socketUrl, {
31
- path: '/v0/radar/ws',
32
- auth: {
33
- 'x-api-key': this.options.apiKey,
34
- },
35
- reconnectionAttempts: 2,
36
- transports: ['websocket'],
37
- })
38
-
39
- // this.socket.on('connect', () => {
40
- // this.isConnecting = false
41
- // this.isConnected = true
42
- // this.usePostMessage = false
43
- // this.flushQueue()
44
- // })
45
-
46
- this.socket.on('ready', () => {
47
- this.isConnecting = false
48
- this.isConnected = true
49
- logger.info('EventExporter', 'Connected to server')
50
- this.flushQueue()
51
- })
52
-
53
- this.socket.on('disconnect', (err: any) => {
54
- this.isConnecting = false
55
- this.isConnected = false
56
- logger.info('EventExporter', 'Disconnected from server')
57
- })
58
-
59
- this.socket.on('connect_error', (err: any) => {
60
- this.isConnecting = false
61
- this.isConnected = false
62
- this.checkReconnectionAttempts()
63
- logger.error('EventExporter', 'Error connecting to server', err)
64
- })
65
-
66
- this.socket.on(SESSION_STOPPED_EVENT, (data: any) => {
67
-
68
- this.unsubscribeFromSession()
69
- })
70
-
71
- this.socket.on(SESSION_AUTO_CREATED, (data: any) => {
72
-
73
- })
74
- }
75
-
76
- private checkReconnectionAttempts(): void {
77
- if (this.attempts >= MAX_RECONNECTION_ATTEMPTS) {
78
-
79
- this.flushQueue()
80
- }
81
- }
82
-
83
-
84
- private flushQueue(): void {
85
- while (this.queue.length > 0 && (this.socket?.connected)) {
86
- const event = this.queue.shift()
87
- if (!event) continue
88
-
89
- if (this.socket?.connected) {
90
- this.socket.emit(event.name, event.data)
91
- }
92
- }
93
- }
94
-
95
- private unsubscribeFromSession() {
96
- const payload = {
97
- debugSessionId: this.sessionId,
98
- }
99
- if (this.socket?.connected) {
100
- this.socket.emit(SESSION_UNSUBSCRIBE_EVENT, payload)
101
- }
102
- }
103
-
104
- public send(event: any): void {
105
- if (this.socket?.connected) {
106
- this.socket.emit(SESSION_ADD_EVENT, event)
107
- } else {
108
- this.queue.push({ data: event, name: SESSION_ADD_EVENT })
109
- this.init()
110
- }
111
- }
112
-
113
- public subscribeToSession(session: ISession): void {
114
- this.sessionId = session.shortId || session._id
115
- const payload = {
116
- projectId: session.project,
117
- workspaceId: session.workspace,
118
- debugSessionId: this.sessionId,
119
- sessionType: session.creationType,
120
- }
121
- if (this.socket?.connected) {
122
- this.socket.emit(SESSION_SUBSCRIBE_EVENT, payload)
123
- } else {
124
- this.queue.push({ data: payload, name: SESSION_SUBSCRIBE_EVENT })
125
- this.init()
126
- }
127
- }
128
-
129
- public close(): void {
130
- if (this.socket?.connected) {
131
- setTimeout(() => {
132
- this.unsubscribeFromSession()
133
- this.attempts = 0
134
- this.isConnected = false
135
- this.isConnecting = false
136
- this.socket?.disconnect()
137
- this.socket = null
138
- }, 500)
139
- }
140
- }
141
- }