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

@vunet/otel-rum

v0.1.2-6.21473182407

Published

Adds OpenTelemetry tracing auto-instrumentation in the browser. Collects spans on network events and sends them to Vunet Systems.

Readme


Table of Contents


Overview

The Vunet RUM Browser SDK is a modular Real User Monitoring solution that captures telemetry data from web applications. Built on OpenTelemetry, it provides automatic instrumentation for page loads, user interactions, network requests, and session replay.

OpenTelemetry Based: The SDK uses industry-standard OpenTelemetry APIs and protocols, ensuring compatibility with a wide range of observability backends.

How It Works

  1. Script Loading: The loader script is added to your page via a <script> tag
  2. Decide API: The SDK calls the decide API to determine sampling percentages
  3. Module Loading: Based on sampling, Core RUM and/or Session Replay modules are loaded
  4. Initialization: Modules register themselves and are initialized with configuration
  5. Data Collection: Telemetry data is captured and batched
  6. Export: Data is sent to the OTLP collector endpoint

Features

| Feature | Description | |---------|-------------| | Modular Architecture | Load only what you need - Core RUM, Session Replay, or both | | Server-Side Sampling | Decide API controls what percentage of users are monitored | | W3C Trace Context | Propagates trace headers across distributed systems | | Automatic Instrumentation | Zero-config capture of common web interactions | | Session Persistence | Maintains session IDs across page navigations | | User Mapping | Associate telemetry with user identities | | Custom Spans | Create custom traces for business-specific flows | | Error Recording | Capture and report JavaScript errors automatically |

Automatic Instrumentations

  • XMLHttpRequest and Fetch APIs auto-instrumentation
  • User interactions (click, submit, drop, etc.)
  • Document load with fetched resources
  • History API and hash change support (SPA navigation)
  • Web Vitals (TTFB, FCP, LCP)
  • Session ID tracking
  • Long tasks with automatic context attaching
  • Uncaught exceptions, unhandled rejections, document errors, and console errors
  • Automatic context carrying through timers, promises, native async-await, events, observers

Core Components

Core RUM

Captures traces, spans, and performance metrics using OpenTelemetry instrumentation:

  • Page load performance (TTFB, FCP, LCP)
  • XHR and Fetch requests
  • User interactions (clicks, inputs)
  • Long tasks (>50ms)
  • JavaScript errors
  • Route changes (SPA navigation)

Session Replay

Records user sessions as visual replays using rrweb:

  • Full DOM snapshots at session start
  • Incremental DOM mutations
  • Mouse movements and clicks
  • Scroll positions
  • Input field changes (with masking for sensitive data)
  • Window resize events
  • Console logs (optional)

Module Registry

Coordinates module loading with a callback-based system:

  • Promise-based module waiting
  • Eliminates race conditions
  • Timeout handling for failed loads
  • Supports parallel module loading

Architecture

Project Structure

src/
├── index.ts                    # Loader - Entry point, loads modules
├── core-rum/
│   ├── index.ts                # Core RUM module - OpenTelemetry setup
│   ├── SessionExporter.ts      # OTLP trace exporter with persistence
│   ├── sumologic-span-processor/  # Span processing and enrichment
│   ├── sumologic-logs-exporter/   # Log/error exporter
│   ├── sumologic-context-manager/ # Context propagation
│   └── FormInstrumentation.ts  # Form navigation tracking
├── session-replay/
│   ├── index.ts                # Session Replay module
│   └── vunet-rrweb/
│       ├── rrweb.ts            # rrweb recorder and exporter
│       ├── decideApi.ts        # Decide API client
│       ├── FailCounter.ts      # API failure tracking
│       └── types.ts            # Type definitions
├── shared/
│   ├── index.ts                # Shared exports
│   ├── moduleRegistry.ts       # Module ready callback system
│   ├── globals.ts              # Global type declarations (window.vunetRum)
│   ├── types.ts                # Shared TypeScript types
│   ├── logger.ts               # Internal logger
│   └── mapUser.ts              # User mapping utilities
└── utils/
    └── helpers.ts              # Utility functions

Module Dependency Flow

