@dottech/ticket-snap
v0.2.3
Published
Capture 500/runtime errors with rich page/request/response snapshots + screenshot; open ticket via public endpoint.
Maintainers
Readme
@dottech/ticket-snap
Capture 500/runtime errors with rich page/request/response snapshots + screenshot; open ticket via public endpoint.
ticket-snap is a lightweight, zero-dependency library that automatically captures errors, network failures, and runtime exceptions in your web application. It collects comprehensive context including screenshots, network activity, console logs, and page metadata, then submits detailed bug reports to your endpoint.
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- API Reference
- Examples
- TypeScript Support
- Browser Compatibility
- Advanced Usage
- Troubleshooting
Features
- 🎯 Automatic Error Capture: Intercepts fetch/XHR requests and global errors
- 📸 Screenshot Support: Captures page screenshots using html2canvas or SVG fallback
- 🔒 Security First: Built-in redaction for sensitive data (passwords, tokens, emails)
- 📦 Offline Queue: Queues reports when offline, retries automatically
- 🎨 Flexible Triggers: Auto-submit, prompt user, or manual reporting modes
- 🔌 Framework Agnostic: Works with React, Vue, Angular, or vanilla JS
- 📊 Rich Context: Captures network activity, console logs, performance timings
- 🪵 Breadcrumbs & Context: Trail of user/app events and mutable context (userId, route) for "what led to this" debugging
- 🎛️ Highly Configurable: Extensive options for customization
Installation
npm
npm install @dottech/ticket-snapCDN (unpkg)
<script src="https://unpkg.com/@dottech/ticket-snap/dist/ticket-snap.umd.js"></script>CDN (jsDelivr)
<script src="https://cdn.jsdelivr.net/npm/@dottech/ticket-snap/dist/ticket-snap.umd.js"></script>ES Modules
import TicketSnap from '@dottech/ticket-snap';CommonJS
const TicketSnap = require('@dottech/ticket-snap');Quick Start
import TicketSnap from '@dottech/ticket-snap';
TicketSnap.init({
endpoint: 'https://your-api.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt', // Show popup on errors
includeScreenshot: true
});That's it! The library will now automatically capture:
- HTTP 500+ errors from fetch/XHR requests
- Unhandled JavaScript errors
- Unhandled promise rejections
Configuration
Required Options
endpoint (string, required)
The API endpoint URL where bug reports will be submitted.
endpoint: 'https://api.example.com/api/v1/public/bug-reports'Project & Routing
projectKey (string, optional)
Project identifier for routing tickets. Default: 'TICKET-WEB'
projectKey: 'MY-PROJECT-KEY'labels (string[], optional)
Array of labels to attach to tickets. Default: ['auto-captured', 'source:web']
labels: ['production', 'critical', 'frontend']Behavior Control
triggerMode ('auto' | 'prompt' | 'manual', optional)
Controls when and how error reports are submitted:
'auto': Automatically submit reports without user interaction'prompt': Show a popup dialog for user to review and add notes before submitting'manual': Only submit whenreportNow()is called explicitly
Default: 'prompt' (or inferred from autoSubmitOn500 for backward compatibility)
// Auto-submit on errors (no user interaction)
triggerMode: 'auto'
// Show popup for user review
triggerMode: 'prompt'
// Manual control only
triggerMode: 'manual'promptUser (boolean, optional)
When calling reportNow(), whether to show the popup dialog. Default: true
promptUser: true // Show popup when reportNow() is called
promptUser: false // Auto-submit without popupautoSubmitOn500 (boolean, optional)
Legacy option: If triggerMode is not set, this determines behavior:
true→ equivalent totriggerMode: 'auto'false→ equivalent totriggerMode: 'prompt'
This option is kept for backward compatibility but is ignored when triggerMode is explicitly set.
Screenshot Options
includeScreenshot (boolean, optional)
Whether to capture screenshots when errors occur. Default: true
includeScreenshot: truescreenshotStrategy ('auto' | 'html2canvas' | 'svg-foreignObject' | 'none', optional)
Screenshot capture method:
'auto': Try html2canvas if available, fallback to SVG method'html2canvas': Use html2canvas library (must be loaded separately)'svg-foreignObject': Use SVG-based capture (built-in, no dependencies)'none': Disable screenshots
Default: 'auto'
// Use html2canvas if available (requires loading html2canvas separately)
screenshotStrategy: 'html2canvas'
// Use built-in SVG method
screenshotStrategy: 'svg-foreignObject'
// Disable screenshots
screenshotStrategy: 'none'Note: To use html2canvas, load it before ticket-snap:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js"></script>
<script src="https://unpkg.com/@dottech/ticket-snap/dist/ticket-snap.umd.js"></script>Authentication
auth (AuthConfig, optional)
Authentication configuration for the API endpoint. Default: { mode: 'none' }
type AuthConfig =
| { mode: 'basic'; secret: string } // Basic auth: Authorization: Basic base64(projectKey:secret)
| { mode: 'ingestKey'; key: string } // Query param: ?ingestKey=...
| { mode: 'none' }; // No authenticationBasic Authentication:
auth: {
mode: 'basic',
secret: 'your-secret-key'
}
// Sends: Authorization: Basic base64(projectKey:secret)Ingest Key (Query Parameter):
auth: {
mode: 'ingestKey',
key: 'your-ingest-key'
}
// Appends: ?ingestKey=your-ingest-keyNo Authentication:
auth: { mode: 'none' }
// or simply omit the auth optiontransport ('formFields' | 'metadataPart', optional)
How to structure the submission payload:
'formFields': All data as individual FormData fields (recommended for most backends)'metadataPart': Single JSON metadata part + attachments
Default: 'formFields' if auth.mode === 'basic', otherwise 'metadataPart'
transport: 'formFields' // Recommended for most backendsData Limits & Redaction
redact (RedactRule[], optional)
Custom redaction rules to sanitize sensitive data. Rules are applied in addition to default redactions.
type RedactRule =
| { key: RegExp; replaceWith?: string } // Redact header/field names
| { pattern: RegExp; replaceWith: string }; // Redact text patternsDefault redactions (always applied):
- Headers:
authorization,cookie - Patterns:
"password":"...",Bearer <token>, phone numbers, emails
Custom redaction example:
redact: [
// Redact custom header
{ key: /x-api-key/i, replaceWith: '***' },
// Redact credit card numbers
{ pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replaceWith: '****-****-****-****' },
// Redact SSN
{ pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replaceWith: '***-**-****' }
]ringSize (number, optional)
Maximum number of recent network requests to keep in memory. Default: 20
ringSize: 50 // Keep last 50 requestsnetworkCaptureMode ('all' | 'errorsOnly', optional)
'all': Capture every request (default).'errorsOnly': Only keep 4xx/5xx and network failures. Ideal for integration-error focus; 200s are not stored.
networkCaptureMode: 'errorsOnly' // Only failed requests in historynetworkRecentCount (number, optional)
How many recent network entries to attach to each snapshot and payload. Default: 10
networkRecentCount: 15 // Last 15 requests per reportmaxBodyChars (number, optional)
Maximum characters to capture from request/response bodies. Default: 2000
maxBodyChars: 5000 // Capture up to 5000 charsmaxConsole (number, optional)
Maximum number of console log entries to capture. Default: 100
maxConsole: 200maxPerf (number, optional)
Maximum number of performance entries to capture. Default: 150
maxPerf: 300Breadcrumbs & context (frontend trail)
maxBreadcrumbs (number, optional)
Maximum breadcrumb entries (user/app event trail) to keep. Default: 30
maxBreadcrumbs: 50captureNavigationBreadcrumbs (boolean, optional)
Auto-add a breadcrumb on popstate and hashchange. Default: true
captureNavigationBreadcrumbs: trueinitialContext (Record<string, unknown>, optional)
Initial key-value context attached to every snapshot (e.g. userId, feature flags).
initialContext: { userId: 'u-123', feature: 'checkout-v2' }Use TicketSnap.setContext(key, value) at runtime to update context.
SPA Integration
routeResolver (() => RouteInfo, optional)
Function to extract current route information for SPA frameworks. Default: returns current pathname
interface RouteInfo {
route?: string; // Route path (e.g., '/users/:id')
params?: Record<string, string | number>; // Route parameters
method?: string; // HTTP method
urlOverride?: string; // Override URL in report
}Vue Router example:
import { useRoute } from 'vue-router';
routeResolver: () => {
const route = useRoute();
return {
route: route.path,
params: route.params,
method: route.meta?.method
};
}React Router example:
import { useLocation, useParams } from 'react-router-dom';
routeResolver: () => {
const location = useLocation();
const params = useParams();
return {
route: location.pathname,
params: params
};
}App Metadata
app (AppContext, optional)
Application metadata to include in reports.
interface AppContext {
appName?: string;
appVersion?: string;
environment?: string; // 'development', 'staging', 'production'
build?: string; // Build number or hash
commit?: string; // Git commit SHA
releaseChannel?: string; // 'stable', 'beta', 'canary'
}Example:
app: {
appName: 'MyApp',
appVersion: '1.2.3',
environment: 'production',
build: '20240115.1234',
commit: 'abc123def',
releaseChannel: 'stable'
}Event Callbacks
onSubmitted ((res: unknown) => void, optional)
Callback invoked when a report is successfully submitted.
onSubmitted: (response) => {
console.log('Ticket created:', response);
// response contains the API response
}onError ((err: unknown) => void, optional)
Callback invoked when submission fails (report is queued for retry).
onError: (error) => {
console.error('Failed to submit report:', error);
// Report is automatically queued for retry
}API Reference
init(config: TicketSnapConfig)
Initializes ticket-snap with the provided configuration. Must be called before the library can capture errors.
const api = TicketSnap.init({
endpoint: 'https://api.example.com/bug-reports',
projectKey: 'MY-PROJECT'
});Returns: The API object with methods below.
reportNow(extraNotes?: string, options?: { includeScreenshot?: boolean })
Manually trigger a bug report. Useful for user-initiated reporting or custom triggers.
// Basic usage
TicketSnap.reportNow('User reported an issue');
// Without screenshot
TicketSnap.reportNow('Issue description', { includeScreenshot: false });
// With additional notes
TicketSnap.reportNow('Button click failed');Parameters:
extraNotes(string, optional): Additional notes to include in the reportoptions(object, optional):includeScreenshot(boolean, optional): Whether to include screenshot. Default:true
Behavior:
- If
promptUser: true, shows popup dialog for user to review and add notes - If
promptUser: false, submits immediately without popup
flushOfflineNow()
Manually retry sending queued offline reports. Returns a Promise.
// Retry queued reports
await TicketSnap.flushOfflineNow();Use cases:
- User clicks "Retry" button after network failure
- Periodic retry in background
- On app visibility change (handled automatically in non-manual modes)
addBreadcrumb(message: string, category?: string)
Add a breadcrumb (e.g. "Clicked checkout", "Opened modal"). Included in the next snapshot for "what led to this" debugging.
TicketSnap.addBreadcrumb('Clicked checkout button', 'ui');
TicketSnap.addBreadcrumb('Opened payment modal');setContext(keyOrObj: string | Record<string, unknown>, value?: unknown)
Set context key-value(s) merged into every snapshot (e.g. current route, userId).
TicketSnap.setContext('userId', 'u-456');
TicketSnap.setContext({ route: '/checkout', step: 2 });getContext(): Record<string, unknown>
Returns a copy of current context (for debugging or custom UI).
getRecentNetwork(): unknown[]
Returns a copy of recent network entries (for debugging or custom UI).
getBreadcrumbs(): BreadcrumbEntry[]
Returns a copy of the breadcrumb trail.
version (string)
Current library version.
console.log(TicketSnap.version); // '0.2.1'Examples
Basic Setup
import TicketSnap from '@dottech/ticket-snap';
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
includeScreenshot: true
});Auto-Submit on Errors
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'auto', // Auto-submit without user interaction
includeScreenshot: true,
onSubmitted: (response) => {
console.log('Error report submitted:', response);
}
});Manual Reporting Only
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'manual', // No automatic reporting
includeScreenshot: true
});
// Add custom error handler
window.addEventListener('error', (event) => {
// Your custom logic
if (shouldReport(event)) {
TicketSnap.reportNow(`Custom error: ${event.message}`);
}
});Vue/Nuxt Integration
// plugins/ticket-snap.client.js (Nuxt 3)
import TicketSnap from '@dottech/ticket-snap';
export default defineNuxtPlugin(() => {
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
routeResolver: () => {
const route = useRoute();
return {
route: route.path,
params: route.params
};
},
app: {
appName: 'MyNuxtApp',
appVersion: '1.0.0',
environment: process.env.NODE_ENV
}
});
});React Integration
// App.js or index.js
import { useEffect } from 'react';
import TicketSnap from '@dottech/ticket-snap';
import { useLocation, useParams } from 'react-router-dom';
function App() {
const location = useLocation();
const params = useParams();
useEffect(() => {
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt',
routeResolver: () => ({
route: location.pathname,
params: params
}),
app: {
appName: 'MyReactApp',
appVersion: process.env.REACT_APP_VERSION,
environment: process.env.NODE_ENV
}
});
}, []);
return <YourApp />;
}Custom Redaction
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
redact: [
// Redact API keys in headers
{ key: /x-api-key/i, replaceWith: '***' },
// Redact credit card numbers in request bodies
{ pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replaceWith: '****-****-****-****' },
// Redact custom tokens
{ pattern: /"token"\s*:\s*"[^"]+"/gi, replaceWith: '"token":"***"' }
]
});Offline Queue Handling
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'auto',
onError: (error) => {
// Report failed to send, queued automatically
console.warn('Report queued for retry:', error);
// Show UI indicator
showOfflineIndicator();
},
onSubmitted: () => {
// Report sent successfully
hideOfflineIndicator();
}
});
// Manual retry button
document.getElementById('retry-reports').addEventListener('click', async () => {
await TicketSnap.flushOfflineNow();
alert('Queued reports sent');
});Basic Authentication
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
auth: {
mode: 'basic',
secret: 'your-secret-key-here'
},
transport: 'formFields'
});TypeScript Support
Full TypeScript definitions are included. Import types as needed:
import TicketSnap, {
TicketSnapConfig,
Snapshot,
RedactRule,
TriggerMode
} from '@dottech/ticket-snap';
const config: TicketSnapConfig = {
endpoint: 'https://api.example.com/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'prompt' as TriggerMode
};
TicketSnap.init(config);Browser Compatibility
ticket-snap works in all modern browsers that support:
fetchAPI (or polyfill)XMLHttpRequestPromiselocalStoragecrypto.subtle(for hashing, with fallback)
Tested in:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Mobile browsers (iOS Safari, Chrome Mobile)
For older browsers, consider polyfills for fetch and Promise.
Advanced Usage
Custom Error Filtering
// Only report errors matching certain criteria
const originalErrorHandler = window.onerror;
window.onerror = (message, source, lineno, colno, error) => {
// Your custom filtering logic
if (shouldReportError(error)) {
TicketSnap.reportNow(`Filtered error: ${message}`);
}
// Call original handler if needed
if (originalErrorHandler) {
originalErrorHandler(message, source, lineno, colno, error);
}
};Integration with Error Boundaries (React)
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
TicketSnap.reportNow(
`React Error Boundary: ${error.message}`,
{ includeScreenshot: true }
);
}
render() {
// Your error boundary UI
}
}Conditional Reporting
TicketSnap.init({
endpoint: 'https://api.example.com/api/v1/public/bug-reports',
projectKey: 'MY-PROJECT',
triggerMode: 'manual', // Manual control
includeScreenshot: true
});
// Only report in production
if (process.env.NODE_ENV === 'production') {
window.addEventListener('error', (event) => {
TicketSnap.reportNow(`Production error: ${event.message}`);
});
}Troubleshooting
Reports not being sent
Check:
- Verify
endpointURL is correct and accessible - Check browser console for errors
- Verify authentication credentials if using
auth - Check Network tab to see if requests are being made
- Ensure
triggerModeis set correctly (not'manual'if expecting auto-reports)
Screenshots not working
Solutions:
- If using
html2canvas, ensure it's loaded before ticket-snap - Try
screenshotStrategy: 'svg-foreignObject'for built-in method - Check browser console for CORS or CSP errors
- Some browsers block screenshots in certain contexts (extensions, iframes)
Too much data being captured
Adjust limits:
maxBodyChars: 1000, // Reduce body size
maxConsole: 50, // Reduce console logs
ringSize: 10 // Reduce network historyReports stuck in offline queue
Manual retry:
await TicketSnap.flushOfflineNow();Check localStorage:
const queue = JSON.parse(localStorage.getItem('tsnap-queue') || '[]');
console.log('Queued reports:', queue.length);CORS errors
Ensure your API endpoint allows requests from your domain. The library sends POST requests with FormData.
TypeScript errors
Ensure you're using TypeScript 4.5+ and have @types/node installed if needed.
License
MIT © 3bbasDev
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
