npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@siteoshq/pulse

v0.9.0

Published

Errors, server Logs, tracing and consented session replay for SiteOS Pulse

Readme

SiteOS Pulse

Capture errors, server Logs, optional browser request traces and masked session replay in SiteOS Pulse. Keep diagnostics connected to your Project environment and deployed release.

Install

pnpm add @siteoshq/pulse

Enable Pulse → Errors → Setup & settings for the selected Project environment. Save the exact website origins and obtain the environment-specific collection keys. Browser and server Errors use separate keys; keep server credentials in server environment variables.

| Support | Requirement | | ---------------------- | ------------------------------------------------------------ | | Browser | ES2022, Fetch, Web Crypto, AbortController | | Server | Node.js 22.12+ | | Next.js request errors | onRequestError instrumentation hook | | React error boundaries | Manual captureException | | Recorder dependency | @rrweb/record 2.1.7 (MIT), loaded only for selected replay | | Errors entry budget | 5 KiB minified + gzip, checked on every build | | Tracing entry budget | 25 KiB minified + gzip, including dependencies | | Replay entry budget | 100 KiB minified + gzip, including recorder |

The root entry and /browser provide the same browser-safe API. Node process hooks are isolated in /node; /nextjs contains only the request-error adapter. Importing the package does not attach handlers or send requests.

SiteOS Cloud: five credentials

For Errors, server Logs/Traces, browser Traces and private source maps in Next.js, configure these five environment variables. Enable only the channels selected for your site; an Errors-only installation does not need Telemetry or browser Traces keys. Use separate values per environment.

| Variable | Scope | | ------------------------------------- | ----------------------------------------- | | NEXT_PUBLIC_SITEOS_PULSE_ERRORS_KEY | Public, browser Errors collection only | | SITEOS_PULSE_ERRORS_KEY | Secret, server Errors collection only | | NEXT_PUBLIC_SITEOS_PULSE_TRACES_KEY | Public, browser Traces collection only | | SITEOS_PULSE_TELEMETRY_KEY | Secret, server Logs and Traces collection | | SITEOS_PULSE_SOURCEMAPS_KEY | Secret, build-only source map upload |

Pass each collection key to its SDK entry; the SDK does not read runtime environment variables or infer consent. The Next.js build adapter reads the source map key and derives the release from SITEOS_RELEASE, VERCEL_GIT_COMMIT_SHA, or Git. Use direct process.env.NEXT_PUBLIC_… accesses in client code so Next.js can inline public values at build time. No separate endpoint or enablement environment variables are required for SiteOS Cloud. Collection still requires explicit SDK initialization and enabled settings in the exact environment.

endpoint is optional starting with SDK 0.8.0. SDK 0.7.x and earlier require explicit collection endpoints. Check the installed SDK before removing existing endpoints. The defaults are:

| Channel | Default endpoint | | --------------------- | ------------------------------------------------------- | | Browser/server Errors | https://errors.siteos.sh/errors/v1/collect | | Browser Traces | https://telemetry.siteos.sh/browser/v1/traces | | Server Logs / Traces | https://telemetry.siteos.sh/v1/logs / /v1/traces | | Source maps | https://app.siteos.sh/api/pulse/errors/v1/source-maps |

For self-hosting or a custom collector, pass the exact endpoint returned by that installation's settings. Explicit endpoints take priority; an empty or invalid endpoint never falls back to Cloud. Errors and server convenience clients disable invalid configuration. Browser tracing and raw exporter constructors reject it synchronously; keep initialization inside the application’s existing fail-open guard. Source map upload configuration errors fail the build. Keys remain scoped to their channel and Project environment. A Cloud default does not select Production, merge keys, enable a feature, or relax consent/GPC/DNT policy. Keep enabled: false as an optional application-controlled switch for server Logs/Traces.

Browser errors

Initialize once in a client module. Use a release ID matching the deployed build, such as a Git SHA.

import { init } from "@siteoshq/pulse";

export const errors = init({
  key: "YOUR_PUBLIC_COLLECTION_KEY",
  release: "YOUR_BUILD_ID",
  collection: "after-consent",
  applicationOrigins: ["https://cdn.example.com"], // optional application bundle origins
});

// Connect these calls to your consent manager's current state and changes.
errors.setConsent(true);
// errors.setConsent(false);