flowchart TB
    subgraph Browser["Browser Window"]
        Script["script tag"]
    end

    subgraph Loader["Loader - index.ts"]
        Init["initialize()"]
        DecideAPI["callDecideApi()"]
        LoadScript["loadScript()"]
    end

    subgraph Registry["Module Registry"]
        WaitFor["waitForModule()"]
        Register["registerModuleReady()"]
    end

    subgraph CoreRUM["Core RUM Module"]
        OTel["OpenTelemetry Setup"]
        Tracer["TracerProvider"]
        Instruments["Instrumentations"]
        Exporter["OTLPTraceExporter"]
    end

    subgraph SessionReplay["Session Replay Module"]
        RRWeb["rrweb Recorder"]
        SRExporter["SessionReplayExporter"]
    end

    subgraph Backend["Backend"]
        DecideServer["Decide API Server"]
        Collector["OTLP Collector"]
    end

    Script --> Init
    Init --> DecideAPI
    DecideAPI --> DecideServer
    DecideServer --> DecideAPI
    Init --> LoadScript
    LoadScript --> WaitFor
    
    CoreRUM --> Register
    SessionReplay --> Register
    Register --> WaitFor
    WaitFor --> Init

    Init --> OTel
    Init --> RRWeb

    OTel --> Tracer
    Tracer --> Instruments
    Tracer --> Exporter
    Exporter --> Collector

    RRWeb --> SRExporter
    SRExporter --> Collector

Data Flow

flowchart LR
    subgraph Client["Browser"]
        UI["User Interactions"]
        Network["Network Requests"]
        Errors["JS Errors"]
        DOM["DOM Changes"]
    end

    subgraph SDK["Vunet RUM SDK"]
        CoreRUM["Core RUM"]
        SR["Session Replay"]
        Buffer["Span Buffer"]
        SRBuffer["Replay Buffer"]
    end

    subgraph Export["Export Layer"]
        OTLP["OTLP Exporter"]
        SRExp["SR Exporter"]
    end

    subgraph Backend["Backend"]
        Collector["OTLP Collector"]
    end

    UI --> CoreRUM
    Network --> CoreRUM
    Errors --> CoreRUM
    DOM --> SR

    CoreRUM --> Buffer
    SR --> SRBuffer

    Buffer --> OTLP
    SRBuffer --> SRExp

    OTLP --> Collector
    SRExp --> Collector

Build Output

The SDK is built into three separate bundles:

| File | Description | Size (approx) | |------|-------------|---------------| | vunet-rum-loader.js | Main loader script - always loaded first | ~15 KB | | vunet-rum-core.js | Core RUM module - OpenTelemetry instrumentations | ~150 KB | | vunet-rum-session-replay.js | Session Replay module - rrweb recorder | ~100 KB |

Modular Loading: Only the loader is initially loaded. Core RUM and Session Replay are loaded on-demand based on the decide API response.


Quick Start

The easiest way to start collecting traces from your website is to put the code below inside the <head></head> tags:

<script
  id="vunet-rum"
  src="https://cdn.vunet.ai/rum/vunet-rum-loader.js"
  data-collection-source-url="https://collector.example.com/v1/traces"
  data-application-name="my-app"
  data-service-name="my-frontend"
></script>

That's it! With properly provided configuration, your website is ready and will send collected traces to the specified collector.


Installation

Script Tag (Recommended)

Auto-initialization with Data Attributes

<script
  id="vunet-rum"
  src="https://cdn.vunet.ai/rum/vunet-rum-loader.js"
  data-collection-source-url="https://collector.example.com/v1/traces"
  data-service-name="my-frontend"
  data-application-name="my-app"
  data-default-attributes='{"version":"1.2.3","env":"production"}'
  data-rum-logging="true"
></script>

Manual Initialization

<script src="https://cdn.vunet.ai/rum/vunet-rum-loader.js"></script>
<script>
  window.vunetRum.initialize({
    collectionSourceUrl: 'https://collector.example.com/v1/traces',
    serviceName: 'my-frontend',
    applicationName: 'my-app',
    propagateTraceHeaderCorsUrls: [/api\.myservice\.com/],
    collectErrors: true,
  });
</script>

Async Loading

You can load the script asynchronously, but some functionalities like user interactions or requests made before script run will be limited:

