# @usereelay/browser

> Reelay SDK for browsers: error capture, breadcrumbs, fetch trace stitching, masked DOM session replay.

Latest version **0.0.1** (published 2026-10-04) · MIT license · 0 weekly downloads

## Install

```sh
npm install @usereelay/browser
pnpm add @usereelay/browser
yarn add @usereelay/browser
bun add @usereelay/browser
```

Provides the command `reelay-sourcemaps`.

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.0.1 |
| Published | 2026-10-04 |
| First published | 2026-10-04 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 4 |
| Unpacked size | 1.8 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | emekarr |

## Links

- npm: https://www.npmjs.com/package/@usereelay/browser
- Repository: https://github.com/OpenResearchGuys/reelay-sdk-browser
- Homepage: https://github.com/OpenResearchGuys/reelay-sdk-browser#readme
- Issues: https://github.com/OpenResearchGuys/reelay-sdk-browser/issues
- npm.io page: https://npm.io/package/@usereelay/browser

## Dependencies (4)

- [rrweb](https://npm.io/package/rrweb.md) ^2.0.1
- [web-vitals](https://npm.io/package/web-vitals.md) ^5.3.0
- [boomerangjs](https://npm.io/package/boomerangjs.md) ^1.815.1
- [@rrweb/packer](https://npm.io/package/@rrweb/packer.md) ^2.0.1

## Recent versions

- 0.0.1 (latest) — 2026-10-04

## README

# @usereelay/browser

Reelay's browser SDK: error monitoring, breadcrumbs, session replay, and
frontend-to-backend trace stitching for web applications.

- **No peer setup.** Production dependencies are bundled with the SDK:
  transport, Google Web Vitals, Akamai Boomerang, and DOM session replay.
- **Zero host disruption.** Every listener and public API runs inside a
  defensive boundary. SDK failures go to the `debug` hook, never your app.
- **PII never leaves the page.** Secrets are redacted client-side, before any
  payload is sent.

---

## Install

```bash
npm install @usereelay/browser
```

Or use it directly without a bundler:

```html
<script type="module">
  import * as Reelay from '/node_modules/@usereelay/browser/dist/index.js';
  Reelay.init({ endpoint: 'https://ingest.customer.example', nodeId: 'node_...', ingestKey: 'rk_...' });
</script>
```

---

## Quick start

Call `init()` once, before your app boots, so errors thrown during
initialisation are captured too.

```ts
import * as Reelay from '@usereelay/browser';

Reelay.init({
  endpoint: 'https://ingest.customer.example',
  nodeId: 'node_…',
  ingestKey: 'rk_…',
});
```

After `init()`:

- Uncaught errors and unhandled promise rejections are automatically captured.
- Clicks, console errors, and fetch requests are recorded as breadcrumbs.
- A masked DOM session replay is recorded and shipped on error.
- Outgoing fetch requests to same-origin URLs get `traceparent` headers so
  backend errors link back to this session.

### Verify your setup

Throw a test error to confirm everything is wired correctly:

```ts
<button onclick="throw new Error('test error')">Test Reelay</button>
```

The error should appear in your Reelay dashboard within seconds.

---

## Automatic instrumentation

After `init()`, the SDK instruments the browser automatically; no further
calls are needed.

### Global errors

`window` `error` and `unhandledrejection` events are captured via
`addEventListener` (never by overwriting `window.onerror`). Other error
monitoring tools on the page continue to work.

<details>
<summary>Error names and stack traces are parsed client-side.</summary>
The SDK parses V8 (Chrome/Edge/Node), SpiderMonkey (Firefox), and JavaScriptCore
(Safari) stack traces. Frames from `node_modules`, vendor scripts, and browser
internal paths are marked `in_app: false` so the dashboard groups errors by your
application code only.

Symbolication requires source maps; see [Source maps](#source-maps) below.
</details>

### Breadcrumbs

Three types of breadcrumbs are recorded automatically:

| Source | What's captured | Example |
|---|---|---|
| **Clicks** | Element tag + ID + up to 2 classes (no text content) | `button.add-to-cart` |
| **Console** | `console.error` and `console.warn` messages (capped at 300 chars) | `Error: something broke` |
| **Fetch** | HTTP method, URL, and response status | `POST /api/checkout → 500` |

Breadcrumbs are stored in a ring buffer of the latest 50 entries. They are
attached to every error event sent to Reelay.

### Session replay

A masked DOM session replay is recorded automatically. It uses [rrweb](https://www.rrweb.io/)
under the hood and operates in **error-buffered mode**: the last 30–60 seconds
of session activity are kept in memory and shipped only when an error is
captured. No replay is sent for pages that never error.

Privacy defaults:

- All `<input>` values are masked (set to `*` before serialization).
- All text content is masked when `maskAllText` (default `true`) is enabled.
- CSS, fonts, and images are inlined so replays render independently of your
  live site.

Replay data is sent to `{endpoint}/api/ingest/sessions` alongside the error
event. The dashboard reconstructs the clip using a shared session identifier,
so each incident gets its own replay video.

> **Note:** Session replay can be disabled with `replay: false`. Text masking
> can be disabled with `maskAllText: false` (not recommended for PII safety).

### Trace stitching

When your frontend makes a `fetch` request to a server instrumented with
`@usereelay/node`, the browser SDK automatically adds two headers:

- `traceparent`: the W3C trace context header, so the backend error shares
  the same `trace_id` as the frontend session.
- `reelay-session-id`: the browser session identifier, so the backend error
  links to the frontend replay.

By default, these headers are only sent to same-origin URLs. You can allow-list
additional origins with `tracePropagationTargets`:

```ts
Reelay.init({
  endpoint: 'https://ingest.customer.example',
  nodeId: 'node_…',
  ingestKey: 'rk_…',
  tracePropagationTargets: [
    'https://api.acme.com',
    /^https:\/\/.*\.internal\.acme\.com/,
  ],
});
```

---

## Performance & Core Web Vitals

Set `tracesSampleRate` above `0` to record a share of page views for
performance. Collection is automatic; there is no manual span API in the
browser. For the sampled share, the SDK records:

- **Page-load and navigation transactions**: how long pages take to load and
  route, shown on the dashboard's Performance page.
- **Core Web Vitals**: LCP, INP, CLS, FCP, and TTFB, shown on the Web Vitals
  tab.

```ts
Reelay.init({
  endpoint: 'https://ingest.customer.example',
  nodeId: 'node_…',
  ingestKey: 'rk_…',
  tracesSampleRate: 0.2, // record 20% of page views
});
```

Core Web Vitals are measured with Google's maintained `web-vitals` library,
using the stable `web-vitals/attribution` build. Alongside each metric, Reelay
records Google's diagnostic breakdown (for example LCP load/render phases,
INP input/processing/presentation phases and longest script, and CLS target).

Akamai Boomerang is enabled for the same sampled page views and adds mature
browser diagnostics beyond Web Vitals: navigation and resource waterfalls,
XHR timing, long-task/continuity, paint/event timing, memory/mobile,
compression, and back-forward-cache behavior. Reelay deliberately does not
load Boomerang's Errors, History/SPA, or fetch monitoring because the SDK
already instruments those paths. Set `browserPerformance: false` to retain
Google Web Vitals while disabling the additional Boomerang diagnostics.

Vitals are collected passively as the page runs and flushed with the page-load
transaction, so a single `init()` is all that's needed.

### Release identity and exact commit attribution

Browsers cannot read a server's runtime environment, so the frontend build
must embed its release and, when available, the full source SHA. For Next.js:

```bash
NEXT_PUBLIC_REELAY_RELEASE="web@2.4.0" \
NEXT_PUBLIC_REELAY_COMMIT_SHA="<full-sha>" \
npm run build
```

```ts
Reelay.init({
  endpoint,
  token,
  release: process.env.NEXT_PUBLIC_REELAY_RELEASE,
  commitSha: process.env.NEXT_PUBLIC_REELAY_COMMIT_SHA,
});
```

The SDK stamps both `release` and `commit_sha` on every error, transaction,
profile, replay, and Boomerang beacon. If `release` is omitted it falls back to
the commit SHA. Both values must be embedded during the browser build; changing
the web server environment afterward cannot rewrite an existing bundle.

A named release still produces useful release history without a commit.
Repository-backed code analysis additionally requires the node to be linked to
its repository and the event to resolve to a full commit. Reelay never guesses
repository HEAD.

Release names are trimmed, limited to 200 characters, and cannot be `.`, `..`,
or contain tabs, newlines, `/`, or `\`. Reusing one release with different
commits is reported as a conflict rather than silently remapped.

### Profiling (flamegraphs)

Set `profiling: true` to capture JS self-profiles that power the flamegraph and
"busiest functions" view. This uses the browser's **JS Self-Profiling API**,
which is **Chromium-only** and requires your server to send the
`Document-Policy: js-profiling` response header on the document. Without both,
profiling is silently inactive: no errors, just no profile data.

---

## Manual error capture

### `captureException(err)`

Report a handled error from a `try/catch` block:

```ts
try {
  riskyOperation();
} catch (err) {
  Reelay.captureException(err);
}
```

Returns the event ID (a string like `evt_...`) or `''` if the event was dropped.

### `captureMessage(message)`

Report a plain string message as an error event (useful for debugging):

```ts
Reelay.captureMessage('Checkout flow completed');
```

A synthetic `Error` is created internally with `name = 'Message'`.

---

## PII scrubbing

The SDK scrubs sensitive data **before** any payload leaves the page. Scrubbing
runs on error messages, breadcrumb messages, HTTP context objects, and timeline
data. It never touches stack trace frames (file paths, line numbers) or event
metadata (timestamps, session IDs).

The scrubbing has two layers:

### Layer 1: Built-in default patterns

The following patterns are always applied to every string value:

- **Bearer/token values**: `bearer: xyz`, `token=abc`, `api_key: xxx`, `password: hunter2`
- **JWTs**: any string matching the `eyJ...` pattern
- **Credit card numbers**: 13–19 digit sequences (with or without separators)
- **Email addresses**: `user@example.com`

These are always active. There is no way to disable them; if there's a false
positive, use `beforeSend` to restore the value on the event object.

### Layer 2: Sensitive keys

When scrubbing an object, any key matching the following list has its value
replaced entirely with `[redacted]`, regardless of what the value looks like:

`authorization`, `cookie`, `set-cookie`, `x-api-key`, `x-reelay-token`,
`password`, `passwd`, `secret`, `token`, `access_token`, `refresh_token`,
`credit_card`, `card_number`, `cvv`, `ssn`

(Match is case-insensitive.)

### Layer 3: User-configured redaction

Every string value in the payload is run through all regex patterns. You can
add custom patterns for your application's secret formats with `scrubPatterns`,
on top of the always-on built-ins:

```ts
// Stripe live keys can appear in log messages or HTTP headers:
Reelay.init({
  endpoint: '...',
  token: '...',
  scrubPatterns: [/sk_live_[A-Za-z0-9]+/g],
});
```

Matching substrings are replaced with `[redacted]`.

### Layer 4: beforeSend

For total control before the event is sent, use `beforeSend`:

```ts
Reelay.init({
  endpoint: '...',
  token: '...',
  beforeSend: (event) => {
    if (event.exception.value.includes('sensitive')) return null; // drop
    return event;
  },
});
```

---

## Configuration reference

### `init(options)`

| Option | Type | Default | Description |
|---|---|---|---|
| `endpoint` | `string` | — | **Required.** Customer-owned HTTPS ingestion URL. |
| `nodeId` | `string` | — | **Required.** Target self-hosted node. |
| `ingestKey` | `string` | — | **Required.** Key validated by Cloud for every event. |
| `token` | `string` | — | **Required.** Node API key (`rlyt_live_...`). Never a ReelayID. |
| `release` | `string` | `commitSha` | Artifact version embedded at build time, e.g. `web@2.4.0`. |
| `commitSha` | `string` | — | Full Git SHA of the frontend artifact. CI should expose it as a public build variable and pass it once to `init`; short/invalid SHAs are ignored. |
| `sampleRate` | `number` (0–1) | `1` | Fraction of **errors** to send. `1` = every error. Lower only to reduce volume on high-traffic apps. |
| `tracesSampleRate` | `number` (0–1) | `0` | Fraction of page views recorded for **performance**: page-load / navigation transactions and Core Web Vitals. `0` = performance monitoring off. Start at `0.1`–`0.2` in production. |
| `browserPerformance` | `boolean` | `true` when tracing | Enable Akamai Boomerang's additional browser diagnostics for sampled page views. |
| `profiling` | `boolean` | `false` | Enables JS self-profiling (flamegraphs). Chromium only, and requires the `Document-Policy: js-profiling` response header; silently inactive otherwise. |
| `beforeSend` | `(event) => event \| null` | — | Mutate or veto (return `null`) an event just before it is sent. Runs after PII scrubbing. |
| `scrubPatterns` | `RegExp[]` | `[]` | Extra regex patterns applied to all string values. Matches are replaced with `[redacted]`. |
| `maxQueueSize` | `number` | `30` | Deprecated compatibility option; bursts remain queued until delivery. |
| `tracePropagationTargets` | `(string \| RegExp)[]` | — | Origins (strings) or URL patterns (regexes) that receive W3C `traceparent` headers. Same-origin only when not set. |
| `replay` | `boolean` | `true` | Enable DOM session replay. Disable if you don't need replay or want to avoid the rrweb dependency. |
| `maskAllText` | `boolean` | `true` | Mask all text content in replay. Disabling exposes page text, only do this on pages without PII. |
| `debug` | `(msg, detail?) => void` | — | Receive SDK-internal diagnostics. Never logs to console by default. |

---

## API reference

### Exported functions

```ts
import * as Reelay from '@usereelay/browser';
```

| Function | Signature | Description |
|---|---|---|
| `init` | `(options: BrowserOptions) => BrowserClient` | Initializes the singleton client and installs all instrumentation. Idempotent; subsequent calls return the existing client. |
| `getClient` | `() => BrowserClient \| undefined` | Returns the active client, or `undefined` if `init()` hasn't been called. |
| `captureException` | `(err: unknown) => string` | Reports an error through the active client. Returns the event ID (`''` if dropped). |
| `captureMessage` | `(message: string) => string` | Reports a string message as an error event. |

### Exported types

```ts
import type { CoreOptions, ErrorEventPayload, Breadcrumb } from '@usereelay/browser';
```

| Type | Description |
|---|---|
| `BrowserOptions` | Extends `CoreOptions` (which includes `tracesSampleRate` and `profiling`) with `tracePropagationTargets`, `replay`, and `maskAllText`. |
| `CoreOptions` | Options shared by all Reelay SDKs (browser and Node). |
| `ErrorEventPayload` | The event object delivered to the ingest API. Useful for type-safe `beforeSend` handlers. |
| `Breadcrumb` | Shape of a breadcrumb object. |

### `BrowserClient` methods

```ts
const client = Reelay.init({ ... });
```

| Method | Description |
|---|---|
| `client.sessionId` | The browser session identifier (`sess_...`), readable at any time. |
| `client.addBreadcrumb(crumb)` | Manually add a breadcrumb. Scrubbed and bounded (latest 50 kept). |
| `client.capture(err, mechanism, trace?)` | Capture an error with an explicit mechanism string and optional trace context. |
| `client.captureMessage(message, ctx?)` | Capture a string as an error, optionally with context. |
| `client.flush()` | Drain the pending event queue. Returns a promise. |
| `client.uninstall()` | Remove all instrumentation and tear down the SDK. |

---

## Source maps

Production frontend bundles are minified, so raw stack frames point at hashed
files like `assets/index-a1b2c3.js:1:54023`. To see original file names, line
numbers, and function names in the Reelay dashboard, you need to upload your
source maps.

The SDK ships a CLI tool for this:

```bash
npx reelay-sourcemaps inject-and-upload ./dist \
  --url https://api.reelay.app \
  --token "$REELAY_TOKEN" \
  --delete
```

How it works:

1. **Inject debug IDs**: stamps each JS chunk with a content-derived debug ID,
   stored in both the chunk (a tiny runtime snippet) and its source map.
2. **Upload**: sends the map to Reelay's ingestion endpoint, keyed by debug ID.
3. **`--delete`**: removes `.map` files after upload so they never reach your CDN.

At capture time, the SDK reads the debug ID registry (the runtime snippet
injected in step 1) and stamps each stack frame with its bundle's debug ID.
The backend resolves the exact map using this ID, regardless of CDN hashing or
release naming.

> **Compatibility:** The SDK also reads `_sentryDebugIds` registries. If you
> already use a Sentry bundler plugin, your existing debug IDs are picked up
> with no extra tooling.

---

## Engineering guarantees

### Zero host disruption

- Every public API and event listener runs inside a `try/catch` boundary.
- SDK failures are silently reported to the `debug` hook, never thrown into
  the host application.
- `fetch` wrapping preserves the original function's exact arguments, return
  type, and `this` binding. If instrumentation fails, the call degrades to a
  transparent passthrough.
- Click listeners are passive (they never block the UI thread) and
  capture-phase (they see every click before any handler's `stopPropagation`).

### Performance

- Session replay serialization runs in `requestIdleCallback`: it never
  competes with first paint or user interaction.
- Breadcrumbs are an O(1) ring buffer with zero allocation on the hot path.
- The transport queue is non-blocking: `send()` queues every event in a burst
  and schedules an async drain. Pending events consume memory while delivery
  falls behind.

### Bounded memory

| Resource | Cap |
|---|---|
| Breadcrumbs | 50 entries (ring buffer) |
| Transport queue | Grows while delivery falls behind; no overflow eviction |
| Replay delta buffer | 2 checkout segments (~60s), 8,000 events each |
| Traced request map | 100 entries |

### Data safety on navigation

When the page becomes hidden (`visibilitychange`), pending events are flushed
using `keepalive` fetch, which survives page unload. Replay chunks are flushed
similarly for payloads under the keepalive budget.

---

## Development

```bash
npm install
npm run build      # tsup: ESM + CJS + type declarations
npm test           # vitest
npm run typecheck  # tsc --noEmit
```

See `../reelay-test-project/frontend` for a runnable test page wired up with
this SDK, including buttons that trigger every class of frontend error.

## Ingestion edge contract

SDK requests use `POST`, `Content-Type: application/json`, `X-Reelay-Token`,
and `X-Reelay-Node-ID`. The backend accepts only the current endpoint schema;
unknown fields and the old `X-Reelay-Ingest-Key` header are rejected. Rebuild
and repack the SDK after source changes so deployed applications use the same
contract as the source.

The production ALB-associated WAF applies one shared 1,000 requests/minute
budget across all customers and routes. Edge throttles return `429` with
`Retry-After: 60`; the transport backs off with a retry queue. This is
AWS's approximate rate protection, not an exact counter or a per-token budget.

---
_Source: https://npm.io/package/@usereelay/browser · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
