@multiplayer-app/session-recorder-react 1.2.25 → 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
@@ -270,6 +270,74 @@ The options passed to `SessionRecorder.init(...)` are forwarded to the underlyin
270
270
 
271
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.
272
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
+
273
341
  ## Next.js integration tips
274
342
 
275
343
  - Initialize the provider in a Client Component (for example `app/providers.tsx`) because the browser SDK requires `window`.
@@ -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.25",
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.25",
39
- "@multiplayer-app/session-recorder-common": "1.2.25"
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'