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

@osuite/rum

v3.0.0

Published

Browser Real User Monitoring SDK for Osuite — OpenTelemetry-native traces and logs, session-aware, with optional React integration.

Downloads

1,046

Readme

@osuite/rum

Browser Real User Monitoring SDK for Osuite. Emits OpenTelemetry-native traces and logs, plus rrweb session replay, so frontends get the same observability shape as backend services: trace-structured user journeys, structured errors, session replay, and end-to-end correlation with backend traces via W3C baggage.

  • One init call. Sensible defaults. Zero ceremony.
  • OpenTelemetry-native — works with any OTLP-compatible collector.
  • Session-aware — every span, log, and replay chunk carries session.id and (when set) user.id.
  • Privacy-first — replay masks all text and inputs by default; opt-in per element.
  • Sampling with error-triggered upgrade — unsampled sessions still get exported when something goes wrong.

Install

npm install @osuite/rum
# or
pnpm add @osuite/rum

For React apps that want the error boundary, install with react as a peer:

npm install @osuite/rum react

Quick start

import { osuite } from '@osuite/rum';

osuite.init({
  apiKey: 'pk_live_...',                          // or OSUITE_INGEST_TOKEN env
  endpoint: 'https://ingest.us-east-1.osuite.io', // or OSUITE_INGEST_ENDPOINT env
  apiEndpoint: 'https://api.us-east-1.osuite.io', // or OSUITE_API_ENDPOINT env

  service: {
    name: 'frontend-web',
    version: process.env.NEXT_PUBLIC_BUILD_SHA ?? 'dev',
    environment: process.env.NEXT_PUBLIC_ENV ?? 'development',
  },
});

osuite.setUser({ id: 'u_123', plan: 'pro' });

try {
  await doWork();
} catch (err) {
  osuite.captureException(err, { extra: { feature: 'checkout' } });
}

That's enough to get traces (page loads, fetch/XHR, route changes, clicks), error logs, and full session replay flowing to your collector.

Configuration

Everything beyond apiKey/endpoint/apiEndpoint/service is optional. Defaults are tuned for a typical SaaS frontend.

Required

osuite.init({
  apiKey: 'pk_live_...',
  endpoint: 'https://ingest.us-east-1.osuite.io',
  apiEndpoint: 'https://api.us-east-1.osuite.io',
  service: {
    name: 'frontend-web',
    version: '1.4.2',
    environment: 'production',
  },
});

| Field | What it does | Notes | |---|---|---| | apiKey | Public ingest token sent on every OTLP request and used to mint session JWTs for replay uploads. | Falls back to OSUITE_INGEST_TOKEN. Public — bundled in your JS. Backend rate-limits per key. | | endpoint | Base URL for OTLP traces and logs. The SDK posts to ${endpoint}/v1/traces and ${endpoint}/v1/logs. | Falls back to OSUITE_INGEST_ENDPOINT. | | apiEndpoint | Base URL for the Osuite control plane (replay presign + session JWT). The SDK posts to ${apiEndpoint}/rum/session/init and ${apiEndpoint}/rum/replay-chunk/presign. | Falls back to OSUITE_API_ENDPOINT. Different host from endpoint because replay blobs go to object storage, not OTLP. | | service.name | Identifies the frontend in your traces (e.g. frontend-web, admin-app). | Required. | | service.version | Build id — git SHA, tag, or semver. | Required. Used for source-map matching later. | | service.environment | production, staging, development, etc. | Required — backend filters and dashboards key off this. |

Session

Controls how long a "session" lasts. A session is a continuous user visit; a new one starts after a long idle, after the hard cap, or when you change the user identity.

session: {
  inactivityTimeoutMs: 30 * 60_000,  // 30 min — default
  hardCapMs: 3 * 60 * 60_000,        // 3 h — default
}

| Field | Default | What it does | |---|---|---| | inactivityTimeoutMs | 1_800_000 (30 min) | Idle time before the session rotates. Activity = clicks, keystrokes, scrolls, fetches, tab focus. | | hardCapMs | 10_800_000 (3 h) | Maximum session length regardless of activity. Prevents week-long zombie sessions. |

A session id is stored in localStorage and shared across tabs via BroadcastChannel. Calling setUser/clearUser rotates the session immediately so a logged-in session is never conflated with the pre-login one.

Sampling

Head-based session sampling for traces and logs, with an error-triggered upgrade safety net. The default is to sample everything, which is fine until your traffic grows.

sampling: {
  sessionSampleRate: 1.0,            // sample 100% of sessions — default
  upgradeOnError: true,              // promote unsampled sessions to sampled on first error
  preSampleBuffer: {
    maxItems: 500,                   // keep up to 500 spans+logs per unsampled session
    maxAgeMs: 60_000,                // ...or 60s of recent activity, whichever is smaller
  },
}

