npm.io
0.0.1 • Published 2h agoCLI

@usereelay/browser

Licence
MIT
Version
0.0.1
Deps
4
Size
1.8 MB
Vulns
0
Weekly
0

@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

npm install @usereelay/browser

Or use it directly without a bundler:

<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.

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:

<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.

Error names and stack traces are parsed client-side. 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 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 (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:

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.
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:

NEXT_PUBLIC_REELAY_RELEASE="web@2.4.0" \
NEXT_PUBLIC_REELAY_COMMIT_SHA="<full-sha>" \
npm run build
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:

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: 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:

// 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. 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
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 (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
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" \
  --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

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.