Browser capture waits for consent by default. No reports or breadcrumbs are buffered while consent is absent. Withdrawal clears breadcrumbs and aborts pending requests; already delivered reports cannot be recalled. A site owner may explicitly select collection: "essential" according to the site's policy. The SDK does not determine a legal basis for collection.

Unhandled exceptions and Promise rejections are captured automatically. Report handled errors, including errors received by React's componentDidCatch or a Next.js error boundary, explicitly:

errors.captureException(error, { handled: true, route: "/contact" });
errors.addBreadcrumb({ category: "action", name: "contact.submit" });

Pass static action names and route templates. The SDK does not record DOM text, form values, cookies, request bodies, headers or visitor IDs. Query strings and common secret patterns are removed before transmission. Application messages can still contain sensitive content; use beforeSend to remove unsuitable reports:

init({
  key,
  release,
  beforeSend: (event) => (event.route?.startsWith("/private") ? null : event),
  ignoreErrors: ["Known optional widget failure"],
  sampleRate: 1,
});

Initialize a single client per page. Call await errors.close() before replacing it during hot reload or teardown. Equivalent errors are reported once per client/page, with at most 20 reports per minute and four requests in flight. Extension errors, known browser noise and known bots are filtered by default. filterNoise: false and ignoreBots: false are available for controlled tests. Third-party scripts remain visible; applicationOrigins identifies your own CDN bundles.

Server

import { createClient } from "@siteoshq/pulse/node";

const errors = createClient({ key: process.env.SITEOS_PULSE_ERRORS_KEY!, release });

try {
  await performOperation();
} catch (error) {
  errors.captureException(error, { route: "/api/contact" });
  await errors.flush();
  throw error;
}

The return value of captureException is a local report ID, not a delivery receipt. Verify accepted reports in Pulse.

Await flush() before a serverless request ends. Transport failures are swallowed and each request has a three-second deadline. Server deduplication resets each minute. The optional Node init() also observes fatal exceptions without changing the process crash policy; delivery during process termination is best effort.

For Next.js, export the adapter from instrumentation.ts:

import { createRequestErrorHandler } from "@siteoshq/pulse/nextjs";

export const onRequestError = createRequestErrorHandler({
  key: process.env.SITEOS_PULSE_ERRORS_KEY!,
  release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
});

The handler awaits bounded delivery and reads only the route template from Next.js context. Use runtime: "edge" when configuring an Edge-specific handler. Keep the secret key out of browser modules and public environment variables.

Automatic source maps

Set this up once with SDK 0.2+ and CLI 2.48+. Select the intended SiteOS Project and environment, then issue a build-only upload key:

siteos pulse errors upload-key issue --environment production \
  --name "Website build" --output /private/path/pulse-maps.json

Save the returned key in your build environment as SITEOS_PULSE_SOURCEMAPS_KEY. It is secret, bound to one environment's Pulse resource and can only upload maps. It cannot read errors or change settings. Keep it out of Git, browser variables and runtime configuration. Keys expire after a year; use upload-key list and upload-key revoke --credential <id> to inspect or replace them.

For Next.js 16.3+ (16.x), wrap the final configuration. SDK 0.2.1+ supports both next.config.ts and ESM configuration files:

// next.config.ts
import { withPulseErrors } from "@siteoshq/pulse/nextjs/config";

export default withPulseErrors(nextConfig);
// With another plugin: withPulseErrors(withMDX(nextConfig))

Use process.env.NEXT_PUBLIC_SITEOS_RELEASE for both browser and server reporting. The wrapper sets it from SITEOS_RELEASE, VERCEL_GIT_COMMIT_SHA, or the checked-out Git revision, in that order. Source archives without Git must provide SITEOS_RELEASE. It must identify the exact build.

The normal next build generates browser/server maps, uploads them after compilation and removes them from the build output before packaging. Existing compile hooks are preserved. Upload failures stop the build; rebuild after fixing the key, quota or connection. Identical uploads are safe to repeat; different artifacts cannot overwrite an existing release/file pair. Without an upload key, local builds remain offline and the wrapper does not enable source map generation.

Other build tools can call the same Node-only uploader from their post-build hook:

import { uploadSourceMaps } from "@siteoshq/pulse/build";

await uploadSourceMaps({
  directories: [{ directory: "dist/assets", prefix: "/assets" }],
  release: process.env.APP_RELEASE,
  key: process.env.SITEOS_PULSE_SOURCEMAPS_KEY,
});

