errormirror
v1.0.0
Published
Record and replay frontend errors for debugging without external services
Maintainers
Readme
ErrorMirror
A lightweight JavaScript/TypeScript library that records frontend errors and replays them later for debugging without using any external services. ErrorMirror captures minimal snapshots of your application state when errors occur, allowing you to reproduce and debug issues in a sandboxed environment.
Features
- 🎯 Automatic Error Recording - Hooks into
window.onerrorandunhandledrejectionevents - 📸 Minimal Snapshots - Captures DOM structure, user events, fetch calls, and framework state
- 🔄 Error Replay - Replays errors in a sandboxed iframe for safe debugging
- 💾 Local Storage - Stores snapshots locally using localStorage (custom storage engines supported)
- 🎨 Framework Support - Detects and captures state from React, Vue, and Svelte
- 📦 Tree Shakeable - Fully tree-shakeable for optimal bundle size
- 🔧 TypeScript - Full TypeScript support with exported types
- 🚀 Zero Dependencies - No external dependencies required
Installation
npm install errormirrorQuick Start
Basic Usage
import { recordErrors, replayError } from 'errormirror';
// Start recording errors
const stopRecording = recordErrors();
// When an error occurs, it's automatically captured
// ... your app code ...
// Stop recording
stopRecording();
// Replay a captured error
await replayError('err_1234567890_abc123');With Custom Storage
import { recordErrors, replayError, LocalStorageEngine } from 'errormirror';
// Create custom storage engine
class CustomStorage implements StorageEngine {
save(id: string, snapshot: ErrorSnapshot): void {
// Your custom save logic
}
load(id: string): ErrorSnapshot | null {
// Your custom load logic
}
list(): string[] {
// Your custom list logic
}
remove(id: string): void {
// Your custom remove logic
}
}
const storage = new CustomStorage();
// Use custom storage
const stopRecording = recordErrors({ storage });API Reference
recordErrors(options?: RecordOptions): () => void
Starts recording errors and returns a cleanup function to stop recording.
Options
interface RecordOptions {
storage?: StorageEngine; // Custom storage engine (default: LocalStorageEngine)
maxEvents?: number; // Maximum events to track (default: 50)
maxFetchCalls?: number; // Maximum fetch calls to track (default: 20)
captureDOM?: boolean; // Capture DOM snapshot (default: true)
captureEvents?: boolean; // Capture user events (default: true)
captureFetch?: boolean; // Capture fetch calls (default: true)
captureFrameworkState?: boolean; // Capture framework state (default: true)
frameworks?: ('react' | 'vue' | 'svelte')[]; // Frameworks to detect (default: all)
}Example
const stopRecording = recordErrors({
maxEvents: 100,
captureDOM: true,
captureEvents: true,
captureFetch: true,
captureFrameworkState: true
});
// Later...
stopRecording();replayError(snapshotId: string, options?: ReplayOptions): Promise<void>
Replays a captured error in a sandboxed iframe.
Options
interface ReplayOptions {
storage?: StorageEngine; // Custom storage engine (default: LocalStorageEngine)
iframeId?: string; // ID for the replay iframe (default: 'errormirror-replay')
onComplete?: (error: Error | null) => void; // Callback when replay completes
}Example
await replayError('err_1234567890_abc123', {
onComplete: (error) => {
if (error) {
console.error('Error replayed:', error);
} else {
console.log('Replay completed successfully');
}
}
});Error Snapshot Structure
Each captured error snapshot contains:
interface ErrorSnapshot {
id: string; // Unique snapshot ID
timestamp: number; // Timestamp when error occurred
error: {
message: string; // Error message
stack?: string; // Error stack trace
filename?: string; // File where error occurred
lineno?: number; // Line number
colno?: number; // Column number
type: 'error' | 'unhandledrejection';
reason?: any; // Rejection reason (for promise rejections)
};
dom: {
html: string; // Minimal HTML structure
bodyAttributes: Record<string, string>;
viewport: { width: number; height: number };
};
events: UserEvent[]; // Recent user events
fetchCalls: FetchCall[]; // Recent fetch API calls
frameworkState?: FrameworkState; // Framework state (if detected)
}Storage Engines
LocalStorageEngine (Default)
Uses browser localStorage to store snapshots. Perfect for development and testing.
import { LocalStorageEngine } from 'errormirror';
const storage = new LocalStorageEngine();Custom Storage Engine
Implement the StorageEngine interface to use your own storage:
import type { StorageEngine, ErrorSnapshot } from 'errormirror';
class MyStorage implements StorageEngine {
async save(id: string, snapshot: ErrorSnapshot): Promise<void> {
// Save to your backend, IndexedDB, etc.
}
async load(id: string): Promise<ErrorSnapshot | null> {
// Load from your storage
}
async list(): Promise<string[]> {
// List all snapshot IDs
}
async remove(id: string): Promise<void> {
// Remove a snapshot
}
}Framework Support
ErrorMirror automatically detects and captures state from:
- React - Detects React components and state via React internals
- Vue - Detects Vue instances via Vue DevTools hooks
- Svelte - Detects Svelte components via DOM markers
Framework detection is optional and can be configured:
recordErrors({
captureFrameworkState: true,
frameworks: ['react'] // Only detect React
});Demo
See the demo/ folder for a complete React example showing how to use ErrorMirror.
To run the demo:
cd demo
npm install
npm run devDevelopment
Building
npm run buildTesting
npm testProject Structure
errormirror/
├── src/
│ ├── index.ts # Main exports
│ ├── types.ts # TypeScript definitions
│ ├── storage.ts # Storage engines
│ ├── dom-capture.ts # DOM snapshot capture
│ ├── event-tracker.ts # User event tracking
│ ├── fetch-tracker.ts # Fetch API tracking
│ ├── framework-detector.ts # Framework state detection
│ ├── compression.ts # Compression utilities
│ ├── recorder.ts # Error recording logic
│ └── replayer.ts # Error replay logic
├── tests/ # Test files
├── demo/ # Demo React app
└── dist/ # Built filesBest Practices
- Start Recording Early - Start recording as early as possible in your app lifecycle
- Limit Event History - Use
maxEventsandmaxFetchCallsto control memory usage - Custom Storage - For production, consider using a custom storage engine that sends snapshots to your backend
- Selective Capture - Disable unnecessary captures (e.g.,
captureFetch: false) if you don't need them - Clean Up - Always call the cleanup function when stopping recording
Limitations
- DOM snapshots are minimal and may not capture all dynamic content
- Framework state capture depends on framework internals and may not work in all scenarios
- Replay accuracy depends on the completeness of captured data
- Large applications may generate large snapshots
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
