@multiplayer-app/session-recorder-react-native 1.0.1-beta.4 → 1.0.1-beta.5
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/android/src/main/java/com/multiplayer/sessionrecordernative/SessionRecorderNativeModule.kt +2 -2
- package/lib/module/components/SessionRecorderWidget/ErrorBanner.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/ModalHeader.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/icons.js.map +1 -1
- package/lib/module/components/SessionRecorderWidget/styles.js.map +1 -1
- package/lib/module/config/constants.js.map +1 -1
- package/lib/module/config/defaults.js.map +1 -1
- package/lib/module/config/masking.js.map +1 -1
- package/lib/module/config/session-recorder.js.map +1 -1
- package/lib/module/config/validators.js.map +1 -1
- package/lib/module/config/widget.js.map +1 -1
- package/lib/module/context/SessionRecorderStore.js.map +1 -1
- package/lib/module/context/useSessionRecorderStore.js.map +1 -1
- package/lib/module/context/useStoreSelector.js.map +1 -1
- package/lib/module/native/SessionRecorderNative.js.map +1 -1
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/otel/helpers.js +1 -1
- package/lib/module/otel/helpers.js.map +1 -1
- package/lib/module/otel/index.js.map +1 -1
- package/lib/module/otel/instrumentations/index.js.map +1 -1
- package/lib/module/patch/xhr.js.map +1 -1
- package/lib/module/recorder/eventExporter.js.map +1 -1
- package/lib/module/recorder/gestureRecorder.js.map +1 -1
- package/lib/module/recorder/index.js.map +1 -1
- package/lib/module/recorder/navigationTracker.js.map +1 -1
- package/lib/module/recorder/screenRecorder.js.map +1 -1
- package/lib/module/services/api.service.js.map +1 -1
- package/lib/module/services/network.service.js.map +1 -1
- package/lib/module/services/screenMaskingService.js.map +1 -1
- package/lib/module/services/storage.service.js.map +1 -1
- package/lib/module/session-recorder.js.map +1 -1
- package/lib/module/types/index.js.map +1 -1
- package/lib/module/types/session-recorder.js.map +1 -1
- package/lib/module/utils/app-metadata.js +2 -2
- package/lib/module/utils/constants.optional.js.map +1 -1
- package/lib/module/utils/createStore.js.map +1 -1
- package/lib/module/utils/logger.js +0 -8
- package/lib/module/utils/logger.js.map +1 -1
- package/lib/module/utils/platform.js +1 -1
- package/lib/module/utils/platform.js.map +1 -1
- package/lib/module/utils/rrweb-events.js.map +1 -1
- package/lib/module/utils/session.js.map +1 -1
- package/lib/module/utils/shallowEqual.js.map +1 -1
- package/lib/module/utils/time.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/components/ScreenRecorderView/index.d.ts +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/ErrorBanner.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/SessionRecorderWidget.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/icons.d.ts.map +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/index.d.ts +1 -1
- package/lib/typescript/src/components/SessionRecorderWidget/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/config/constants.d.ts.map +1 -1
- package/lib/typescript/src/config/defaults.d.ts.map +1 -1
- package/lib/typescript/src/config/index.d.ts.map +1 -1
- package/lib/typescript/src/config/masking.d.ts.map +1 -1
- package/lib/typescript/src/config/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/config/validators.d.ts.map +1 -1
- package/lib/typescript/src/config/widget.d.ts +1 -1
- package/lib/typescript/src/config/widget.d.ts.map +1 -1
- package/lib/typescript/src/context/SessionRecorderStore.d.ts.map +1 -1
- package/lib/typescript/src/context/useSessionRecorderStore.d.ts +3 -3
- package/lib/typescript/src/context/useSessionRecorderStore.d.ts.map +1 -1
- package/lib/typescript/src/context/useStoreSelector.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/SessionRecorderNative.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +1 -1
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/otel/helpers.d.ts.map +1 -1
- package/lib/typescript/src/otel/index.d.ts.map +1 -1
- package/lib/typescript/src/otel/instrumentations/index.d.ts.map +1 -1
- package/lib/typescript/src/patch/index.d.ts.map +1 -1
- package/lib/typescript/src/patch/xhr.d.ts.map +1 -1
- package/lib/typescript/src/recorder/eventExporter.d.ts.map +1 -1
- package/lib/typescript/src/recorder/gestureRecorder.d.ts.map +1 -1
- package/lib/typescript/src/recorder/index.d.ts.map +1 -1
- package/lib/typescript/src/recorder/navigationTracker.d.ts.map +1 -1
- package/lib/typescript/src/recorder/screenRecorder.d.ts.map +1 -1
- package/lib/typescript/src/services/api.service.d.ts.map +1 -1
- package/lib/typescript/src/services/network.service.d.ts.map +1 -1
- package/lib/typescript/src/services/screenMaskingService.d.ts.map +1 -1
- package/lib/typescript/src/services/storage.service.d.ts.map +1 -1
- package/lib/typescript/src/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/configs.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/types/session-recorder.d.ts +4 -4
- package/lib/typescript/src/types/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/session.d.ts.map +1 -1
- package/lib/typescript/src/utils/app-metadata.d.ts.map +1 -1
- package/lib/typescript/src/utils/constants.optional.d.ts.map +1 -1
- package/lib/typescript/src/utils/constants.optional.expo.d.ts.map +1 -1
- package/lib/typescript/src/utils/createStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/logger.d.ts +1 -1
- package/lib/typescript/src/utils/logger.d.ts.map +1 -1
- package/lib/typescript/src/utils/platform.d.ts.map +1 -1
- package/lib/typescript/src/utils/request-utils.d.ts.map +1 -1
- package/lib/typescript/src/utils/rrweb-events.d.ts.map +1 -1
- package/lib/typescript/src/utils/session.d.ts.map +1 -1
- package/lib/typescript/src/utils/shallowEqual.d.ts.map +1 -1
- package/lib/typescript/src/utils/time.d.ts.map +1 -1
- package/lib/typescript/src/utils/type-utils.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ScreenRecorderView/index.ts +1 -1
- package/src/components/SessionRecorderWidget/ErrorBanner.tsx +14 -14
- package/src/components/SessionRecorderWidget/ModalHeader.tsx +11 -9
- package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +70 -56
- package/src/components/SessionRecorderWidget/icons.tsx +58 -30
- package/src/components/SessionRecorderWidget/index.ts +1 -1
- package/src/components/SessionRecorderWidget/styles.ts +17 -18
- package/src/components/index.ts +2 -2
- package/src/config/constants.ts +19 -20
- package/src/config/defaults.ts +35 -31
- package/src/config/index.ts +5 -5
- package/src/config/masking.ts +44 -18
- package/src/config/session-recorder.ts +54 -26
- package/src/config/validators.ts +43 -20
- package/src/config/widget.ts +24 -15
- package/src/context/SessionRecorderStore.ts +19 -18
- package/src/context/useSessionRecorderStore.ts +17 -10
- package/src/context/useStoreSelector.ts +20 -18
- package/src/index.ts +7 -7
- package/src/native/SessionRecorderNative.ts +83 -67
- package/src/native/index.ts +5 -1
- package/src/otel/helpers.ts +109 -93
- package/src/otel/index.ts +46 -49
- package/src/otel/instrumentations/index.ts +44 -41
- package/src/patch/index.ts +1 -1
- package/src/patch/xhr.ts +77 -78
- package/src/recorder/eventExporter.ts +63 -68
- package/src/recorder/gestureRecorder.ts +359 -212
- package/src/recorder/index.ts +75 -62
- package/src/recorder/navigationTracker.ts +120 -97
- package/src/recorder/screenRecorder.ts +214 -163
- package/src/services/api.service.ts +49 -48
- package/src/services/network.service.ts +67 -58
- package/src/services/screenMaskingService.ts +81 -50
- package/src/services/storage.service.ts +99 -70
- package/src/session-recorder.ts +270 -214
- package/src/types/configs.ts +53 -31
- package/src/types/expo-constants.d.ts +2 -2
- package/src/types/index.ts +16 -18
- package/src/types/session-recorder.ts +106 -111
- package/src/types/session.ts +45 -45
- package/src/utils/app-metadata.ts +9 -9
- package/src/utils/constants.optional.expo.ts +3 -3
- package/src/utils/constants.optional.ts +14 -12
- package/src/utils/createStore.ts +23 -20
- package/src/utils/index.ts +7 -7
- package/src/utils/logger.ts +87 -58
- package/src/utils/platform.ts +149 -118
- package/src/utils/request-utils.ts +15 -15
- package/src/utils/rrweb-events.ts +47 -34
- package/src/utils/session.ts +15 -12
- package/src/utils/shallowEqual.ts +16 -10
- package/src/utils/time.ts +7 -4
- package/src/utils/type-utils.ts +36 -36
- package/src/version.ts +1 -1
- package/android/src/main/java/com/multiplayer/sessionrecordernative/SessionRecorderNativeModuleSpec.kt +0 -51
- package/android/src/main/java/com/xxx/XxxModule.kt +0 -23
- package/ios/Xxx.h +0 -5
- package/ios/Xxx.mm +0 -21
|
@@ -1,164 +1,180 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
NativeEventEmitter,
|
|
3
|
+
Platform,
|
|
4
|
+
TurboModuleRegistry,
|
|
5
|
+
type TurboModule,
|
|
6
|
+
} from 'react-native';
|
|
3
7
|
|
|
4
8
|
export interface Spec extends TurboModule {
|
|
5
9
|
/**
|
|
6
10
|
* Capture the current screen and apply masking to sensitive elements
|
|
7
11
|
* @returns Promise that resolves to base64 encoded image
|
|
8
12
|
*/
|
|
9
|
-
captureAndMask(): Promise<string
|
|
13
|
+
captureAndMask(): Promise<string>;
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Capture the current screen and apply masking with custom options
|
|
13
17
|
* @param options Custom masking options
|
|
14
18
|
* @returns Promise that resolves to base64 encoded image
|
|
15
19
|
*/
|
|
16
|
-
captureAndMaskWithOptions(options: MaskingOptions): Promise<string
|
|
20
|
+
captureAndMaskWithOptions(options: MaskingOptions): Promise<string>;
|
|
17
21
|
|
|
18
22
|
// Gesture recording APIs
|
|
19
|
-
startGestureRecording(): Promise<void
|
|
20
|
-
stopGestureRecording(): Promise<void
|
|
21
|
-
isGestureRecordingActive(): Promise<boolean
|
|
22
|
-
setGestureCallback(callback: (event: any) => void): void
|
|
23
|
+
startGestureRecording(): Promise<void>;
|
|
24
|
+
stopGestureRecording(): Promise<void>;
|
|
25
|
+
isGestureRecordingActive(): Promise<boolean>;
|
|
26
|
+
setGestureCallback(callback: (event: any) => void): void;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export interface MaskingOptions {
|
|
26
30
|
/** Quality of the captured image (0.1 to 1.0, default: 0.3 for smaller file size) */
|
|
27
|
-
quality?: number
|
|
31
|
+
quality?: number;
|
|
28
32
|
/** Scale of the captured image (0.1 to 1.0, default: 1.0) */
|
|
29
|
-
scale?: number
|
|
33
|
+
scale?: number;
|
|
30
34
|
/** Whether to mask text inputs (UITextField, UITextView, React Native text components) */
|
|
31
|
-
maskTextInputs?: boolean
|
|
35
|
+
maskTextInputs?: boolean;
|
|
32
36
|
/** Whether to mask images (UIImageView, React Native Image components) */
|
|
33
|
-
maskImages?: boolean
|
|
37
|
+
maskImages?: boolean;
|
|
34
38
|
/** Whether to mask buttons (UIButton) */
|
|
35
|
-
maskButtons?: boolean
|
|
39
|
+
maskButtons?: boolean;
|
|
36
40
|
/** Whether to mask labels (UILabel) */
|
|
37
|
-
maskLabels?: boolean
|
|
41
|
+
maskLabels?: boolean;
|
|
38
42
|
/** Whether to mask web views (WKWebView) */
|
|
39
|
-
maskWebViews?: boolean
|
|
43
|
+
maskWebViews?: boolean;
|
|
40
44
|
/** Whether to mask sandboxed views (system views that don't belong to current process) */
|
|
41
|
-
maskSandboxedViews?: boolean
|
|
45
|
+
maskSandboxedViews?: boolean;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
|
|
45
48
|
export interface SessionRecorderNativeModule {
|
|
46
49
|
/**
|
|
47
50
|
* Capture the current screen and apply masking to sensitive elements
|
|
48
51
|
* @returns Promise that resolves to base64 encoded image
|
|
49
52
|
*/
|
|
50
|
-
captureAndMask(): Promise<string
|
|
53
|
+
captureAndMask(): Promise<string>;
|
|
51
54
|
|
|
52
55
|
/**
|
|
53
56
|
* Capture the current screen and apply masking with custom options
|
|
54
57
|
* @param options Custom masking options
|
|
55
58
|
* @returns Promise that resolves to base64 encoded image
|
|
56
59
|
*/
|
|
57
|
-
captureAndMaskWithOptions(options: MaskingOptions): Promise<string
|
|
60
|
+
captureAndMaskWithOptions(options: MaskingOptions): Promise<string>;
|
|
58
61
|
|
|
59
62
|
// Gesture recording APIs
|
|
60
|
-
startGestureRecording(): Promise<void
|
|
61
|
-
stopGestureRecording(): Promise<void
|
|
62
|
-
isGestureRecordingActive(): Promise<boolean
|
|
63
|
-
setGestureCallback(callback: (event: any) => void): void
|
|
63
|
+
startGestureRecording(): Promise<void>;
|
|
64
|
+
stopGestureRecording(): Promise<void>;
|
|
65
|
+
isGestureRecordingActive(): Promise<boolean>;
|
|
66
|
+
setGestureCallback(callback: (event: any) => void): void;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// Check if we're on web platform
|
|
67
|
-
const isWeb = Platform.OS === 'web'
|
|
70
|
+
const isWeb = Platform.OS === 'web';
|
|
68
71
|
|
|
69
72
|
// Get the Turbo Module
|
|
70
|
-
let SessionRecorderNative: Spec | null = null
|
|
71
|
-
let eventEmitter: NativeEventEmitter | null = null
|
|
73
|
+
let SessionRecorderNative: Spec | null = null;
|
|
74
|
+
let eventEmitter: NativeEventEmitter | null = null;
|
|
72
75
|
|
|
73
76
|
if (!isWeb) {
|
|
74
77
|
try {
|
|
75
|
-
SessionRecorderNative = TurboModuleRegistry.getEnforcing<Spec>(
|
|
76
|
-
|
|
78
|
+
SessionRecorderNative = TurboModuleRegistry.getEnforcing<Spec>(
|
|
79
|
+
'SessionRecorderNative'
|
|
80
|
+
);
|
|
81
|
+
eventEmitter = new NativeEventEmitter(SessionRecorderNative as any);
|
|
77
82
|
} catch (error) {
|
|
78
|
-
console.warn('Failed to access SessionRecorderNative Turbo Module:', error)
|
|
83
|
+
console.warn('Failed to access SessionRecorderNative Turbo Module:', error);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
// Validate that the native module is available
|
|
83
88
|
if (!SessionRecorderNative && !isWeb) {
|
|
84
|
-
console.warn(
|
|
89
|
+
console.warn(
|
|
90
|
+
'SessionRecorderNative Turbo Module is not available. Auto-linking may not have completed yet.'
|
|
91
|
+
);
|
|
85
92
|
} else if (isWeb) {
|
|
86
|
-
console.info(
|
|
93
|
+
console.info(
|
|
94
|
+
'SessionRecorderNative: Running on web platform, native module disabled'
|
|
95
|
+
);
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
// Create a safe wrapper that handles web platform
|
|
90
99
|
const SafeSessionRecorderNative: Spec = {
|
|
91
100
|
async captureAndMask(): Promise<string> {
|
|
92
101
|
if (isWeb || !SessionRecorderNative) {
|
|
93
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
102
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
94
103
|
}
|
|
95
|
-
return SessionRecorderNative.captureAndMask()
|
|
104
|
+
return SessionRecorderNative.captureAndMask();
|
|
96
105
|
},
|
|
97
106
|
|
|
98
107
|
async captureAndMaskWithOptions(options: MaskingOptions): Promise<string> {
|
|
99
108
|
if (isWeb || !SessionRecorderNative) {
|
|
100
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
109
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
101
110
|
}
|
|
102
|
-
return SessionRecorderNative.captureAndMaskWithOptions(options)
|
|
111
|
+
return SessionRecorderNative.captureAndMaskWithOptions(options);
|
|
103
112
|
},
|
|
104
113
|
|
|
105
114
|
async startGestureRecording(): Promise<void> {
|
|
106
115
|
if (isWeb || !SessionRecorderNative) {
|
|
107
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
116
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
108
117
|
}
|
|
109
|
-
return SessionRecorderNative.startGestureRecording()
|
|
118
|
+
return SessionRecorderNative.startGestureRecording();
|
|
110
119
|
},
|
|
111
120
|
|
|
112
121
|
async stopGestureRecording(): Promise<void> {
|
|
113
122
|
if (isWeb || !SessionRecorderNative) {
|
|
114
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
123
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
115
124
|
}
|
|
116
|
-
return SessionRecorderNative.stopGestureRecording()
|
|
125
|
+
return SessionRecorderNative.stopGestureRecording();
|
|
117
126
|
},
|
|
118
127
|
|
|
119
128
|
async isGestureRecordingActive(): Promise<boolean> {
|
|
120
129
|
if (isWeb || !SessionRecorderNative) {
|
|
121
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
130
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
122
131
|
}
|
|
123
|
-
return SessionRecorderNative.isGestureRecordingActive()
|
|
132
|
+
return SessionRecorderNative.isGestureRecordingActive();
|
|
124
133
|
},
|
|
125
134
|
|
|
126
135
|
setGestureCallback(callback: (event: any) => void): void {
|
|
127
136
|
if (isWeb || !SessionRecorderNative) {
|
|
128
|
-
throw new Error('SessionRecorderNative is not available on web platform')
|
|
137
|
+
throw new Error('SessionRecorderNative is not available on web platform');
|
|
129
138
|
}
|
|
130
139
|
// Native side will also invoke callback if provided; also subscribe to events here
|
|
131
140
|
try {
|
|
132
|
-
SessionRecorderNative.setGestureCallback(callback as any)
|
|
133
|
-
} catch {
|
|
134
|
-
eventEmitter?.removeAllListeners('onGestureDetected')
|
|
135
|
-
eventEmitter?.addListener('onGestureDetected', callback)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
141
|
+
SessionRecorderNative.setGestureCallback(callback as any);
|
|
142
|
+
} catch {}
|
|
143
|
+
eventEmitter?.removeAllListeners('onGestureDetected');
|
|
144
|
+
eventEmitter?.addListener('onGestureDetected', callback);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
138
147
|
|
|
139
148
|
export interface NativeGestureEvent {
|
|
140
|
-
type:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
type:
|
|
150
|
+
| 'tap'
|
|
151
|
+
| 'pan_start'
|
|
152
|
+
| 'pan_move'
|
|
153
|
+
| 'pan_end'
|
|
154
|
+
| 'long_press'
|
|
155
|
+
| 'pinch'
|
|
156
|
+
| 'swipe';
|
|
157
|
+
timestamp: number;
|
|
158
|
+
x: number;
|
|
159
|
+
y: number;
|
|
160
|
+
target?: string;
|
|
145
161
|
targetInfo?: {
|
|
146
|
-
identifier: string
|
|
147
|
-
label?: string
|
|
148
|
-
role?: string
|
|
149
|
-
testId?: string
|
|
150
|
-
text?: string
|
|
151
|
-
}
|
|
162
|
+
identifier: string;
|
|
163
|
+
label?: string;
|
|
164
|
+
role?: string;
|
|
165
|
+
testId?: string;
|
|
166
|
+
text?: string;
|
|
167
|
+
};
|
|
152
168
|
metadata?: {
|
|
153
|
-
pressure?: number
|
|
154
|
-
velocity?: number
|
|
155
|
-
scale?: number
|
|
156
|
-
direction?: string
|
|
157
|
-
distance?: number
|
|
158
|
-
}
|
|
169
|
+
pressure?: number;
|
|
170
|
+
velocity?: number;
|
|
171
|
+
scale?: number;
|
|
172
|
+
direction?: string;
|
|
173
|
+
distance?: number;
|
|
174
|
+
};
|
|
159
175
|
}
|
|
160
176
|
|
|
161
|
-
export default SafeSessionRecorderNative
|
|
177
|
+
export default SafeSessionRecorderNative;
|
|
162
178
|
|
|
163
179
|
// Export event emitter for gesture events to maintain previous API
|
|
164
|
-
export const gestureEventEmitter = eventEmitter
|
|
180
|
+
export const gestureEventEmitter = eventEmitter;
|
package/src/native/index.ts
CHANGED
package/src/otel/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Span } from '@opentelemetry/api'
|
|
1
|
+
import { type Span } from '@opentelemetry/api';
|
|
2
2
|
import {
|
|
3
3
|
MULTIPLAYER_TRACE_DEBUG_PREFIX,
|
|
4
4
|
MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX,
|
|
@@ -6,24 +6,22 @@ import {
|
|
|
6
6
|
ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS,
|
|
7
7
|
ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY,
|
|
8
8
|
ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS,
|
|
9
|
-
} from '@multiplayer-app/session-recorder-common'
|
|
10
|
-
import { logger } from '../utils'
|
|
11
|
-
import { type TracerReactNativeConfig } from '../types'
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
} from '@multiplayer-app/session-recorder-common';
|
|
10
|
+
import { logger } from '../utils';
|
|
11
|
+
import { type TracerReactNativeConfig } from '../types';
|
|
14
12
|
|
|
15
13
|
export interface HttpPayloadData {
|
|
16
|
-
requestBody?: any
|
|
17
|
-
responseBody?: any
|
|
18
|
-
requestHeaders?: Record<string, string
|
|
19
|
-
responseHeaders?: Record<string, string
|
|
14
|
+
requestBody?: any;
|
|
15
|
+
responseBody?: any;
|
|
16
|
+
requestHeaders?: Record<string, string>;
|
|
17
|
+
responseHeaders?: Record<string, string>;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export interface ProcessedHttpPayload {
|
|
23
|
-
requestBody?: string
|
|
24
|
-
responseBody?: string
|
|
25
|
-
requestHeaders?: string
|
|
26
|
-
responseHeaders?: string
|
|
21
|
+
requestBody?: string;
|
|
22
|
+
responseBody?: string;
|
|
23
|
+
requestHeaders?: string;
|
|
24
|
+
responseHeaders?: string;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
/**
|
|
@@ -33,7 +31,7 @@ export function shouldProcessTrace(traceId: string): boolean {
|
|
|
33
31
|
return (
|
|
34
32
|
traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) ||
|
|
35
33
|
traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)
|
|
36
|
-
)
|
|
34
|
+
);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
/**
|
|
@@ -42,22 +40,22 @@ export function shouldProcessTrace(traceId: string): boolean {
|
|
|
42
40
|
export function processBody(
|
|
43
41
|
payload: HttpPayloadData,
|
|
44
42
|
config: TracerReactNativeConfig,
|
|
45
|
-
span: Span
|
|
43
|
+
span: Span
|
|
46
44
|
): { requestBody?: string; responseBody?: string } {
|
|
47
|
-
const { captureBody, masking } = config
|
|
48
|
-
const traceId = span.spanContext().traceId
|
|
45
|
+
const { captureBody, masking } = config;
|
|
46
|
+
const traceId = span.spanContext().traceId;
|
|
49
47
|
|
|
50
48
|
if (!captureBody) {
|
|
51
|
-
return {}
|
|
49
|
+
return {};
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
let { requestBody, responseBody } = payload
|
|
52
|
+
let { requestBody, responseBody } = payload;
|
|
55
53
|
|
|
56
54
|
if (requestBody !== undefined && requestBody !== null) {
|
|
57
|
-
requestBody = JSON.parse(JSON.stringify(requestBody))
|
|
55
|
+
requestBody = JSON.parse(JSON.stringify(requestBody));
|
|
58
56
|
}
|
|
59
57
|
if (responseBody !== undefined && responseBody !== null) {
|
|
60
|
-
responseBody = JSON.parse(JSON.stringify(responseBody))
|
|
58
|
+
responseBody = JSON.parse(JSON.stringify(responseBody));
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
// Apply masking for debug traces
|
|
@@ -66,24 +64,24 @@ export function processBody(
|
|
|
66
64
|
traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)
|
|
67
65
|
) {
|
|
68
66
|
if (masking.isContentMaskingEnabled) {
|
|
69
|
-
requestBody = requestBody && masking.maskBody?.(requestBody, span)
|
|
70
|
-
responseBody = responseBody && masking.maskBody?.(responseBody, span)
|
|
67
|
+
requestBody = requestBody && masking.maskBody?.(requestBody, span);
|
|
68
|
+
responseBody = responseBody && masking.maskBody?.(responseBody, span);
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
// Convert to string if needed
|
|
75
73
|
if (typeof requestBody !== 'string') {
|
|
76
|
-
requestBody = JSON.stringify(requestBody)
|
|
74
|
+
requestBody = JSON.stringify(requestBody);
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
if (typeof responseBody !== 'string') {
|
|
80
|
-
responseBody = JSON.stringify(responseBody)
|
|
78
|
+
responseBody = JSON.stringify(responseBody);
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
return {
|
|
84
82
|
requestBody: requestBody?.length ? requestBody : undefined,
|
|
85
83
|
responseBody: responseBody?.length ? responseBody : undefined,
|
|
86
|
-
}
|
|
84
|
+
};
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
/**
|
|
@@ -92,71 +90,74 @@ export function processBody(
|
|
|
92
90
|
export function processHeaders(
|
|
93
91
|
payload: HttpPayloadData,
|
|
94
92
|
config: TracerReactNativeConfig,
|
|
95
|
-
span: Span
|
|
93
|
+
span: Span
|
|
96
94
|
): { requestHeaders?: string; responseHeaders?: string } {
|
|
97
|
-
const { captureHeaders, masking } = config
|
|
95
|
+
const { captureHeaders, masking } = config;
|
|
98
96
|
|
|
99
97
|
if (!captureHeaders) {
|
|
100
|
-
return {}
|
|
98
|
+
return {};
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
let { requestHeaders = {}, responseHeaders = {} } = payload
|
|
101
|
+
let { requestHeaders = {}, responseHeaders = {} } = payload;
|
|
104
102
|
|
|
105
103
|
// Handle header filtering
|
|
106
|
-
if (
|
|
107
|
-
!masking.headersToInclude?.length &&
|
|
108
|
-
!masking.headersToExclude?.length
|
|
109
|
-
) {
|
|
104
|
+
if (!masking.headersToInclude?.length && !masking.headersToExclude?.length) {
|
|
110
105
|
// Add null checks to prevent JSON.parse error when headers is undefined
|
|
111
106
|
if (requestHeaders !== undefined && requestHeaders !== null) {
|
|
112
|
-
requestHeaders = JSON.parse(JSON.stringify(requestHeaders))
|
|
107
|
+
requestHeaders = JSON.parse(JSON.stringify(requestHeaders));
|
|
113
108
|
}
|
|
114
109
|
if (responseHeaders !== undefined && responseHeaders !== null) {
|
|
115
|
-
responseHeaders = JSON.parse(JSON.stringify(responseHeaders))
|
|
110
|
+
responseHeaders = JSON.parse(JSON.stringify(responseHeaders));
|
|
116
111
|
}
|
|
117
112
|
} else {
|
|
118
113
|
if (masking.headersToInclude) {
|
|
119
|
-
const _requestHeaders: Record<string, string> = {}
|
|
120
|
-
const _responseHeaders: Record<string, string> = {}
|
|
114
|
+
const _requestHeaders: Record<string, string> = {};
|
|
115
|
+
const _responseHeaders: Record<string, string> = {};
|
|
121
116
|
|
|
122
117
|
for (const headerName of masking.headersToInclude) {
|
|
123
118
|
if (requestHeaders[headerName]) {
|
|
124
|
-
_requestHeaders[headerName] = requestHeaders[headerName]
|
|
119
|
+
_requestHeaders[headerName] = requestHeaders[headerName];
|
|
125
120
|
}
|
|
126
121
|
if (responseHeaders[headerName]) {
|
|
127
|
-
_responseHeaders[headerName] = responseHeaders[headerName]
|
|
122
|
+
_responseHeaders[headerName] = responseHeaders[headerName];
|
|
128
123
|
}
|
|
129
124
|
}
|
|
130
125
|
|
|
131
|
-
requestHeaders = _requestHeaders
|
|
132
|
-
responseHeaders = _responseHeaders
|
|
126
|
+
requestHeaders = _requestHeaders;
|
|
127
|
+
responseHeaders = _responseHeaders;
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
if (masking.headersToExclude?.length) {
|
|
136
131
|
for (const headerName of masking.headersToExclude) {
|
|
137
|
-
delete requestHeaders[headerName]
|
|
138
|
-
delete responseHeaders[headerName]
|
|
132
|
+
delete requestHeaders[headerName];
|
|
133
|
+
delete responseHeaders[headerName];
|
|
139
134
|
}
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
// Apply masking
|
|
144
|
-
const maskedRequestHeaders =
|
|
145
|
-
|
|
139
|
+
const maskedRequestHeaders =
|
|
140
|
+
masking.maskHeaders?.(requestHeaders, span) || requestHeaders;
|
|
141
|
+
const maskedResponseHeaders =
|
|
142
|
+
masking.maskHeaders?.(responseHeaders, span) || responseHeaders;
|
|
146
143
|
|
|
147
144
|
// Convert to string
|
|
148
|
-
const requestHeadersStr =
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
const requestHeadersStr =
|
|
146
|
+
typeof maskedRequestHeaders === 'string'
|
|
147
|
+
? maskedRequestHeaders
|
|
148
|
+
: JSON.stringify(maskedRequestHeaders);
|
|
151
149
|
|
|
152
|
-
const responseHeadersStr =
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
const responseHeadersStr =
|
|
151
|
+
typeof maskedResponseHeaders === 'string'
|
|
152
|
+
? maskedResponseHeaders
|
|
153
|
+
: JSON.stringify(maskedResponseHeaders);
|
|
155
154
|
|
|
156
155
|
return {
|
|
157
156
|
requestHeaders: requestHeadersStr?.length ? requestHeadersStr : undefined,
|
|
158
|
-
responseHeaders: responseHeadersStr?.length
|
|
159
|
-
|
|
157
|
+
responseHeaders: responseHeadersStr?.length
|
|
158
|
+
? responseHeadersStr
|
|
159
|
+
: undefined,
|
|
160
|
+
};
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
/**
|
|
@@ -165,110 +166,125 @@ export function processHeaders(
|
|
|
165
166
|
export function processHttpPayload(
|
|
166
167
|
payload: HttpPayloadData,
|
|
167
168
|
config: TracerReactNativeConfig,
|
|
168
|
-
span: Span
|
|
169
|
+
span: Span
|
|
169
170
|
): void {
|
|
170
|
-
const traceId = span.spanContext().traceId
|
|
171
|
+
const traceId = span.spanContext().traceId;
|
|
171
172
|
|
|
172
173
|
if (!shouldProcessTrace(traceId)) {
|
|
173
|
-
return
|
|
174
|
+
return;
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
const { requestBody, responseBody } = processBody(payload, config, span)
|
|
177
|
-
const { requestHeaders, responseHeaders } = processHeaders(
|
|
177
|
+
const { requestBody, responseBody } = processBody(payload, config, span);
|
|
178
|
+
const { requestHeaders, responseHeaders } = processHeaders(
|
|
179
|
+
payload,
|
|
180
|
+
config,
|
|
181
|
+
span
|
|
182
|
+
);
|
|
178
183
|
|
|
179
184
|
// Set span attributes
|
|
180
185
|
if (requestBody) {
|
|
181
|
-
span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_BODY, requestBody)
|
|
186
|
+
span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_BODY, requestBody);
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
if (responseBody) {
|
|
185
|
-
span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY, responseBody)
|
|
190
|
+
span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY, responseBody);
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
if (requestHeaders) {
|
|
189
|
-
span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS, requestHeaders)
|
|
194
|
+
span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS, requestHeaders);
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
if (responseHeaders) {
|
|
193
|
-
span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, responseHeaders)
|
|
198
|
+
span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, responseHeaders);
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
/**
|
|
198
203
|
* Converts Headers object to plain object
|
|
199
204
|
*/
|
|
200
|
-
export function headersToObject(
|
|
201
|
-
|
|
205
|
+
export function headersToObject(
|
|
206
|
+
headers:
|
|
207
|
+
| Headers
|
|
208
|
+
| Record<string, string>
|
|
209
|
+
| Record<string, string | string[]>
|
|
210
|
+
| string[][]
|
|
211
|
+
| undefined
|
|
212
|
+
): Record<string, string> {
|
|
213
|
+
const result: Record<string, string> = {};
|
|
202
214
|
|
|
203
215
|
if (!headers) {
|
|
204
|
-
return result
|
|
216
|
+
return result;
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
if (headers instanceof Headers) {
|
|
208
220
|
headers.forEach((value: string, key: string) => {
|
|
209
|
-
result[key] = value
|
|
210
|
-
})
|
|
221
|
+
result[key] = value;
|
|
222
|
+
});
|
|
211
223
|
} else if (Array.isArray(headers)) {
|
|
212
224
|
// Handle array of [key, value] pairs
|
|
213
225
|
for (const [key, value] of headers) {
|
|
214
226
|
if (typeof key === 'string' && typeof value === 'string') {
|
|
215
|
-
result[key] = value
|
|
227
|
+
result[key] = value;
|
|
216
228
|
}
|
|
217
229
|
}
|
|
218
230
|
} else if (typeof headers === 'object' && !Array.isArray(headers)) {
|
|
219
231
|
for (const [key, value] of Object.entries(headers)) {
|
|
220
232
|
if (typeof key === 'string' && typeof value === 'string') {
|
|
221
|
-
result[key] = value
|
|
233
|
+
result[key] = value;
|
|
222
234
|
}
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
|
|
226
|
-
return result
|
|
238
|
+
return result;
|
|
227
239
|
}
|
|
228
240
|
|
|
229
241
|
/**
|
|
230
242
|
* Extracts response body as string from Response object
|
|
231
243
|
*/
|
|
232
|
-
export async function extractResponseBody(
|
|
244
|
+
export async function extractResponseBody(
|
|
245
|
+
response: Response
|
|
246
|
+
): Promise<string | null> {
|
|
233
247
|
if (!response.body) {
|
|
234
|
-
return null
|
|
248
|
+
return null;
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
try {
|
|
238
252
|
if (response.body instanceof ReadableStream) {
|
|
239
253
|
// Check if response body is already consumed
|
|
240
254
|
if (response.bodyUsed) {
|
|
241
|
-
return null
|
|
255
|
+
return null;
|
|
242
256
|
}
|
|
243
257
|
|
|
244
|
-
const responseClone = response.clone()
|
|
245
|
-
return responseClone.text()
|
|
258
|
+
const responseClone = response.clone();
|
|
259
|
+
return responseClone.text();
|
|
246
260
|
} else {
|
|
247
|
-
return JSON.stringify(response.body)
|
|
261
|
+
return JSON.stringify(response.body);
|
|
248
262
|
}
|
|
249
263
|
} catch (error) {
|
|
250
264
|
// If cloning fails (body already consumed), return null
|
|
251
|
-
|
|
252
|
-
logger.warn('DEBUGGER_LIB', 'Failed to extract response body', error)
|
|
253
|
-
return null
|
|
265
|
+
|
|
266
|
+
logger.warn('DEBUGGER_LIB', 'Failed to extract response body', error);
|
|
267
|
+
return null;
|
|
254
268
|
}
|
|
255
269
|
}
|
|
256
270
|
|
|
257
271
|
export const getExporterEndpoint = (exporterEndpoint: string): string => {
|
|
258
|
-
const hasPath =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
const hasPath =
|
|
273
|
+
exporterEndpoint &&
|
|
274
|
+
(() => {
|
|
275
|
+
try {
|
|
276
|
+
const url = new URL(exporterEndpoint);
|
|
277
|
+
return url.pathname !== '/' && url.pathname !== '';
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
})();
|
|
266
282
|
|
|
267
283
|
if (hasPath) {
|
|
268
|
-
return exporterEndpoint
|
|
284
|
+
return exporterEndpoint;
|
|
269
285
|
}
|
|
270
286
|
|
|
271
|
-
const trimmedExporterEndpoint = new URL(exporterEndpoint).origin
|
|
287
|
+
const trimmedExporterEndpoint = new URL(exporterEndpoint).origin;
|
|
272
288
|
|
|
273
|
-
return `${trimmedExporterEndpoint}/v1/traces
|
|
274
|
-
}
|
|
289
|
+
return `${trimmedExporterEndpoint}/v1/traces`;
|
|
290
|
+
};
|