<script>
  (function (w, s, d, r, e, n) {
    (w[s] = w[s] || {
      readyListeners: [],
      onReady: function (e) {
        w[s].readyListeners.push(e);
      },
    }),
      ((e = d.createElement('script')).async = 1),
      (e.src = r),
      (n = d.getElementsByTagName('script')[0]).parentNode.insertBefore(e, n);
  })(window, 'vunetRum', document, 'https://cdn.vunet.ai/rum/vunet-rum-loader.js');
  
  window.vunetRum.onReady(function () {
    window.vunetRum.initialize({
      collectionSourceUrl: 'https://collector.example.com/v1/traces',
      serviceName: 'my-frontend',
      propagateTraceHeaderCorsUrls: [/api\.myservice\.com/],
      collectErrors: true,
    });
  });
</script>

NPM Installation

npm install @vunet/otel-rum
import { initialize } from '@vunet/otel-rum';

initialize({
  collectionSourceUrl: 'https://collector.example.com/v1/traces',
  serviceName: 'my-frontend',
  applicationName: 'my-app',
  propagateTraceHeaderCorsUrls: [/api\.myservice\.com/],
});

Configuration

Initialize Options

All configuration options passed to initialize():

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | collectionSourceUrl | string | Yes | - | OTLP collector endpoint URL for sending telemetry | | decideApiEndpoint | string | No | /vuSmartMaps/api/rum/ | Decide API endpoint or static config (sr:100,sp:100) | | authorizationToken | string | No | - | Authorization header value for collector requests | | serviceName | string | No | unknown_service | Service name for telemetry attribution | | applicationName | string | No | - | Application name identifier | | deploymentEnvironment | string | No | - | Environment name (e.g., 'production', 'staging') | | defaultAttributes | object | No | {} | Custom attributes added to all spans | | bufferMaxSpans | number | No | 2048 | Maximum spans to buffer before forcing export | | maxExportBatchSize | number | No | 50 | Maximum spans per export batch | | bufferTimeout | number | No | 2000 | Timeout in ms before forcing export | | ignoreUrls | (string\|RegExp)[] | No | [] | URL patterns to exclude from instrumentation | | propagateTraceHeaderCorsUrls | (string\|RegExp)[] | No | [] | URLs where trace headers should be propagated | | collectSessionId | boolean | No | true | Include session ID in spans | | collectErrors | boolean | No | false | Enable automatic error collection | | dropSingleUserInteractionTraces | boolean | No | false | Drop traces with only one user interaction span | | dropShortTracesMs | number | No | 0 | Drop traces shorter than this duration (0 = disabled) | | userInteractionElementNameLimit | number | No | 20 | Max length for user interaction element names | | rumLogging | boolean | No | false | Enable SDK debug logging to console |

Decide API Response

The decide API returns sampling configuration:

interface ApiResponseData {
  sampling_percentage: number;       // 0-100, controls Core RUM sampling
  session_replay_percentage: number; // 0-100, controls Session Replay sampling
  eventFilter: EventFilter[];        // Optional event filtering rules
}

Sampling Logic

Important: Session Replay requires Core RUM to be loaded first.

| sampling_percentage | session_replay_percentage | Result | |---------------------|---------------------------|--------| | 0 | Any | Nothing loads - SDK disabled | | 100 | 0 | Only Core RUM loads | | 100 | 50 | Core RUM + Session Replay (50% of sessions recorded) | | 50 | 100 | 50% of users get Core RUM + Session Replay |

Static Decide Configuration

For testing or when you don't have a decide API server:

// Static format: sr:SESSION_REPLAY%,sp:SAMPLING%
window.vunetRum.initialize({
  collectionSourceUrl: 'https://collector.example.com/v1/traces',
  decideApiEndpoint: 'sr:100,sp:100', // 100% for both
});

Data Attributes

Configuration can be provided via script tag data attributes:

| Data Attribute | Maps To | |----------------|---------| | data-collection-source-url | collectionSourceUrl | | data-authorization-token | authorizationToken | | data-decide-api-endpoint | decideApiEndpoint | | data-service-name | serviceName | | data-application-name | applicationName | | data-default-attributes | defaultAttributes (JSON string) | | data-buffer-max-spans | bufferMaxSpans | | data-buffer-timeout | bufferTimeout | | data-ignore-urls | ignoreUrls (JSON array or comma-separated) | | data-propagate-trace-header-cors-urls | propagateTraceHeaderCorsUrls | | data-rum-logging | rumLogging ("true" or "") |


