@multiplayer-app/session-recorder-browser 0.0.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.
- package/LICENSE +22 -0
- package/README.md +560 -0
- package/dist/browser/index.js +50249 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/common/SessionRecorderHttpInstrumentationHooksNode.d.ts.map +1 -0
- package/dist/common/SessionRecorderHttpTraceExporterBrowser.d.ts.map +1 -0
- package/dist/common/SessionRecorderIdGenerator.d.ts.map +1 -0
- package/dist/common/SessionRecorderJsonTraceSerializer.d.ts.map +1 -0
- package/dist/common/SessionRecorderTraceIdRatioBasedSampler.d.ts.map +1 -0
- package/dist/common/constants.base.d.ts.map +1 -0
- package/dist/common/constants.browser.d.ts.map +1 -0
- package/dist/common/constants.node.d.ts.map +1 -0
- package/dist/common/index-browser.d.ts.map +1 -0
- package/dist/common/index-node.d.ts.map +1 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/sdk/capture-exception.d.ts.map +1 -0
- package/dist/common/sdk/id-generator.d.ts.map +1 -0
- package/dist/common/sdk/index.d.ts.map +1 -0
- package/dist/common/sdk/is-gzip.d.ts.map +1 -0
- package/dist/common/sdk/mask.d.ts.map +1 -0
- package/dist/common/sdk/save-continuous-deb-session.d.ts.map +1 -0
- package/dist/common/sdk/schemify.d.ts.map +1 -0
- package/dist/common/sdk/set-attribute.d.ts.map +1 -0
- package/dist/common/type/index.d.ts.map +1 -0
- package/dist/common/type/session-type.enum.d.ts.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/masking.d.ts.map +1 -0
- package/dist/config/session-recorder.d.ts.map +1 -0
- package/dist/exporters/index.js +3 -0
- package/dist/exporters/index.js.LICENSE.txt +725 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +1661 -0
- package/dist/index.js.map +1 -0
- package/dist/listeners.d.ts.map +1 -0
- package/dist/otel/helpers.d.ts.map +1 -0
- package/dist/otel/index.d.ts.map +1 -0
- package/dist/patch/index.d.ts.map +1 -0
- package/dist/patch/xhr.d.ts.map +1 -0
- package/dist/rrweb/exporter.d.ts.map +1 -0
- package/dist/rrweb/index.d.ts.map +1 -0
- package/dist/services/api.service.d.ts.map +1 -0
- package/dist/sessionRecorder.d.ts.map +1 -0
- package/dist/sessionWidget/UIManager.d.ts.map +1 -0
- package/dist/sessionWidget/buttonStateConfigs.d.ts.map +1 -0
- package/dist/sessionWidget/index.d.ts.map +1 -0
- package/dist/sessionWidget/templates/finalPopover.d.ts.map +1 -0
- package/dist/sessionWidget/templates/initialPopover.d.ts.map +1 -0
- package/dist/sessionWidget/templates/toast.d.ts.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/sessionRecorder.d.ts.map +1 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/navigator.d.ts.map +1 -0
- package/dist/utils/request-utils.d.ts.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024 Multiplayer Software, Inc.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# Multiplayer Session Recorder
|
|
2
|
+
|
|
3
|
+
The Multiplayer **Session Recorder** is a powerful tool that offers deep session replays with insights spanning frontend screens, platform traces, metrics, and logs. It helps your team pinpoint and resolve bugs faster by providing a complete picture of your backend system architecture. No more wasted hours combing through APM data; the Multiplayer Session Recorder does it all in one place.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Reduced Inefficiencies**: Effortlessly capture the exact steps to reproduce an issue along with backend data in one click. No more hunting through scattered documentation, APM data, logs, or traces.
|
|
8
|
+
- **Faster Cross-Team Alignment**: Engineers can share session links containing all relevant information, eliminating the need for long tickets or clarifying issues through back-and-forth communication.
|
|
9
|
+
- **Uninterrupted Deep Work**: All system information—from architecture diagrams to API designs—is consolidated in one place. Minimize context switching and stay focused on what matters.
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
You can install the Multiplayer Session Recorder using npm or yarn:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @multiplayer-app/session-recorder-browser
|
|
19
|
+
# or
|
|
20
|
+
yarn add @multiplayer-app/session-recorder-browser
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Basic Setup
|
|
24
|
+
|
|
25
|
+
To initialize the Multiplayer Session Recorder in your application, follow the steps below.
|
|
26
|
+
|
|
27
|
+
#### Import the Session Recorder
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### Initialization
|
|
34
|
+
|
|
35
|
+
Use the following code to initialize the session recorder with your application details:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
SessionRecorder.init({
|
|
39
|
+
version: '{YOUR_APPLICATION_VERSION}',
|
|
40
|
+
application: '{YOUR_APPLICATION_NAME}',
|
|
41
|
+
environment: '{YOUR_APPLICATION_ENVIRONMENT}',
|
|
42
|
+
apiKey: '{YOUR_API_KEY}'
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace the placeholders with your application’s version, name, environment, and API key (OpenTelemetry Frontend Token).
|
|
47
|
+
|
|
48
|
+
#### Add User attributes
|
|
49
|
+
|
|
50
|
+
To track user-specific attributes in session replays, add the following:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
SessionRecorder.setSessionAttributes({
|
|
54
|
+
userId: '{userId}',
|
|
55
|
+
userName: '{userName}'
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Replace the placeholders with the actual user information (e.g., user ID and username).
|
|
60
|
+
|
|
61
|
+
## Dependencies
|
|
62
|
+
|
|
63
|
+
This library relies on the following packages:
|
|
64
|
+
|
|
65
|
+
- **[rrweb](https://github.com/rrweb-io/rrweb)**: Provides the frontend session replay functionality, recording the user’s interactions with the app.
|
|
66
|
+
- **[OpenTelemetry](https://opentelemetry.io/)**: Used to capture backend traces, metrics, and logs that integrate seamlessly with the session replays for comprehensive debugging.
|
|
67
|
+
|
|
68
|
+
## Configuration Options
|
|
69
|
+
|
|
70
|
+
The Session Recorder supports various configuration options with sensible defaults:
|
|
71
|
+
|
|
72
|
+
### Default Values
|
|
73
|
+
|
|
74
|
+
- `showWidget`: `true` - Show the recording widget by default
|
|
75
|
+
- `recordCanvas`: `false` - Disable canvas recording by default
|
|
76
|
+
- `docTraceRatio`: `0.15` - 15% of traces for auto-documentation
|
|
77
|
+
- `sampleTraceRatio`: `0.15` - 15% sampling ratio
|
|
78
|
+
- `schemifyDocSpanPayload`: `true` - Enable payload schematization
|
|
79
|
+
- `maxCapturingHttpPayloadSize`: `100000` - 100KB max payload size
|
|
80
|
+
- `usePostMessageFallback`: `false` - Disable post message fallback
|
|
81
|
+
- `widgetButtonPlacement`: `'bottom-right'` - Default widget position
|
|
82
|
+
- `masking.maskAllInputs`: `true` - Mask all inputs by default
|
|
83
|
+
- `masking.isMaskingEnabled`: `true` - Enable masking for debug span payload by default
|
|
84
|
+
- `captureBody`: `true` - Capture body in traces by default
|
|
85
|
+
- `captureHeaders`: `true` - Capture headers in traces by default
|
|
86
|
+
|
|
87
|
+
## Example Usage
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import SessionRecorder from '@multiplayer-app/debugger-browser'
|
|
91
|
+
|
|
92
|
+
SessionRecorder.init({
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
application: 'my-app',
|
|
95
|
+
environment: 'production',
|
|
96
|
+
apiKey: 'your-api-key',
|
|
97
|
+
showWidget: true,
|
|
98
|
+
recordCanvas: true,
|
|
99
|
+
ignoreUrls: [
|
|
100
|
+
/https:\/\/domain\.to\.ignore\/.*/, // can be regex or string
|
|
101
|
+
/https:\/\/another\.domain\.to\.ignore\/.*/
|
|
102
|
+
],
|
|
103
|
+
// NOTE: if frontend domain doesn't match to backend one, set backend domain to `propagateTraceHeaderCorsUrls` parameter
|
|
104
|
+
propagateTraceHeaderCorsUrls: [
|
|
105
|
+
new RegExp('https://your.backend.api.domain', 'i'), // can be regex or string
|
|
106
|
+
new RegExp('https://another.backend.api.domain', 'i')
|
|
107
|
+
],
|
|
108
|
+
docTraceRatio: 0.15, // 15% of traces will be sent for auto-documentation
|
|
109
|
+
sampleTraceRatio: 0.15, // 15% sampling ratio
|
|
110
|
+
schemifyDocSpanPayload: true,
|
|
111
|
+
maxCapturingHttpPayloadSize: 100000,
|
|
112
|
+
usePostMessageFallback: false, // Enable post message fallback if needed
|
|
113
|
+
exporterApiBaseUrl: 'https://api.multiplayer.app', // Custom API base URL (optional)
|
|
114
|
+
captureBody: true, // Capture body in traces
|
|
115
|
+
captureHeaders: true, // Capture headers in traces
|
|
116
|
+
// Configure masking for sensitive data in session recordings
|
|
117
|
+
masking: {
|
|
118
|
+
maskAllInputs: true, // Masks all input fields by default
|
|
119
|
+
maskInputOptions: {
|
|
120
|
+
password: true, // Always mask password fields
|
|
121
|
+
email: false, // Don't mask email fields by default
|
|
122
|
+
tel: false, // Don't mask telephone fields by default
|
|
123
|
+
number: false, // Don't mask number fields by default
|
|
124
|
+
url: false, // Don't mask URL fields by default
|
|
125
|
+
search: false, // Don't mask search fields by default
|
|
126
|
+
textarea: false // Don't mask textarea elements by default
|
|
127
|
+
},
|
|
128
|
+
// Class-based masking
|
|
129
|
+
maskTextClass: /sensitive|private/, // Mask text in elements with these classes
|
|
130
|
+
// CSS selector for text masking
|
|
131
|
+
maskTextSelector: '.sensitive-data', // Mask text in elements matching this selector
|
|
132
|
+
// Custom masking functions
|
|
133
|
+
maskInput: (text, element) => {
|
|
134
|
+
if (element.classList.contains('credit-card')) {
|
|
135
|
+
return '****-****-****-' + text.slice(-4)
|
|
136
|
+
}
|
|
137
|
+
return '***MASKED***'
|
|
138
|
+
},
|
|
139
|
+
maskText: (text, element) => {
|
|
140
|
+
if (element.dataset.type === 'email') {
|
|
141
|
+
const [local, domain] = text.split('@')
|
|
142
|
+
return local.charAt(0) + '***@' + domain
|
|
143
|
+
}
|
|
144
|
+
return '***MASKED***'
|
|
145
|
+
},
|
|
146
|
+
maskConsoleEvent: (payload) => {
|
|
147
|
+
// Custom console event masking
|
|
148
|
+
if (payload && payload.payload && payload.payload.args) {
|
|
149
|
+
// Mask sensitive console arguments
|
|
150
|
+
payload.payload.args = payload.payload.args.map((arg) =>
|
|
151
|
+
typeof arg === 'string' && arg.includes('password') ? '***MASKED***' : arg
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
return payload
|
|
155
|
+
},
|
|
156
|
+
isMaskingEnabled: true, // Enable masking for debug span payload in traces
|
|
157
|
+
maskBody: (payload, span) => {
|
|
158
|
+
// Custom trace payload masking
|
|
159
|
+
if (payload && typeof payload === 'object') {
|
|
160
|
+
const maskedPayload = { ...payload }
|
|
161
|
+
// Mask sensitive trace data
|
|
162
|
+
if (maskedPayload.requestHeaders) {
|
|
163
|
+
maskedPayload.requestHeaders = '***MASKED***'
|
|
164
|
+
}
|
|
165
|
+
if (maskedPayload.responseBody) {
|
|
166
|
+
maskedPayload.responseBody = '***MASKED***'
|
|
167
|
+
}
|
|
168
|
+
return maskedPayload
|
|
169
|
+
}
|
|
170
|
+
return payload
|
|
171
|
+
},
|
|
172
|
+
maskHeaders: (headers, span) => {
|
|
173
|
+
// Custom headers masking
|
|
174
|
+
if (headers && typeof headers === 'object') {
|
|
175
|
+
const maskedHeaders = { ...headers }
|
|
176
|
+
// Mask sensitive headers
|
|
177
|
+
if (maskedHeaders.authorization) {
|
|
178
|
+
maskedHeaders.authorization = '***MASKED***'
|
|
179
|
+
}
|
|
180
|
+
if (maskedHeaders.cookie) {
|
|
181
|
+
maskedHeaders.cookie = '***MASKED***'
|
|
182
|
+
}
|
|
183
|
+
return maskedHeaders
|
|
184
|
+
}
|
|
185
|
+
return headers
|
|
186
|
+
},
|
|
187
|
+
// List of body fields to mask in traces
|
|
188
|
+
maskBodyFieldsList: ['password', 'token', 'secret'],
|
|
189
|
+
// List of headers to mask in traces
|
|
190
|
+
maskHeadersList: ['authorization', 'cookie', 'x-api-key'],
|
|
191
|
+
// List of headers to include in traces (if specified, only these headers will be captured)
|
|
192
|
+
headersToInclude: ['content-type', 'user-agent'],
|
|
193
|
+
// List of headers to exclude from traces
|
|
194
|
+
headersToExclude: ['authorization', 'cookie']
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
SessionRecorder.setSessionAttributes({
|
|
199
|
+
userId: '12345',
|
|
200
|
+
userName: 'John Doe'
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## API Methods
|
|
205
|
+
|
|
206
|
+
The Session Recorder provides several methods for controlling session recording:
|
|
207
|
+
|
|
208
|
+
### Session Control
|
|
209
|
+
|
|
210
|
+
- `SessionRecorder.start(type?, session?)` - Start a new session with optional existing session
|
|
211
|
+
- `type`: Optional `SessionType.PLAIN` or `SessionType.CONTINUOUS`, default: `SessionType.PLAIN`
|
|
212
|
+
- `session`: Optional existing session object
|
|
213
|
+
- `SessionRecorder.stop(comment?)` - Stop the current session with optional comment
|
|
214
|
+
- `SessionRecorder.pause()` - Pause the current session
|
|
215
|
+
- `SessionRecorder.resume()` - Resume the current session
|
|
216
|
+
- `SessionRecorder.cancel()` - Cancel the current session
|
|
217
|
+
- `SessionRecorder.save()` - Save the continuous debugging session
|
|
218
|
+
|
|
219
|
+
### Configuration
|
|
220
|
+
|
|
221
|
+
- `SessionRecorder.setSessionAttributes(attributes)` - Set session metadata
|
|
222
|
+
- `SessionRecorder.recordingButtonClickHandler = handler` - Set custom click handler
|
|
223
|
+
|
|
224
|
+
### Properties
|
|
225
|
+
|
|
226
|
+
- `SessionRecorder.sessionId` - Get current session ID (readonly)
|
|
227
|
+
- `SessionRecorder.sessionType` - Get current session type (readonly)
|
|
228
|
+
- `SessionRecorder.sessionState` - Get current session state (readonly)
|
|
229
|
+
- `SessionRecorder.session` - Get current session object (readonly)
|
|
230
|
+
- `SessionRecorder.sessionAttributes` - Get current session attributes (readonly)
|
|
231
|
+
- `SessionRecorder.error` - Get/set error message
|
|
232
|
+
- `SessionRecorder.sessionWidgetButtonElement` - Get the widget button element (readonly)
|
|
233
|
+
|
|
234
|
+
### Session Types
|
|
235
|
+
|
|
236
|
+
- `SessionType.PLAIN` - Standard session recording
|
|
237
|
+
- `SessionType.CONTINUOUS` - Continuous debugging session
|
|
238
|
+
|
|
239
|
+
### Session States
|
|
240
|
+
|
|
241
|
+
- `SessionState.started` - Session is currently recording
|
|
242
|
+
- `SessionState.paused` - Session is paused
|
|
243
|
+
- `SessionState.stopped` - Session is stopped
|
|
244
|
+
|
|
245
|
+
### Session Attributes
|
|
246
|
+
|
|
247
|
+
You can set various session attributes for better tracking:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
SessionRecorder.setSessionAttributes({
|
|
251
|
+
userId: '12345',
|
|
252
|
+
userName: 'John Doe',
|
|
253
|
+
userEmail: 'john@example.com',
|
|
254
|
+
accountId: 'acc_123',
|
|
255
|
+
accountName: 'Enterprise Account'
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Masking Configuration
|
|
260
|
+
|
|
261
|
+
The Session Recorder includes comprehensive masking options to protect sensitive data during session recordings. You can configure masking behavior through the `masking` option:
|
|
262
|
+
|
|
263
|
+
### Basic Masking Options
|
|
264
|
+
|
|
265
|
+
- `maskAllInputs`: If `true`, masks all input fields in the recording (default: `true`)
|
|
266
|
+
- `isMaskingEnabled`: If `true`, enables masking for debug span payload in traces (default: `true`)
|
|
267
|
+
|
|
268
|
+
### Input Type Masking
|
|
269
|
+
|
|
270
|
+
You can control masking for specific input types:
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
maskInputOptions: {
|
|
274
|
+
password: true, // Always mask password fields (default: true)
|
|
275
|
+
email: false, // Don't mask email fields by default
|
|
276
|
+
tel: false, // Don't mask telephone fields by default
|
|
277
|
+
number: false, // Don't mask number fields by default
|
|
278
|
+
url: false, // Don't mask URL fields by default
|
|
279
|
+
search: false, // Don't mask search fields by default
|
|
280
|
+
textarea: false, // Don't mask textarea elements by default
|
|
281
|
+
select: false, // Don't mask select elements by default
|
|
282
|
+
// ...other types
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### CSS Selector Masking
|
|
287
|
+
|
|
288
|
+
You can mask specific elements using CSS selectors:
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
masking: {
|
|
292
|
+
// Mask text in elements matching this selector
|
|
293
|
+
maskTextSelector: '.sensitive-data, [data-private="true"], .user-profile .email',
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Class-Based Masking
|
|
298
|
+
|
|
299
|
+
You can mask text based on CSS classes using string or RegExp patterns:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
masking: {
|
|
303
|
+
maskTextClass: 'sensitive', // Mask text in elements with class 'sensitive'
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Or with RegExp pattern:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
masking: {
|
|
311
|
+
maskTextClass: /private|confidential/, // Mask text in elements with classes 'private' or 'confidential'
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Custom Masking Functions
|
|
316
|
+
|
|
317
|
+
For advanced masking scenarios, you can provide custom functions:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
masking: {
|
|
321
|
+
// Custom function for input masking
|
|
322
|
+
maskInput: (text, element) => {
|
|
323
|
+
// Custom logic to mask input text
|
|
324
|
+
if (element.classList.contains('credit-card')) {
|
|
325
|
+
return '****-****-****-' + text.slice(-4);
|
|
326
|
+
}
|
|
327
|
+
return '***MASKED***';
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
// Custom function for text masking
|
|
331
|
+
maskText: (text, element) => {
|
|
332
|
+
// Custom logic to mask text content
|
|
333
|
+
if (element.dataset.type === 'email') {
|
|
334
|
+
const [local, domain] = text.split('@');
|
|
335
|
+
return local.charAt(0) + '***@' + domain;
|
|
336
|
+
}
|
|
337
|
+
return '***MASKED***';
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Custom function for masking body in traces
|
|
341
|
+
maskBody: (payload, span) => {
|
|
342
|
+
// Custom logic to mask sensitive data in trace payloads
|
|
343
|
+
if (payload && typeof payload === 'object') {
|
|
344
|
+
const maskedPayload = { ...payload };
|
|
345
|
+
// Mask sensitive fields
|
|
346
|
+
if (maskedPayload.headers) {
|
|
347
|
+
maskedPayload.headers = '***MASKED***';
|
|
348
|
+
}
|
|
349
|
+
if (maskedPayload.body) {
|
|
350
|
+
maskedPayload.body = '***MASKED***';
|
|
351
|
+
}
|
|
352
|
+
return maskedPayload;
|
|
353
|
+
}
|
|
354
|
+
return payload;
|
|
355
|
+
},
|
|
356
|
+
// Custom function for masking headers in traces
|
|
357
|
+
maskHeaders: (headers, span) => {
|
|
358
|
+
// Custom logic to mask sensitive headers
|
|
359
|
+
if (headers && typeof headers === 'object') {
|
|
360
|
+
const maskedHeaders = { ...headers };
|
|
361
|
+
// Mask sensitive headers
|
|
362
|
+
if (maskedHeaders.authorization) {
|
|
363
|
+
maskedHeaders.authorization = '***MASKED***';
|
|
364
|
+
}
|
|
365
|
+
if (maskedHeaders.cookie) {
|
|
366
|
+
maskedHeaders.cookie = '***MASKED***';
|
|
367
|
+
}
|
|
368
|
+
return maskedHeaders;
|
|
369
|
+
}
|
|
370
|
+
return headers;
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Example: Comprehensive Masking Setup
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
SessionRecorder.init({
|
|
379
|
+
// ... other options
|
|
380
|
+
masking: {
|
|
381
|
+
maskAllInputs: true,
|
|
382
|
+
maskInputOptions: {
|
|
383
|
+
password: true,
|
|
384
|
+
email: true, // Mask email fields for privacy
|
|
385
|
+
tel: true, // Mask telephone fields for privacy
|
|
386
|
+
number: false, // Allow number fields
|
|
387
|
+
url: false, // Allow URL fields
|
|
388
|
+
search: false, // Allow search fields
|
|
389
|
+
textarea: false // Allow textarea elements
|
|
390
|
+
// ...other types
|
|
391
|
+
},
|
|
392
|
+
maskTextClass: /sensitive|private|confidential/, // Mask text in elements with these classes
|
|
393
|
+
maskTextSelector: '.user-email, .user-phone, .credit-card, [data-sensitive="true"]', // Mask text in elements matching this selector
|
|
394
|
+
maskInput: (text, element) => {
|
|
395
|
+
// Custom credit card masking
|
|
396
|
+
if (element.classList.contains('credit-card')) {
|
|
397
|
+
return '****-****-****-' + text.slice(-4)
|
|
398
|
+
}
|
|
399
|
+
return '***MASKED***'
|
|
400
|
+
},
|
|
401
|
+
maskText: (text, element) => {
|
|
402
|
+
// Custom email masking
|
|
403
|
+
if (element.dataset.type === 'email') {
|
|
404
|
+
const [local, domain] = text.split('@')
|
|
405
|
+
return local.charAt(0) + '***@' + domain
|
|
406
|
+
}
|
|
407
|
+
return '***MASKED***'
|
|
408
|
+
},
|
|
409
|
+
maskConsoleEvent: (payload) => {
|
|
410
|
+
// Custom console event masking
|
|
411
|
+
if (payload && payload.payload && payload.payload.args) {
|
|
412
|
+
payload.payload.args = payload.payload.args.map((arg) =>
|
|
413
|
+
typeof arg === 'string' && arg.includes('password') ? '***MASKED***' : arg
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
return payload
|
|
417
|
+
},
|
|
418
|
+
isMaskingEnabled: true, // Enable masking for debug span payload in traces
|
|
419
|
+
maskBody: (payload, span) => {
|
|
420
|
+
// Custom trace payload masking
|
|
421
|
+
if (payload && typeof payload === 'object') {
|
|
422
|
+
const maskedPayload = { ...payload }
|
|
423
|
+
// Mask sensitive trace data
|
|
424
|
+
if (maskedPayload.requestHeaders) {
|
|
425
|
+
maskedPayload.requestHeaders = '***MASKED***'
|
|
426
|
+
}
|
|
427
|
+
if (maskedPayload.responseBody) {
|
|
428
|
+
maskedPayload.responseBody = '***MASKED***'
|
|
429
|
+
}
|
|
430
|
+
return maskedPayload
|
|
431
|
+
}
|
|
432
|
+
return payload
|
|
433
|
+
},
|
|
434
|
+
maskHeaders: (headers, span) => {
|
|
435
|
+
// Custom headers masking
|
|
436
|
+
if (headers && typeof headers === 'object') {
|
|
437
|
+
const maskedHeaders = { ...headers }
|
|
438
|
+
// Mask sensitive headers
|
|
439
|
+
if (maskedHeaders.authorization) {
|
|
440
|
+
maskedHeaders.authorization = '***MASKED***'
|
|
441
|
+
}
|
|
442
|
+
if (maskedHeaders.cookie) {
|
|
443
|
+
maskedHeaders.cookie = '***MASKED***'
|
|
444
|
+
}
|
|
445
|
+
return maskedHeaders
|
|
446
|
+
},
|
|
447
|
+
// List of body fields to mask in traces
|
|
448
|
+
maskBodyFieldsList: ['password', 'token', 'secret'],
|
|
449
|
+
// List of headers to mask in traces
|
|
450
|
+
maskHeadersList: ['authorization', 'cookie', 'x-api-key'],
|
|
451
|
+
// List of headers to include in traces (if specified, only these headers will be captured)
|
|
452
|
+
headersToInclude: ['content-type', 'user-agent'],
|
|
453
|
+
// List of headers to exclude from traces
|
|
454
|
+
headersToExclude: ['authorization', 'cookie']
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Session Recorder for Next.js
|
|
460
|
+
|
|
461
|
+
To integrate the MySessionRecorder component into your Next.js application, follow these steps:
|
|
462
|
+
|
|
463
|
+
- Create a new file (e.g., MySessionRecorder.js or MySessionRecorder.tsx) in your root directory or a components directory.
|
|
464
|
+
|
|
465
|
+
- Import the component
|
|
466
|
+
|
|
467
|
+
In the newly created file, add the following code:
|
|
468
|
+
|
|
469
|
+
```javascript
|
|
470
|
+
'use client' // Mark as Client Component
|
|
471
|
+
import { useEffect } from 'react'
|
|
472
|
+
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
|
|
473
|
+
|
|
474
|
+
export default function MySessionRecorder() {
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
if (typeof window !== 'undefined') {
|
|
477
|
+
SessionRecorder.init({
|
|
478
|
+
version: '{YOUR_APPLICATION_VERSION}',
|
|
479
|
+
application: '{YOUR_APPLICATION_NAME}',
|
|
480
|
+
environment: '{YOUR_APPLICATION_ENVIRONMENT}',
|
|
481
|
+
apiKey: '{YOUR_API_KEY}',
|
|
482
|
+
recordCanvas: true, // Enable canvas recording
|
|
483
|
+
masking: {
|
|
484
|
+
maskAllInputs: true,
|
|
485
|
+
maskInputOptions: {
|
|
486
|
+
password: true,
|
|
487
|
+
email: false,
|
|
488
|
+
tel: false
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
SessionRecorder.setSessionAttributes({
|
|
494
|
+
userId: '{userId}',
|
|
495
|
+
userName: '{userName}'
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
}, [])
|
|
499
|
+
|
|
500
|
+
return null // No UI output needed
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Replace the placeholders with the actual information.
|
|
505
|
+
|
|
506
|
+
Now, you can use the MySessionRecorder component in your application by adding it to your desired page or layout file:
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
import MySessionRecorder from './MySessionRecorder' // Adjust the path as necessary
|
|
510
|
+
|
|
511
|
+
export default function MyApp() {
|
|
512
|
+
return (
|
|
513
|
+
<>
|
|
514
|
+
<MySessionRecorder />
|
|
515
|
+
{/* Other components */}
|
|
516
|
+
</>
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Note
|
|
522
|
+
|
|
523
|
+
If frontend domain doesn't match to backend one, set backend domain to `propagateTraceHeaderCorsUrls` parameter:
|
|
524
|
+
|
|
525
|
+
```javascript
|
|
526
|
+
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
|
|
527
|
+
|
|
528
|
+
SessionRecorder.init({
|
|
529
|
+
version: '{YOUR_APPLICATION_VERSION}',
|
|
530
|
+
application: '{YOUR_APPLICATION_NAME}',
|
|
531
|
+
environment: '{YOUR_APPLICATION_ENVIRONMENT}',
|
|
532
|
+
apiKey: '{YOUR_API_KEY}',
|
|
533
|
+
propagateTraceHeaderCorsUrls: new RegExp(`https://your.backend.api.domain`, 'i')
|
|
534
|
+
})
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
If frontend sends api requests to two or more different domains put them to `propagateTraceHeaderCorsUrls` as array:
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
|
|
541
|
+
|
|
542
|
+
SessionRecorder.init({
|
|
543
|
+
version: '{YOUR_APPLICATION_VERSION}',
|
|
544
|
+
application: '{YOUR_APPLICATION_NAME}',
|
|
545
|
+
environment: '{YOUR_APPLICATION_ENVIRONMENT}',
|
|
546
|
+
apiKey: '{YOUR_API_KEY}',
|
|
547
|
+
propagateTraceHeaderCorsUrls: [
|
|
548
|
+
new RegExp(`https://your.backend.api.domain`, 'i'),
|
|
549
|
+
new RegExp(`https://another.backend.api.domain`, 'i')
|
|
550
|
+
]
|
|
551
|
+
})
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Documentation
|
|
555
|
+
|
|
556
|
+
For more details on how the Multiplayer Session Recorder integrates with your backend architecture and system auto-documentation, check out our [official documentation](https://www.multiplayer.app/docs/features/system-auto-documentation/).
|
|
557
|
+
|
|
558
|
+
## License
|
|
559
|
+
|
|
560
|
+
This library is distributed under the [MIT License](LICENSE).
|