error-explorer-angular
Advanced Angular SDK for Error Explorer - Comprehensive error tracking and monitoring with offline support, rate limiting, security validation, and real-time analytics.
✨ Features
- 🚀 Advanced Error Tracking - Automatic capture of unhandled errors, HTTP failures, and performance issues
- 🔄 Offline Support - Queue errors when offline, sync when back online
- ⚡ Rate Limiting - Smart deduplication and request throttling
- 🔒 Security First - Built-in data sanitization and validation
- 📊 Real-time Analytics - SDK health monitoring and performance metrics
- 🧩 Angular Native - Deep Angular integration with route tracking and HTTP interceptors
- 📱 Responsive Design - Works seamlessly across all devices and browsers
- 🎯 Zero Config - Sensible defaults with extensive customization options
Installation
npm install error-explorer-angular
# ou
yarn add error-explorer-angular
Quick Start
Module Setup
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { ErrorExplorerModule } from 'error-explorer-angular';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
ErrorExplorerModule.forRoot({
projectToken: 'your-project-token',
apiUrl: 'https://error-explorer.com',
projectName: 'my-angular-app',
environment: 'production'
})
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Standalone Bootstrap (Angular 14+)
import { bootstrapApplication } from '@angular/platform-browser';
import { importProvidersFrom } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { ErrorExplorerModule } from 'error-explorer-angular';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
HttpClientModule,
ErrorExplorerModule.forRoot({
webhookUrl: 'https://error-explorer.com/webhook/project-token',
projectName: 'my-angular-app',
environment: 'production'
})
)
]
});
Using in Components
import { Component } from '@angular/core';
import { ErrorExplorerService } from 'error-explorer-angular';
@Component({
selector: 'app-example',
template: `
<button (click)="triggerError()">Trigger Error</button>
<button (click)="captureMessage()">Capture Message</button>
`
})
export class ExampleComponent {
constructor(private errorExplorer: ErrorExplorerService) {
// Set user context
this.errorExplorer.setUser({
id: 123,
email: 'user@example.com',
username: 'john_doe'
});
}
triggerError() {
try {
throw new Error('This is a test error');
} catch (error) {
this.errorExplorer.captureException(error as Error, {
component: 'ExampleComponent',
action: 'triggerError'
});
}
}
captureMessage() {
this.errorExplorer.addBreadcrumb('User clicked capture message', 'user');
this.errorExplorer.captureMessage('User performed an action', 'info', {
action: 'captureMessage',
component: 'ExampleComponent'
});
}
}
Configuration
Required Options
projectToken
: Your Error Explorer project tokenapiUrl
: Your Error Explorer API URLprojectName
: Name of your project
All Configuration Options
ErrorExplorerModule.forRoot({
// Required
projectToken: 'your-project-token',
apiUrl: 'https://error-explorer.com',
projectName: 'my-angular-app',
// Basic settings
environment: 'production', // Default: 'production'
enabled: true, // Default: true
debug: false, // Default: false
version: '1.0.0', // App version
commitHash: 'abc123', // Git commit hash
// User context
userId: 'user123', // Default user ID
userEmail: 'user@example.com', // Default user email
customData: { plan: 'premium' }, // Custom metadata
// Performance & Limits
maxBreadcrumbs: 50, // Default: 50
maxRequestsPerMinute: 10, // Default: 10
duplicateErrorWindow: 5000, // Default: 5000ms
// Retry configuration
maxRetries: 3, // Default: 3
initialRetryDelay: 1000, // Default: 1000ms
maxRetryDelay: 30000, // Default: 30000ms
// Offline support
enableOfflineSupport: true, // Default: true
maxOfflineQueueSize: 50, // Default: 50
offlineQueueMaxAge: 86400000, // Default: 24h
// Security & Network
requestTimeout: 30000, // Default: 30000ms
requireHttps: false, // Default: false
allowedDomains: ['mydomain.com'], // Optional: restrict domains
// Angular specific
captureRouteChanges: true, // Default: true
captureHttpErrors: true, // Default: true
// Data filtering
beforeSend: (data) => { // Optional: Filter/modify data
// Filter sensitive data
if (data.context?.password) {
data.context.password = '[FILTERED]';
}
return data;
}
})
### Providing the Commit Hash
To link errors with your source code, you should provide the git commit hash of the current build. You can do this by setting an environment variable during your build process.
**1. Get the commit hash and set it in your environment files:**
You can use a script to get the commit hash and write it to your environment file before building.
```bash
# In your package.json scripts
"scripts": {
"prebuild": "node -e \"require('fs').writeFileSync('./src/environments/commit.ts', 'export const commit = { hash: \\"' + require('child_process').execSync('git rev-parse HEAD').toString().trim() + '\\" };')\"",
"build": "npm run prebuild && ng build"
}
2. Import the commit hash and use it in your configuration:
// src/environments/commit.ts (will be generated by the script)
export const commit = { hash: "a1b2c3d..." };
// app.module.ts
import { commit } from '../environments/commit';
ErrorExplorerModule.forRoot({
// ... other config
commitHash: commit.hash,
})
Features
Automatic Error Handling
The module automatically captures:
- Unhandled Exceptions: Global error handler catches all unhandled errors
- HTTP Errors: Interceptor captures HTTP 4xx/5xx errors
- Route Changes: Navigation events are tracked as breadcrumbs
- Slow HTTP Requests: Requests taking longer than 5 seconds
Manual Error Capture
// Capture exceptions (new async API)
try {
await riskyOperation();
} catch (error) {
await this.errorExplorer.reportError(error as Error, {
operation: 'riskyOperation',
userId: this.currentUserId
});
}
// Capture messages (enhanced)
await this.errorExplorer.reportMessage('User logged in', 'info', {
userId: 123,
timestamp: Date.now(),
sessionId: 'abc123'
});
// Add breadcrumbs (improved)
this.errorExplorer.addBreadcrumb('User clicked submit', 'user', 'info', {
formData: this.formValue,
buttonId: 'submit-btn'
});
// User actions (new)
this.errorExplorer.logUserAction('form_submitted', {
formType: 'contact',
validationPassed: true
});
// Navigation tracking (enhanced)
this.errorExplorer.logNavigation('/home', '/profile', {
trigger: 'menu_click'
});
HTTP Error Monitoring
HTTP errors are automatically captured and include:
// The interceptor captures:
// - Request method, URL, headers
// - Response status, headers
// - Request duration
// - Error details
// You can also manually capture HTTP errors
this.http.get('/api/data').subscribe({
next: (data) => {
// Success
},
error: (error: HttpErrorResponse) => {
this.errorExplorer.captureHttpError(error, {
customContext: 'additional data'
});
}
});
User Context Management
// Set user context globally
this.errorExplorer.setUser({
id: 123,
email: 'user@example.com',
username: 'john_doe',
subscription: 'premium',
customData: {
preferences: {},
permissions: []
}
});
// User context is automatically included in all error reports
Breadcrumb Management
// Add custom breadcrumbs
this.errorExplorer.addBreadcrumb('User started checkout', 'user');
this.errorExplorer.addBreadcrumb('Payment method selected', 'user', 'info', {
paymentMethod: 'credit_card'
});
// Access breadcrumb manager directly
const breadcrumbManager = this.errorExplorer.getBreadcrumbManager();
breadcrumbManager.addComponentLifecycle('MyComponent', 'ngOnInit');
breadcrumbManager.addUserInteraction('click', 'submit-button');
🚀 Advanced Features
Real-time Analytics & Monitoring
export class DashboardComponent {
constructor(private errorExplorer: ErrorExplorerService) {}
getSDKStats() {
const stats = this.errorExplorer.getStats();
console.log({
queueSize: stats.queueSize,
isOnline: stats.isOnline,
rateLimitRemaining: stats.rateLimitRemaining,
sdkMetrics: stats.sdkMetrics,
quotaUsage: stats.quotaUsage,
healthStatus: stats.healthStatus
});
}
async flushOfflineQueue() {
// Manually flush any queued errors
await this.errorExplorer.flushQueue();
}
updateConfiguration() {
// Dynamic configuration updates
this.errorExplorer.updateConfig({
debug: true,
maxRequestsPerMinute: 20
});
}
}
Offline Support & Queue Management
// The SDK automatically handles offline scenarios
export class OfflineAwareComponent implements OnInit {
constructor(private errorExplorer: ErrorExplorerService) {}
ngOnInit() {
// Errors reported while offline are automatically queued
this.errorExplorer.reportError(new Error('Network issue'), {
context: 'offline_scenario'
});
// When back online, queued errors are automatically sent
window.addEventListener('online', async () => {
await this.errorExplorer.flushQueue();
console.log('Offline queue processed');
});
}
}
Rate Limiting & Security
// The SDK includes intelligent rate limiting and security features
export class SecurityExampleComponent {
constructor(private errorExplorer: ErrorExplorerService) {}
demonstrateRateLimiting() {
// Duplicate errors within 5 seconds are automatically deduplicated
for (let i = 0; i < 10; i++) {
this.errorExplorer.reportError(new Error('Same error'));
}
// Only the first error will be sent, others are rate-limited
// Rate limiting prevents spam
for (let i = 0; i < 100; i++) {
this.errorExplorer.reportError(new Error(`Error ${i}`));
}
// Only 10 requests per minute (default) are allowed
}
demonstrateDataSanitization() {
// Sensitive data is automatically sanitized
this.errorExplorer.reportError(new Error('Login failed'), {
username: 'user@example.com',
password: 'secret123', // This will be filtered out
apiKey: 'abc123' // This will be filtered out
});
}
}
Quota Management
export class QuotaExampleComponent {
constructor(private errorExplorer: ErrorExplorerService) {}
checkQuotaUsage() {
const stats = this.errorExplorer.getStats();
const quotaUsage = stats.quotaUsage;
console.log('Daily usage:', {
used: quotaUsage.daily.used,
limit: quotaUsage.daily.limit,
remaining: quotaUsage.daily.limit - quotaUsage.daily.used
});
console.log('Monthly usage:', {
used: quotaUsage.monthly.used,
limit: quotaUsage.monthly.limit
});
console.log('Burst usage:', {
used: quotaUsage.burst.used,
limit: quotaUsage.burst.limit
});
}
}
Enhanced User Context Management
export class UserContextComponent {
constructor(private errorExplorer: ErrorExplorerService) {}
setDetailedUserContext() {
// Enhanced user context with custom data
this.errorExplorer.setUser({
id: 'user-123',
email: 'user@example.com',
username: 'john_doe',
subscription: 'premium',
role: 'admin',
customData: {
preferences: { theme: 'dark', language: 'en' },
permissions: ['read', 'write', 'admin'],
lastLogin: new Date().toISOString(),
deviceInfo: {
type: 'desktop',
os: 'Windows 10',
browser: 'Chrome'
}
}
});
// Individual setters
this.errorExplorer.setUserId('user-456');
this.errorExplorer.setUserEmail('newuser@example.com');
this.errorExplorer.setCustomData({
experiment: 'A',
feature_flags: ['new_ui', 'beta_feature']
});
}
}
Advanced Usage
Custom Error Handler
import { ErrorHandler, Injectable } from '@angular/core';
import { ErrorExplorerService } from 'error-explorer-angular';
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
constructor(private errorExplorer: ErrorExplorerService) {}
handleError(error: any): void {
// Add custom logic
console.error('Custom error handling:', error);
// Capture with Error Explorer
if (error instanceof Error) {
this.errorExplorer.captureException(error, {
handled_by: 'CustomErrorHandler',
custom_data: 'additional context'
});
}
}
}
// In your module
@NgModule({
providers: [
{
provide: ErrorHandler,
useClass: CustomErrorHandler
}
]
})
export class AppModule { }
Performance Monitoring
@Component({
// ...
})
export class PerformanceComponent implements OnInit, OnDestroy {
private startTime = Date.now();
constructor(private errorExplorer: ErrorExplorerService) {}
ngOnInit() {
this.errorExplorer.addBreadcrumb('Component initialized', 'angular.lifecycle');
}
ngOnDestroy() {
const duration = Date.now() - this.startTime;
if (duration > 5000) {
this.errorExplorer.captureMessage(
'Slow component lifecycle',
'warning',
{
component: 'PerformanceComponent',
duration,
lifecycle: 'ngOnDestroy'
}
);
}
}
onSlowOperation() {
const start = Date.now();
// Perform operation
this.heavyComputation();
const duration = Date.now() - start;
this.errorExplorer.addBreadcrumb(
`Heavy computation completed in ${duration}ms`,
'performance',
duration > 1000 ? 'warning' : 'info'
);
}
}
Environment-specific Configuration
// environment.ts
export const environment = {
production: false,
errorExplorer: {
webhookUrl: 'http://localhost:8000/webhook/dev-token',
projectName: 'my-app-dev',
environment: 'development',
enabled: true,
captureConsoleErrors: true
}
};
// environment.prod.ts
export const environment = {
production: true,
errorExplorer: {
webhookUrl: 'https://error-explorer.com/webhook/prod-token',
projectName: 'my-app',
environment: 'production',
enabled: true,
captureConsoleErrors: false
}
};
// app.module.ts
@NgModule({
imports: [
ErrorExplorerModule.forRoot(environment.errorExplorer)
]
})
export class AppModule { }
Route-specific Error Handling
import { Router, NavigationError } from '@angular/router';
@Component({
// ...
})
export class AppComponent {
constructor(
private router: Router,
private errorExplorer: ErrorExplorerService
) {
// Handle navigation errors
this.router.events.subscribe(event => {
if (event instanceof NavigationError) {
this.errorExplorer.captureException(
new Error(`Navigation failed: ${event.error}`),
{
url: event.url,
navigation_error: true
}
);
}
});
}
}
📚 API Reference
ErrorExplorerService
Core Methods
reportError(error: Error, additionalData?: Record<string, any>): Promise<void>
- Report an error (new async API)reportMessage(message: string, level?: 'info' | 'warning' | 'error', additionalData?: Record<string, any>): Promise<void>
- Report a messageaddBreadcrumb(message: string, category?: string, level?: ErrorLevel, data?: Record<string, any>): void
- Add breadcrumblogUserAction(action: string, data?: Record<string, any>): void
- Log user actionlogNavigation(from: string, to: string, data?: Record<string, any>): void
- Log navigation
User Context Methods
setUser(user: UserContext): void
- Set complete user contextsetUserId(userId: string): void
- Set user IDsetUserEmail(email: string): void
- Set user emailsetCustomData(data: Record<string, any>): void
- Set custom metadata
Utility Methods
clearBreadcrumbs(): void
- Clear all breadcrumbsisEnabled(): boolean
- Check if SDK is enabledgetStats()
- Get SDK statistics and health metricsflushQueue(): Promise<void>
- Flush offline queueupdateConfig(updates: Partial<ErrorExplorerConfig>): void
- Update configurationdestroy(): void
- Clean up SDK resources
Legacy Methods (Backward Compatible)
captureException(error: Error, context?: Record<string, any>): void
- Capture an exceptioncaptureMessage(message: string, level?: ErrorLevel, context?: Record<string, any>): void
- Capture a messagecaptureHttpError(error: HttpErrorResponse, context?: Record<string, any>): void
- Capture HTTP errorgetBreadcrumbManager(): BreadcrumbManager
- Get breadcrumb managergetConfig()
- Get current configuration
BreadcrumbManager
New Methods
logHttpRequest(method: string, url: string, status: number, data?: Record<string, any>): void
- Log HTTP requestlogNavigation(from: string, to: string, data?: Record<string, any>): void
- Log navigationlogUserAction(action: string, data?: Record<string, any>): void
- Log user actionlogConsole(level: 'error' | 'warn' | 'info' | 'debug', message: string): void
- Log console messageclearBreadcrumbs(): void
- Clear all breadcrumbs
Legacy Methods
addHttpRequest(method: string, url: string, statusCode?: number, duration?: number): void
addNavigation(from: string, to: string): void
addUserInteraction(event: string, target: string, data?: Record<string, any>): void
addComponentLifecycle(componentName: string, lifecycle: string): void
addConsoleLog(level: string, message: string, data?: any): void
addCustom(message: string, data?: Record<string, any>): void
Statistics Interface
interface SDKStats {
queueSize: number;
isOnline: boolean;
rateLimitRemaining: number;
rateLimitReset: number;
sdkMetrics: SDKMetrics;
quotaUsage: QuotaUsage;
healthStatus: HealthStatus;
}
interface SDKMetrics {
errorsReported: number;
errorsDropped: number;
requestsSuccessful: number;
requestsFailed: number;
averageResponseTime: number;
queueSize: number;
bytesTransmitted: number;
}
interface HealthStatus {
status: 'healthy' | 'degraded' | 'unhealthy';
issues: string[];
uptime: number;
}
🔄 Migration Guide (v1 to v2)
Breaking Changes
- Configuration Format -
webhookUrl
replaced withprojectToken
+apiUrl
- New Async API -
reportError()
andreportMessage()
are now async - Enhanced Types - More comprehensive TypeScript interfaces
Configuration Migration
// v1 Configuration
ErrorExplorerModule.forRoot({
webhookUrl: 'https://error-explorer.com/webhook/project-token',
projectName: 'my-app'
})
// v2 Configuration
ErrorExplorerModule.forRoot({
projectToken: 'project-token',
apiUrl: 'https://error-explorer.com',
projectName: 'my-app'
})
API Migration
// v1 - Synchronous
this.errorExplorer.captureException(error);
// v2 - Asynchronous (recommended)
await this.errorExplorer.reportError(error);
// Legacy API still works for backward compatibility
this.errorExplorer.captureException(error); // Still supported
New Features Available
- ✅ Offline error queuing
- ✅ Intelligent rate limiting
- ✅ Security validation
- ✅ Real-time SDK monitoring
- ✅ Enhanced breadcrumbs
- ✅ Quota management
- ✅ Performance metrics
🛠️ Development
Building the SDK
npm install
npm run build
Running Tests
npm test
npm run test:coverage
Type Checking
npm run typecheck
TypeScript Support
Full TypeScript support with comprehensive type definitions included.
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
- Modern mobile browsers
🤝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
📄 License
MIT License - see the LICENSE file for details.