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

@spanwise/rum

v0.8.0

Published

Real User Monitoring SDK for Spanwise - track sessions, page views, clicks, and Core Web Vitals

Downloads

38

Readme

@spanwise/rum

Real User Monitoring SDK for browser applications. Captures page views, clicks, errors, Web Vitals, and session replays.

Installation

npm install @spanwise/rum
# or
pnpm add @spanwise/rum
# or
yarn add @spanwise/rum

Quick Start

import { createSpanwiseBrowser } from "@spanwise/rum"

const spanwise = createSpanwiseBrowser({
  apiKey: "sw_...",
  serviceName: "my-app",
})

// That's it! Page views, clicks, errors, and vitals are tracked automatically.

Configuration

const spanwise = createSpanwiseBrowser({
  // Required
  apiKey: "sw_...",

  // Optional
  serviceName: "my-app",           // Service name for filtering
  sampleRate: 1.0,                 // Sample 100% of sessions (default: 1.0)
  trackPageViews: true,            // Track page views (default: true)
  trackClicks: true,               // Track clicks (default: true)
  trackVitals: true,               // Track Web Vitals (default: true)
  trackErrors: true,               // Track errors (default: true)
  trackResources: true,            // Track fetch/XHR requests (default: true)
  sessionStorage: "cookie",        // "cookie" or "sessionStorage" (default: "cookie")

  // Distributed Tracing
  tracing: {
    enabled: true,                 // Inject traceparent headers (default: true)
    propagateToOrigins: [          // Origins to propagate trace context to
      "https://api.example.com",
      /\.example\.com$/,
    ],
  },

  // Session Replay (opt-in)
  replay: {
    enabled: true,                 // Enable replay recording (default: false)
    sampleRate: 0.1,               // Record 10% of sessions (default: 0.1)
    onErrorSampleRate: 1.0,        // Record 100% of sessions with errors (default: 1.0)
    privacyMode: "strict",         // Privacy mode (default: "strict")
  },
})

API

spanwise.trackEvent(name, properties?)

Track a custom event.

spanwise.trackEvent("purchase", {
  productId: "123",
  amount: 99.99,
})

spanwise.trackPageView(url?)

Manually track a page view. Called automatically on navigation.

spanwise.trackPageView("/checkout")

spanwise.trackError(error, options?)

Manually track an error.

try {
  await riskyOperation()
} catch (error) {
  spanwise.trackError(error, {
    requestId: "req_123",
    traceId: "trace_abc",
  })
}

spanwise.setUser(id, traits?)

Identify the current user.

spanwise.setUser("user_123", {
  email: "[email protected]",
  plan: "pro",
})

spanwise.getSessionId()

Get the current session ID.

const sessionId = spanwise.getSessionId()

spanwise.forceReplayUpload()

Force replay upload for specific users (bypasses sampling).

// Force upload for VIP users regardless of sample rate
if (user.isVIP) {
  spanwise.forceReplayUpload()
}

spanwise.stopReplay()

Stop replay recording entirely.

spanwise.isReplayUploading()

Check if replay is currently uploading to server (vs buffering in memory).


Session Replay

Session Replay records user interactions as a video-like playback. It captures DOM mutations, mouse movements, clicks, and scrolls.

Enabling Replay

const spanwise = createSpanwiseBrowser({
  apiKey: "sw_...",
  replay: {
    enabled: true,
  },
})

How It Works: Rolling Buffer

Session Replay uses a rolling buffer architecture (similar to Sentry):

  1. Always recording - When enabled, the SDK always records to a 60-second memory buffer
  2. Sampled sessions (default 10%) - Upload immediately to server
  3. Non-sampled sessions (90%) - Keep buffer in memory, zero network cost
  4. On error - Flush the 60-second buffer and start uploading

This means you always capture what happened before an error, not just after.

Sampling

replay: {
  enabled: true,
  sampleRate: 0.1,         // 10% upload immediately
  onErrorSampleRate: 1.0,  // 100% upload on error (includes 60s buffer)
}

| Scenario | What's Captured | |----------|-----------------| | Sampled (10%) | Full session from start | | Not sampled + error | 60 seconds before error + everything after | | Not sampled + no error | Nothing sent (zero network cost) |


Privacy & Data Protection

Session Replay is designed with privacy as the default. No configuration is required for most applications - sensitive data is automatically protected.

Privacy Modes

| Mode | Text | Inputs | Use Case | |------|------|--------|----------| | "strict" (default) | Masked | Masked | Most applications | | "balanced" | Visible | Masked | Marketing sites, blogs | | "permissive" | Visible | Visible | Internal tools only |

replay: {
  enabled: true,
  privacyMode: "strict",  // Default - masks everything
}

What Each Mode Does

Strict Mode (Default)

All text content is replaced with ****. Form inputs show •••••. You see the page structure and user interactions, but no actual content.