| Field | Default | What it does | |---|---|---| | sessionSampleRate | 1.0 | Probability that a session is "sampled" (everything exports immediately). Range [0, 1]. Decided once per session. Set to 0.1 to sample 10% of sessions. | | upgradeOnError | true | When true, an unsampled session is upgraded to sampled the first time an error log fires (window.error, unhandledrejection, React error, captureException, captureMessage('fatal')). Buffered spans/logs drain immediately and exporting stays on for the rest of the session. | | preSampleBuffer.maxItems | 500 | Hard cap on items held for an unsampled session. Older items evicted FIFO. | | preSampleBuffer.maxAgeMs | 60_000 | Items older than this are dropped on each push. |

Bypass list. Even when a session is unsampled, certain log records always export so the backend can see lifecycle and error context: session.start, session.end, user.changed, sampling.upgraded, browser.error, browser.resource_error, browser.unhandled_rejection, browser.react_error, replay.chunk, plus any record with severity ERROR or higher.

Replay

rrweb session replay. Privacy defaults are strict — all text and inputs are masked unless you opt in per element.

replay: {
  mode: 'always',                    // default
  sampleRate: 0.1,                   // when mode='probabilistic', record 10% of sessions
  bufferSeconds: 30,                 // rolling buffer window (used by hybrid/on-error)
  trailingSeconds: 15,               // post-trigger upload window (used by hybrid/on-error)
  maxBufferMB: 50,
  maskAllText: true,
  maskAllInputs: true,
  blockClass: 'osuite-block',
  ignoreClass: 'osuite-ignore',
  unmaskClass: 'osuite-unmask',
  uploadOnSessionEnd: false,
}

| Field | Default | What it does | |---|---|---| | mode | 'always' | always: record + upload everything. probabilistic: roll once per session at sampleRate; sampled sessions behave like always, others don't record. on-error / hybrid: planned — currently fall back to always with a warning. | | sampleRate | 0.1 | Used only by probabilistic and (when shipped) hybrid. Range [0, 1]. | | bufferSeconds | 30 | Rolling-window length used by on-error/hybrid (planned). | | trailingSeconds | 15 | How long to keep uploading after a trigger fires (planned). | | maxBufferMB | 50 | Per-session cap on IndexedDB usage. Oldest non-uploaded chunks evicted first. | | maskAllText | true | All text nodes recorded as ***. Opt back in per element with class="osuite-unmask" (or whatever unmaskClass is). | | maskAllInputs | true | All <input> / <textarea> / <select> values masked. | | blockClass | 'osuite-block' | Elements with this class become a placeholder in replay (e.g. payment forms). | | ignoreClass | 'osuite-ignore' | Events on these elements aren't recorded at all. | | unmaskClass | 'osuite-unmask' | Per-element opt-out of maskAllText. | | uploadOnSessionEnd | false | When true, force-flush the rolling buffer when the session rotates. |

HTML annotations:

<!-- Don't mask this text -->
<span class="osuite-unmask">Hello, Jane</span>

<!-- Replace with placeholder in replay -->
<div class="osuite-block">
  <CreditCardForm />
</div>

<!-- Don't record events on this element at all -->
<button class="osuite-ignore">Internal debug button</button>

Interactions

User-action spans. click is always on; richer detection is opt-in.

interactions: {
  level: 'standard',                 // default
  rageClicks: true,                  // detect frustrated repeated clicks
  deadClicks: false,                 // detect clicks that did nothing
  scrollDepth: { thresholds: [50, 100] },
}

| Field | Default | What it does | |---|---|---| | level | 'standard' | 'minimal' = clicks only. 'standard' = clicks + form submits + input focus/blur + visibility change. | | rageClicks | false | When true (or an object), emits user_interaction.rage_click when 3 clicks happen within 1 s in a 20 px radius. Also flushes the replay buffer when shipped. Tunable: {clickCount, windowMs, radiusPx}. | | deadClicks | false | Emits user_interaction.dead_click when a click triggers no DOM mutation or network within max(mutationWaitMs=100, networkWaitMs=500). Off by default — false-positive prone for legitimate "do nothing" clicks. Tunable: {mutationWaitMs, networkWaitMs}. | | scrollDepth | false | Emits user_interaction.scroll_depth when the user crosses configured percentages of page height. Defaults {thresholds: [25, 50, 75, 100]} when set to true. |

Input field values are never captured — only field name and type.

Errors

Which uncaught error sources to listen on.

errors: {
  captureWindowError: true,           // default
  captureUnhandledRejection: true,    // default
  captureResourceError: true,         // default
}

| Field | Default | What it does | |---|---|---| | captureWindowError | true | Listen on window.error for runtime errors. | | captureUnhandledRejection | true | Listen on unhandledrejection for unhandled Promise rejections. | | captureResourceError | true | Listen on window.error for failed <img>/<script>/<link>/<audio>/<video>/<source> loads. |

All routed through osuite.captureException internally — same log schema, distinguished by event.name.

Console

Default off. Opt-in to capture console.* calls as log records.

console: {
  capture: ['error', 'warn'],         // default: []
}

Each captured call:

  • Still calls the original console.* (your dev tools logs unchanged).
  • Emits event.name='browser.console_<level>'.
  • Serializes args with a circular-and-bigint-safe replacer, capped at 4 KB body.
  • console.log/info map to OTel severity INFO; warnWARN; errorERROR; debugDEBUG.

