Introduction
Browsonic SDK is a lightweight, zero-dependency
JavaScript library that automatically captures frontend errors and
anomalies in real time. It is published on the public npm registry
as @browsonic/sdk under the Apache 2.0 licence.
What it captures
console.error, warn, infowindow.onerror eventsKey features
| Feature | Description |
|---|---|
| Zero dependencies | No external libraries required. |
| Automatic collection | Intercepts console, errors, and fetch automatically. |
| Smart batching | Groups events and flushes at a configurable interval. |
| Deduplication | Prevents duplicates via fingerprinting. |
| Offline support | Persists the queue to localStorage when offline. |
| Privacy first | Automatic redaction of sensitive keys. |
| Circuit breaker | Auto-disables on repeated internal errors. |
Installation
Install with your preferred package manager.
npm
npm install @browsonic/sdkpnpm
pnpm add @browsonic/sdkyarn
yarn add @browsonic/sdkQuick Start
Get up and running in under two minutes.
Basic setup
import { Browsonic } from '@browsonic/sdk';
const sdk = new Browsonic();sdk.init({ apiEndpoint: 'https://api.browsonic.com', appKey: 'your-app-key', apiKey: 'pk_live_…', // mint in dashboard (required when trackPageViews is on, which is the default)});
// That's it. Errors are now captured automatically.React integration
import React from 'react';import ReactDOM from 'react-dom/client';import { Browsonic } from '@browsonic/sdk';import App from './App';
const sdk = new Browsonic();sdk.init({ apiEndpoint: import.meta.env.VITE_BROWSONIC_API, appKey: import.meta.env.VITE_BROWSONIC_APP_KEY, apiKey: import.meta.env.VITE_BROWSONIC_API_KEY, environment: import.meta.env.MODE, clientVersion: import.meta.env.VITE_GIT_SHA, debug: import.meta.env.DEV,});
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode>);
Framework-specific adapters (@browsonic/react, @browsonic/vue, @browsonic/svelte, @browsonic/angular, @browsonic/nextjs, @browsonic/astro, @browsonic/remix) add
router instrumentation and navigation breadcrumbs on top of the
core SDK. See the
integration guide
for framework-specific wiring.
Configuration
Every option accepted by init(), grouped by purpose.
Required
| Parameter | Type | Description |
|---|---|---|
apiEndpoint | string | Base URL of your Browsonic API. |
appKey | string | Application identifier for multi-tenant routing. |
apiKey | string | Tenant API key — sent as X-API-KEY. Required when trackPageViews is on (the default). |
Environment & release
| Parameter | Type | Default | Description |
|---|---|---|---|
environment | string | "production" | Environment name. |
clientVersion | string | null | null | Client version tag (surfaced as Versions in the dashboard). |
debug | boolean | false | Verbose console logging. |
Batching & transport
| Parameter | Type | Default | Description |
|---|---|---|---|
flushIntervalMs | number | 10000 | Interval between batch sends (ms). |
maxBatchSize | number | 25 | Maximum events per batch. |
maxPayloadBytes | number | 51200 | Maximum batch payload size (bytes; sized to stay under the 64 KB sendBeacon ceiling). |
Event capture
| Parameter | Type | Default | Description |
|---|---|---|---|
captureLevels | EventLevel[] | ["error"] | Console levels to capture. |
cooldownMs | number | 60000 | Dedup time window (ms). |
Queue & persistence
| Parameter | Type | Default | Description |
|---|---|---|---|
maxQueueSize | number | 200 | Maximum events held in memory. |
persistQueue | boolean | false | Save queue to localStorage across reloads. |
Ignore rules
| Parameter | Type | Default | Description |
|---|---|---|---|
ignoreExtensions | boolean | true | Auto-ignore browser extension errors. |
ignoreScriptErrors | boolean | true | Ignore cross-origin "Script error" messages. |
ignorePatterns | string[] | [] | Stack trace patterns to ignore. |
ignoreMessages | string[] | [] | Error message patterns to ignore. |
ignoreUrls | string[] | [] | URL patterns to ignore errors from. |
Complete example
import { COMMON_THIRD_PARTY_PATTERNS } from '@browsonic/sdk';
sdk.init({ // Required apiEndpoint: 'https://api.browsonic.com', appKey: 'my-production-app', apiKey: 'pk_live_…',
// Environment environment: 'production', clientVersion: 'v2.1.0', debug: false,
// Batching flushIntervalMs: 10000, maxBatchSize: 25,
// Deduplication cooldownMs: 60000,
// Capture captureLevels: ['warn', 'error'],
// Queue maxQueueSize: 300, persistQueue: true,
// Privacy redactKeys: ['token', 'password', 'secret'],
// Ignore Rules (filter noise) ignoreExtensions: true, ignoreScriptErrors: true, ignorePatterns: COMMON_THIRD_PARTY_PATTERNS, ignoreMessages: ['ResizeObserver loop'],});API Reference
All methods available on the SDK instance.
captureMessage(message, level?)
Capture a message manually. level defaults to info.
sdk.captureMessage('User completed checkout', 'info');sdk.captureMessage('Payment failed', 'error');captureError(error)
Capture an Error instance manually.
try { await riskyOperation();} catch (error) { sdk.captureError(error as Error);}setUser(user) · clearUser()
Attach a user context to all subsequent events. Sensitive keys (token / password / secret / auth) are redacted before transport.
sdk.setUser({ id: 'user-123', email: 'user@example.com', plan: 'premium',});
// On logoutsdk.clearUser();addMetadata(key, value) · removeMetadata(key) · clearMetadata()
Attach custom key/value pairs to every subsequent event.
sdk.addMetadata('feature', 'checkout');sdk.addMetadata('cartValue', 99.99);sdk.addMetadata('isNewUser', true);
sdk.removeMetadata('feature');sdk.clearMetadata();Lifecycle: flush(), pause(), resume(), destroy()
| Method | Description |
|---|---|
flush() | Force immediate flush of queued events. Returns a Promise. |
pause() / resume() | Pause and resume event collection without losing the in-memory queue. |
destroy() | Destroy the SDK and release all listeners and timers. |
getState() | Returns 'uninitialized' | 'initializing' | 'running' | 'paused' | 'destroyed'. |
getPendingCount() | Number of events currently queued for transport. |
Event Types
Built-in event types captured by the SDK.
| Type | Level | Source | Description |
|---|---|---|---|
console_debug | info | console.debug() | Debug log (verb preserved on telemetry). |
console_info | info | console.info() | Informational log. |
console_warn | warn | console.warn() | Warning log. |
console_error | error | console.error() | Error log. |
error | error | window.onerror | Unhandled exception. |
fatal | fatal | manual | Capture-on-purpose fatal report. |
unhandledrejection | error | Promise rejection | Unhandled promise rejection. |
network_error | warn / error | fetch() / XHR | HTTP 4xx / 5xx response. |
Wire format
interface BrowsonicEvent { eventId: string; // Unique UUID timestamp: string; // ISO 8601 type: EventType; level: EventLevel; message: string; stack?: string | null; context: EventContext; // url, referrer, pageAge telemetry?: Timeline; // events leading up to error metadata?: MetadataEntry[];}
interface EventBatch { batchId: string; timestamp: string; appKey: string; environment: string; clientVersion?: string; sessionId: string; sessionContext: SessionContext; user?: UserContext; events: BrowsonicEvent[];}Telemetry Timeline
Chronological events leading up to each error — the debugging context that turns a stack trace into a story.
Categories
console.log, warn, error calls.How it works
- The SDK collects events in a ring buffer (FIFO, configurable size).
- When an error fires, the last N events ship with the error payload.
- The dashboard renders the timeline in the Timeline tab of each event.
- It answers the only question that matters: what happened right before this error?
Configuration
sdk.init({ apiEndpoint: 'https://api.browsonic.com', appKey: 'my-app', apiKey: 'pk_live_…',
// Telemetry Timeline maxTelemetryEntries: 50, // ring buffer size (default 20) includeTelemetry: true, // attach with errors (default true)
// Network telemetry captureXHR: true, networkTelemetry: true,
// Navigation trackNavigation: true,
// Visitor (see Visitor Tracking) trackVisitor: false, // OFF by default for privacy});Visitor Tracking
Track user interactions (clicks, inputs) in a privacy-safe manner.
Visitor tracking is off by default. When you enable it, actual input values are never stored — only patterns and lengths. Password fields are skipped entirely.
What is collected
| Data | Example | Purpose |
|---|---|---|
| Element tag | button, input | Identify element type. |
| Element text | "Submit Order" | Human-readable identification. |
| Element ID | submit-btn | Identify specific element. |
| CSS classes | btn primary | Debugging context. |
| Input type | email, text | Understand input purpose. |
| Value length | 16 | Know input size without content. |
| Value pattern | email, numeric | Understand input format. |
What is never collected
- Actual input values (e.g.
"john@example.com"). - Password field content — completely skipped.
- Credit card numbers.
- Any typed text content.
Configuration
sdk.init({ trackVisitor: true, visitor: { click: true, // track click events input: true, // track input events inputThrottleMs: 500, // throttle (default 500 ms) },});Session Replay
Reconstruct what the user saw and did in the moments around an error — a DOM-level recording that plays back next to the stack trace. Session Replay is an opt-in plugin; it is not part of the default SDK.
Session Replay is a Professional-tier feature. On lower plans
the plugin registers cleanly but the server withholds the
recording entitlement, so nothing is captured. The effective
sample rate is min(sampleRate × 100, plan ceiling %).
Setup
Import the plugin from the @browsonic/sdk/replay
sub-path and register it before init(),
exactly like any other plugin.
import { getBrowsonic } from '@browsonic/sdk';import { sessionReplayPlugin } from '@browsonic/sdk/replay';
const sdk = getBrowsonic();
// Register the plugin BEFORE init().sdk.register(sessionReplayPlugin());
sdk.init({ apiEndpoint: 'https://api.browsonic.com', appKey: 'web', apiKey: 'pk_live_…', clientVersion: '1.0.0',});Masking defaults
Recording is privacy-first by default — matching Sentry parity. You have to loosen masking deliberately; you never have to remember to turn it on.
| Option | Default | Effect |
|---|---|---|
maskAllText | true | Every text node is masked before it leaves the browser. |
maskAllInputs | true | Input values are masked. Password inputs stay masked even when this is false. |
blockAllMedia | true | Images, video and <canvas> render as sized placeholders. |
sampleRate | 1 | Host-requested rate (0–1); clamped by the plan ceiling. |
sdk.register(sessionReplayPlugin({ maskAllText: true, // default — mask every text node (Sentry parity) maskAllInputs: true, // default — password inputs stay masked even if false blockAllMedia: true, // default — images / video / canvas become placeholders blockSelector: '.pii', // extra elements to block, rendered as a sized box}));The active sample ceiling is controlled from the dashboard under Settings → Session Replay — there is no server-bypassing replay switch in the browser.
Web Vitals
Report Core Web Vitals from real sessions. Like Session Replay,
it is an opt-in plugin: the collector is a small
native PerformanceObserver, but it changes what the SDK
reports, so you turn it on consciously.
Setup
webVitalsPlugin is exported from the main
@browsonic/sdk entry. Register it before
init().
import { getBrowsonic, webVitalsPlugin } from '@browsonic/sdk';
const sdk = getBrowsonic();
// Register BEFORE init(). Not in the default plugin set —// you opt in explicitly.sdk.register(webVitalsPlugin());
sdk.init({ apiEndpoint: 'https://api.browsonic.com', appKey: 'web', apiKey: 'pk_live_…', clientVersion: '1.0.0',});What it collects
| Metric | Name | Measures |
|---|---|---|
LCP | Largest Contentful Paint | Loading — when the largest element renders. |
INP | Interaction to Next Paint | Responsiveness — latency of user interactions. |
CLS | Cumulative Layout Shift | Visual stability — unexpected layout movement. |
Samples surface in the dashboard on the Performance page.
A per-session cap (maxSamples, default 30)
guards against runaway observers in long-lived SPAs.
Source Maps
Ship minified bundles and still read original TypeScript in every
stack frame. @browsonic/build-tools provides bundler
plugins for Vite, Webpack, Rollup and esbuild that
emit and upload source maps to Browsonic at build time.
Symbolication of uploaded maps is a Professional-tier feature. The upload runs on any plan; symbolicated frames appear once the tenant is entitled.
The release you upload maps under has to match the
clientVersion you pass to init()
byte-for-byte — that string is the only key the service uses to
pair a runtime error with its map. If they drift, frames stay
minified.
Vite plugin
Emit hidden source maps (generated, uploaded, but
not referenced from the shipped bundle) and add the plugin. Webpack,
Rollup and esbuild use the matching sub-path
(@browsonic/build-tools/webpack, /rollup,
/esbuild).
import { defineConfig } from 'vite';import { browsonicSourceMaps } from '@browsonic/build-tools/vite';
export default defineConfig({ // Emit hidden maps: symbolication works, but the map URL is // NOT referenced from the shipped bundle. build: { sourcemap: 'hidden' }, plugins: [ browsonicSourceMaps({ appKey: 'web', release: '1.0.0', // MUST equal the SDK's clientVersion // token: process.env.BROWSONIC_SOURCEMAP_TOKEN (default) baseUrl: 'https://api.browsonic.com', }), ],});Upload token
Uploads authenticate with a dedicated source-map token. It is a
build-time secret — read from
BROWSONIC_SOURCEMAP_TOKEN when the
token option is omitted. Never inline it into browser
code. If the token is missing, the plugin prints a warning and skips
the upload rather than failing the build.
# Build-time secret — mint under Settings → Source Maps in the# dashboard. Never expose this in the browser bundle.export BROWSONIC_SOURCEMAP_TOKEN=bsm_…Privacy & Security
How Browsonic handles sensitive data and what compliance posture you inherit by default.
Data collection summary
| Data type | Collected | How it is stored |
|---|---|---|
| Error messages | ✅ Yes | Full text. |
| Stack traces | ✅ Yes | Full text. |
| Console logs | ✅ Yes | Full text (telemetry). |
| Network URLs | ✅ Yes | Full URL — no body / headers. |
| User clicks | ⚠️ Optional | Element info only. |
| User inputs | ⚠️ Optional | Pattern + length only. |
| Input values | ❌ Never | N/A. |
| Passwords | ❌ Never | Completely skipped. |
Privacy-safe defaults
| Setting | Default | Description |
|---|---|---|
trackVisitor | false | Visitor tracking off by default. |
trackNavigation | true | URL changes only, no user data. |
networkTelemetry | true | URLs only — no request / response bodies. |
captureAsyncStack | false | Performance opt-in feature. |
Automatic redaction
These keys are automatically redacted in
localStorage, sessionStorage, cookies, and
user context:
const DEFAULT_REDACT_KEYS = [ 'token', 'password', 'authorization', 'secret', 'key', 'credential', 'auth',];
sdk.init({ redactKeys: [ ...DEFAULT_REDACT_KEYS, 'ssn', 'creditCard', 'bankAccount', ], redactCookieNames: ['session_id', 'csrf_token'],});Pattern-based redaction
In addition to key-based redaction, the SDK scans free-text values
(error messages, telemetry payloads, captured network metadata)
and replaces matches with [REDACTED]:
| Pattern | What it catches |
|---|---|
name@host.tld tokens anywhere in a value. | |
| JWT | eyJ… three-segment Base64 tokens. |
| Credit card | 12–19 digit runs (with optional spaces / hyphens). |
| Opaque secret | 32-char-or-longer continuous [A-Za-z0-9_-] tokens. |
HTTP header filtering
Network telemetry captures only headers on an explicit allowlist — everything else is dropped. A hard blocklist (authorization, cookie, set-cookie, token, api-key, password, …) is dropped even if a user tries to add it to the allowlist. Header values are also passed through pattern redaction as defense in depth.
Compliance
For the full security policy, see SECURITY.md on GitHub.
Troubleshooting
Common issues and how to resolve them.
SDK not capturing events
- Check initialization —
sdk.getState()should return"running". - Enable debug mode:
sdk.updateConfig({ debug: true }). - Verify
captureLevelsincludes the event type you expect.
Events not reaching the server
- Check pending count —
sdk.getPendingCount(). - Force flush —
await sdk.flush(). - Verify
apiEndpointis correct and CORS is configured. - Check the Network tab for 4xx / 5xx responses.
High event volume
- Increase cooldown —
cooldownMs: 300000(5 minutes). - Reduce capture levels —
captureLevels: ['error']. - Increase flush interval —
flushIntervalMs: 60000.