Enable map generation in that build tool. prefix must match the generated stack URL path. The uploader strips embedded source text, uploads JavaScript maps and removes JS/CSS maps by default, including after upload failure. removeAfterUpload: false is only for artifacts kept outside public output. Do not deploy a failed or partial build. The build entries are excluded from browser bundles and add no runtime dependencies. A self-hosted installation can pass the endpoint returned by CLI.

Source-map validation is shared with the server. The build accepts at most 25 MB per input map, 8 MB per upload and 6 MB per normalized map; embedded sources are discarded before transfer. A map may contain up to 1,000,000 segments and 5,000,000 mapping characters, 200,000 lines, 4,000 sources and 50,000 names. Indexed sections share these budgets (2,000 nodes, five nested levels). Long generated names are shortened to 120 characters while preserving mapping indices. Validation failures identify the exceeded limit or invalid map; split oversized generated bundles into smaller chunks and rebuild. Upload failures continue to stop the build.

Manual repairs remain available through siteos pulse errors upload-map --release <id> --file <generated-path> --input <map-file> --environment <slug> using CLI authorization. SiteOS stores source locations and mappings, never fetches remote maps and discards embedded source text. Missing maps preserve generated locations; uploads improve diagnostics without changing grouping.

Reports expire after 30 days; inactive issue summaries and source maps expire after 90 days. Counts represent received reports, not all failures or affected users: sampling, deduplication and admission limits intentionally reduce traffic. No reports does not mean the application is healthy.

Correlate server errors with traces

When your server already uses OpenTelemetry, pass its active span identifiers to Errors:

import { context, trace } from "@opentelemetry/api";
import { createClient } from "@siteoshq/pulse/node";

const errors = createClient({
  key,
  release,
  getTraceContext: () => trace.getSpan(context.active())?.spanContext(),
});

You can also pass traceId and spanId in captureException(error, context). Pulse links retained server traces to the error occurrence. Correlation does not change grouping or issue status. Server Logs can use the optional @siteoshq/pulse/otel adapter below. Server tracing can use your existing OpenTelemetry provider, or the Next.js server adapter below.

Next.js server Traces (0.7.0+)

The @siteoshq/pulse/nextjs/server entry is Node-only. Install only Pulse; its OpenTelemetry dependencies install with it. Import it inside the Node branch of instrumentation.register():

export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { registerServerTracing } = await import("@siteoshq/pulse/nextjs/server");
    registerServerTracing({
      key: process.env.SITEOS_PULSE_TELEMETRY_KEY!, // private server key
      service: "next-app",
      release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
      enabled: process.env.SITEOS_PULSE_TELEMETRY_KEY !== undefined,
    });
  }
}

Keep the returned client in a server module and call client.flush() inside Next.js after() for observed routes, or await it in onRequestError. Timers alone do not guarantee serverless delivery. flush() and close() resolve to a delivery outcome without throwing collector errors; onDiagnostic receives bounded codes, never collector response text or credentials.

Registration uses one provider, W3C trace context without baggage and 10% parent-based root sampling (sampleRate overrides the root rate). Existing sampled/unsampled parents are respected. It uses Next.js instrumentation without adding another fetch patch, resource auto-detection, console interception, browser collection or replay. Batches contain at most 32 spans, the queue holds 256 and flush waits at most three seconds. Configure once per process; repeated registration returns the same client. Changing settings or restarting a closed client requires a new process. enabled: false or OTEL_SDK_DISABLED=true installs nothing.

Raw span names, URLs, headers, payloads, arbitrary attributes, status messages, events, links and tracestate are removed before export. Defaults retain timing, context IDs, method/status and a generic operation name. Optional describeSpan(span) returns only a reviewed { name, route } projection, with static names and route templates, or null to drop a span. Application-specific route/operation labels stay in your application. A throwing policy drops that span. Do not return user values or concrete dynamic paths. getServerTraceContext supplies the current IDs to Errors or an existing logger without another direct OpenTelemetry import.

If a provider already exists, registration returns state: "existing_provider" and does not change it, its sampling or context. Instead, add createServerTraceProcessor(options) to that provider's spanProcessors when constructing it, and retain its existing sampler and lifecycle. The processor uses the same redaction and bounded JSON export; it never registers a provider. Do not call both setup paths. The adapter does not enable SiteOS collection or publish settings.

Manual server stages (0.9.0+)

