replay-tracker
Version:
A lightweight session replay tracker for websites using rrweb
421 lines (328 loc) • 11.9 kB
Markdown
# 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
```bash
# 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:
```jsx
// 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:
```jsx
// 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:
```js
// 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
```javascript
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)
```javascript
// 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
```javascript
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
```javascript
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
```javascript
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
```javascript
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