@usereelay/browser
v0.1.5
Published
Reelay SDK for browsers: error capture, breadcrumbs, fetch trace stitching, masked DOM session replay.
Readme
@usereelay/browser
Reelay's browser SDK — error monitoring, breadcrumbs, session replay, and frontend-to-backend trace stitching for web applications.
- Zero runtime dependencies. The SDK ships everything it needs: transport, PII scrubbing, stack parsing, trace IDs, and DOM session replay.
- Zero host disruption. Every listener and public API runs inside a
defensive boundary. SDK failures go to the
debughook, never your app. - PII never leaves the page. Secrets are redacted client-side, before any payload is sent.
Install
npm install @usereelay/browserOr use it directly without a bundler:
<script type="module">
import * as Reelay from '/node_modules/@usereelay/browser/dist/index.js';
Reelay.init({ endpoint: '...', token: '...' });
</script>Quick start
Call init() once, as early as possible in your application — before your app
boots, so uncaught errors during initialisation are captured too.
import * as Reelay from '@usereelay/browser';
Reelay.init({
endpoint: 'https://api.usereelay.com',
token: 'rlyt_live_…',
});That's it. 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
traceparentheaders so backend errors link back to this session.
Verify your setup
Throw a test error to confirm everything is wired correctly:
<button onclick="throw new Error('test error')">Test Reelay</button>The error should appear in your Reelay dashboard within seconds.
Automatic instrumentation
Once init() runs, the SDK instruments the browser automatically. You don't
need to call anything else.
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.
Symbolication requires source maps — see Source maps below.
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 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(defaulttrue) 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 withmaskAllText: 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 sametrace_idas 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:
Reelay.init({
endpoint: 'https://api.usereelay.com',
token: 'rlyt_live_…',
tracePropagationTargets: [
'https://api.acme.com',
/^https:\/\/.*\.internal\.acme\.com/,
],
});Manual error capture
captureException(err)
Report a handled error from a try/catch block:
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):
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 —
[email protected]
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
You can redact additional object keys by name:
Reelay.init({
endpoint: '...',
token: '...',
redactedFields: ['lastMeal', 'creditCard', 'internalNote'],
});Every string value in the payload is also run through all regex patterns.
You can add custom patterns with scrubPatterns:
// 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:
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. Ingestion URL, e.g. https://api.usereelay.com. |
| token | string | — | Required. Node API key (rlyt_live_...). Never a ReelayID. |
| sampleRate | number (0–1) | 1 | Fraction of errors to send. 1 = every error. Lower only to reduce volume on high-traffic apps. |
| 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]. |
| redactedFields | string[] | [] | Object key names whose values are replaced with [redacted] (case-sensitive, every nesting level). |
| maxQueueSize | number | 30 | Maximum buffered events while offline or rate-limited. Oldest events are dropped when full. |
| 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
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
import type { CoreOptions, ErrorEventPayload, Breadcrumb } from '@usereelay/browser';| Type | Description |
|---|---|
| BrowserOptions | Extends CoreOptions 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
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:
npx reelay-sourcemaps inject-and-upload ./dist \
--url https://api.reelay.app \
--token "$REELAY_TOKEN" \
--deleteHow it works:
- 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.
- Upload — sends the map to Reelay's ingestion endpoint, keyed by debug ID.
--delete— removes.mapfiles 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
_sentryDebugIdsregistries. 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/catchboundary. - SDK failures are silently reported to the
debughook — never thrown into the host application. fetchwrapping preserves the original function's exact arguments, return type, andthisbinding. 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 bounded and non-blocking:
send()just pushes to an array and schedules an async drain. It never awaits network I/O.
Bounded memory
| Resource | Cap | |---|---| | Breadcrumbs | 50 entries (ring buffer) | | Transport queue | 30 entries (drop oldest) | | 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
npm install
npm run build # tsup — ESM + CJS + type declarations
npm test # vitest
npm run typecheck # tsc --noEmitSee ../reelay-test-project/frontend for a runnable test page wired up with
this SDK, including buttons that trigger every class of frontend error.
