
Research
/Security News
Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.
@multiplayer-app/session-recorder-browser
Advanced tools
Multiplayer Fullstack Session Recorder for Browser

The Multiplayer Full Stack 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 Full Stack Session Recorder does it all in one place.
npm i @multiplayer-app/session-recorder-browser
# or
yarn add @multiplayer-app/session-recorder-browser
Use the following code below to initialize and run the session recorder.
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
SessionRecorder.init({
application: 'my-web-app',
version: '1.0.0',
environment: 'production',
apiKey: 'MULTIPLAYER_API_KEY' // note: replace with your Multiplayer API key
// IMPORTANT: in order to propagate OTLP headers to a backend
// domain(s) with a different origin, add backend domain(s) below.
// e.g. if you serve your website from www.example.com
// and your backend domain is at api.example.com set value as shown below:
// format: string|RegExp|Array
propagateTraceHeaderCorsUrls: [new RegExp('https://api.example.com', 'i')],
})
// Use session attributes to attach user context to recordings.
// The provided `userName` and `userId` will be visible in the Multiplayer
// sessions list and in the session details (shown as the reporter),
// making it easier to identify who reported or recorded the session.
SessionRecorder.setSessionAttributes({
userId: '12345',
userName: 'John Doe'
})
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
SessionRecorder.init({
version: '1.0.0', // optional: version of your application
application: 'my-app', // name of your application
environment: 'production',
apiKey: 'MULTIPLAYER_API_KEY', // note: replace with your Multiplayer API key
apiBaseUrl: 'https://api.multiplayer.app', // override API base URL if needed
exporterEndpoint: 'https://otlp.multiplayer.app', // override OTLP collector URL if needed
showWidget: true, // show in‑app recording widget (default: true)
recordCanvas: true, // record canvas elements (default: false)
inlineImages: true, // opt-in: inline images as base64 (requires CORS on cross-origin asset hosts; default: false)
inlineStylesheet: true, // opt-in: inline stylesheets (cross-origin hosts may need CORS; default: false)
// Add domains to not capture OTLP data in the session recording
ignoreUrls: [
/https:\/\/domain\.to\.ignore\/.*/, // can be regex or string
/https:\/\/another\.domain\.to\.ignore\/.*/
],
// NOTE: if frontend domain doesn't match to backend one, set backend domain to `propagateTraceHeaderCorsUrls` parameter
propagateTraceHeaderCorsUrls: [
new RegExp('https://your.backend.api.domain', 'i'), // can be regex or string
new RegExp('https://another.backend.api.domain', 'i')
],
// sample trace ratio used when session recording is not active.
// configures what percentage (0.00-1.00) of OTLP data
// should be sent through `exporters`
sampleTraceRatio: 0,
// crash buffer: keep a rolling window of rrweb + traces when no recording is active.
// when user starts a session or saves (e.g. on error), the buffer is flushed into the session.
buffering: {
enabled: true, // enable/disable buffering (default: true)
windowMinutes: 0.5, // rolling window size in minutes (default: 0.5)
snapshotIntervalMs: 20000 // full snapshot interval while buffering in ms (default: 20000)
},
// optional: exporters allow you to send
// OTLP data to observability platforms
exporters: [
// example:
// import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
// new OTLPTraceExporter({
// url: '<opentelemetry-collector-url>',
// })
],
captureBody: true, // capture request/response content
captureHeaders: true, // capture request/response header content
// set the maximum request/response content size (in bytes) that will be captured
// any request/response content greater than size will be not included in session recordings
maxCapturingHttpPayloadSize: 100000,
// configure masking for sensitive data in session recordings
masking: {
maskAllInputs: false, // masks all input fields
maskInputOptions: {
password: true, // mask password fields
email: false, // mask email fields
tel: false, // mask telephone fields
number: false, // mask number fields
url: false, // mask URL fields
search: false, // mask search fields
textarea: false // mask textarea elements
},
// class-based masking
maskTextClass: /sensitive|private/, // mask text in elements with these classes
// CSS selector for text masking
maskTextSelector: '.sensitive-data', // mask text in elements matching this selector
// custom masking functions
maskInput: (text, element) => {
if (element.classList.contains('credit-card')) {
return '****-****-****-' + text.slice(-4)
}
return '***MASKED***'
},
maskText: (text, element) => {
if (element.dataset.type === 'email') {
const [local, domain] = text.split('@')
return local.charAt(0) + '***@' + domain
}
return '***MASKED***'
},
maskConsoleEvent: (payload) => {
if (payload && payload.payload && payload.payload.args) {
// mask sensitive console arguments
payload.payload.args = payload.payload.args.map((arg) =>
typeof arg === 'string' && arg.includes('password') ? '***MASKED***' : arg
)
}
return payload
},
isContentMaskingEnabled: true, // enable content masking in session recordings
maskBody: (payload, span) => {
// note: `payload` is already a copy of the original request/response content
if (payload && typeof payload === 'object') {
// mask sensitive data
if (payload.requestHeaders) {
payload.requestHeaders = '***MASKED***'
}
if (payload.responseBody) {
payload.responseBody = '***MASKED***'
}
}
return payload
},
maskHeaders: (headers, span) => {
// note: `headers` is already a copy of the original request/response content
if (headers && typeof headers === 'object') {
// mask sensitive headers
if (headers.authorization) {
headers.authorization = '***MASKED***'
}
if (headers.cookie) {
headers.cookie = '***MASKED***'
}
}
return headers
},
// list of field names to mask in request/response content
maskBodyFieldsList: ['password', 'token', 'secret'],
// list of headers to mask in request/response headers
maskHeadersList: ['authorization', 'cookie', 'x-api-key'],
// list of headers to capture. An empty array will capture all headers
headersToInclude: ['content-type', 'user-agent'],
// list of headers to exclude from capturing
headersToExclude: ['authorization', 'cookie']
}
})
By default, inlineImages and inlineStylesheet are false. That avoids failed cross-origin requests and DevTools noise when assets are served from another host (for example S3 or a CDN) that does not send CORS headers such as Access-Control-Allow-Origin for your app.
If you enable either option:
src and stylesheet links; replay loads them like a normal page when URLs remain valid.Below is an example showing how to create a session recording in MANUAL mode. Manual session recordings stream and save all the data between calling start and stop.
// add any key value pairs which should be associated with a session
SessionRecorder.setSessionAttributes({
userId: '12345',
userName: 'John Doe'
})
// optionally control via API (widget is enabled by default)
// if you're not using widget (see: `showWidget: true/false`)
// then you can programatically control the session recorder
// by using the method below
// Wire up your own UI controls
const startButton = document.getElementById('start')
const pauseButton = document.getElementById('pause')
const resumeButton = document.getElementById('resume')
const stopButton = document.getElementById('stop')
startButton?.addEventListener('click', () => {
SessionRecorder.start()
})
startContinuousButton?.addEventListener('click', () => {
SessionRecorder.start(SessionType.CONTINUOUS)
})
pauseButton?.addEventListener('click', () => {
SessionRecorder.pause()
})
resumeButton?.addEventListener('click', () => {
SessionRecorder.resume()
})
stopButton?.addEventListener('click', () => {
SessionRecorder.stop('Finished session') // optional reason
})
Below is an example showing how to create a session in CONTINUOUS mode. Continuous session recordings stream all the data received between calling start and stop -
but only save a rolling window data (90 seconds by default) when:
save is called; or// add any key value pairs which should be associated with a session
SessionRecorder.setSessionAttributes({
userId: '12345',
userName: 'John Doe'
})
// optionally control via API (widget is enabled by default)
// if you're not using widget (see: `showWidget: true/false`)
// then you can programatically control the session recorder
// by using the methods below
// Option A: fire-and-forget (simple)
// SessionRecorder.start(SessionType.CONTINUOUS)
// ... later ...
// SessionRecorder.save()
// ... later ...
// SessionRecorder.stop('Finished session')
// Option B: wire up your own UI controls
const startContinuousButton = document.getElementById('start-continuous')
const saveButton = document.getElementById('save')
const stopButton = document.getElementById('stop')
startContinuousButton?.addEventListener('click', () => {
SessionRecorder.start(SessionType.CONTINUOUS)
})
saveButton?.addEventListener('click', () => {
SessionRecorder.save()
})
stopButton?.addEventListener('click', () => {
SessionRecorder.stop('Finished session') // optional reason
})
The browser SDK captures uncaught errors and unhandled promise rejections automatically and turns them into error traces that are linked to your session.
For each error span we record:
ERRORexception.type, exception.message, exception.stacktraceManual reporting (e.g. inside try/catch or library boundaries):
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
try {
// code that may throw
} catch (err) {
SessionRecorder.captureException(err) // Error | unknown | string
}
// You can also send arbitrary reasons
SessionRecorder.captureException('Payment form validation failed')
When running in CONTINUOUS mode, any captured exception automatically marks the current trace as an error and auto‑saves the rolling window so you can replay the seconds leading up to the failure.
Continuous session recordings may also be saved from within any service or component involved in a trace by adding the attributes below to a span:
import { trace, context } from '@opentelemetry/api'
import SessionRecorder from '@multiplayer-app/session-recorder-browser'
const activeContext = context.active()
const activeSpan = trace.getSpan(activeContext)
activeSpan.setAttribute(SessionRecorder.ATTR_MULTIPLAYER_CONTINUOUS_SESSION_AUTO_SAVE, true)
activeSpan.setAttribute(SessionRecorder.ATTR_MULTIPLAYER_CONTINUOUS_SESSION_AUTO_SAVE_REASON, 'Some reason')
The Multiplayer Session Recorder works with any web framework, but we provide specialized packages and examples for popular frameworks to make integration easier.
For React and Next.js applications, use the dedicated React package which includes idiomatic hooks, context helpers, and navigation tracking:
For Vue.js applications, use the browser package directly. We provide comprehensive examples and integration guides:
@multiplayer-app/session-recorder-browser (this package)For Angular applications, use the browser package with Angular-specific setup. We provide detailed examples and integration guides:
@multiplayer-app/session-recorder-browser (this package)For React Native applications (iOS and Android), use the dedicated React Native package:
For more details on how the Multiplayer Session Recorder integrates with your backend architecture and system auto-documentation, check out our official documentation.
Inlining and CORS: See Inlining images and stylesheets (CORS) above for defaults and when you must configure CORS on asset hosts.
This library is distributed under the MIT License.
FAQs
Multiplayer Fullstack Session Recorder for Browser
We found that @multiplayer-app/session-recorder-browser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.

Research
/Security News
The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Security News
The Rust project is moving toward formal rules on LLM use in contributions after months of internal debate over maintainer burden, code quality, and contributor experience.