API Reference

All methods are available under the window.vunetRum object.

initialize(options)

Initializes the SDK with the provided configuration.

await window.vunetRum.initialize({
  collectionSourceUrl: 'https://collector.example.com/v1/traces',
  applicationName: 'my-app',
  serviceName: 'frontend',
  rumLogging: true,
});

mapUser(id, userData)

Associates user identity with the current session. User data is attached to all subsequent spans and session replay data.

window.vunetRum.mapUser('user-123', {
  email: '[email protected]',
  name: 'John Doe',
  plan: 'premium',
  company: 'Acme Corp',
});

setDefaultAttribute(key, value)

Adds a custom attribute to all spans.

window.vunetRum.setDefaultAttribute('feature.darkMode', true);
window.vunetRum.setDefaultAttribute('experiment.variant', 'B');
window.vunetRum.setDefaultAttribute('app.version', '2.1.0');

recordError(message, attributes?)

Records a custom error with optional attributes.

window.vunetRum.recordError('Payment failed', {
  errorCode: 'PAYMENT_DECLINED',
  amount: 99.99,
  paymentMethod: 'credit_card',
});

getCurrentSessionId()

Returns the current session ID (UUID format).

const sessionId = window.vunetRum.getCurrentSessionId();
console.log('Session:', sessionId);
// Output: "abc123-def456-789..."

onReady(callback)

Registers a callback to be invoked when the SDK is fully initialized.

window.vunetRum.onReady((getConfigOverrides) => {
  console.log('SDK is ready!');
  window.vunetRum.setDefaultAttribute('ready', true);
});

disableInstrumentations()

Stops all automatic instrumentations.

window.vunetRum.disableInstrumentations();
// ... perform sensitive operation ...
window.vunetRum.registerInstrumentations();

registerInstrumentations()

Re-enables all automatic instrumentations after they were disabled.

window.vunetRum.registerInstrumentations();

stopProcessing()

Completely stops all SDK processing. This cannot be undone - page reload required to restart.

window.vunetRum.stopProcessing();

tracer

OpenTelemetry Tracer instance for creating custom spans.

window.vunetRum.tracer.startActiveSpan('custom-operation', (span) => {
  span.setAttribute('custom.attribute', 'value');
  // Do work...
  span.end();
});

api

OpenTelemetry API instance for context and propagation.

const { context, trace } = window.vunetRum.api;
const activeSpan = trace.getActiveSpan();
const ctx = context.active();

Usage Examples

User Identification

// After user logs in
function onUserLogin(user) {
  window.vunetRum.mapUser(user.id, {
    email: user.email,
    name: user.name,
    plan: user.subscription?.plan || 'free',
    company: user.company?.name,
    role: user.role,
  });
}

// After user logs out
function onUserLogout() {
  window.vunetRum.mapUser('anonymous', {});
}

Custom Spans for Business Logic

async function checkout(cart) {
  return window.vunetRum.tracer.startActiveSpan('checkout', async (span) => {
    try {
      span.setAttribute('cart.items', cart.items.length);
      span.setAttribute('cart.total', cart.total);

      // Nested span for payment
      const result = await window.vunetRum.tracer.startActiveSpan('process-payment', async (paymentSpan) => {
        paymentSpan.setAttribute('payment.method', cart.paymentMethod);
        const res = await processPayment(cart);
        paymentSpan.setAttribute('payment.transactionId', res.transactionId);
        paymentSpan.end();
        return res;
      });

      span.setAttribute('checkout.success', true);
      span.setAttribute('order.id', result.orderId);
      return result;
    } catch (error) {
      span.recordException(error);
      span.setStatus({ code: 2 }); // ERROR
      throw error;
    } finally {
      span.end();
    }
  });
}

Feature Flags & A/B Testing

function initializeFeatureFlags(flags) {
  Object.entries(flags).forEach(([key, value]) => {
    window.vunetRum.setDefaultAttribute(`feature.${key}`, value);
  });
}

function trackExperiment(experimentName, variant) {
  window.vunetRum.setDefaultAttribute(`experiment.${experimentName}`, variant);
}