SDK 0.9.0 adds createServerTracingClient from @siteoshq/pulse/nextjs/server (or @siteoshq/pulse/node for other Node hosts). SDK 0.8.x and earlier do not expose it. Use this API for explicit application stages without direct OpenTelemetry imports. Existing registerServerTracing, createServerTraceProcessor and standard OTLP integrations remain supported. The manual client owns a private provider and async context; it never replaces an application provider, reads request headers, patches fetch/console or captures Errors.

import { createServerTracingClient } from "@siteoshq/pulse/nextjs/server";
import { after } from "next/server";

const tracing = createServerTracingClient({
  key: process.env.SITEOS_PULSE_TELEMETRY_KEY ?? "",
  service: "website-forms",
  release: process.env.NEXT_PUBLIC_SITEOS_RELEASE ?? "",
  enabled: process.env.VERCEL_ENV === "production",
  routes: ["/api/forms/submit"],
  steps: ["payload.validate", "wordpress.submit"],
  onDiagnostic: ({ code }) => console.warn("Pulse trace delivery:", code),
});

// Inside the route handler. Trust/origin policy is application-owned; pass a
// traceparent only after that policy accepts it. Never forward baggage/tracestate.
return tracing.withRequest(
  {
    requestId: request.headers.get("X-Request-ID"),
    route: "/api/forms/submit",
    method: "POST",
    parentTraceparent: trustedParentTraceparent,
    after,
    isError: (response: Response) => response.status >= 500,
    statusCode: (response: Response) => response.status,
  },
  async () => {
    await tracing.step("payload.validate", validate);
    const result = await tracing.step("wordpress.submit", submit, {
      kind: "client",
      isError: (value) => !value.ok,
    });
    const context = tracing.getContext()!;
    const response = makeResponse(result);
    response.headers.set("X-Request-ID", context.requestId);
    if (context.sampled)
      response.headers.set("X-Pulse-Traceparent", `00-${context.traceId}-${context.spanId}-01`);
    return response;
  },
);

withRequest accepts or generates a UUID v4 attempt ID even when disabled, invalidly configured, closed or unsampled. getContext() returns a fresh plain object with requestId and sampled; traceId, spanId and traceFlags: 1 exist only for a sampled active request. Outside a request it returns undefined. IDs follow await, nested steps and parallel work. They are diagnostics, never visitor identity, authorization or idempotency. Put request.id in reviewed Logs yourself; pass a sampled context to Logs/Errors via getTraceContext. Do not invent a retained-trace link when sampled is false. Browser Errors consent remains required; no server Errors are enabled.

Root sampling defaults to 10%; strict nonzero W3C version 00 parent IDs with flags 00/01 override the root decision. sampleRate only controls new roots. OTEL_SDK_DISABLED=true disables tracing. Routes and steps must come from static reviewed catalogs (at most 128 each). Unknown routes or steps still run the operation, without recording that span. The SDK cannot determine whether a syntactically valid catalog label contains personal data: never construct catalogs from requests.

Operations execute once and preserve their exact result or exception. An isError predicate can mark an unsuccessful result without throwing. Throwing metadata/diagnostic callbacks do not alter the result. No arguments, results, exception text, arbitrary attributes or raw request data are serialized. Only approved labels/routes, timing, IDs, span kind, error status and optional validated HTTP method/status reach the existing sanitized OTLP exporter.

Each client holds at most 128 active/pending request buffers, 32 spans per request including its root, and four concurrent exports. Overflow drops diagnostics, never business work. Detached steps that finish after the root are discarded. Buffers expire 60 seconds after request entry; the expiry timer only discards, never exports. Completed spans stay outside all automatic queues until the host invokes after. Each callback awaits its own batch, with a two-second exporter timeout and three-second outer deadline. Repeated callbacks reuse the same delivery promise. Delivery uses bounded OTLP retry behavior and cannot retry the business operation. Collector/scheduler failures produce safe diagnostic codes, never keys, collector responses or exception text.

Call close() only at process shutdown: it drops callbacks that have not started and bounds waiting for active exports. It never sends a pending request early. Local tests do not prove a serverless host keeps after() alive: verify actual Preview delivery separately before publication acceptance.

Server Logs (0.5.0+)

Install @siteoshq/pulse; its official OpenTelemetry dependencies are installed transitively. Only the explicit server entry loads them. Browser Errors, tracing and replay bundles do not include this adapter. Create one client per process in server instrumentation and reuse it:

import { createLogsClient } from "@siteoshq/pulse/otel";

