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

@rushobservability/rum-sdk

v0.2.0

Published

Rush RUM (Real User Monitoring) browser SDK

Readme

@rushobservability/rum-sdk

Real User Monitoring (RUM) browser SDK for Rush Observability. Lightweight, dependency-light, and framework-agnostic — it captures Web Vitals, JS errors, page views, interactions, resource timing, and optional session replay, then ships them to your Rush ingest endpoint.

Install

npm install @rushobservability/rum-sdk

Usage

import RushRUM from '@rushobservability/rum-sdk'

RushRUM.init({
  endpoint: 'https://your-rush-host/rum/ingest',
  app: { name: 'my-web-app', version: '1.4.2' },
  environment: 'production',

  // What to collect (defaults shown)
  trackWebVitals: true,     // LCP, INP, CLS, FCP, TTFB (via web-vitals)
  trackErrors: true,        // uncaught errors + unhandled rejections
  trackPageViews: true,     // SPA-aware page views
  trackLongTasks: true,     // long tasks + Long Animation Frames (LoAF)
  trackInteractions: false, // click/input + frustration signals
  trackResources: false,    // resource timing entries
  trackSessionReplay: false,// DOM session replay (via rrweb)

  // Optional
  sampleRate: 1.0,                       // 0..1
  user: () => ({ id: currentUserId }),   // attach a user id
  propagateTraces: { origins: [/^https:\/\/api\.example\.com/] },

  // Replay privacy (default 'mask' — masks ALL text + inputs, blocks media)
  replayPrivacy: 'mask',

  // Mutate/drop events before they're queued (return null to drop)
  beforeSend: (event) => event,
})

// Custom events
RushRUM.trackEvent('checkout_completed', { plan: 'pro', amount: 49 })

// Dynamically set/override the current user (overrides config.user)
RushRUM.setUser({ id: 'user-123' })
RushRUM.setUser(null) // clear

// Attributes merged into every event's `attributes` (event keys win)
RushRUM.setGlobalAttributes({ tenant: 'acme', release: 'canary' })
RushRUM.clearGlobalAttributes()

// Flush the queue manually (e.g. before a hard navigation)
RushRUM.flush()

// Stop collecting + detach handlers/timers (tests, SPA hot-reload)
RushRUM.destroy()

Configuration

| Option | Type | Default | Description | |---|---|---|---| | endpoint | string | — (required) | Rush RUM ingest URL | | app | { name, version? } | — (required) | Application identity | | environment | string | — | e.g. production, staging | | sampleRate | number | 1.0 | Fraction of sessions to record (0–1) | | user | () => { id? } \| null | — | Resolver for the current user id | | trackWebVitals | boolean | true | Core Web Vitals | | trackErrors | boolean | true | Uncaught errors + promise rejections | | trackPageViews | boolean | true | SPA-aware page views | | trackLongTasks | boolean | true | Long tasks (>50ms) + Long Animation Frames (LoAF), via PerformanceObserver | | trackInteractions | boolean | false | Click/input events + frustration signals (rage/dead/error clicks) | | trackResources | boolean | false | Resource timing | | trackSessionReplay | boolean | false | DOM session replay (rrweb) | | propagateTraces | { origins: RegExp[] } | — | Inject trace headers on matching XHR/fetch origins | | replayEndpoint | string | <endpoint>/rum/replay/ingest | Override the replay ingest URL | | captureQueryParams | boolean | false | Keep query strings + hash on captured URLs. Off by default — query params often carry tokens/PII | | maskInteractionText | boolean | false | Drop clicked-element text from interaction events (keep only tag/id/classes) | | compress | boolean | false | gzip the request body (Content-Encoding: gzip) via CompressionStream. Only enable if your ingest endpoint decodes gzip — the current Rush backend does not. The unload/beacon path is always uncompressed | | beforeSend | (event) => RumEvent \| null | — | Mutate or drop each event before it's queued (return null to drop). A throwing hook never breaks collection | | replayPrivacy | 'mask' \| 'mask-user-input' \| 'allow' | 'mask' | Replay masking level (see below) | | replayMaskSelector | string | — | Extra CSS selector for text/elements to mask in replay (merged with [data-pii]) | | replayBlockSelector | string | — | Extra CSS selector for elements to block (not record) in replay | | replayUnmaskSelector | string | — | Extra CSS selector for text to leave un-masked in replay (only applies when text masking is on) | | replayBeforeAddEvent | (event) => unknown \| null | — | Scrub or drop replay events before buffering (return null to drop) |

Methods

| Method | Description | |---|---| | RushRUM.init(config) | Initialize and start collection (SSR-safe no-op; ignores duplicate calls) | | RushRUM.trackEvent(name, attributes?) | Send a custom event | | RushRUM.setUser(user \| null) | Dynamically set/override the user id (overrides config.user); null clears | | RushRUM.setGlobalAttributes(attrs) | Merge attributes into every subsequent event's attributes (event-specific keys win) | | RushRUM.clearGlobalAttributes() | Clear all global attributes | | RushRUM.flush() | Force-flush the queue | | RushRUM.destroy() | Stop collection; detach all observers/timers/listeners |

Replay privacy

As of 0.2.0, session replay is private by default — a behavior change from 0.1.x, which masked inputs but recorded all visible text. The replayPrivacy level maps to rrweb options:

| Level | Inputs | Text | Media | |---|---|---|---| | 'mask' (default) | masked | all text masked | img,video,audio,picture,source,canvas blocked | | 'mask-user-input' | masked | visible | recorded | | 'allow' | recorded | visible | recorded |

[data-pii] is always masked regardless of level. replayMaskSelector / replayBlockSelector add more, and replayUnmaskSelector carves out exceptions when text masking is on.

Reliability

Event batches are sent through a hardened transport: failed sends (network error, HTTP 429/5xx) are retried up to 3× with exponential backoff + jitter. Batches that still fail — or any sent while navigator.onLine === false — are persisted to localStorage (versioned key, ~1 MB / 50-batch cap, oldest dropped on overflow) and drained oldest-first on the next init() and whenever the online event fires. The in-memory queue is capped at 1000 events (oldest dropped). The unload/beacon path uses sendBeacon (uncompressed, no retry). All of this is best-effort and never throws into your app.

Frustration signals

When trackInteractions is enabled, the SDK emits frustration events:

  • rage_click — more than 3 clicks on the same element within 1s.
  • dead_click — a click on an interactive element that produces no DOM mutation, URL change, or scroll within 3s (conservative, to avoid false positives).
  • error_click — a JS error fires within 1s of a click.

sampleRate is decided once per session and applied to every event, so sampled sessions are complete (no half-captured sessions).

init() is a no-op outside the browser (SSR-safe) and ignores duplicate calls. RushRUM.destroy() stops collection and detaches handlers/timers (useful for tests / SPA hot-reload).

Build

npm install
npm run build      # tsc → dist/
npm run typecheck  # type-check only

License

MIT