UNPKG

replay-tracker

Version:

A lightweight session replay tracker for websites using rrweb

421 lines (328 loc) 11.9 kB
# 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