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 🙏

© 2025 – Pkg Stats / Ryan Hefner

replay-tracker

v0.4.0

Published

A lightweight session replay tracker for websites using rrweb

Readme

Replay Tracker

A lightweight session replay tracker for websites using rrweb.

Features

  • Records user sessions on websites
  • Logs captured events or can save them locally
  • Upload recordings directly to Wasabi cloud storage
  • Supports text masking for privacy
  • Works in browser environments
  • Simple start/stop functionality
  • Environment variable support via dotenv
  • Automatic uploads to Wasabi storage
  • Playback recorded sessions with built-in replayer

Installation

# Install replay-tracker and its required peer dependency
npm install replay-tracker rrweb

# Or using yarn
yarn add replay-tracker rrweb

Important: This package requires rrweb as a peer dependency. Make sure to install it in your project.

Integration with Frontend Frameworks

When using frameworks like Next.js or other bundlers, you might encounter module resolution issues. Here are solutions for common problems:

Next.js Integration

In your Next.js application, create a component that dynamically imports replay-tracker:

// components/ReplayTracker.tsx
'use client';
import { useEffect } from 'react';
import dynamic from 'next/dynamic';

// Manually provide rrweb to avoid "Module not found" errors
const createTracker = async () => {
  // First import rrweb
  const rrweb = await import('rrweb');
  
  // Then import replay-tracker
  const { createTracker } = await import('replay-tracker');
  
  // Create and return tracker with rrweb instance
  return createTracker({
    debug: true,
    autoUpload: true,
    rrwebInstance: rrweb // Pass the rrweb instance directly
  });
};

export default function ReplayTracking() {
  useEffect(() => {
    let tracker: any;
    
    (async () => {
      try {
        tracker = await createTracker();
        await tracker.start();
      } catch (error) {
        console.error('Failed to initialize replay tracker:', error);
      }
    })();
    
    // Cleanup
    return () => {
      if (tracker) {
        tracker.stop();
      }
    };
  }, []);
  
  return null; // This component doesn't render anything
}

Add this component to your layout or pages where you want to track user sessions:

// app/layout.tsx
import ReplayTracker from '../components/ReplayTracker';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <ReplayTracker />
      </body>
    </html>
  );
}

Troubleshooting Common Issues

"Module not found: Can't resolve 'rrweb'"

This error occurs when the bundler can't find the rrweb package, even though it's a peer dependency. Solutions:

  1. Direct import: Use the example above to manually import and provide rrweb
  2. Check dependencies: Make sure rrweb is correctly installed in your project
  3. Webpack configuration: If using webpack, you might need to add rrweb to the externals:
// next.config.js
module.exports = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.externals = [...(config.externals || []), 'rrweb'];
    }
    return config;
  },
};

"ConnectError: [aborted] read ECONNRESET"

This error typically occurs when there's a connection issue. Solutions:

  1. Check your network connection
  2. Increase request timeout settings if applicable
  3. If using a proxy, verify proxy settings

Usage

import { createTracker } from 'replay-tracker';

// Create a tracker instance (API key is optional, defaults to 'developer1@')
const tracker = createTracker({
  // Optional configurations
  apiKey: 'your-custom-api-key',
  maskText: true, // Set to true to mask all text content for privacy
  autoUpload: true, // Automatically upload events to Wasabi storage
  debug: true, // Enable debug logs in the console
});

// Start recording user session
await tracker.start();

// Later, when you want to stop recording
tracker.stop();

// Manual upload to Wasabi (if autoUpload is disabled)
await tracker.uploadToWasabi();

Environment Variables Setup

Create a .env file in your project root:

# API key for Replay Tracker
REPLAY_API_KEY=your_api_key_here

# Wasabi configuration
WASABI_ACCESS_KEY_ID=your_wasabi_access_key
WASABI_SECRET_ACCESS_KEY=your_wasabi_secret_key
WASABI_ENDPOINT=https://s3.ap-southeast-1.wasabisys.com
WASABI_BUCKET=your_bucket_name
WASABI_REGION=ap-southeast-1

Wasabi Cloud Storage Integration

Using Environment Variables (Recommended)

// Make sure you have a .env file with the following variables:
// WASABI_ACCESS_KEY_ID=your-wasabi-access-key
// WASABI_SECRET_ACCESS_KEY=your-wasabi-secret-key
// WASABI_BUCKET=your-bucket-name
// WASABI_ENDPOINT=https://s3.ap-southeast-1.wasabisys.com
// WASABI_REGION=ap-southeast-1

import { createTracker } from 'replay-tracker';

// The tracker will automatically read from environment variables
const tracker = createTracker();

// Start recording
tracker.start();

// Later, upload to Wasabi
const fileUrl = await tracker.uploadToWasabi();

Using Configuration Options

import { createTracker } from 'replay-tracker';

const tracker = createTracker({
  // Wasabi configuration (overrides environment variables)
  wasabi: {
    accessKeyId: 'your-wasabi-access-key',
    secretAccessKey: 'your-wasabi-secret-key',
    endpoint: 'https://s3.ap-southeast-1.wasabisys.com',  // Or your specific endpoint
    bucket: 'your-bucket-name',
    region: 'ap-southeast-1'  // Optional
  }
});

// Start recording
tracker.start();

// ... Record user session ...

