@syncropel/error-reporter
v0.1.2
Published
Browser error capture for Syncropel apps. Pluggable transports (Mock / Console / HTTP), privacy-aware sanitization, per-session rate limiting, fingerprint deduplication.
Downloads
299
Maintainers
Readme
@syncropel/error-reporter
Browser error capture for Syncropel apps. Reports errors to a pluggable transport (Mock for tests, Console for dev, HTTP for production) with privacy-aware sanitization, per-session rate limiting, and fingerprint deduplication built in.
Why
Most error trackers add a third-party SaaS dependency, ship your data to vendor servers, and force you to learn one more dashboard. This reporter ships errors as structured records to whatever endpoint you choose — your own backend, a debug log, or an in-memory buffer for tests. Same query surface as the rest of your app data; no separate vendor.
Install
npm install @syncropel/error-reporter
# Peer dependencies:
npm install react @syncropel/config| Peer | Version | Why |
|---|---|---|
| react | >=18 | Used internally for the optional React error boundary helpers |
| @syncropel/config | ^0.10.0 | Provides the canonical schemas the report bodies pair with |
Quick start
import { Reporter, ConsoleTransport } from '@syncropel/error-reporter';
const reporter = new Reporter({
transport: new ConsoleTransport(),
release: process.env.NEXT_PUBLIC_RELEASE_VERSION ?? 'dev',
enabled: () => process.env.NODE_ENV !== 'test',
maxSessionEmits: 100, // cap per session (default 100)
fingerprintCooldownMs: 60_000, // dedupe window (default 60s)
});
// Wire to window error events
window.addEventListener('error', (event) => {
reporter.captureWindowError(event);
});
window.addEventListener('unhandledrejection', (event) => {
reporter.captureUnhandledRejection(event);
});
// Or call directly from your error boundary / route error / fetch-failure path
reporter.report(error, {
source: 'react-boundary',
route: '/dashboard',
component: 'WorkspaceRenderer',
});Transports
Three reference implementations ship in-tree. All implement the same Transport interface:
interface Transport {
send(body: ErrorBody): Promise<void>;
}MockTransport — in-memory buffer. Use in tests. Records are inspectable via transport.records.
ConsoleTransport — console.error() with formatted output. Use in dev.
HttpTransport — POST to a configured URL with the report body as JSON. Use in production. Constructor accepts { url, headers?, beforeSend? }.
import { HttpTransport } from '@syncropel/error-reporter';
const reporter = new Reporter({
transport: new HttpTransport({
url: '/api/observability/errors',
headers: { Authorization: `Bearer ${token}` },
beforeSend: (body) => {
// Hook for last-mile sanitization. Return null to drop.
return body;
},
}),
release: '...',
});API surface
import {
// Main orchestrator
Reporter,
// Transports
Transport, // interface
MockTransport,
ConsoleTransport,
HttpTransport,
// Pure helpers
fingerprint, // sync hash from name+message+top-frame
fingerprintAsync, // SHA-256 fingerprint (preferred)
normalizeMessage,
sanitizeMessage,
sanitizeStack,
sanitizeFunctionName,
sanitizeFilePath,
collectRuntime,
parseUserAgent,
getSessionId,
isOptedOut,
OPT_OUT_KEY,
SESSION_KEY,
// Types
ErrorBody, // the report body shape
ResolvedBody, // the deploy-time resolution body shape
ReporterConfig,
ReportContext,
Severity,
CaptureSource,
StackFrame,
Runtime,
} from '@syncropel/error-reporter';Sub-path imports
import { ConsoleTransport } from '@syncropel/error-reporter/transports/console';
import { HttpTransport } from '@syncropel/error-reporter/transports/http';
import { MockTransport } from '@syncropel/error-reporter/transports/mock';Configuration
interface ReporterConfig {
transport: Transport; // required
release: string; // required; e.g. "1.2.3" or "git-sha"
enabled?: () => boolean; // default: always enabled
maxSessionEmits?: number; // default: 100
fingerprintCooldownMs?: number; // default: 60_000 (1 minute)
}enabled— re-evaluated on everyreport()call. Use to gate by environment, user opt-out, feature flag, etc.maxSessionEmits— caps emissions per session (sessionStorage-backed). Once exceeded,suppressed_in_windowincrements and emits stop. Prevents one user with a broken page from flooding your transport.fingerprintCooldownMs— same fingerprint within window is suppressed. Each emit carriessuppressed_in_windowcount of how many were dropped during dedupe.
Privacy + sanitization
- Stack frames are sanitized: function names + file paths only; no inline closures, no source maps applied client-side
sanitizeMessagestrips email-like patterns and anybearer ...-style tokensruntime.viewport.{w,h}is rounded to nearest 50px to avoid fingerprinting precision- Users can opt out via
localStorage.setItem('@syncropel/error-reporter:opt-out', '1')— theReporterchecksisOptedOut()and short-circuits
Report body shape
interface ErrorBody {
kind: 'syncropel.error.v1';
fingerprint: string;
severity: 'error' | 'warning' | 'info';
name: string;
message: string;
stack: StackFrame[];
source: CaptureSource;
component?: string;
route: string;
runtime: Runtime;
release: string;
session_id: string;
suppressed_in_window?: number;
}A companion ResolvedBody shape closes an error thread when a deploy ships the fix:
interface ResolvedBody {
kind: 'syncropel.error.resolved.v1';
fingerprint: string;
fixed_in_release: string;
note?: string;
resolver: string; // DID of whoever resolved it
}License
Apache-2.0. See LICENSE.
Contributing
Source lives at syncropic/syncropel-web/packages/error-reporter. Issues + PRs welcome.
