//#region src/error-tracking/redact-values.d.ts /** * Browser-safe string redaction. Duplicate of autotel's redact-values.ts. * Must NOT import from `autotel` (Node.js package). */ type StringRedactor = (value: string) => string; //#endregion //#region src/error-tracking/types.d.ts interface SuppressionRule { /** Field to match against */ key: 'type' | 'value'; /** Match operator */ operator: 'exact' | 'contains' | 'regex'; /** Value or pattern to match */ value: string; } interface RateLimitConfig { /** Max exceptions per type within the window (default: 10) */ maxPerType: number; /** Time window in milliseconds (default: 10000) */ windowMs: number; } interface ErrorTrackingConfig { /** Rate limit per exception type */ rateLimit?: RateLimitConfig; /** Suppression rules to filter known noise */ suppressionRules?: SuppressionRule[]; /** Capture console.error as exceptions (default: false) */ captureConsoleErrors?: boolean; /** Skip autocapture if window.posthog is detected (default: true) */ deferToPostHog?: boolean; /** * Drop exceptions thrown on a local origin (default: false). * * A dev server reloads on every keystroke and each reload can throw. Those * exceptions group with the production ones — same code, same stack — so a * real regression ends up buried under a day of local typos. Off by default, * because plenty of teams do want their dev errors captured. */ skipLocalhost?: boolean; /** Debug logging */ debug?: boolean; /** String redactor for PII in error messages and stack traces */ redactor?: StringRedactor; } //#endregion //#region src/breadcrumbs.d.ts /** * What the user did just before it broke. * * An exception on its own is a stack and a shrug: it says where the code gave * up, never what the person was doing when it did. The trail leading in is the * half that turns a report into a reproduction, and no tracing convention * carries it — spans record what the code did, and the interesting steps here * are the ones that ran no code at all. * * Bounded in bytes rather than entries, because a breadcrumb holds whatever the * caller put in it and an entry count is no protection against one enormous * one. The newest step is never dropped: it is the one nearest the error. */ interface Breadcrumb { /** What happened, in the words a person would use. */ message: string; /** Coarse grouping, e.g. `ui`, `navigation`, `console`, `fetch`. */ category?: string; /** Anything else worth having. Kept as-is, so keep it small. */ data?: Record; /** Epoch milliseconds. Filled in when the step is recorded. */ timestamp: number; } interface BreadcrumbsConfig { /** Total budget across all kept steps. Default 32KB, as PostHog uses. */ maxBytes?: number; /** Applied to each message before it is stored, for PII. */ redactor?: (text: string) => string; } declare function configureBreadcrumbs(config: BreadcrumbsConfig | false): void; /** Record a step. Never throws: a breadcrumb must not break the thing it describes. */ declare function addBreadcrumb(crumb: Omit & { timestamp?: number; }): void; /** The trail, oldest first. */ declare function readBreadcrumbs(): Breadcrumb[]; interface BreadcrumbCollectors { /** * Capture `console.log/info/warn/error` as steps. * * Deliberately not a second telemetry pipeline: console output is read when * something has already gone wrong, which is exactly when the trail is read, * so it belongs on the error rather than in a log stream of its own. */ console?: boolean; /** Capture clicks as steps. */ clicks?: boolean; } /** * Start recording steps automatically. Returns a teardown that restores * everything it patched. */ declare function collectBreadcrumbs(collectors: BreadcrumbCollectors): () => void; //#endregion //#region src/frustration.d.ts /** * Frustration signals: the clicks that did nothing, and the clicks that came * after. * * A dead click is a bug report nobody filed. It is also the one browser signal * no tracing backend can produce on its own, for a structural reason: a click * that does nothing runs no code, issues no request, and opens no span. The * trace is empty precisely when the user is most stuck, and the absence looks * exactly like a page nobody visited. * * Both detectors are heuristics, and the thresholds below are the interesting * part — they are ported from PostHog's, which have been tuned against real * traffic. Loosening them turns this into a noise generator; a "dead click" * that fires on working buttons teaches people to ignore the signal. * * ## How a click is judged dead * * A click is queued as a candidate and re-examined about a second later. * Something-happened-fast wins; otherwise nothing-happened-in-time loses. * * **Gates** (never even a candidate): a non-element target or the `` * node, the same node clicked again within a second, a modifier key held, an * anchor (following a link is a legitimate activation the DOM need not react * to), or a match for the caller's ignore selector. * * **Liveness** — any one, inside its window, means the click did something, so * the candidate is dropped: a DOM mutation under 2500ms, a scroll under 100ms, * a `selectionchange` under 100ms, or a visibility/focus change within 1000ms * either side of the click. Visibility and focus count on *both* sides because * a click that opens a new tab may only ever surface as this window losing * focus — and because a click that hides the tab suspends the check timer, so * the transition has to be recorded onto the candidate as it fires rather than * read from a shared timestamp that a later transition would overwrite. * * **Timeouts** — with no liveness signal, any one of these makes it dead: a * mutation but only after 2500ms, a scroll after 100ms, a selection change * after 100ms, or nothing at all within 2750ms (the backstop). Visibility and * focus are deliberately absent: they may only ever suppress. * * Touch (dead swipes) is not covered. It needs its own gesture tracking and an * exclusion for surfaces whose repaints are invisible to a MutationObserver — * canvas, video, WebGL — where a swipe can never be fairly judged. */ interface DeadClickConfig { mutationThresholdMs?: number; scrollThresholdMs?: number; selectionThresholdMs?: number; /** Clicks on elements matching this selector are never judged. */ ignoreSelector?: string; /** Judge clicks held with ctrl/meta/alt/shift. Off by default. */ captureWithModifierKeys?: boolean; } interface RageClickConfig { thresholdPx?: number; timeoutMs?: number; clickCount?: number; } interface FrustrationConfig { debug: boolean; /** `false` disables dead-click detection. */ deadClicks?: DeadClickConfig | false; /** `false` disables rage-click detection. */ rage?: RageClickConfig | false; } /** * Start watching for dead and rage clicks. Returns a teardown that removes * every listener and observer it installed. */ declare function setupFrustrationSignals(config: FrustrationConfig): () => void; //#endregion //#region src/span-exporter.d.ts interface FlushOptions { /** * Send via `sendBeacon`, which survives the page going away but reports no * outcome. For unload only. */ beacon?: boolean; } declare function flushSpans(options?: FlushOptions): void; /** Severity numbers from the OpenTelemetry logs data model. */ declare const SEVERITY: { readonly debug: { readonly number: 5; readonly text: "DEBUG"; }; readonly info: { readonly number: 9; readonly text: "INFO"; }; readonly warn: { readonly number: 13; readonly text: "WARN"; }; readonly error: { readonly number: 17; readonly text: "ERROR"; }; }; type LogSeverity = keyof typeof SEVERITY; /** * Record one OTLP log record. * * Session attributes ride along for the same reason they do on spans: a log * line nobody can join to the visit it came from explains very little. */ declare function recordLog(severity: LogSeverity, body: string, attributes?: Record, scope?: string): void; /** * Record an OpenTelemetry **event** — a log record carrying an event name. * * This is what `app.widget.click`, `browser.web_vital`, `app.jank`, * `session.start` and the rest actually are in the data model. The name is set * both as the record's `eventName` field and as the `event.name` attribute, * because collectors and backends are split on which one they read and an event * nobody can find by name is not an event. */ declare function recordEvent(name: string, attributes?: Record): void; /** Spans waiting to be delivered. Exported for tests and health checks. */ declare function pendingSpanCount(): number; /** Log records waiting to be delivered. */ declare function pendingLogCount(): number; //#endregion //#region src/browser-logs.d.ts interface ConsoleLogsConfig { /** Lowest level to capture. @default 'info' — `debug` is usually noise. */ minLevel?: LogSeverity; /** Applied to each rendered line before it is sent, for PII. */ redactor?: (text: string) => string; } /** * Patch `console` so its output is exported as log records. Returns a teardown * that puts the original methods back. */ declare function captureConsoleAsLogs(config: ConsoleLogsConfig): () => void; //#endregion //#region src/functional.d.ts /** * Minimal functional API for browser tracing * * These are DX wrappers that DON'T create real browser spans. * The real spans and timing happen on the backend via Autotel. * * The browser's job is just to propagate trace context via headers. */ /** * Minimal trace context (browser-side) * * This is a lightweight version that just holds IDs. * NO actual span object - the real span lives on the backend. */ interface TraceContext { /** Current trace ID (may be extracted from request) */ readonly traceId: string; /** Current span ID (generated for this browser "span") */ readonly spanId: string; /** Correlation ID (same as trace ID) */ readonly correlationId: string; } /** * Wrap a function with trace() for better DX * * **Important:** This does NOT create real spans in the browser. * It's purely for API consistency. The real tracing happens on the backend. * * The traceparent header is automatically injected by init() on fetch/XHR calls. * * @example Basic usage * ```typescript * const fetchUser = trace(async (id: string) => { * const response = await fetch(`/api/users/${id}`) * return response.json() * }) * ``` * * @example With context (for accessing trace IDs) * ```typescript * const fetchUser = trace(ctx => async (id: string) => { * console.log('Trace ID:', ctx.traceId) * const response = await fetch(`/api/users/${id}`) * return response.json() * }) * ``` */ declare function trace any>(fn: T | ((ctx: TraceContext) => T)): T; /** * Get the current trace context (if any) * * @returns Current trace context or undefined * * @example * ```typescript * const ctx = getActiveContext() * if (ctx) { * console.log('Trace ID:', ctx.traceId) * } * ``` */ declare function getActiveContext(): TraceContext | undefined; /** * Manual helper to create a traceparent header * * Useful if you need to manually set headers or disable auto-instrumentation. * * @returns W3C traceparent header value * * @example * ```typescript * import { init, getTraceparent } from 'autotel-web' * * // Disable auto-instrumentation * init({ service: 'my-app', instrumentFetch: false }) * * // Manually inject headers * fetch('/api/data', { * headers: { * 'traceparent': getTraceparent() * } * }) * ``` */ declare function getTraceparent(): string; /** * Extract trace context from a traceparent header * * Useful for SSR scenarios where you want to continue a trace from the server. * * @param traceparent - W3C traceparent header value * @returns Parsed trace context or undefined if invalid * * @example * ```typescript * // In an SSR handler * const traceparent = request.headers.get('traceparent') * if (traceparent) { * const ctx = extractContext(traceparent) * console.log('Continuing trace:', ctx?.traceId) * } * ``` */ declare function extractContext(traceparent: string): TraceContext | undefined; //#endregion export { collectBreadcrumbs as C, SuppressionRule as D, ErrorTrackingConfig as E, addBreadcrumb as S, readBreadcrumbs as T, RageClickConfig as _, trace as a, BreadcrumbCollectors as b, FlushOptions as c, pendingLogCount as d, pendingSpanCount as f, FrustrationConfig as g, DeadClickConfig as h, getTraceparent as i, LogSeverity as l, recordLog as m, extractContext as n, ConsoleLogsConfig as o, recordEvent as p, getActiveContext as r, captureConsoleAsLogs as s, TraceContext as t, flushSpans as u, setupFrustrationSignals as v, configureBreadcrumbs as w, BreadcrumbsConfig as x, Breadcrumb as y };