// Example usage
trackExperiment('checkout_redesign', 'variant_b');

React Integration

// src/rum.js
export function initializeRUM(config) {
  return window.vunetRum.initialize({
    collectionSourceUrl: config.collectorUrl,
    applicationName: config.appName,
    serviceName: 'react-frontend',
    defaultAttributes: {
      'app.version': config.version,
      'react.version': React.version,
    },
  });
}

// src/App.jsx
import { useEffect } from 'react';
import { initializeRUM } from './rum';

function App() {
  useEffect(() => {
    initializeRUM({
      collectorUrl: process.env.REACT_APP_COLLECTOR_URL,
      appName: process.env.REACT_APP_NAME,
      version: process.env.REACT_APP_VERSION,
    });
  }, []);

  return <YourApp />;
}

Session Correlation with Support

function getSupportContext() {
  return {
    sessionId: window.vunetRum.getCurrentSessionId(),
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: new Date().toISOString(),
  };
}

function reportBug(description) {
  const context = getSupportContext();
  return fetch('/api/support/ticket', {
    method: 'POST',
    body: JSON.stringify({ description, ...context }),
  });
}

Trace Context Propagation

By default, trace context propagation for cross-origin requests is not enabled due to browser CORS security restrictions.

Enabling CORS Trace Propagation

Set exact URLs or URL patterns in propagateTraceHeaderCorsUrls:

window.vunetRum.initialize({
  collectionSourceUrl: 'https://collector.example.com/v1/traces',
  propagateTraceHeaderCorsUrls: [
    /^https:\/\/api\.myservice\.com\/.*/,
    /^http:\/\/localhost:3000\/api\/.*/,
  ],
});

You must configure your server to accept and return these CORS headers:

Access-Control-Allow-Headers: traceparent, tracestate

Read W3C Trace Context for more details.

Baggage

Baggage is contextual information passed between spans - a key-value store that resides alongside span context:

const baggage =
  window.vunetRum.api.propagation.getBaggage(
    window.vunetRum.api.context.active(),
  ) || window.vunetRum.api.propagation.createBaggage();

baggage.setEntry('customerId', { value: 'customer-id-value' });
window.vunetRum.api.propagation.setBaggage(
  window.vunetRum.api.context.active(),
  baggage,
);

Development Setup

Prerequisites

  • Node.js: Version >= 18.0.0 (recommended: 20.19.4)
  • npm: Version >= 9.0.0

Quick Start (Automated)

# Clone with submodules
git clone --recurse-submodules [email protected]:vunet-systems/vunet-rum-browser-sdk.git
cd vunet-rum-browser-sdk

# Run setup script
./setup.sh

# Build
npm run build

Manual Setup

# Clone repository
git clone --recurse-submodules [email protected]:vunet-systems/vunet-rum-browser-sdk.git
cd vunet-rum-browser-sdk

# If cloned without submodules
git submodule update --init --recursive

# Install dependencies
npm install

# Build
npm run build

Git Submodules

This project uses Git submodules for:

  • src/opentelemetry-js
  • src/opentelemetry-js-contrib

Running Tests

# Run all tests
npm test

# Unit tests only
npm run test:ut

# End-to-end tests only
npm run test:e2e

Development Workflow

# Linting
npm run eslint-fix

# Formatting
npm run prettier-format-all

# Type checking
npm run types

Troubleshooting

Submodule Issues:

git submodule deinit --all -f
git submodule update --init --recursive

Node Version Mismatch:

nvm use  # Uses version from .nvmrc

Dependency Issues:

rm -rf node_modules package-lock.json
npm install

For detailed setup instructions, see HOW_TO_RUN.md.


Browser Support

The SDK supports all modern browsers:

| Browser | Minimum Version | |---------|-----------------| | Chrome | 60+ | | Firefox | 55+ | | Safari | 12+ | | Edge | 79+ |

Note: Internet Explorer is not supported. Some features may have limited functionality in older browser versions.


License

This project is released under the Apache 2.0 License.


Getting Started

Please refer to our How to Run documentation to set up and run the project on your local machine.

Contributing

Please refer to our Code of Conduct for contribution guidelines.

For detailed documentation, see the docs/ folder.