// Upload to Wasabi and get the URL to the file
const fileUrl = await tracker.uploadToWasabi();
console.log('Recording available at:', fileUrl);

// Or with custom filename
const customFileUrl = await tracker.uploadToWasabi('my-session-recording.json');

React Integration

import { useEffect, useState } from 'react';
import { createTracker } from 'replay-tracker';

function MyComponent() {
  const [uploadUrl, setUploadUrl] = useState('');
  const [isUploading, setIsUploading] = useState(false);
  
  useEffect(() => {
    // Environment variables are automatically used if available
    const tracker = createTracker();
    
    tracker.start();
    
    // Clean up function to stop tracking when component unmounts
    return () => {
      tracker.stop();
    };
  }, []);
  
  const handleUploadToWasabi = async () => {
    setIsUploading(true);
    try {
      const tracker = createTracker();
      const url = await tracker.uploadToWasabi();
      if (url) {
        setUploadUrl(url);
      }
    } finally {
      setIsUploading(false);
    }
  };
  
  return (
    <div>
      <button onClick={handleUploadToWasabi} disabled={isUploading}>
        {isUploading ? 'Uploading...' : 'Upload to Wasabi'}
      </button>
      {uploadUrl && (
        <div>
          <p>Recording uploaded successfully:</p>
          <a href={uploadUrl} target="_blank" rel="noopener noreferrer">
            {uploadUrl}
          </a>
        </div>
      )}
    </div>
  );
}

Replaying Sessions

You can replay sessions either by loading recorded events directly from Wasabi or by using events you have stored locally:

Replaying from Wasabi Storage

import { createTracker } from 'replay-tracker';
import 'rrweb/dist/rrweb.min.css'; // Import CSS for the replayer

function ReplaySession() {
  const tracker = createTracker({
    debug: true // Enable logs for debugging
  });
  
  // Reference to container element where replay will be shown
  const containerRef = useRef(null);
  
  const handleReplay = async () => {
    // Filename of the recording to replay (from Wasabi)
    const filename = 'replay-2023-05-15T14-30-45-000Z.json';
    
    // Get the recording data from Wasabi
    const recordingData = await tracker.getRecording(filename);
    
    if (recordingData && recordingData.events && containerRef.current) {
      // Play the recording in the container element
      await tracker.replay(recordingData.events, containerRef.current, {
        speed: 1, // Playback speed (0.5 to 2)
        showController: true // Show playback controls
      });
    }
  };
  
  return (
    <div>
      <button onClick={handleReplay}>Replay Session</button>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }}></div>
    </div>
  );
}

Replaying Local Events

import { createTracker } from 'replay-tracker';
import 'rrweb/dist/rrweb.min.css'; // Import CSS for the replayer

function RecordAndReplay() {
  const [events, setEvents] = useState([]);
  const tracker = createTracker({
    debug: true
  });
  const containerRef = useRef(null);
  
  // Start recording and collect events
  const startRecording = () => {
    // Keep local copy of events
    const localEvents = [];
    
    // Override the rrweb recorder to also store events locally
    import('rrweb').then(({ record }) => {
      const stopFn = record({
        emit: (event) => {
          localEvents.push(event);
        }
      });
      
      // Store stopFn to use later
      window.stopRecording = () => {
        stopFn();
        setEvents(localEvents); // Save events to state
      };
    });
  };
  
  // Play the recorded events
  const playRecording = () => {
    if (events.length > 0 && containerRef.current) {
      tracker.replay(events, containerRef.current);
    } else {
      console.error('No events to replay or container not ready');
    }
  };
  
  return (
    <div>
      <button onClick={startRecording}>Start Recording</button>
      <button onClick={() => window.stopRecording()}>Stop Recording</button>
      <button onClick={playRecording}>Play Recording</button>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }}></div>
    </div>
  );
}

Options

The createTracker function accepts the following options:

| Option | Type | Description | Default | |--------|------|-------------|---------| | apiKey | string | API key for authentication | From env or 'developer1@' | | endpoint | string | Custom endpoint for sending events | logs to console | | maskText | boolean | Whether to mask text content | false | | sampleRate | number | Rate at which to sample events | 1 | | wasabi | object | Wasabi storage configuration | undefined | | autoUpload | boolean | Automatically upload events to Wasabi | true | | debug | boolean | Enable detailed debug logs | false |

Automatic Uploads

When autoUpload is set to true, the tracker will automatically upload recordings to Wasabi:

  • After every 50 recorded events
  • On mouse interaction events (type 4)
  • When stop() is called

This eliminates the need to manually call uploadToWasabi(), making integration simpler.

Environment Variables

  • REPLAY_API_KEY - API key for Replay Tracker
  • WASABI_ACCESS_KEY_ID - Wasabi access key ID
  • WASABI_SECRET_ACCESS_KEY - Wasabi secret access key
  • WASABI_BUCKET - Wasabi bucket name
  • WASABI_ENDPOINT - Wasabi endpoint URL
  • WASABI_REGION - Wasabi region

Wasabi Configuration Object

| Option | Type | Description | Required | |--------|------|-------------|----------| | accessKeyId | string | Wasabi access key ID | Yes* | | secretAccessKey | string | Wasabi secret access key | Yes* | | endpoint | string | Wasabi endpoint URL | Yes* | | bucket | string | Wasabi bucket name | Yes* | | region | string | Wasabi region | No |

*Can be provided through environment variables instead

License

MIT