export const logs = createLogsClient({
  key: process.env.SITEOS_PULSE_TELEMETRY_KEY!, // private server Telemetry key
  service: "website-forms",
  release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
  enabled: process.env.SITEOS_PULSE_TELEMETRY_KEY !== undefined,
});

For Next.js, bind the client to the request's supported background lifecycle:

import { after } from "next/server";
import { logs } from "./server-logs";

export async function POST() {
  const requestLogs = logs.forRequest(after);
  requestLogs.emit({
    severity: "warn",
    body: "forms.wp_timeout", // fixed event text; never form contents or arbitrary exceptions
    attributes: { "event.name": "forms.wp_timeout", "http.route": "/api/contact" },
  });
  return new Response(null, { status: 503 });
}

forRequest buffers at most 32 records, schedules one callback, and emits/flushes only in that callback. Adapt another platform's waitUntil explicitly; a detached timer does not keep a serverless invocation alive. emit outside a request uses a 256-record bounded process queue; call await logs.flush() before a short-lived process exits and await logs.close() when retiring it. Flush defaults to a three-second deadline and collector requests to two seconds. Concurrent flushes include records emitted while an earlier flush was pending. Do not await these operations in a customer's primary request path. A false return or safe onDiagnostic({ code, count?, reason? }) indicates disabled/rejected/bounded work; diagnostic callbacks and export failures never throw through the convenience client's request logger. No endpoint, key or collector error text is included in diagnostics. The standalone exporter validates configuration when constructed.

Warn/Error/Fatal are retained by default; set minimumSeverity explicitly to opt into lower levels. Choose fixed, non-personal event text. SDK 0.6.0+ supports custom server Log attributes: strings, finite numbers and booleans, including form.name, page.route, feature.variant, attempt: 0 and retryable: false. Arrays, objects and null are unsupported. Attribute names are 1–80 ASCII characters, starting with a letter, then letters, digits, dots, dashes or underscores. Each record retains at most 32 attributes, each text value at most 240 UTF-8 bytes, and at most 4 KiB of encoded attribute JSON. At most 128 input keys are inspected; standard correlation keys are prioritized within that window, followed by lexical key order. Oversized values are dropped. Credential, body/header, SQL statement and common personal-data keys are rejected. Text scrubbing is defense in depth, not a guarantee: choose safe context explicitly. Use route templates from a reviewed catalog for page.route/http.route; query strings and fragments are removed.

onDiagnostic reports attributes_dropped with a count and reason (invalid_key, sensitive_key, unsupported_value, value_too_large, attribute_limit, byte_limit, scan_limit, or upstream_limit when an existing OTel provider already removed fields). attributes_sanitized reports a count when retained values changed during scrubbing. These diagnostics contain no attribute keys or values and do not indicate a failed export. Keep their handler separate from export_failed/dropped_records, and avoid logging diagnostics back through the same client.

The exporter opts into the extended server contract with X-SiteOS-Logs-Attributes: custom-v1. Standard OTLP exporters can send that header explicitly after reviewing their emitted attributes. Without it, the collector keeps the legacy 12-field technical allowlist. Browser and server traces keep their existing attribute rules. Deploy the compatible SiteOS origin and Telemetry Edge before upgrading the SDK. Messages remain capped at 2 KiB UTF-8; actual serialized OTLP batches remain under 96 KiB and at most 100 records. The service remains lossy, with capacity limits and seven-day retention. Flush success means exporter completion, not verified SQL retention; read back a synthetic event for acceptance.

Numeric attributes accept finite numbers; integral values must be within JavaScript’s safe integer range (−9,007,199,254,740,991 through 9,007,199,254,740,991) to survive OTLP int64 serialization without rounding. Unsupported numbers are dropped with diagnostics, and filters reject them.

Logs filters use exact typed values (up to three AND matches). For example, with CLI 3.4.0+:

siteos pulse telemetry logs --attributes '{"form.name":"Book a demo","retryable":false}' --json

The Logs screen offers the same key, type and value controls. MCP siteos_pulse_list_logs accepts an attributes object. Keep filters unchanged when following nextCursor. Text matching is case sensitive; 0, "0", false and a missing attribute are distinct.

The client captures active trace/span IDs at emit time without registering a global provider. Use getTraceContext: () => undefined for intentionally uncorrelated Logs, or supply explicit verified IDs. It never captures baggage, request bodies, headers or console output.

