@bugspotter/sdk
Version:
Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications
382 lines (277 loc) • 8.46 kB
Markdown
# Session Replay Feature
## Overview
The BugSpotter SDK now includes session replay functionality that captures DOM mutations, mouse movements, clicks, and scrolls using [rrweb](https://www.rrweb.io/). This feature maintains a circular buffer that keeps only the last 15-30 seconds of events (configurable), ensuring minimal memory footprint while providing valuable context for bug reports.
## Key Features
- **Circular Buffer**: Time-based buffer that automatically prunes events older than the configured duration
- **Performance Optimized**: Configurable sampling rates for mousemove and scroll events
- **Automatic Recording**: Starts recording when BugSpotter initializes
- **Minimal Overhead**: Slim DOM recording options to reduce payload size
- **Serializable Events**: All events are JSON-serializable for easy transmission
## Configuration
### Basic Setup
By default, session replay is enabled with a 15-second buffer:
```typescript
import { BugSpotter } from '@bugspotter/sdk';
BugSpotter.init({
apiKey: 'your-api-key',
endpoint: 'https://api.bugspotter.com',
});
```
### Custom Configuration
```typescript
BugSpotter.init({
apiKey: 'your-api-key',
endpoint: 'https://api.bugspotter.com',
replay: {
enabled: true, // Enable/disable replay (default: true)
duration: 30, // Duration in seconds (default: 15, recommended max: 30)
sampling: {
mousemove: 50, // Throttle mousemove events to every 50ms (default: 50)
scroll: 100, // Throttle scroll events to every 100ms (default: 100)
},
},
});
```
### Disabling Replay
```typescript
BugSpotter.init({
apiKey: 'your-api-key',
replay: {
enabled: false,
},
});
```
## How It Works
### 1. Automatic Recording
When BugSpotter initializes, the DOM collector automatically starts recording if replay is enabled:
```typescript
const bugspotter = BugSpotter.init(config);
// Recording starts automatically!
```
### 2. Circular Buffer
The circular buffer maintains only recent events:
- Events are timestamped as they're captured
- When new events are added, old events are automatically pruned
- Only events within the configured duration are kept
### 3. Bug Report Inclusion
When a bug is reported, the last N seconds of events are included:
```typescript
const report = await bugspotter.capture();
console.log(report.replay); // Array of rrweb events
```
### 4. Event Transmission
The replay events are included in the bug report payload:
```typescript
{
title: "Bug title",
description: "Bug description",
report: {
screenshot: "...",
console: [...],
network: [...],
metadata: {...},
replay: [...] // rrweb events
}
}
```
## Event Types
The DOM collector captures the following event types:
- **DOM Mutations**: Element additions, removals, and attribute changes
- **Mouse Movements**: Throttled mousemove events
- **Mouse Interactions**: Clicks, double-clicks, context menu, etc.
- **Scroll Events**: Throttled scroll events
- **Viewport Resizes**: Window resize events
- **Input Changes**: Form input changes
## Performance Considerations
### Memory Usage
- The circular buffer automatically prunes old events
- Recommended duration: 15-30 seconds
- Longer durations = more memory usage
### Sampling Rates
Default sampling rates are optimized for performance:
- **Mousemove**: 50ms (20 events/second)
- **Scroll**: 100ms (10 events/second)
Adjust these based on your needs:
```typescript
replay: {
sampling: {
mousemove: 100, // Reduce to 10 events/second for better performance
scroll: 200, // Reduce to 5 events/second
}
}
```
### Slim DOM Options
The collector uses slim DOM options by default:
- Doesn't record script tags
- Doesn't record comments
- Doesn't record certain meta tags
- Doesn't inline images (reduces payload size)
## Advanced Usage
### Direct Access to DOM Collector
You can access the DOM collector directly for advanced use cases:
```typescript
import { DOMCollector } from '@bugspotter/sdk';
const collector = new DOMCollector({
duration: 20,
sampling: {
mousemove: 100,
scroll: 200,
},
});
// Start recording
collector.startRecording();
// Get events
const events = collector.getEvents();
// Clear buffer
collector.clearBuffer();
// Stop recording
collector.stopRecording();
// Clean up
collector.destroy();
```
### Direct Access to Circular Buffer
```typescript
import { CircularBuffer } from '@bugspotter/sdk';
const buffer = new CircularBuffer({
duration: 30, // 30 seconds
});
// Add events
buffer.add(event);
buffer.addBatch([event1, event2, event3]);
// Get events
const events = buffer.getEvents();
// Change duration
buffer.setDuration(60); // Change to 60 seconds
// Clear buffer
buffer.clear();
```
## Edge Cases
### Iframes
The collector handles iframes gracefully:
- Cross-origin iframes are not recorded by default (security restriction)
- Same-origin iframes can be recorded with `recordCrossOriginIframes: true`
### Shadow DOM
Shadow DOM is handled automatically by rrweb.
### Single Page Applications
The collector works seamlessly with SPAs - it captures all DOM changes regardless of routing.
## Troubleshooting
### High Memory Usage
If you're experiencing high memory usage:
1. Reduce the buffer duration: `duration: 10`
2. Increase sampling rates: `mousemove: 200, scroll: 300`
3. Check for memory leaks in your application
### Missing Events
If events seem to be missing:
1. Verify replay is enabled: `replay.enabled: true`
2. Check the buffer duration is sufficient
3. Verify events are within the time window
### Performance Issues
If you're experiencing performance issues:
1. Increase sampling rates to reduce event frequency
2. Reduce buffer duration
3. Consider disabling replay in development mode
## Browser Compatibility
Session replay works in all modern browsers that support:
- MutationObserver API
- ES6 features
- Proxy API
Tested on:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
## Dependencies
- `rrweb@^2.0.0-alpha.4`: Core recording library
- `rrweb-snapshot@^2.0.0-alpha.4`: DOM snapshot library
- `@rrweb/types@^2.0.0-alpha.18`: TypeScript types
## API Reference
### BugSpotterConfig.replay
```typescript
interface ReplayConfig {
enabled?: boolean; // Default: true
duration?: number; // Default: 15 seconds
sampling?: {
mousemove?: number; // Default: 50ms
scroll?: number; // Default: 100ms
};
}
```
### DOMCollector
```typescript
class DOMCollector {
constructor(config?: DOMCollectorConfig);
startRecording(): void;
stopRecording(): void;
getEvents(): eventWithTime[];
getCompressedEvents(): eventWithTime[];
clearBuffer(): void;
isCurrentlyRecording(): boolean;
getBufferSize(): number;
setDuration(seconds: number): void;
getDuration(): number;
destroy(): void;
}
```
### CircularBuffer
```typescript
class CircularBuffer {
constructor(config: CircularBufferConfig);
add(event: eventWithTime): void;
addBatch(events: eventWithTime[]): void;
getEvents(): eventWithTime[];
getCompressedEvents(): eventWithTime[];
clear(): void;
size(): number;
setDuration(seconds: number): void;
getDuration(): number;
}
```
## Examples
### Example 1: Capturing a Bug Report
```typescript
const bugspotter = BugSpotter.init({
apiKey: 'your-api-key',
replay: {
duration: 20,
},
});
// ... user interacts with the app ...
// When user reports a bug
const report = await bugspotter.capture();
console.log(report.replay.length); // Number of events captured
```
### Example 2: Custom Sampling
```typescript
BugSpotter.init({
apiKey: 'your-api-key',
replay: {
duration: 15,
sampling: {
mousemove: 200, // Less frequent mousemove events
scroll: 300, // Less frequent scroll events
},
},
});
```
### Example 3: Programmatic Control
```typescript
import { BugSpotter, DOMCollector } from '@bugspotter/sdk';
// Initialize without auto-start
const bugspotter = BugSpotter.init({
apiKey: 'your-api-key',
replay: {
enabled: false,
},
});
// Manually create and control collector
const collector = new DOMCollector({ duration: 30 });
// Start recording when needed
document.getElementById('start-btn').addEventListener('click', () => {
collector.startRecording();
});
// Stop recording when needed
document.getElementById('stop-btn').addEventListener('click', () => {
collector.stopRecording();
});
```
## License
See main package LICENSE file.