Off by default because console output is high-volume and app code typically expects console to be side-effect-free.

Navigation

SPA route-change tracking.

navigation: {
  enabled: true,                     // default
  framework: 'auto',                 // 'next' | 'history' | 'auto' (default)
}

| Field | Default | What it does | |---|---|---| | enabled | true | Set false to disable route_change spans. | | framework | 'auto' | 'auto' sniffs __NEXT_DATA__/__next_f and uses the Next adapter; otherwise falls back to a generic History API adapter. |

Both adapters patch pushState/replaceState/popstate, then declare the route settled when the DOM is quiet for 500 ms (or hits a 5 s hard cap). Fetch/XHR spans during that window become children of the route_change span automatically.

Propagation

Which outgoing requests get the traceparent and baggage headers (carrying session.id + user.id).

propagation: {
  allowlist: [/\/api\//, 'api.example.com'],   // default: []
}
  • Empty allowlist (default) = same-origin only. Same-origin requests (those starting with / or matching window.location.origin) get the headers; everything else does not.
  • Non-empty allowlist replaces same-origin matching. Strings match by substring; regexes match by .test(url).

Why an explicit allowlist matters: trace context is not normally something you want to leak to third-party APIs (analytics, ad pixels, vendor SDKs). Default same-origin avoids that.

Debug

debug: true,                         // default false

When true, the SDK prints internal diagnostics (init steps, sampling decisions, transport failures) to console.debug. Use during integration; turn off in production.

Public API

import { osuite } from '@osuite/rum';

// Identity (any session-rotating call)
osuite.setUser({ id: 'u_123', plan: 'pro', tenantId: 't_99' });
osuite.clearUser();

// Errors and messages
osuite.captureException(err, {
  extra: { feature: 'checkout', cartTotal: 42.5 },
  level: 'error',                    // default 'error'
});

osuite.captureMessage('payment retried', 'warn', {
  extra: { attempt: 2 },
});

// Active span attributes
osuite.addSpanAttributes({
  'cart.total': 42.5,
  'cart.items': 3,
});

// Replay (Phase 1: no-op debug log; will force-flush rolling buffer once hybrid mode lands)
osuite.replay.capture('user-reported-issue');

// Force everything out (useful before a navigation away)
await osuite.flush();

// Stop everything (useful in tests)
await osuite.shutdown();

extra keys become app.<key> log attributes. Levels: 'debug' | 'info' | 'warn' | 'error' | 'fatal'. 'fatal' triggers a sampling upgrade.

React adapter

Subpath @osuite/rum/react so the baseline bundle does not pull React.

import { OsuiteErrorBoundary } from '@osuite/rum/react';

export default function Root() {
  return (
    <OsuiteErrorBoundary
      fallback={<ErrorPage />}
      onError={(err, info) => {/* optional side-effect */}}
    >
      <App />
    </OsuiteErrorBoundary>
  );
}

Render errors get event.name='browser.react_error' and a react.component_stack attribute. The boundary also triggers a sampling upgrade so the buffered context around the crash exports.

What gets emitted

The full inventory lives in docs/design.md §15. Quick summary:

  • Spans (OTLP /v1/traces): documentLoad, documentFetch, resourceFetch, HTTP {METHOD}, route_change, user_interaction.click, user_interaction.form_submit, user_interaction.input_focus/input_blur, user_interaction.visibility_change, plus opt-in user_interaction.rage_click / dead_click / scroll_depth.
  • Log records (OTLP /v1/logs): session.start, session.end, user.changed, sampling.upgraded, browser.error, browser.resource_error, browser.unhandled_rejection, browser.react_error, browser.console_* (opt-in), browser.manual, replay.chunk.
  • Replay chunks (S3 presigned PUT, not OTLP): gzipped rrweb event blobs. Each successful upload also emits a replay.chunk pointer log so they're queryable from the session log stream.

Every span and log carries session.id, user.id?, service.name, service.version, service.environment, plus browser/connection metadata.

Privacy

  • Replay masking is on by default. Text → ***, inputs → masked. Opt-in per element with osuite-unmask / osuite-block / osuite-ignore.
  • No PII in user.id or extra — by policy. The SDK does not inspect them; you're responsible for not passing email, phone, etc.
  • Trace headers same-origin only by default.
  • JWT in memory only — never written to localStorage.
  • apiKey is bundled (it's a public token). Backend enforces per-key, per-IP rate limits.

Bundle size

| Entry | Target (gzip) | |---|---| | @osuite/rum baseline | < 15 KB | | @osuite/rum/react | < 1 KB | | Replay (rrweb + pako, lazy-loaded) | ~40 KB |

Replay is loaded via dynamic import() so the cost lands only when replay actually starts.

Browser support

Modern evergreen browsers (Chrome, Edge, Firefox, Safari). ES2020 target. Requires BroadcastChannel for cross-tab session sync (falls back to storage event), IndexedDB for replay buffering (falls back to in-memory), and localStorage (falls back to in-memory; cross-tab disabled).

Design

Full spec, decisions, and signals inventory: docs/design.md.