/** * Enhanced Error Reporter with user notifications and environment-specific handling */ import { ErrorReporter } from '../interfaces/ErrorReporter'; import { Logger } from '../interfaces/Logger'; import { ApiError } from '../api/base/ApiError'; import { connectionMonitor } from '../api/base/ApiConnectionMonitor'; // Import notification system (will be used if available) let notificationSystem: any = null; try { // Dynamically import to avoid circular dependencies import('@/components/notifications/NotificationProvider').then(module => { notificationSystem = module; }).catch(() => { // Notification system not available yet }); } catch { // Silent fail - notification system may not be initialized } export interface EnhancedErrorReporterOptions { /** * Whether to show user-facing notifications */ showUserNotifications?: boolean; /** * Whether to log to console (always true in dev) */ logToConsole?: boolean; /** * Whether to report to external service (e.g., Sentry) */ reportToExternal?: boolean; /** * Custom error handler for specific error types */ customHandlers?: Record void>; } export class EnhancedErrorReporter implements ErrorReporter { private options: EnhancedErrorReporterOptions; private logger: Logger; private errorBuffer: Array<{ error: ApiError; context: unknown }> = []; private readonly MAX_BUFFER_SIZE = 50; constructor(logger: Logger, options: EnhancedErrorReporterOptions = {}) { this.logger = logger; this.options = { showUserNotifications: options.showUserNotifications ?? true, logToConsole: options.logToConsole ?? true, reportToExternal: options.reportToExternal ?? false, customHandlers: options.customHandlers || {}, }; } /** * Main error reporting method */ report(error: Error, context?: unknown): void { // Only handle ApiError instances for enhanced reporting if (!(error instanceof ApiError)) { // For non-API errors, use basic logging if (this.options.logToConsole) { console.error('Non-API Error:', error, context); } return; } // Add to buffer for potential batch reporting this.addToBuffer(error, context); // Log based on environment and severity this.logError(error, context); // Handle custom error types this.handleCustomHandlers(error); // Show user notifications if enabled if (this.options.showUserNotifications) { this.showUserNotification(error); } // Report to external services if configured if (this.options.reportToExternal) { this.reportToExternal(error, context); } // Update connection monitor if (error.isConnectivityIssue()) { connectionMonitor.recordFailure(error); } } /** * Log error with appropriate severity */ private logError(error: ApiError, context: unknown): void { if (!this.options.logToConsole) return; const isDev = process.env.NODE_ENV === 'development'; const severity = error.getSeverity(); const message = isDev ? error.getDeveloperMessage() : error.getUserMessage(); const contextObj = typeof context === 'object' && context !== null ? context : {}; const errorContextObj = typeof error.context === 'object' && error.context !== null ? error.context : {}; const logContext = { ...errorContextObj, ...contextObj, type: error.type, isRetryable: error.isRetryable(), isConnectivity: error.isConnectivityIssue(), }; if (severity === 'error') { this.logger.error(message, error, logContext); if (isDev) { console.error(`[API-ERROR] ${message}`, { error, context: logContext }); } } else if (severity === 'warn') { this.logger.warn(message, logContext); if (isDev) { console.warn(`[API-WARN] ${message}`, { context: logContext }); } } else { this.logger.info(message, logContext); if (isDev) { console.log(`[API-INFO] ${message}`, { context: logContext }); } } } /** * Show user-facing notification */ private showUserNotification(error: ApiError): void { const isDev = process.env.NODE_ENV === 'development'; // In development, we might want to show more details if (isDev) { // Use console notification in dev console.log(`%c[USER-NOTIFICATION] ${error.getUserMessage()}`, 'background: #222; color: #bada55; padding: 4px 8px; border-radius: 4px;' ); return; } // In production, use the notification system if available // This is a deferred import to avoid circular dependencies if (typeof window !== 'undefined') { setTimeout(() => { try { // Try to access notification context if available const notificationEvent = new CustomEvent('gridpilot-notification', { detail: { type: 'error', title: this.getNotificationTitle(error), message: error.getUserMessage(), variant: error.isConnectivityIssue() ? 'modal' : 'toast', autoDismiss: !error.isConnectivityIssue(), } }); window.dispatchEvent(notificationEvent); } catch (e) { // Fallback to browser alert if notification system unavailable if (error.isConnectivityIssue()) { console.warn('API Error:', error.getUserMessage()); } } }, 100); } } /** * Get appropriate notification title */ private getNotificationTitle(error: ApiError): string { switch (error.type) { case 'NETWORK_ERROR': return 'Connection Lost'; case 'TIMEOUT_ERROR': return 'Request Timed Out'; case 'AUTH_ERROR': return 'Authentication Required'; case 'SERVER_ERROR': return 'Server Error'; case 'RATE_LIMIT_ERROR': return 'Rate Limit Reached'; default: return 'Something Went Wrong'; } } /** * Handle custom error type handlers */ private handleCustomHandlers(error: ApiError): void { if (this.options.customHandlers && this.options.customHandlers[error.type]) { try { this.options.customHandlers[error.type]!(error); } catch (handlerError) { console.error('Custom error handler failed:', handlerError); } } } /** * Report to external services (placeholder for future integration) */ private reportToExternal(error: ApiError, context: unknown): void { // Placeholder for external error reporting (e.g., Sentry, LogRocket) // In a real implementation, this would send to your error tracking service if (process.env.NODE_ENV === 'development') { const contextObj = typeof context === 'object' && context !== null ? context : {}; console.log('[EXTERNAL-REPORT] Would report:', { type: error.type, message: error.message, context: { ...error.context, ...contextObj }, timestamp: new Date().toISOString(), }); } } /** * Add error to buffer for potential batch reporting */ private addToBuffer(error: ApiError, context: unknown): void { this.errorBuffer.push({ error, context }); // Keep buffer size in check if (this.errorBuffer.length > this.MAX_BUFFER_SIZE) { this.errorBuffer.shift(); } } /** * Get buffered errors */ getBufferedErrors(): Array<{ error: ApiError; context: unknown }> { return [...this.errorBuffer]; } /** * Clear error buffer */ clearBuffer(): void { this.errorBuffer = []; } /** * Batch report buffered errors */ flush(): void { if (this.errorBuffer.length === 0) return; if (this.options.logToConsole) { console.groupCollapsed(`[API-REPORT] Flushing ${this.errorBuffer.length} buffered errors`); this.errorBuffer.forEach(({ error, context }) => { console.log(`${error.type}: ${error.message}`, { error, context }); }); console.groupEnd(); } // In production, this would batch send to external service if (this.options.reportToExternal) { const batch = this.errorBuffer.map(({ error, context }) => { const contextObj = typeof context === 'object' && context !== null ? context : {}; return { type: error.type, message: error.message, context: { ...error.context, ...contextObj }, timestamp: new Date().toISOString(), }; }); console.log('[EXTERNAL-REPORT] Batch:', batch); } this.clearBuffer(); } /** * Update options dynamically */ updateOptions(newOptions: Partial): void { this.options = { ...this.options, ...newOptions }; } } /** * Global error reporter instance */ let globalReporter: EnhancedErrorReporter | null = null; export function getGlobalErrorReporter(): EnhancedErrorReporter { if (!globalReporter) { // Import the console logger const { ConsoleLogger } = require('./ConsoleLogger'); globalReporter = new EnhancedErrorReporter(new ConsoleLogger(), { showUserNotifications: true, logToConsole: true, reportToExternal: process.env.NODE_ENV === 'production', }); } return globalReporter; } /** * Helper function to report API errors easily */ export function reportApiError( error: ApiError, context?: unknown, reporter?: EnhancedErrorReporter ): void { const rep = reporter || getGlobalErrorReporter(); rep.report(error, context); } /** * React hook for error reporting */ export function useErrorReporter() { const reporter = getGlobalErrorReporter(); return { report: (error: Error, context?: unknown) => reporter.report(error, context), flush: () => reporter.flush(), getBuffered: () => reporter.getBufferedErrors(), updateOptions: (opts: Partial) => reporter.updateOptions(opts), }; }