@bugspotter/sdk
v2.1.0
Published
Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications
Downloads
174
Maintainers
Readme
@bugspotter/sdk
Core SDK for capturing and reporting bugs with session replay
The BugSpotter SDK provides a comprehensive solution for capturing bug reports in web applications, including screenshots, console logs, network requests, session replay, and browser metadata.
📦 Installation
NPM Package
# npm
npm install @bugspotter/sdk
# yarn
yarn add @bugspotter/sdk
# pnpm
pnpm add @bugspotter/sdkCDN
<!-- BugSpotter CDN (versioned - recommended for production) -->
<script src="https://cdn.bugspotter.io/sdk/bugspotter-2.0.0.min.js"></script>
<!-- Latest version (for development only) -->
<script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
<!-- Or unpkg -->
<script src="https://unpkg.com/@bugspotter/sdk@latest/dist/bugspotter.min.js"></script>📘 See CDN Usage Guide for detailed CDN integration, SRI hashes, and troubleshooting.
From Source
# Clone and build from source
git clone https://github.com/apex-bridge/bugspotter-sdk.git
cd bugspotter-sdk
pnpm install
pnpm buildThe built SDK will be available at dist/bugspotter.min.js (~99 KB minified with session replay).
🚀 Quick Start
Basic Usage
ES Modules (React, Vue, Angular, etc.)
import BugSpotter from '@bugspotter/sdk';
// Initialize with auto-widget (note: init is async)
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: true,
});CommonJS (Node.js)
const BugSpotter = require('@bugspotter/sdk');
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: true,
});UMD (Browser script tag)
<script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
<script>
// Initialize with auto-widget
(async () => {
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: true,
});
})();
</script>How It Works
The SDK automatically uses an optimized presigned URL upload flow (40% fewer HTTP requests):
import BugSpotter from '@bugspotter/sdk';
// 1. Initialize SDK with required auth
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: true,
});
// 2. Submit a bug report (SDK handles everything automatically)
// Optimized flow (3 HTTP requests instead of 5):
// - Step 1: POST /api/v1/reports with hasScreenshot/hasReplay flags
// → Returns bug ID + presigned URLs for screenshot & replay
// - Step 2: PUT to S3 presigned URLs (parallel uploads)
// - Step 3: POST /api/v1/reports/{id}/confirm-upload (confirm each file)Benefits:
- 40% fewer HTTP requests - 3 requests vs 5 in legacy flow
- Files upload directly to S3 (faster, no API bottleneck)
- Automatic compression for replay events
- Concurrent uploads (screenshot + replay in parallel)
- Reduced server load
Manual Capture & Programmatic Submission
// Initialize without widget
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: false,
});
// Capture bug report data
const report = await bugSpotter.capture();
console.log('Captured:', report);
// report contains: screenshot, console, network, metadata, replay
// Submit programmatically (SDK handles presigned URL uploads)
await bugSpotter.submit({
title: 'Application crashed',
description: 'Error occurred during form submission',
priority: 'high',
report,
});
// ✅ SDK automatically:
// 1. Detects hasScreenshot/hasReplay from report data
// 2. Gets presigned URLs from backend
// 3. Uploads screenshot + replay to S3 (parallel)
// 4. Confirms uploads with backend🎨 Using the Widget
Automatic Widget
// Widget appears automatically with showWidget: true
const bugSpotter = await BugSpotter.init({
endpoint: 'https://api.bugspotter.com',
apiKey: 'bgs_your_api_key',
showWidget: true,
widgetOptions: {
position: 'bottom-right',
icon: '⚡',
backgroundColor: '#1a365d',
size: 48,
},
});Custom Widget
// Default professional SVG icon (recommended)
const button = new BugSpotter.FloatingButton({
position: 'bottom-right',
// icon: 'svg' is default - professional bug icon
backgroundColor: '#2563eb', // Professional blue (default)
tooltip: 'Report an Issue',
});
// Custom emoji/text icon
const button2 = new BugSpotter.FloatingButton({
position: 'bottom-right',
icon: '🐛',
backgroundColor: '#ff4444',
size: 56,
offset: { x: 24, y: 24 },
});
// Custom SVG icon
const button3 = new BugSpotter.FloatingButton({
position: 'bottom-left',
customSvg: `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
`,
tooltip: 'Custom Report Button',
backgroundColor: '#1a365d',
size: 48,
});
// Handle click
button.onClick(async () => {
const report = await bugSpotter.capture();
const modal = new BugSpotter.BugReportModal({
onSubmit: async (data) => {
// Use SDK's submit() method for automatic presigned URL uploads
await bugSpotter.submit({
...data, // title, description, priority
report,
});
// ✅ SDK handles presigned URLs and S3 uploads automatically
},
});
modal.show(report._screenshotPreview || '');
});
// Control button
button.show();
button.hide();
button.setIcon('⚠️');
button.setBackgroundColor('#00ff00');🔒 PII Sanitization
Automatic detection and masking of sensitive text data in the browser before upload. When enabled (default), text fields (console logs, network URLs, metadata) are sanitized before submission. For visual exclusion: use replay.blockSelectors to hide DOM elements from session replay, and data-bugspotter-exclude attribute to exclude elements from screenshots.
Built-in Patterns (9 types)
| Pattern | Detects | Example |
| ------------ | ------------------------- | ----------------------------------------------- |
| email | Email addresses | [email protected] → [REDACTED-EMAIL] |
| phone | Phone numbers | +7 701 123-4567 → [REDACTED-PHONE] |
| creditcard | Credit card numbers | 4111-1111-1111-1111 → [REDACTED-CREDITCARD] |
| ssn | US Social Security | 123-45-6789 → [REDACTED-SSN] |
| iin | Kazakhstan IIN/BIN | 860101350478 → [REDACTED-IIN] |
| ip | IPv4 and IPv6 | 192.168.1.1 → [REDACTED-IP] |
| apikey | Stripe, AWS, Google keys | sk_live_... → [REDACTED-APIKEY] |
| token | Bearer, JWT, OAuth tokens | eyJhbG... → [REDACTED-TOKEN] |
| password | Password field values | •••••••• → [REDACTED-PASSWORD] |
Presets
Use a preset name instead of listing patterns individually:
| Preset | Patterns included |
| ------------------ | ----------------------- |
| 'all' | All 9 patterns |
| 'minimal' | email, creditcard, ssn |
| 'financial' | creditcard, ssn |
| 'contact' | email, phone |
| 'identification' | ssn, iin |
| 'kazakhstan' | email, phone, iin |
| 'gdpr' | email, phone, ip |
| 'pci' | creditcard |
| 'credentials' | apikey, token, password |
// Use a preset
await BugSpotter.init({
sanitize: { patterns: 'kazakhstan' }, // email + phone + IIN
});
// Or pick individual patterns
await BugSpotter.init({
sanitize: { patterns: ['email', 'phone', 'creditcard'] },
});Custom Patterns
Add your own regex patterns for industry-specific data:
await BugSpotter.init({
sanitize: {
enabled: true,
patterns: 'all',
customPatterns: [
{
name: 'broker-account',
regex: /FRH\d{9}/gi,
description: 'Freedom Finance broker account number',
},
{
name: 'iban-kz',
regex: /KZ\d{2}[A-Z]{4}\d{13}/gi,
description: 'Kazakhstan IBAN',
},
{
name: 'internal-id',
regex: /ORD-\d{6,}/gi,
description: 'Internal order ID',
},
],
excludeSelectors: ['.public-info'], // Don't redact inside these elements
},
});Custom patterns produce [REDACTED-BROKER-ACCOUNT], [REDACTED-IBAN-KZ], etc.
Performance: < 50ms overhead, recursive sanitization of nested objects, supports Cyrillic text
📋 API Reference
BugSpotter Class
BugSpotter.init(config)
Initialize the SDK. This method is async to support fetching backend-controlled replay quality settings.
Parameters:
interface BugSpotterConfig {
apiKey: string; // Required: API key (bgs_...)
endpoint?: string; // Base URL of BugSpotter API
sampleRate?: number; // Session sampling (0-1, default: 1 = all sessions)
showWidget?: boolean; // Auto-show widget (default: true)
widgetOptions?: FloatingButtonOptions;
replay?: {
enabled?: boolean; // Enable replay (default: true)
duration?: number; // Buffer duration in seconds (default: 15)
sampling?: {
mousemove?: number; // Mousemove throttle in ms (default: 50)
scroll?: number; // Scroll throttle in ms (default: 100)
};
blockSelectors?: string[]; // CSS selectors to exclude from replay
blockClass?: string; // CSS class to exclude from replay
// Quality settings (optional, backend controlled by default)
inlineStylesheet?: boolean;
inlineImages?: boolean;
collectFonts?: boolean;
recordCanvas?: boolean;
recordCrossOriginIframes?: boolean;
};
sanitize?: {
enabled?: boolean; // Enable PII sanitization (default: true)
patterns?:
| 'all'
| 'minimal'
| 'financial'
| 'contact'
| 'identification'
| 'credentials'
| 'kazakhstan'
| 'gdpr'
| 'pci'
| Array<
| 'email'
| 'phone'
| 'creditcard'
| 'ssn'
| 'iin'
| 'ip'
| 'apikey'
| 'token'
| 'password'
| 'custom'
>;
customPatterns?: Array<{
name: string; // Pattern name → [REDACTED-NAME]
regex: RegExp; // Detection regex
description?: string; // Human-readable description
examples?: string[]; // Example values for testing
priority?: number; // Pattern ordering hint (optional)
}>;
excludeSelectors?: string[]; // CSS selectors to exclude from DOM text sanitization (replay)
};
}Returns: BugSpotter instance
bugSpotter.submit(payload)
Submit a bug report with automatic file uploads via presigned URLs.
Parameters:
interface BugReportPayload {
title: string; // Bug title (required)
description?: string; // Bug description (optional)
priority?: 'low' | 'medium' | 'high' | 'critical';
report: BugReport; // Report data from capture()
}Returns: Promise<void>
Throws: Error if submission fails
Example:
const report = await bugSpotter.capture();
await bugSpotter.submit({
title: 'Button not responding',
description: 'Submit button does not work on checkout page',
priority: 'high',
report,
});What it does:
- Detects screenshot and replay from report data
- Requests presigned URLs from backend
- Uploads files directly to S3 (parallel)
- Confirms uploads with backend
- Handles errors and retries automatically
bugSpotter.capture()
Capture current bug report data.
Returns: Promise<BugReport>
interface BugReport {
screenshotKey?: string; // Storage key after presigned URL upload
console: ConsoleLog[]; // Array of console entries
network: NetworkRequest[]; // Array of network requests
metadata: BrowserMetadata; // Browser/system info
replay?: eventWithTime[]; // Session replay events (rrweb format)
replayKey?: string; // Storage key after presigned URL upload
_screenshotPreview?: string; // Internal: screenshot preview for modal (not sent to API)
}
interface ConsoleLog {
level: string; // 'log', 'warn', 'error', 'info', 'debug'
message: string; // Formatted message
timestamp: number; // Unix timestamp
stack?: string; // Error stack trace (for errors)
}
interface NetworkRequest {
url: string; // Request URL
method: string; // HTTP method
status: number; // HTTP status code
duration: number; // Request duration in ms
timestamp: number; // Unix timestamp
error?: string; // Error message if failed
}
interface BrowserMetadata {
userAgent: string;
viewport: { width: number; height: number };
browser: string; // Detected browser name
os: string; // Detected OS
url: string; // Current page URL
timestamp: number; // Capture timestamp
}bugSpotter.getConfig()
Get current configuration.
Returns: Readonly<BugSpotterConfig>
bugSpotter.destroy()
Clean up and destroy the SDK instance.
FloatingButton Class
Constructor
new FloatingButton(options?: FloatingButtonOptions)
interface FloatingButtonOptions {
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
icon?: string; // 'svg' for default bug icon, or custom emoji/text
customSvg?: string; // Custom SVG icon (overrides icon if provided)
backgroundColor?: string; // CSS color (default: '#2563eb' professional blue)
size?: number; // Size in pixels (default: 56)
offset?: { x: number; y: number };
tooltip?: string; // Custom tooltip text (default: 'Report an Issue')
style?: Record<string, string>; // Additional CSS
}Methods
button.onClick(handler: () => void | Promise<void>)- Set click handlerbutton.show()- Show the buttonbutton.hide()- Hide the buttonbutton.setIcon(icon: string)- Change iconbutton.setBackgroundColor(color: string)- Change colorbutton.destroy()- Remove button from DOM
BugReportModal Class
Constructor
new BugReportModal(options: BugReportModalOptions)
interface BugReportModalOptions {
onSubmit: (data: BugReportData) => void | Promise<void>;
onClose?: () => void;
}
interface BugReportData {
title: string;
description: string;
}Methods
modal.show(screenshot: string)- Display modal with screenshotmodal.close()- Close the modalmodal.destroy()- Remove modal from DOM
Features:
- Form validation (title and description required)
- Loading state during async submission
- Error handling with user feedback
- Escape key to close
- Click X button to close
- Cannot close by clicking outside (prevents data loss)
DirectUploader Class
Direct client-to-storage uploads using presigned URLs (97% memory reduction, 3x faster).
Constructor
new DirectUploader(config: DirectUploadConfig)
interface DirectUploadConfig {
apiEndpoint: string; // Backend API URL
apiKey: string; // bgs_... API key
bugId: string; // Bug report UUID
}Methods
uploadScreenshot(file, onProgress?)
Upload screenshot directly to storage.
const screenshotBlob = await fetch(dataUrl).then((r) => r.blob());
const result = await uploader.uploadScreenshot(screenshotBlob, (progress) => {
console.log(
`Screenshot: ${progress.loaded}/${progress.total} (${progress.percentage}%)`
);
});
// result: { success: true, storageKey: "screenshots/..." }uploadReplay(compressedBlob, onProgress?)
Upload compressed replay data to storage.
const compressed = await compressReplayEvents(events);
const result = await uploader.uploadReplay(compressed);uploadAttachment(file, onProgress?)
Upload attachment file to storage.
const file = document.querySelector('input[type="file"]').files[0];
const result = await uploader.uploadAttachment(file);Upload Progress
interface UploadProgress {
loaded: number; // Bytes uploaded
total: number; // Total bytes
percentage: number; // 0-100
}
type UploadProgressCallback = (progress: UploadProgress) => void;Upload Result
interface UploadResult {
success: boolean;
storageKey?: string; // Storage location (if successful)
error?: string; // Error message (if failed)
}Compression Utilities
Compress replay events before uploading (2MB JSON → ~200KB gzip).
compressReplayEvents(events)
import { compressReplayEvents } from '@bugspotter/sdk';
const events = [
/* rrweb events */
];
const compressed = await compressReplayEvents(events);
console.log(`Compressed: ${(compressed.size / 1024).toFixed(2)} KB`);Uses native CompressionStream API (Chrome 80+, Firefox 113+, Safari 16.4+).
estimateCompressedReplaySize(events)
Estimate compressed size without actually compressing.
const estimatedSize = estimateCompressedReplaySize(events);
console.log(`Estimated: ${(estimatedSize / 1024).toFixed(2)} KB`);isWithinSizeLimit(blob, limitMB)
Check if blob is within size limit.
if (!isWithinSizeLimit(compressed, 10)) {
console.warn('File exceeds 10MB limit');
}📊 Capture Modules
The SDK automatically captures:
- 📸 Screenshots - CSP-safe full page capture (~500ms)
- 🎥 Session Replay - Last 15-30s of user interactions (rrweb)
- 📝 Console - All log levels with stack traces
- 🌐 Network - fetch/XHR timing and responses
- 💻 Metadata - Browser, OS, viewport, URL
See Session Replay Documentation for detailed configuration.
Screenshot Capture
import { ScreenshotCapture } from '@bugspotter/sdk';
const screenshotCapture = new ScreenshotCapture();
const screenshot = await screenshotCapture.capture();
// Returns: Base64 PNG data URL or 'SCREENSHOT_FAILED'Features:
- CSP-safe using
html-to-image - Full page capture
- Automatic error handling
- ~500ms average capture time
Console Capture
import { ConsoleCapture } from '@bugspotter/sdk';
const consoleCapture = new ConsoleCapture();
const logs = consoleCapture.getLogs();Features:
- Captures: log, warn, error, info, debug
- Stack traces for errors
- Timestamps for all entries
- Object stringification
- Circular reference handling
- Configurable max logs (default: 100)
Network Capture
import { NetworkCapture } from '@bugspotter/sdk';
const networkCapture = new NetworkCapture();
const requests = networkCapture.getRequests();Features:
- Captures fetch() and XMLHttpRequest
- Request/response timing
- HTTP status codes
- Error tracking
- Singleton pattern (one instance per page)
Metadata Capture
import { MetadataCapture } from '@bugspotter/sdk';
const metadataCapture = new MetadataCapture();
const metadata = metadataCapture.capture();Features:
- Browser detection (Chrome, Firefox, Safari, Edge, etc.)
- OS detection (Windows, macOS, Linux, iOS, Android)
- Viewport dimensions
- User agent string
- Current URL
- Timestamp
🧪 Testing
pnpm test # All tests
pnpm test --watch # Watch mode
pnpm test --coverage # Coverage report345 tests passing (unit + E2E + Playwright)
🛠️ Building
pnpm run dev # Development with watch
pnpm run build # Production buildOutput: dist/bugspotter.min.js (~99 KB)
📈 Performance
- Bundle: ~99 KB minified
- Load: < 100ms
- Memory: < 15 MB (30s replay buffer)
- Screenshot: ~500ms
- PII sanitization: <50ms
🔒 Security
- CSP-safe (no eval, no inline scripts)
- Automatic PII detection and masking
- Input validation
- HTTPS recommended
🤝 Contributing
See the main CONTRIBUTING.md guide.
📄 License
MIT License - see LICENSE
Part of the BugSpotter project
