import { t as clearBaggage } from "./baggage-BeapCZrZ.js"; import { PrivacyConfig } from "./privacy.js"; import { C as collectBreadcrumbs, D as SuppressionRule, S as addBreadcrumb, T as readBreadcrumbs, _ as RageClickConfig, a as trace, b as BreadcrumbCollectors, c as FlushOptions, d as pendingLogCount, f as pendingSpanCount, g as FrustrationConfig, h as DeadClickConfig, i as getTraceparent, l as LogSeverity, m as recordLog, n as extractContext, o as ConsoleLogsConfig, p as recordEvent, r as getActiveContext, s as captureConsoleAsLogs, t as TraceContext, u as flushSpans, v as setupFrustrationSignals, w as configureBreadcrumbs, x as BreadcrumbsConfig, y as Breadcrumb } from "./functional-BxmUx0Y6.js"; import { Sampler } from "@opentelemetry/sdk-trace-base"; //#region src/init.d.ts interface AutotelWebConfig { /** * Service name for the browser application * Used only for logging/debugging - not sent in headers */ service: string; /** * Enable debug logging to console * @default false */ debug?: boolean; /** * Enable automatic traceparent injection on fetch calls * @default true */ instrumentFetch?: boolean; /** * Enable automatic traceparent injection on XMLHttpRequest * @default true */ instrumentXHR?: boolean; /** * OTLP endpoint for exporting browser spans. * When set, browser spans are sent via sendBeacon so the traceparent * spanId exists as a real span in the collector. * Use '' (empty string) for same-origin (requires /v1/traces proxy). */ endpoint?: string; /** * Cross-origin destinations allowed to receive `traceparent` and `baggage`. * * Same-origin requests always propagate; everything else is opt-in. This is a * compatibility control, not a privacy one: an unexpected request header * makes the browser preflight, and a server that does not list `traceparent` * in `Access-Control-Allow-Headers` rejects the request outright. It is the * same default OpenTelemetry's web instrumentation applies. * * Not propagating is not the same as not tracing — the browser span is still * recorded, with its timing, status and errors. Only the join with that * server's own spans is lost, which is what listing the origin here restores. * * Substring-matched against the destination origin. * * @example * ```typescript * init({ * service: 'my-spa', * // your API is on another origin, and it allows the header * propagateTo: ['api.myapp.com'], * }); * ``` * * @default [] (same-origin only) */ propagateTo?: string[]; /** * Treat every request to the collector's origin as telemetry, not only the * OTLP paths. * * Set this when the collector serves more than OTLP - autotel's devtools * collector serves its own UI and query API beside `/v1/traces` - so the * widget that displays the traces is itself a fetch from this page. Tracing * that makes the tool a source of the data it displays: every poll of the * trace list writes another trace to the list. * * Off by default, because nothing in the URL can tell a dedicated collector * from an OTLP endpoint proxied through the application's own server, and * getting that wrong silences the requests the page exists to make. The * page's own origin is never excluded, whatever this says. * * @example * ```typescript * init({ * service: 'my-spa', * endpoint: 'http://localhost:4848', // devtools collector, nothing else * collectorOwnsOrigin: true, * }); * ``` * * @default false */ collectorOwnsOrigin?: boolean; /** * Privacy controls for traceparent header injection * * Configure origin filtering and privacy signal respecting (DNT, GPC) * to ensure compliance with GDPR, CCPA, and user privacy preferences. * * @example Basic origin filtering * ```typescript * { * privacy: { * allowedOrigins: ['api.myapp.com'], // Only inject on API calls * respectDoNotTrack: true // Respect user's DNT setting * } * } * ``` * * @example Block third-party analytics * ```typescript * { * privacy: { * blockedOrigins: ['analytics.google.com', 'facebook.com'] * } * } * ``` */ privacy?: PrivacyConfig; /** * Business-context baggage propagated end-to-end as a W3C `baggage` header. * * Set values at runtime with {@link setBaggage} (e.g. after login or a tenant * switch); they are injected on every instrumented same-origin request and * tagged onto every browser-recorded span. On the backend, autotel's * `BaggageSpanProcessor` (`init({ baggage: '' })` for bare keys, or * `baggage: true` for `baggage.`-prefixed keys) copies them onto server spans. * * **Fail-closed:** baggage is sent only to same-origin requests unless a * destination is explicitly listed in `allowedOrigins`. This keeps * customer-identifying values (e.g. `tenant.id`) from leaking to third-party * origins. Baggage never travels wider than traceparent. * * @example * ```typescript * init({ * service: 'my-spa', * endpoint: 'https://collector.example.com', * baggage: { allowedOrigins: ['api.example.com'] }, * }); * setBaggage({ 'tenant.id': 'acme' }); * ``` */ baggage?: { /** * Initial baggage entries, applied during init() before any request fires. * Use this for context known at startup (e.g. tenant from the subdomain). */ initial?: Record; /** * Cross-origin destinations permitted to receive the baggage header. * Same-origin is always allowed; everything else is fail-closed. * Substring-matched, same convention as `privacy.allowedOrigins`. */ allowedOrigins?: string[]; }; /** * Session identity stamped on every browser span as `session.id`, so spans * from one visit can be reassembled into a journey. * * The id is a random UUID held in `sessionStorage` — tab-scoped, nothing * derived from the person, and it identifies a visit rather than a visitor. * A gap longer than `timeoutMs` starts a new session and links it to the old * one via `session.previous_id` on the first span. * * Pass `false` to emit no session attributes at all. * * Where another SDK on the page already owns a session, hand its id in via * `id` and autotel carries that instead of minting one, so spans and that * SDK's own records key on the same value: * * ```ts * import { posthogSessionId } from 'autotel-posthog'; * init({ service: 'web', session: { id: posthogSessionId } }); * ``` * * @default { timeoutMs: 1_800_000 } */ /** * Fraction of sessions to export, 0..1. Default 1. * * Hashed on `session.id`, so a sampled visit is kept whole — and applied to * spans, logs and events alike, since a visit whose events survive but whose * spans do not is unreadable either way. With `session: false` there is no * key to be consistent about and the draw is per record. */ sampleRate?: number; session?: false | { timeoutMs?: number; id?: () => string | undefined; /** * Emit `session.start` / `session.end` events, so session count and session * duration are direct queries rather than something a backend has to infer * by grouping every span it has. * * @default false */ emitEvents?: boolean; }; } /** * Initialize autotel-web * * Patches fetch() and XMLHttpRequest to auto-inject traceparent headers. * * **SSR-safe:** Safe to call in SSR environments (checks for window). * **Call once:** Subsequent calls are ignored. * * @example * ```typescript * import { init } from 'autotel-web' * * init({ service: 'my-frontend-app' }) * * // Now all fetch/XHR calls include traceparent headers! * fetch('/api/users') // <-- traceparent header automatically injected * ``` * * @example With React (client-only) * ```typescript * import { useEffect } from 'react' * import { init } from 'autotel-web' * * function App() { * useEffect(() => { * init({ service: 'my-spa' }) * }, []) * * return
...
* } * ``` */ declare function init(userConfig: AutotelWebConfig): void; /** * Set business-context baggage that propagates end-to-end. * * Merges `record` into the active baggage (additive, like Sentry `setTags` / * Datadog `setGlobalContextProperty`). Every subsequent instrumented request * carries it as a W3C `baggage` header (same-origin / allowlisted only), and * every browser-recorded span is tagged with it. Invalid entries are dropped * (warned in `debug` mode); this never throws in the request path. * * Safe to call any time after {@link init} — typically right after login or a * tenant switch. Requests fired before the call won't carry the new value. * * @example * ```typescript * setBaggage({ 'tenant.id': 'acme' }); * ``` */ declare function setBaggage(record: Record): void; //#endregion //#region src/emit-event.d.ts /** * Where browser events go. * * OpenTelemetry events are **log records**, not spans. A zero-duration span * named `browser.web_vital` is invisible to every log and event dashboard, and * turns up in trace search as noise — so the names this package emits reach the * log pipeline instead, and the repository's "emit events through the Logs API * model" direction holds here as it does everywhere else. * * The sink is injected rather than imported because the modules that emit * events (`session`, `web-vitals`, `frustration`, …) are also read *by* the * exporter, and importing it back would make the cycle real. `init()` and * `initFull()` install it; without one, emitting is a no-op, which is the right * behaviour for an app that configured no endpoint. */ /** Attribute values an OTLP log record can carry. */ type EventAttributes = Record; type EventSink = (name: string, attributes: EventAttributes) => void; /** Install (or clear) the destination for browser events. */ declare function setEventSink(fn: EventSink | undefined): void; /** * Emit one event. Never throws: an event describes something the application * did, and must not become the reason that thing fails. */ declare function emitEvent(name: string, attributes: EventAttributes): void; //#endregion //#region src/semconv.d.ts /** * Canonical OpenTelemetry names for browser telemetry. * * Everything this package observes — clicks, web vitals, jank, sessions — has a * name the specification already owns. Emitting the same signal under a * homegrown one costs nothing to write and everything to use: a Grafana or * Honeycomb dashboard built on the browser conventions finds an empty panel, * and the person reading it concludes the thing never happened. * * So this file is the single source of truth for those strings, in the same * spirit as `autotel-genai/semconv`. Constants rather than a dependency on * `@opentelemetry/semantic-conventions`, which would pull a package into every * browser bundle to carry string literals. * * Where the spec names an event but has not yet published its body fields, the * fields live in {@link AUTOTEL_WEB} and are prefixed with the canonical event * name — an extension that reads as an extension, and that lines up with the * spec if and when it lands. * * ## Events are log records * * The names in {@link WEB_EVENT} are event names, and an OpenTelemetry event is * a **log record** — not a span. They are emitted through `emitEvent`, which * writes an OTLP log record carrying the name in both the record's `eventName` * field and its `event.name` attribute. A zero-duration span would be invisible * to every log and event dashboard, and would show up in trace search as noise. */ /** Canonical `browser.*` attributes. */ declare const BROWSER: { readonly LANGUAGE: "browser.language"; readonly MOBILE: "browser.mobile"; readonly PLATFORM: "browser.platform"; readonly BRANDS: "browser.brands"; readonly DOCUMENT_URL_FULL: "browser.document.url.full"; }; /** Canonical `user_agent.*` attributes. */ declare const USER_AGENT: { readonly NAME: "user_agent.name"; readonly VERSION: "user_agent.version"; readonly OS_NAME: "user_agent.os.name"; readonly OS_VERSION: "user_agent.os.version"; readonly SYNTHETIC_TYPE: "user_agent.synthetic.type"; }; /** * Canonical `app.*` attributes. Written for mobile first, but a widget is a * widget and a dropped frame is a dropped frame — the browser equivalents map * onto them exactly, which is why they are used here rather than reinvented. */ declare const APP: { readonly SCREEN_ID: "app.screen.id"; readonly SCREEN_NAME: "app.screen.name"; readonly SCREEN_COORDINATE_X: "app.screen.coordinate.x"; readonly SCREEN_COORDINATE_Y: "app.screen.coordinate.y"; readonly WIDGET_ID: "app.widget.id"; readonly WIDGET_NAME: "app.widget.name"; readonly JANK_FRAME_COUNT: "app.jank.frame_count"; readonly JANK_PERIOD: "app.jank.period"; readonly JANK_THRESHOLD: "app.jank.threshold"; }; /** Canonical `session.*` attributes. */ declare const SESSION: { readonly ID: "session.id"; readonly PREVIOUS_ID: "session.previous_id"; }; /** Canonical event names. Emitted as OTLP log records, never as spans. */ declare const WEB_EVENT: { readonly WIDGET_CLICK: "app.widget.click"; readonly SCREEN_CLICK: "app.screen.click"; readonly WEB_VITAL: "browser.web_vital"; readonly JANK: "app.jank"; readonly SESSION_START: "session.start"; readonly SESSION_END: "session.end"; }; /** * autotel extensions — **not** in the published specification. * * The `browser.web_vital.*` keys mirror the body the specification's event * describes (name, value, delta, id) while the released semantic-conventions * package carries the event name alone. * * Each extends a canonical event name rather than inventing a namespace, so it * is obvious at a glance which half of an attribute set is spec and which is * ours, and so a future spec field can take over without a rename. */ declare const AUTOTEL_WEB: { /** Web vital name, e.g. `LCP`. The spec names the event but not its body. */ readonly WEB_VITAL_NAME: "browser.web_vital.name"; /** Web vital value, in the metric's own unit (ms, or unitless for CLS). */ readonly WEB_VITAL_VALUE: "browser.web_vital.value"; /** * Change since this metric was last reported. Without it, a run with * `reportAllChanges` on is a series of absolute values nobody can difference. */ readonly WEB_VITAL_DELTA: "browser.web_vital.delta"; /** * Identifier for this metric instance. Repeated reports of one measurement * share an id, which is the only way to deduplicate them. */ readonly WEB_VITAL_ID: "browser.web_vital.id"; /** `good` / `needs-improvement` / `poor`, as `web-vitals` reports it. */ readonly WEB_VITAL_RATING: "browser.web_vital.rating"; /** Element tag name for a click, when a widget name is not enough. */ readonly WIDGET_TAG: "app.widget.tag"; /** * What the click achieved: `normal`, `dead` (nothing observable happened) or * `rage` (repeated in the same spot). The signal no tracing backend produces * on its own, because a click that does nothing runs no code to trace. */ readonly CLICK_OUTCOME: "app.widget.click.outcome"; /** The liveness or timeout signal that decided a `dead` verdict. */ readonly CLICK_VERDICT_SIGNAL: "app.widget.click.verdict_signal"; /** Clicks counted in a `rage` burst. */ readonly CLICK_RAGE_COUNT: "app.widget.click.rage_count"; /** * Event name for a click judged frustrating. Separate from * {@link WEB_EVENT.WIDGET_CLICK} so a frustration query and a click count * never double-count the same gesture. */ readonly CLICK_FRUSTRATION: "app.widget.click.frustration"; /** Seconds the session had been running when it ended. */ readonly SESSION_DURATION: "session.duration"; /** Why a session ended: `timeout` or `unload`. */ readonly SESSION_END_REASON: "session.end.reason"; }; //#endregion //#region src/browser-context.d.ts /** * Which browser this is. * * `browser.*` is a canonical OpenTelemetry resource convention, and without it * a span cannot answer "does this only break on mobile Safari?" — the single * most common first question about a front-end bug. * * Only what the platform states outright is recorded. `user_agent.name` and * friends need a real user-agent database to derive, and every OTLP collector * ships one; guessing them here would ship a stale regex to every visitor and * be wrong in a way nobody could correct without a release. */ type BrowserResourceAttributes = Record; /** * Canonical `browser.*` attributes for this page, ready to spread into a * resource. Empty off-browser, so callers can spread unconditionally. */ declare function browserResourceAttributes(): BrowserResourceAttributes; //#endregion //#region src/sampling.d.ts /** * Sampling that keeps whole sessions. * * Random per-event sampling at 10% gives you a tenth of every session — enough * to draw a chart, never enough to reconstruct what one person hit. Hashing a * stable key instead gives you all of a tenth of the sessions, which is the * one you can actually debug from. * * The decision is a pure function of the key, so it needs no coordination: two * tabs, the browser and the server, or two services on the same trace all reach * the same answer without talking to each other. */ /** * Whether `key` is in the sampled `rate` (0..1, clamped). * * Monotonic in `rate`: raising it can only add keys, never swap them. That is * what makes turning sampling up mid-incident safe — the sessions you were * already watching stay in the set. */ declare function sampleByKey(key: string, rate: number): boolean; //#endregion //#region src/sampler.d.ts declare function createSessionRatioSampler(ratio: number): Sampler; //#endregion //#region src/engagement.d.ts /** * How much of the page anyone actually read. * * Scroll depth and content depth are different questions and the difference is * the whole point. Scroll depth is how far the reader moved; content depth is * how far down the page has been on screen — scroll position plus the height of * the viewport. On a page shorter than the window nothing scrolls, so a * scroll-only reading says 0% and the page looks like a bounce, when in fact * every word of it was visible. * * Both are reported at the end of a page view, alongside how long it lasted. * No OpenTelemetry convention covers this, so the attributes are autotel's, * named under `browser.page.*` beside the conventions that do exist. */ /** Event name for the end of a page view (autotel extension). */ declare const PAGE_ENGAGEMENT_EVENT = "browser.page_engagement"; declare const PAGE_ENGAGEMENT_ATTR: { /** Deepest scroll reached, as a percentage of the scrollable distance. */ readonly MAX_SCROLL_PERCENTAGE: "browser.page.max_scroll_percentage"; /** Deepest point of the document that was on screen, as a percentage. */ readonly MAX_CONTENT_PERCENTAGE: "browser.page.max_content_percentage"; /** Seconds this page view lasted. */ readonly DURATION: "browser.page.duration"; }; interface EngagementConfig { debug: boolean; } /** * Start measuring engagement. Reports on `pagehide` and on history navigation, * so a single-page app gets one report per route rather than one per visit. * Returns a teardown. */ declare function setupEngagement(config: EngagementConfig): () => void; //#endregion //#region src/remote-config.d.ts interface RemoteConfig { /** Fraction of sessions to keep, 0..1. */ sampleRate?: number; captureDeadClicks?: boolean; captureRageClicks?: boolean; captureEngagement?: boolean; /** * Exceptions to stop reporting, without a release. Same shape as * `errorTracking.suppressionRules`, so a rule can be moved between the two * without rewriting it. */ errorSuppression?: SuppressionRule[]; } /** * The last config known to be good, available synchronously. Reads through to * storage once, so a reload applies the previous visit's config immediately. */ declare function cachedRemoteConfig(): RemoteConfig | undefined; interface RefreshOptions { /** Injected for tests, and to bypass instrumented fetch in production. */ fetchImpl?: typeof globalThis.fetch; } /** * Fetch the config and cache it. Resolves to the config now in force — the * fetched one, or the cached one when the fetch could not improve on it. * Never rejects. */ declare function refreshRemoteConfig(url: string, options?: RefreshOptions): Promise; /** * Local suppression rules plus any the remote config adds. * * Additive on purpose: a rule in the source is there for a reason, and a * fetched file must not be able to switch off error reporting the application * asked for. Remote can only ever quieten more, never less. */ declare function applyRemoteSuppression(local: SuppressionRule[] | undefined, remote: RemoteConfig | undefined): SuppressionRule[] | undefined; /** What the local config asked for, before remote has its say. */ interface LocalCaptureToggles { frustration: boolean; engagement: boolean; } /** What should actually run. */ interface ResolvedCaptureToggles { frustration: boolean; engagement: boolean; /** `undefined` leaves the local/default setting for that half alone. */ deadClicks: boolean | undefined; rage: boolean | undefined; } /** * Merge local capture settings with the remote ones. * * Remote wins in **both** directions. A toggle that can only ever say "off" is * not a control, and turning a signal on without a release is half the reason * remote config exists — this is the opposite of `applyRemoteSuppression`, * where remote may only ever quieten, because there the failure mode is * silently losing errors. */ declare function resolveCaptureToggles(local: LocalCaptureToggles, remote: RemoteConfig | undefined): ResolvedCaptureToggles; //#endregion //#region src/traceparent.d.ts /** * Minimal W3C Trace Context implementation for browser * * Generates traceparent headers in the W3C format: * traceparent: 00-{trace-id}-{span-id}-{flags} * * No OpenTelemetry dependencies - just crypto.getRandomValues() */ /** * Generate a random 128-bit (16 byte) trace ID * @returns 32 character hex string */ declare function generateTraceId(): string; /** * Generate a random 64-bit (8 byte) span ID * @returns 16 character hex string */ declare function generateSpanId(): string; /** * Create a W3C traceparent header value * * Format: version-traceId-spanId-flags * - version: 00 (W3C Trace Context spec) * - traceId: 128-bit hex (32 chars) * - spanId: 64-bit hex (16 chars) * - flags: 01 (sampled) * * @param traceId - Optional existing trace ID (for continuing traces) * @param parentSpanId - Optional parent span ID (unused in browser, included for API compat) * @returns W3C traceparent header value * * @example * ```typescript * const header = createTraceparent() * // "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" * ``` */ declare function createTraceparent(traceId?: string, _parentSpanId?: string): string; /** * Parse a traceparent header value * Useful for extracting trace context from incoming headers * * @param traceparent - W3C traceparent header value * @returns Parsed components or null if invalid * * @example * ```typescript * const parsed = parseTraceparent('00-4bf92f...0e4736-00f067...902b7-01') * console.log(parsed?.traceId) // "4bf92f...0e4736" * ``` */ declare function parseTraceparent(traceparent: string): { version: string; traceId: string; spanId: string; flags: string; } | null; //#endregion export { APP, AUTOTEL_WEB, type AutotelWebConfig, BROWSER, type Breadcrumb, type BreadcrumbCollectors, type BreadcrumbsConfig, type BrowserResourceAttributes, type ConsoleLogsConfig, type DeadClickConfig, type EngagementConfig, type EventAttributes, type EventSink, type FlushOptions, type FrustrationConfig, type LogSeverity, PAGE_ENGAGEMENT_ATTR, PAGE_ENGAGEMENT_EVENT, type PrivacyConfig, type RageClickConfig, type RemoteConfig, SESSION, type TraceContext, USER_AGENT, WEB_EVENT, addBreadcrumb, applyRemoteSuppression, browserResourceAttributes, cachedRemoteConfig, captureConsoleAsLogs, clearBaggage, collectBreadcrumbs, configureBreadcrumbs, createSessionRatioSampler, createTraceparent, emitEvent, extractContext, flushSpans, generateSpanId, generateTraceId, getActiveContext, getTraceparent, init, parseTraceparent, pendingLogCount, pendingSpanCount, readBreadcrumbs, recordEvent, recordLog, refreshRemoteConfig, resolveCaptureToggles, sampleByKey, setBaggage, setEventSink, setupEngagement, setupFrustrationSignals, trace };