replay-tracker
v0.4.0
Published
A lightweight session replay tracker for websites using rrweb
Maintainers
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 rrwebImportant: This package requires
rrwebas 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:
- Direct import: Use the example above to manually import and provide rrweb
- Check dependencies: Make sure rrweb is correctly installed in your project
- 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:
- Check your network connection
- Increase request timeout settings if applicable
- 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-1Wasabi 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 TrackerWASABI_ACCESS_KEY_ID- Wasabi access key IDWASABI_SECRET_ACCESS_KEY- Wasabi secret access keyWASABI_BUCKET- Wasabi bucket nameWASABI_ENDPOINT- Wasabi endpoint URLWASABI_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
