npm.io
0.1.55 • Published 2d ago

@observtech/rum

Licence
MIT
Version
0.1.55
Deps
20
Size
232 kB
Vulns
0
Weekly
2.4K

@observtech/rum

Browser RUM + Session Replay SDK for Observ — replay-as-context. One call captures a web session (rrweb replay + OpenTelemetry spans + semantic events + JS errors) under a single session.id, so the whole session can be read as context rather than watched as a video.

What it does

A single observ.init() wires all of the following, each stamped with the same session.id so the backend can correlate them:

  • Session replay@rrweb/record DOM capture, sent as gzip chunks.
  • Traceshttp.client spans for fetch/XHR + page load, over OTLP/HTTP, with W3C traceparent propagation (so front and backend traces stitch).
  • Session lifecyclesession.start / session.end events with previous_id chaining, a 30 min inactivity window + 4 h hard cap, and an activity sweep (per-tab sessionStorage by default; opt-in cross-tab).
  • End-user identityenduser.pseudo.id (anonymous, persistent) on every signal, plus enduser.id once you call setUser().
  • Semantic eventsclick, rage_click, navigate as OTel log records.
  • Page views — rich app.page_view (referrer, time-on-page, navigation type).
  • JS errors — uncaught errors and unhandled promise rejections, observe-only.
  • Console forwarding — optionally mirror console.* to the logs pipeline.
  • User-interaction spans — optional click/submit/keydown spans with ZoneContextManager (opt-in; pulls in zone.js).
  • Consent gate — optionally hold ALL telemetry until GDPR analytics consent.
  • PII masking — input values are masked by default (safe-by-default).

observ.init() is idempotent and never throws: any setup failure degrades to "no telemetry" instead of breaking the host page.

Install

yarn add @observtech/rum   # or: npm install / pnpm add

rrweb is pulled in as a transitive dependency and resolved by your bundler. Requires an evergreen browser (uses native CompressionStream, fetch, sessionStorage, history).

Configure

Call it as early as possible, before the first fetch/XHR you want traced:

import { observ } from '@observtech/rum'

observ.init({
  endpoint: 'https://observ.example.com', // Observ backend base URL
  key: '<api-key>', // sent as the x-observ-key header
})

Main options:

Option Type Default Role
endpoint string Base URL of the Observ backend (/v1/... paths are appended).
key string API key sent as x-observ-key (empty ⇒ header omitted).
propagateTraceHeaderCorsUrls (string | RegExp)[] [] Cross-origin backends allowed to receive the W3C traceparent header.
disableReplay boolean false Keep tracing/events but turn off the heavy rrweb replay.
privacy PrivacyOptions masked PII masking of the replay stream (see below).
serviceName / serviceVersion / environment string Resource attributes (service.name, …) stamped on every signal.
sampleRate number 1 Head-based trace sampling ratio in [0,1) (ParentBased).
propagateBaggage boolean false Send session.id to allowed backends via the W3C baggage header.
disableMetrics boolean false Turn off Core Web Vitals + OTLP metrics.
sessionInactivityMs number 30 min Inactivity window before the session.id rotates.
sessionMaxDurationMs number 4 h Hard cap on a single session's duration (rotates even while active).
crossTabSessions boolean false Share one session across tabs (localStorage) instead of per-tab.
disablePseudoUser boolean false Disable the persistent pseudonymous enduser.pseudo.id.
forwardConsole boolean | ConsoleLevel[] false Mirror console.* to the logs pipeline (all levels, or a subset).
disablePageViews boolean false Turn off rich app.page_view events.
userInteraction boolean false Interaction spans + ZoneContextManager (opt-in; pulls in zone.js).
requireConsent boolean false Hold ALL telemetry until analytics consent is present (see below).
consentKey / consentGrantedValues string / string[] observ.consent / ['granted','true','1','yes','all'] Where/what consent is read from.
Privacy / PII masking

The replay records the live DOM, so input values are PII. Masking is on by default: omit privacy entirely and every <input>/<textarea>/<select> value is masked. Three CSS classes mark sensitive nodes declaratively:

  • observ-mask — mask the element's text
  • observ-block — drop the element from the replay
  • observ-ignore — record the element but ignore its input value
observ.init({
  endpoint: 'https://observ.example.com',
  key: '<api-key>',
  privacy: {
    maskAllInputs: true, // default; set false only on a surface free of PII
    maskAllText: false, // true masks ALL visible text (degrades the replay)
  },
})
End-user identity

Every signal carries a pseudonymous enduser.pseudo.id (persisted in localStorage, anonymous). After sign-in, attach the authenticated identity; clear it on sign-out:

observ.setUser({ id: user.id, role: user.role, name: user.name }) // → enduser.id/role/name
observ.clearUser() // on logout (the pseudo id persists)
Sessions

A session.id is shared by every signal. It rotates after 30 min of inactivity or a 4 h hard cap, emitting session.start (with session.previous_id on a continuation) and a best-effort session.end (with session.duration_ms). Sessions are per-tab (sessionStorage) by default — two tabs are two visits, by design. Set crossTabSessions: true to share one session across a visitor's tabs.

Console forwarding (optional)
observ.init({ endpoint, key, forwardConsole: ['warn', 'error'] }) // or `true` for all levels

Off by default — console output can be noisy and may contain PII. The original console.* is always still called.

User-interaction spans (optional)
observ.init({ endpoint, key, userInteraction: true })

Turns clicks/submits/keydowns into spans with a ZoneContextManager so trace context survives the async work they trigger. Opt-in: it pulls in zone.js, which monkey-patches the host's global timers/Promise on import — so it is dynamically loaded only when enabled (the default bundle and host globals stay untouched).

Hold ALL telemetry until analytics consent is present (no session, no persistent id, no network):

observ.init({ endpoint, key, requireConsent: true }) // reads cookie/localStorage `observ.consent`
// …later, from your consent banner:
observ.grantConsent() // persists consent + starts the waiting SDK
observ.revokeConsent() // clears consent + shuts the SDK down

The SDK also auto-starts if another tab grants consent (via the storage event) or the banner writes the key in this tab (a light poll picks it up).

Stop (optional)
await observ.shutdown() // stops replay (flushing the residual) + tears down OTel

The SDK already flushes on visibilitychange/pagehide, so shutdown() is mainly for SPA teardown or tests.

Documentation

  • Full usage & backend (observ-server) setup — endpoints, CORS, API keys, session storage, troubleshooting: see the Observ docs (docs/session-replay-rum-sdk.md).
  • Building, releasing & maintaining this package: docs/observ-rum-sdk-maintainers.md.

MIT licensed.

Keywords