For an existing OpenTelemetry Logs provider, add createLogsExporter({ endpoint, key }) to that provider's batch processor instead of creating another client/provider. The adapter uses the official OTLP/HTTP JSON exporter, sanitizes records and splits by actual encoded bytes; an export call is bounded to 256 records and four concurrent export operations. Existing sampling policy and other destinations remain application-owned. The supported integration is tested with OTel Logs 0.223.x and Resources 2.12.x. No server spans are installed automatically.

Browser tracing

Use the optional @siteoshq/pulse/tracing entry. It adds no OpenTelemetry or replay dependency to an Errors-only installation. In Pulse → Traces → Collection settings → Browser, configure exact website origins and approved route templates, then create a public browser tracing key.

import { initTracing } from "@siteoshq/pulse/tracing";

export const tracing = initTracing({
  key: "YOUR_PUBLIC_BROWSER_TRACING_KEY",
  release: "YOUR_BUILD_ID",
  routes: [{ origin: "https://example.com", path: "/api/orders/:id" }],
  sampleRate: 0.1,
});

// Connect to the same consent manager as Errors.
tracing.setConsent(true);
// tracing.setConsent(false);

Only matching fetch/XHR requests receive a W3C traceparent. The instrumented server must accept that header; cross-origin APIs must allow it through CORS. Existing trace headers are preserved. Pulse sends route templates, method, duration and status; it does not read request/response bodies, export headers or retain concrete paths. Server spans with matching IDs appear in the same trace.

Initial navigation timing is available for approved page routes when navigation finishes after consent. Earlier buffered navigation entries are not read. These load timings are independent roots, not an inferred causal parent for later requests. SPA navigation does not invent a new page-load span. Set navigation: false to disable these timings.

Correlate only an error from the actual traced operation:

try {
  await fetch("/api/orders/123");
} catch (error) {
  errors.captureException(error, { ...tracing.contextFor(error), route: "/orders/:id" });
}

contextFor recognizes the same rejected request error, not arbitrary later exceptions. An HTTP error response does not reject fetch. Server error/log correlation uses the server's active span. Call await tracing.close() at teardown. Revocation removes hooks, clears pending records and aborts collection requests. DNT and Global Privacy Control suppress collection. Export is bounded and best effort; a failed collector never fails the application request.

For existing browser OpenTelemetry, add createTraceExporter(options) to the existing provider's span processor instead of initTracing. The exporter accepts client request spans with standard url.full/http.url, method and status attributes and projects only approved routes. It installs no global provider or fetch/XHR patch. Connect its setConsent method to the CMP and retain the provider's sampling policy. Unsupported spans are omitted.

| Browser entry | Complete consumer budget (minified + gzip) | | ------------------------- | ------------------------------------------ | | Root / browser (Errors) | 5 KiB | | tracing | 25 KiB |

The build verifies these budgets and dependency isolation. Browser traces expire after seven days. Sampling and bounded delivery mean a trace can be partial; absent data is not evidence of health.

Optional replay

Enable Replay separately in the same Project environment. Use its own public collection key, returned endpoint and approved page templates. The player is part of SiteOS, not this package.

import { initReplay } from "@siteoshq/pulse/replay";

const replay = initReplay({
  endpoint: replayEndpoint,
  key: replayKey,
  release,
  routes: ["/", "/products/:id"],
  // sampleRate: 0.01 is the default
});

// Wire the current permission and subsequent changes from your consent manager.
replay.setConsent(hasConsent);
// On teardown or withdrawal: replay.close() / replay.setConsent(false).

// Optional: add this callback to your existing Errors and tracing initializers.
const getReplayId = () => replay.getRecordingId();

Replay loads its recorder only after consent and sampling and honors DNT/GPC. The identifier exists only in memory for one page lifetime. Withdrawal clears pending data, aborts export and ends this recording; accepted data remains subject to retention. Unsampled errors have no recording.

Text and input values are always masked. Mark private subtrees with data-pulse-private or .pulse-private. Authentication/account/payment routes, embedded pages, media and canvas are excluded. The privacy projection removes arbitrary attributes, URLs and CSS. Recordings preserve limited layout and interactions; fonts and external assets may appear as placeholders. Masking is not a guarantee of anonymity.

Recordings stop after 5 minutes idle or 15 minutes total and expire after 7 days. Transport is best effort with 96 KiB chunks and a 5 MiB recording limit; incomplete data is shown as gaps. A collector failure never blocks the page. Importing only Errors or tracing does not include the recorder.