@multiplayer-app/session-recorder-react 1.2.24 → 1.2.26

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/README.md CHANGED
@@ -51,18 +51,18 @@ To get full‑stack session recording working, set up one of our backend SDKs/CL
51
51
 
52
52
  ## Quick start
53
53
 
54
- 1. Wrap your application with the `SessionRecorderProvider`.
55
- 2. Pass the same configuration you would supply to the browser SDK.
54
+ 1. Recommended: Call `SessionRecorder.init(options)` before you mount your React app to avoid losing any data.
55
+ 2. Wrap your application with the `SessionRecorderProvider`.
56
56
  3. Start or stop sessions using the widget or the provided hooks.
57
57
 
58
- ### Minimal setup with manual initialization
58
+ ### Minimal setup with manual initialization (Recommended)
59
59
 
60
60
  ```tsx
61
61
  // src/main.tsx or src/index.tsx app root
62
62
  import React from 'react'
63
63
  import ReactDOM from 'react-dom/client'
64
64
  import App from './App'
65
- import { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
65
+ import SessionRecorder, { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
66
66
 
67
67
  const sessionRecorderConfig = {
68
68
  version: '1.0.0',
@@ -77,8 +77,8 @@ const sessionRecorderConfig = {
77
77
  propagateTraceHeaderCorsUrls: [new RegExp('https://api.example.com', 'i')]
78
78
  }
79
79
 
80
- // Initialize the session recorder manually
81
- SessionRecorderProvider.init(sessionRecorderConfig)
80
+ // Initialize the session recorder before mounting (Recommended)
81
+ SessionRecorder.init(sessionRecorderConfig)
82
82
 
83
83
  ReactDOM.createRoot(document.getElementById('root')!).render(
84
84
  <SessionRecorderProvider>
@@ -87,30 +87,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
87
87
  )
88
88
  ```
89
89
 
90
- ### Minimal setup with provider initialization
91
-
92
- ```tsx
93
- // src/main.tsx or src/index.tsx app root
94
- import React from 'react'
95
- import ReactDOM from 'react-dom/client'
96
- import App from './App'
97
- import { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
98
-
99
- const sessionRecorderConfig = {
100
- version: '1.0.0',
101
- environment: 'production',
102
- application: 'my-react-app',
103
- apiKey: 'YOUR_MULTIPLAYER_API_KEY'
104
- }
105
-
106
- ReactDOM.createRoot(document.getElementById('root')!).render(
107
- <SessionRecorderProvider options={sessionRecorderConfig}>
108
- <App />
109
- </SessionRecorderProvider>
110
- )
111
- ```
112
-
113
- Behind the scenes, the provider initializes the shared Browser SDK (if you pass the configuration as options to the provider) — or you can initialize it manually as shown in the example above. It then sets up listeners and exposes helper APIs through React context and selectors.
90
+ Behind the scenes, the provider sets up listeners and exposes helper APIs through React context and selectors.
114
91
 
115
92
  ### Set session attributes to provide context for the session
116
93
 