┌─────────────────────────────┐
│  **** ********              │  ← Masked heading
│                             │
│  ******** **** ** *******   │  ← Masked paragraph
│  ******* ** *** ****        │
│                             │
│  Email: [•••••••••••••]     │  ← Masked input
│  Password: [••••••••]       │
│                             │
│  [**********]               │  ← Masked button
└─────────────────────────────┘

Balanced Mode

Text is visible, but all form inputs are masked. Good for content-heavy sites.

┌─────────────────────────────┐
│  Welcome Back               │  ← Visible heading
│                             │
│  Please sign in to continue │  ← Visible text
│                             │
│  Email: [•••••••••••••]     │  ← Masked input
│  Password: [••••••••]       │
│                             │
│  [Sign In]                  │  ← Visible button
└─────────────────────────────┘

Permissive Mode

Everything is recorded as-is. Only use for internal tools or with explicit user consent.

Fine-Grained Control with HTML Attributes

Regardless of privacy mode, you can control specific elements:

Block Elements Completely

Use data-sw-block to completely hide an element. It will appear as a gray placeholder in replays.

<!-- Credit card form - never record -->
<div data-sw-block>
  <input type="text" placeholder="Card number" />
  <input type="text" placeholder="CVV" />
</div>

<!-- Sensitive user data -->
<div data-sw-block class="user-profile">
  <p>SSN: 123-45-6789</p>
</div>

Mask Specific Elements

Use data-sw-mask to mask text while preserving structure (useful in balanced/permissive modes).

<!-- Mask email in a visible section -->
<p>Contact: <span data-sw-mask>[email protected]</span></p>

Unmask Elements in Strict Mode

Use data-sw-unmask to show content even when using strict mode.

<!-- Show navigation labels in strict mode -->
<nav data-sw-unmask>
  <a href="/">Home</a>
  <a href="/products">Products</a>
  <a href="/about">About</a>
</nav>

<!-- Show static content -->
<footer data-sw-unmask>
  <p>© 2024 My Company</p>
</footer>

rrweb Classes (Alternative)

The standard rrweb classes also work:

<div class="rr-block">Hidden content</div>
<div class="rr-mask">Masked content</div>

Best Practices

  1. Start with strict mode - It's the safest default. Only relax if needed.

  2. Block sensitive sections entirely - For areas with PII (profile pages, account settings), use data-sw-block.

  3. Review before production - Test your app with replay enabled and verify no sensitive data appears.

  4. Consider user consent - For GDPR compliance, inform users that sessions may be recorded.

  5. Use unmask sparingly - Only unmask truly static, non-sensitive content like navigation labels.

What's Automatically Protected

Even without configuration:

  • All <input> values are masked (except in permissive mode)
  • Password fields are always masked
  • Credit card patterns are detected and masked
  • <script> content is never recorded
  • <head> metadata is stripped

Sensitive Data Checklist

Before enabling replay, ensure these are blocked or masked:

  • [ ] User profile information (name, email, phone)
  • [ ] Payment forms and credit card inputs
  • [ ] Social security numbers, government IDs
  • [ ] Medical or health information
  • [ ] Authentication tokens displayed on screen
  • [ ] Private messages or chat content
  • [ ] Financial account numbers
  • [ ] Any PII in confirmation pages

Distributed Tracing

The SDK automatically injects traceparent headers into fetch/XHR requests, connecting frontend sessions to backend traces.

const spanwise = createSpanwiseBrowser({
  apiKey: "sw_...",
  tracing: {
    enabled: true,
    propagateToOrigins: [
      "https://api.myapp.com",
      /\.myapp\.com$/,
    ],
  },
})

Your backend will receive headers like:

traceparent: 00-{traceId}-{spanId}-01

Browser Support

  • Chrome 64+
  • Firefox 67+
  • Safari 12+
  • Edge 79+

Session Replay requires:

  • MutationObserver
  • WeakMap
  • requestAnimationFrame

Bundle Size

The base SDK is ~8KB gzipped. Session Replay adds ~40KB gzipped (rrweb).

Replay is dynamically imported only when enabled, so it won't affect your bundle if not used.


TypeScript

Full TypeScript support with exported types:

import {
  createSpanwiseBrowser,
  type BrowserConfig,
  type SpanwiseBrowser,
  type ReplayConfig,
  type PrivacyMode,
  type TracingConfig,
} from "@spanwise/rum"

Troubleshooting

Replay not recording

  1. Check that replay.enabled is true
  2. Verify you're within the sample rate (try sampleRate: 1.0 for testing)
  3. Check browser console for errors

High data volume

  1. Reduce sampleRate (e.g., 0.05 for 5%)
  2. Use "strict" privacy mode (smaller payloads due to masked content)

Missing trace correlation

  1. Ensure tracing.propagateToOrigins includes your API domain
  2. Check that your backend parses the traceparent header

License

MIT