
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-react
Advanced tools
Multiplayer Fullstack Session Recorder for React (browser wrapper)

React bindings for the Multiplayer Full Stack Session Recorder.
Use this wrapper to wire the browser SDK into your React or Next.js application with idiomatic hooks, context helpers, and navigation tracking.
npm install @multiplayer-app/session-recorder-react
# or
yarn add @multiplayer-app/session-recorder-react
To get full‑stack session recording working, set up one of our backend SDKs/CLI apps:
SessionRecorder.init(options) before you mount your React app to avoid losing any data.SessionRecorderProvider.// src/main.tsx or src/index.tsx app root
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import SessionRecorder, { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
const sessionRecorderConfig = {
version: '1.0.0',
environment: 'production',
application: 'my-react-app',
apiKey: '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')]
}
// Initialize the session recorder before mounting (Recommended)
SessionRecorder.init(sessionRecorderConfig)
ReactDOM.createRoot(document.getElementById('root')!).render(
<SessionRecorderProvider>
<App />
</SessionRecorderProvider>
)
Behind the scenes, the provider sets up listeners and exposes helper APIs through React context and selectors.
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.
import { useEffect } from 'react'
import SessionRecorder from '@multiplayer-app/session-recorder-react'
//... your code
const MyComponent = () => {
useEffect(() => {
SessionRecorder.setSessionAttributes({
userId: '12345', // replace with your user id
userName: 'John Doe' // replace with your user name
})
}, [])
//... your code
}
//... your code
If you prefer not to render our floating widget, disable it and rely purely on the imperative hooks. Use the context hook when you need imperative control (for example, to bind to buttons or QA tooling) as shown in the example below:
import SessionRecorder, { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
// Initialize without the built‑in widget
SessionRecorder.init({
application: 'my-react-app',
version: '1.0.0',
environment: 'production',
apiKey: 'YOUR_MULTIPLAYER_API_KEY',
showWidget: false // hide the built-in widget
})
// Wrap your app with the provider to enable hooks/context
<SessionRecorderProvider>
<App />
</SessionRecorderProvider>
Create your own UI and wire it to the hook methods. Render only the relevant actions based on the current session state (e.g., show Stop only when recording is started):
import React from 'react'
import {
useSessionRecorder,
useSessionRecordingState,
SessionState,
SessionType
} from '@multiplayer-app/session-recorder-react'
export function SmartSessionControls() {
const { startSession, stopSession, pauseSession, resumeSession } = useSessionRecorder()
const sessionState = useSessionRecordingState()
const isStarted = sessionState === SessionState.started
const isPaused = sessionState === SessionState.paused
return (
<div>
{/* Idle state: allow starting */}
{!isStarted && !isPaused && (
<>
<button onClick={() => startSession()}>Start</button>
<button onClick={() => startSession(SessionType.CONTINUOUS)}>Start Continuous</button>
</>
)}
{/* Started state: allow pause or stop */}
{isStarted && (
<>
<button onClick={() => pauseSession()}>Pause</button>
<button onClick={() => stopSession('Finished recording')}>Stop</button>
</>
)}
{/* Paused state: allow resume or stop */}
{isPaused && (
<>
<button onClick={() => resumeSession()}>Resume</button>
<button onClick={() => stopSession('Finished recording')}>Stop</button>
</>
)}
</div>
)
}
The package ships a lightweight observable store that mirrors the browser SDK. Use the selectors to drive UI state without forcing rerenders on unrelated updates.
import React from 'react'
import {
useSessionRecordingState,
useSessionType,
useIsInitialized,
SessionState,
SessionType
} from '@multiplayer-app/session-recorder-react'
export function RecorderStatusBanner() {
const isReady = useIsInitialized()
const sessionState = useSessionRecordingState()
const sessionType = useSessionType()
if (!isReady) {
return <span>Session recorder initializing…</span>
}
return (
<span>
State: {sessionState ?? SessionState.stopped} | Mode: {sessionType ?? SessionType.MANUAL}
</span>
)
}
The Session Recorder React package includes a useNavigationRecorder hook that forwards router changes to the shared navigation recorder. Attach it inside your routing layer to correlate screen changes with traces and replays.
// React Router v7/v6
import { useLocation, useNavigationType } from 'react-router-dom'
import { useNavigationRecorder } from '@multiplayer-app/session-recorder-react'
export function NavigationTracker() {
const location = useLocation()
const navigationType = useNavigationType()
useNavigationRecorder(location.pathname, {
navigationType,
params: location.state as Record<string, unknown> | undefined
})
return null
}
// React Router v5 (older)
import { useLocation, useHistory } from 'react-router-dom'
import { useNavigationRecorder } from '@multiplayer-app/session-recorder-react'
export function NavigationTrackerLegacy() {
const location = useLocation()
const history = useHistory()
// PUSH | REPLACE | POP => push | replace | pop
const navigationType = (history.action || 'PUSH').toLowerCase()
useNavigationRecorder(location.pathname, {
navigationType,
params: location.state as Record<string, unknown> | undefined
})
return null
}
useNavigationRecorder accepts an options object allowing you to override the detected path, attach custom routeName, include query params, or disable document title capture. For full control you can call SessionRecorder.navigation.record({ ... }) directly using the shared browser instance exported by this package.
The options passed to SessionRecorder.init(...) are forwarded to the underlying browser SDK. Refer to the browser README for the full option list, including:
Any time recordNavigation is enabled, the browser SDK will emit OpenTelemetry navigation spans and keep an in-memory stack of visited routes. You can access the navigation helpers through SessionRecorder.navigation if you need to introspect from React components.
Options passed to SessionRecorder.init are forwarded to the browser SDK. By default, inlineImages and inlineStylesheet are false, which avoids failed cross-origin requests when assets come from hosts without CORS (for example S3 or a CDN).
If you set either to true, the asset origin must allow your app via headers such as Access-Control-Allow-Origin, or you should serve those assets same-origin (for example through a proxy). With defaults off, replays still record normal URLs and load images and stylesheets like a regular page when links stay valid.
Details match the browser package README.
The browser SDK auto‑captures uncaught errors and unhandled promise rejections. In React apps you’ll typically also want an Error Boundary to catch render errors and report them. This package ships a ready‑to‑use boundary and also shows how to wire React 18/19 root error callbacks.
import React from 'react'
import { ErrorBoundary } from '@multiplayer-app/session-recorder-react'
export function AppWithBoundary() {
return (
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<App />
</ErrorBoundary>
)
}
The boundary calls SessionRecorder.captureException(error) internally and renders the provided fallback on error.
import React from 'react'
import SessionRecorder from '@multiplayer-app/session-recorder-react'
class MyErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error: unknown, errorInfo?: Record<string, any>) {
SessionRecorder.captureException(error as any, errorInfo)
}
render() {
return this.state.hasError ? <h1>Oops.</h1> : this.props.children
}
}
React 19 adds onUncaughtError and onCaughtError to createRoot options (along with onRecoverableError that exists in 18/19). You can wire all three to SessionRecorder.captureException similar to Sentry’s handler:
import React from 'react'
import ReactDOM from 'react-dom/client'
import SessionRecorder from '@multiplayer-app/session-recorder-react'
import App from './App'
SessionRecorder.init({
/* ... your config ... */
})
const container = document.getElementById('root')!
const root = ReactDOM.createRoot(container, {
// React 19: thrown and not caught by an Error Boundary
onUncaughtError(error, errorInfo) {
SessionRecorder.captureException(error, { componentStack: errorInfo?.componentStack })
},
// React 19: caught by an Error Boundary
onCaughtError(error, errorInfo) {
SessionRecorder.captureException(error, { componentStack: errorInfo?.componentStack })
},
// React 18/19: recoverable runtime errors
onRecoverableError(error) {
SessionRecorder.captureException(error)
}
})
root.render(<App />)
Notes:
errorInfo.componentStack).app/providers.tsx) because the browser SDK requires window.SessionRecorderProvider at the top of app/layout.tsx and add the NavigationTracker component inside your root layout so every route change is captured.propagateTraceHeaderCorsUrls so backend traces correlate correctly.Next.js 15.3+ adds client-side instrumentation via src/instrumentation-client.ts, which runs before hydration. Initialize the recorder at top-level and optionally export onRouterTransitionStart for navigation tracking. See the official docs: instrumentation-client.ts.
src/instrumentation-client.ts:import SessionRecorder from '@multiplayer-app/session-recorder-react'
// Initialize as early as possible (before hydration)
try {
SessionRecorder.init({
application: 'my-next-app',
version: '1.0.0',
environment: process.env.NEXT_PUBLIC_ENVIRONMENT ?? 'production',
apiKey: process.env.NEXT_PUBLIC_MULTIPLAYER_API_KEY!,
showWidget: true,
// If your APIs are on different origins, add them so OTLP headers are propagated
// format: string | RegExp | Array
propagateTraceHeaderCorsUrls: [new RegExp('https://api.example.com', 'i')]
})
} catch (error) {
// Keep instrumentation resilient
console.warn('[SessionRecorder] init failed in instrumentation-client:', error)
}
// Optional: Next.js will call this when navigation begins
export function onRouterTransitionStart(url: string, navigationType: 'push' | 'replace' | 'traverse') {
try {
SessionRecorder.navigation.record({
path: url || '/',
navigationType,
framework: 'nextjs',
source: 'instrumentation-client'
})
} catch (error) {
console.warn('[SessionRecorder] navigation record failed:', error)
}
}
Notes:
NEXT_PUBLIC_ environment variables for values needed on the client (e.g. NEXT_PUBLIC_MULTIPLAYER_API_KEY).instrumentation-client.ts ensures initialization happens before your UI mounts; the provider is still required to wire React context and hooks.onRouterTransitionStart for navigation tracking in Next.js 15.3+.An official Next.js-specific wrapper is coming soon. Until then, you can use this package safely in Next.js by:
'use client'
import React, { useEffect } from 'react'
import dynamic from 'next/dynamic'
const SessionRecorderProvider = dynamic(
() => import('@multiplayer-app/session-recorder-react').then((m) => m.SessionRecorderProvider),
{ ssr: false }
)
export function Providers({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (typeof window === 'undefined') return
let isMounted = true
const initSessionRecorder = async () => {
try {
const { default: SessionRecorder } = await import('@multiplayer-app/session-recorder-react')
if (!isMounted) return
SessionRecorder.init({
application: 'my-next-app',
version: '1.0.0',
environment: process.env.NEXT_PUBLIC_ENVIRONMENT ?? 'production',
apiKey: process.env.NEXT_PUBLIC_MULTIPLAYER_API_KEY!,
showWidget: true,
// If your APIs are on different origins, add them so OTLP headers are propagated
// format: string | RegExp | Array
propagateTraceHeaderCorsUrls: [new RegExp('https://api.example.com', 'i')]
})
} catch (error) {
console.error('Failed to initialize session recorder', error)
}
}
initSessionRecorder()
return () => {
isMounted = false
}
}, [])
return <SessionRecorderProvider>{children}</SessionRecorderProvider>
}
src/app/layout.tsximport React from 'react'
import dynamic from 'next/dynamic'
// Render provider client-only as a belt-and-suspenders against SSR
const Providers = dynamic(() => import('./providers').then((m) => m.Providers), { ssr: false })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import { useNavigationRecorder } from '@multiplayer-app/session-recorder-react'
export function NavigationTracker() {
const pathname = usePathname()
const searchParams = useSearchParams()
// Convert search params to an object for richer metadata
const params = Object.fromEntries(searchParams?.entries?.() ?? [])
// Hook records whenever pathname changes (query changes included via params)
useNavigationRecorder(pathname || '/', {
params,
framework: 'nextjs',
source: 'next/navigation'
})
return null
}
'use client'
import { useRouter } from 'next/router'
import { useNavigationRecorder } from '@multiplayer-app/session-recorder-react'
export function NavigationTrackerLegacy() {
const { asPath, query } = useRouter()
const pathname = asPath.split('?')[0]
useNavigationRecorder(pathname, {
params: query,
framework: 'nextjs',
source: 'next/router'
})
return null
}
When using this package in Next.js App Router, ensure any code that uses Session Recorder hooks or APIs runs in a Client Component.
useSessionRecorder, useSessionRecordingState, useIsInitialized, and useNavigationRecorder must be called from files that start with 'use client'.SessionRecorder.* that touches the browser SDK must also run on the client.SessionRecorderProvider from a Client Component.'use client'
import React from 'react'
import { useSessionRecordingState, SessionState } from '@multiplayer-app/session-recorder-react'
export default function SessionStatus() {
const state = useSessionRecordingState()
return <span>Session state: {state ?? SessionState.stopped}</span>
}
All hooks and helpers ship with TypeScript types. To extend the navigation metadata, annotate the params or metadata properties in your own app code. The package re-exports all relevant browser SDK types for convenience.
inlineImages or inlineStylesheet: See Inlining images and stylesheets (CORS) under Configuration reference. Defaults are off so you usually do not need to change CDN or bucket CORS unless you opt in.SessionRecorder.init runs only once and before your app mounts.document or window usage behind typeof window !== 'undefined' checks (the helper hooks already do this).Distributed under the MIT License.
FAQs
Multiplayer Fullstack Session Recorder for React (browser wrapper)
The npm package @multiplayer-app/session-recorder-react receives a total of 271 weekly downloads. As such, @multiplayer-app/session-recorder-react popularity was classified as not popular.
We found that @multiplayer-app/session-recorder-react 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.