@@ -140,16 +117,19 @@ const MyComponent = () => {
140
117
  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:
141
118
 
142
119
  ```tsx
143
- // Provider configuration
144
- <SessionRecorderProvider
145
- options={{
146
- application: 'my-react-app',
147
- version: '1.0.0',
148
- environment: 'production',
149
- apiKey: 'YOUR_MULTIPLAYER_API_KEY',
150
- showWidget: false // hide the built-in widget
151
- }}
152
- >
120
+ import SessionRecorder, { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
121
+
122
+ // Initialize without the built‑in widget
123
+ SessionRecorder.init({
124
+ application: 'my-react-app',
125
+ version: '1.0.0',
126
+ environment: 'production',
127
+ apiKey: 'YOUR_MULTIPLAYER_API_KEY',
128
+ showWidget: false // hide the built-in widget
129
+ })
130
+
131
+ // Wrap your app with the provider to enable hooks/context
132
+ <SessionRecorderProvider>
153
133
  <App />
154
134
  </SessionRecorderProvider>
155
135
  ```
@@ -279,7 +259,7 @@ export function NavigationTrackerLegacy() {
279
259
 
280
260
  ## Configuration reference
281
261
 
282
- The `options` prop passed to `SessionRecorderProvider` is forwarded to the underlying browser SDK. Refer to the [browser README](../session-recorder-browser/README.md#initialize) for the full option list, including:
262
+ The options passed to `SessionRecorder.init(...)` are forwarded to the underlying browser SDK. Refer to the [browser README](../session-recorder-browser/README.md#initialize) for the full option list, including:
283
263
 
284
264
  - `application`, `version`, `environment`, `apiKey`
285
265
  - `showWidget`, `showContinuousRecording`
@@ -290,6 +270,74 @@ The `options` prop passed to `SessionRecorderProvider` is forwarded to the under
290
270
 
291
271
  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.
292
272
 
273
+ ## Capturing exceptions in React apps
274
+
275
+ 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.
276
+
277
+ ### Using the built‑in error boundary
278
+
279
+ ```tsx
280
+ import React from 'react'
281
+ import { ErrorBoundary } from '@multiplayer-app/session-recorder-react'
282
+
283
+ export function AppWithBoundary() {
284
+ return (
285
+ <ErrorBoundary fallback={<div>Something went wrong</div>}>
286
+ <App />
287
+ </ErrorBoundary>
288
+ )
289
+ }
290
+ ```
291
+
292
+ The boundary calls `SessionRecorder.captureException(error)` internally and renders the provided `fallback` on error.
293
+
294
+ ### Custom boundary (if you need full control)
295
+
296
+ ```tsx
297
+ import React from 'react'
298
+ import SessionRecorder from '@multiplayer-app/session-recorder-react'
299
+
300
+ class MyErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
301
+ state = { hasError: false }
302
+ static getDerivedStateFromError() {
303
+ return { hasError: true }
304
+ }
305
+ componentDidCatch(error: unknown) {
306
+ SessionRecorder.captureException(error as any)
307
+ }
308
+ render() {
309
+ return this.state.hasError ? <h1>Oops.</h1> : this.props.children
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### React 18/19 root error hooks
315
+
316
+ React surfaces recoverable runtime errors via the root option `onRecoverableError`. You can forward these to the session recorder as well. This works in React 18 and React 19.
317
+
318
+ ```tsx
319
+ import React from 'react'
320
+ import ReactDOM from 'react-dom/client'
321
+ import SessionRecorder from '@multiplayer-app/session-recorder-react'
322
+ import App from './App'
323
+
324
+ SessionRecorder.init({
325
+ /* ... your config ... */
326
+ })
327
+
328
+ ReactDOM.createRoot(document.getElementById('root')!, {
329
+ onRecoverableError(error) {
330
+ SessionRecorder.captureException(error as any)
331
+ }
332
+ }).render(<App />)
333
+ ```
334
+
335
+ Notes:
336
+
337
+ - Uncaught errors and unhandled rejections are captured automatically by the browser SDK.
338
+ - Error Boundary + `onRecoverableError` covers both render and runtime recoverable errors.
339
+ - In Continuous mode, any captured exception marks the trace as ERROR and auto‑saves the rolling session window.
340
+
293
341
  ## Next.js integration tips
294
342
 
295
343
  - Initialize the provider in a Client Component (for example `app/providers.tsx`) because the browser SDK requires `window`.
@@ -300,27 +348,24 @@ Any time `recordNavigation` is enabled, the browser SDK will emit OpenTelemetry
300
348
 
301
349
  An official Next.js-specific wrapper is coming soon. Until then, you can use this package safely in Next.js by:
302
350
 
303
- 1. Initializing in a Client Component
351
+ 1. Initializing in a Client Component (Recommended)
304
352
 
305
353
  ```tsx
306
354
  'use client'
307
355
  import React from 'react'
308
- import { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
356
+ import SessionRecorder, { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react'
357
+
358
+ // Initialize before mount to avoid losing any data
359
+ SessionRecorder.init({
360
+ application: 'my-next-app',
361
+ version: '1.0.0',
362
+ environment: 'production',
363
+ apiKey: 'YOUR_MULTIPLAYER_API_KEY',
364
+ showWidget: true
365
+ })
309
366
 
310
367
  export function Providers({ children }: { children: React.ReactNode }) {
311
- return (
312
- <SessionRecorderProvider
313
- options={{
314
- application: 'my-next-app',
315
- version: '1.0.0',
316
- environment: 'production',
317
- apiKey: 'YOUR_MULTIPLAYER_API_KEY',
318
- showWidget: true
319
- }}
320
- >
321
- {children}
322
- </SessionRecorderProvider>
323
- )
368
+ return <SessionRecorderProvider>{children}</SessionRecorderProvider>
324
369
  }
325
370
  ```
326
371
 
@@ -377,7 +422,7 @@ All hooks and helpers ship with TypeScript types. To extend the navigation metad
377
422
  ## Troubleshooting
378
423
 
379
424
  - Ensure the provider wraps your entire component tree so context hooks resolve.
380
- - Confirm `SessionRecorder.init` runs only once. The provider handles this automatically if you pass the configuration as options to the provider; do not call it manually elsewhere.
425
+ - Confirm `SessionRecorder.init` runs only once and before your app mounts.
381
426
  - Ensure the session recorder required options are passed and the API key is valid.
382
427
  - For SSR environments, guard any direct `document` or `window` usage behind `typeof window !== 'undefined'` checks (the helper hooks already do this).
383
428
 
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export type ErrorBoundaryProps = {
3
+ children: React.ReactNode;
4
+ /** Optional fallback UI to render when an error is caught */
5
+ fallback?: React.ReactNode;
6
+ };
7
+ type State = {
8
+ hasError: boolean;
9
+ };
10
+ export declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, State> {
11
+ constructor(props: ErrorBoundaryProps);
12
+ static getDerivedStateFromError(): State;
13
+ componentDidCatch(error: unknown): void;
14
+ render(): React.ReactNode;
15
+ }
16
+ export default ErrorBoundary;
17
+ //# sourceMappingURL=ErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../src/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,CAAA;AAED,KAAK,KAAK,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAA;AAElC,qBAAa,aAAc,SAAQ,KAAK,CAAC,SAAS,CAAC,kBAAkB,EAAE,KAAK,CAAC;gBAC/D,KAAK,EAAE,kBAAkB;IAKrC,MAAM,CAAC,wBAAwB,IAAI,KAAK;IAIxC,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQvC,MAAM,IAAI,KAAK,CAAC,SAAS;CAM1B;AAED,eAAe,aAAa,CAAA"}
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import SessionRecorder from '@multiplayer-app/session-recorder-browser';
3
+ export class ErrorBoundary extends React.Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = { hasError: false };
7
+ }
8
+ static getDerivedStateFromError() {
9
+ return { hasError: true };
10
+ }
11
+ componentDidCatch(error) {
12
+ try {
13
+ SessionRecorder.captureException(error);
14
+ }
15
+ catch (_e) {
16
+ // no-op
17
+ }
18
+ }
19
+ render() {
20
+ if (this.state.hasError) {
21
+ return this.props.fallback ?? null;
22
+ }
23
+ return this.props.children;
24
+ }
25
+ }
26
+ export default ErrorBoundary;
27
+ //# sourceMappingURL=ErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../src/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,eAAe,MAAM,2CAA2C,CAAA;AAUvE,MAAM,OAAO,aAAc,SAAQ,KAAK,CAAC,SAAoC;IAC3E,YAAY,KAAyB;QACnC,KAAK,CAAC,KAAK,CAAC,CAAA;QACZ,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAClC,CAAC;IAED,MAAM,CAAC,wBAAwB;QAC7B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IAC3B,CAAC;IAED,iBAAiB,CAAC,KAAc;QAC9B,IAAI,CAAC;YACH,eAAe,CAAC,gBAAgB,CAAC,KAAY,CAAC,CAAA;QAChD,CAAC;QAAC,OAAO,EAAE,EAAE,CAAC;YACZ,QAAQ;QACV,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAA;QACpC,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IAC5B,CAAC;CACF;AAED,eAAe,aAAa,CAAA"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from '@multiplayer-app/session-recorder-browser';
2
2
  export { default } from '@multiplayer-app/session-recorder-browser';
3
3
  export * from './context/SessionRecorderContext';
4
4
  export * from './context/useSessionRecorderStore';
5
+ export { ErrorBoundary } from './ErrorBoundary';
5
6
  export { useNavigationRecorder } from './navigation';
6
7
  export type { UseNavigationRecorderOptions } from './navigation';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,2CAA2C,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAEpE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,2CAA2C,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAEpE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAA"}
package/dist/index.js CHANGED
@@ -2,5 +2,6 @@ export * from '@multiplayer-app/session-recorder-browser';
2
2
  export { default } from '@multiplayer-app/session-recorder-browser';
3
3
  export * from './context/SessionRecorderContext';
4
4
  export * from './context/useSessionRecorderStore';
5
+ export { ErrorBoundary } from './ErrorBoundary';
5
6
  export { useNavigationRecorder } from './navigation';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,2CAA2C,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAEpE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,2CAA2C,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAEpE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/session-recorder-react",
3
- "version": "1.2.24",
3
+ "version": "1.2.26",
4
4
  "description": "Multiplayer Fullstack Session Recorder for React (browser wrapper)",
5
5
  "author": {
6
6
  "name": "Multiplayer Software, Inc.",
@@ -35,8 +35,8 @@
35
35
  "@opentelemetry/api": "^1.9.0"
36
36
  },
37
37
  "dependencies": {
38
- "@multiplayer-app/session-recorder-browser": "1.2.24",
39
- "@multiplayer-app/session-recorder-common": "1.2.24"
38
+ "@multiplayer-app/session-recorder-browser": "1.2.26",
39
+ "@multiplayer-app/session-recorder-common": "1.2.26"
40
40
  },
41
41
  "devDependencies": {
42
42
  "eslint": "8.48.0",
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import SessionRecorder from '@multiplayer-app/session-recorder-browser'
3
+
4
+ export type ErrorBoundaryProps = {
5
+ children: React.ReactNode
6
+ /** Optional fallback UI to render when an error is caught */
7
+ fallback?: React.ReactNode
8
+ }
9
+
10
+ type State = { hasError: boolean }
11
+
12
+ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, State> {
13
+ constructor(props: ErrorBoundaryProps) {
14
+ super(props)
15
+ this.state = { hasError: false }
16
+ }
17
+
18
+ static getDerivedStateFromError(): State {
19
+ return { hasError: true }
20
+ }
21
+
22
+ componentDidCatch(error: unknown): void {
23
+ try {
24
+ SessionRecorder.captureException(error as any)
25
+ } catch (_e) {
26
+ // no-op
27
+ }
28
+ }
29
+
30
+ render(): React.ReactNode {
31
+ if (this.state.hasError) {
32
+ return this.props.fallback ?? null
33
+ }
34
+ return this.props.children
35
+ }
36
+ }
37
+
38
+ export default ErrorBoundary
package/src/index.ts CHANGED
@@ -3,6 +3,6 @@ export { default } from '@multiplayer-app/session-recorder-browser';
3
3
 
4
4
  export * from './context/SessionRecorderContext';
5
5
  export * from './context/useSessionRecorderStore';
6
-
6
+ export { ErrorBoundary } from './ErrorBoundary'
7
7
  export { useNavigationRecorder } from './navigation'
8
8
  export type { UseNavigationRecorderOptions } from './navigation'