Spaces:
				
			
			
	
			
			
		Paused
		
	
	
	
			
			
	
	
	
	
		
		
		Paused
		
	| // error-handler.service.ts | |
| // Path: /flare-ui/src/app/services/error-handler.service.ts | |
| import { ErrorHandler, Injectable, Injector } from '@angular/core'; | |
| import { MatSnackBar } from '@angular/material/snack-bar'; | |
| import { Router } from '@angular/router'; | |
| import { HttpErrorResponse } from '@angular/common/http'; | |
| interface FlareError { | |
| error: string; | |
| message: string; | |
| details?: any; | |
| request_id?: string; | |
| timestamp?: string; | |
| user_action?: string; | |
| } | |
| ({ | |
| providedIn: 'root' | |
| }) | |
| export class GlobalErrorHandler implements ErrorHandler { | |
| constructor(private injector: Injector) {} | |
| handleError(error: Error | HttpErrorResponse): void { | |
| try { | |
| // Get services lazily to avoid circular dependency | |
| const snackBar = this.injector.get(MatSnackBar); | |
| const router = this.injector.get(Router); | |
| console.error('Global error caught:', error); | |
| // Handle HTTP errors | |
| if (error instanceof HttpErrorResponse) { | |
| this.handleHttpError(error, snackBar, router); | |
| } else { | |
| // Handle client-side errors | |
| this.handleClientError(error, snackBar); | |
| } | |
| } catch (handlerError) { | |
| // Fallback if error handler itself fails | |
| console.error('Error in error handler:', handlerError); | |
| console.error('Original error:', error); | |
| } | |
| } | |
| private handleHttpError(error: HttpErrorResponse, snackBar: MatSnackBar, router: Router): void { | |
| try { | |
| const flareError = error.error as FlareError; | |
| // Race condition error (409) | |
| if (error.status === 409) { | |
| const isRaceCondition = flareError?.error === 'RaceConditionError' || | |
| error.error?.type === 'race_condition'; | |
| if (isRaceCondition) { | |
| const snackBarRef = snackBar.open( | |
| flareError?.message || 'The data was modified by another user. Please refresh and try again.', | |
| 'Refresh', | |
| { | |
| duration: 0, | |
| panelClass: ['error-snackbar', 'race-condition-snackbar'] | |
| } | |
| ); | |
| snackBarRef.onAction().subscribe(() => { | |
| window.location.reload(); | |
| }); | |
| // Show additional info if available | |
| if (flareError?.details?.last_update_user) { | |
| console.info(`Last updated by: ${flareError.details.last_update_user} at ${flareError.details.last_update_date}`); | |
| } | |
| return; | |
| } | |
| } | |
| // Authentication error (401) | |
| if (error.status === 401) { | |
| snackBar.open( | |
| 'Your session has expired. Please login again.', | |
| 'Login', | |
| { | |
| duration: 5000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ).onAction().subscribe(() => { | |
| router.navigate(['/login']); | |
| }); | |
| return; | |
| } | |
| // Validation error (422) | |
| if (error.status === 422 && flareError?.details) { | |
| const fieldErrors = Array.isArray(flareError.details) | |
| ? flareError.details.map((d: any) => `${d.field}: ${d.message}`).join('\n') | |
| : 'Validation error occurred'; | |
| snackBar.open( | |
| flareError.message || 'Validation failed. Please check your input.', | |
| 'Close', | |
| { | |
| duration: 8000, | |
| panelClass: ['error-snackbar', 'validation-snackbar'] | |
| } | |
| ); | |
| console.error('Validation errors:', flareError.details); | |
| return; | |
| } | |
| // Not found error (404) | |
| if (error.status === 404) { | |
| snackBar.open( | |
| flareError?.message || 'The requested resource was not found.', | |
| 'Close', | |
| { | |
| duration: 5000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ); | |
| return; | |
| } | |
| // Server errors (5xx) | |
| if (error.status >= 500) { | |
| const message = flareError?.message || 'A server error occurred. Please try again later.'; | |
| const requestId = flareError?.request_id || error.headers?.get('X-Request-ID'); | |
| snackBar.open( | |
| requestId ? `${message} (Request ID: ${requestId})` : message, | |
| 'Close', | |
| { | |
| duration: 8000, | |
| panelClass: ['error-snackbar', 'server-error-snackbar'] | |
| } | |
| ); | |
| return; | |
| } | |
| // Network error (0 status usually indicates network issues) | |
| if (error.status === 0) { | |
| snackBar.open( | |
| 'Network connection error. Please check your internet connection.', | |
| 'Retry', | |
| { | |
| duration: 0, | |
| panelClass: ['error-snackbar', 'network-error-snackbar'] | |
| } | |
| ).onAction().subscribe(() => { | |
| window.location.reload(); | |
| }); | |
| return; | |
| } | |
| // Generic HTTP error | |
| const errorMessage = flareError?.message || error.message || `HTTP Error ${error.status}: ${error.statusText}`; | |
| snackBar.open( | |
| errorMessage, | |
| 'Close', | |
| { | |
| duration: 6000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ); | |
| } catch (err) { | |
| console.error('Error in handleHttpError:', err); | |
| this.showGenericError(snackBar); | |
| } | |
| } | |
| private handleClientError(error: Error, snackBar: MatSnackBar): void { | |
| try { | |
| // Check if it's a network error | |
| if (error.message?.includes('NetworkError') || error.message?.includes('Failed to fetch')) { | |
| snackBar.open( | |
| 'Network connection error. Please check your internet connection.', | |
| 'Retry', | |
| { | |
| duration: 0, | |
| panelClass: ['error-snackbar', 'network-error-snackbar'] | |
| } | |
| ).onAction().subscribe(() => { | |
| window.location.reload(); | |
| }); | |
| return; | |
| } | |
| // Check for specific Angular errors | |
| if (error.name === 'HttpErrorResponse') { | |
| // This might be an HTTP error that wasn't caught properly | |
| this.handleHttpError(error as any, snackBar, this.injector.get(Router)); | |
| return; | |
| } | |
| // Generic client error | |
| snackBar.open( | |
| 'An unexpected error occurred. Please refresh the page.', | |
| 'Refresh', | |
| { | |
| duration: 6000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ).onAction().subscribe(() => { | |
| window.location.reload(); | |
| }); | |
| } catch (err) { | |
| console.error('Error in handleClientError:', err); | |
| this.showGenericError(snackBar); | |
| } | |
| } | |
| private showGenericError(snackBar: MatSnackBar): void { | |
| snackBar.open( | |
| 'An error occurred. Please try again.', | |
| 'Close', | |
| { | |
| duration: 5000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ); | |
| } | |
| } | |
| // Error interceptor for consistent error format | |
| import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'; | |
| import { Observable, throwError } from 'rxjs'; | |
| import { catchError, finalize } from 'rxjs/operators'; | |
| () | |
| export class ErrorInterceptor implements HttpInterceptor { | |
| private activeRequests = new Map<string, AbortController>(); | |
| intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
| // Create abort controller for request cancellation | |
| const requestId = this.generateRequestId(); | |
| const abortController = new AbortController(); | |
| this.activeRequests.set(requestId, abortController); | |
| // Clone request with additional headers | |
| const clonedReq = req.clone({ | |
| setHeaders: { | |
| 'X-Request-ID': requestId | |
| } | |
| }); | |
| return next.handle(clonedReq).pipe( | |
| catchError((error: HttpErrorResponse) => { | |
| // Log request details for debugging | |
| console.error('HTTP Error:', { | |
| url: req.url, | |
| method: req.method, | |
| status: error.status, | |
| statusText: error.statusText, | |
| error: error.error, | |
| requestId: requestId, | |
| headers: error.headers?.keys() | |
| }); | |
| // Enhanced error object | |
| const enhancedError = { | |
| ...error, | |
| requestId: requestId, | |
| timestamp: new Date().toISOString(), | |
| url: req.url, | |
| method: req.method | |
| }; | |
| // Re-throw to be handled by global error handler | |
| return throwError(() => enhancedError); | |
| }), | |
| finalize(() => { | |
| // Clean up abort controller | |
| this.activeRequests.delete(requestId); | |
| }) | |
| ); | |
| } | |
| private generateRequestId(): string { | |
| return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; | |
| } | |
| // Method to cancel a specific request | |
| cancelRequest(requestId: string): void { | |
| const controller = this.activeRequests.get(requestId); | |
| if (controller) { | |
| controller.abort(); | |
| this.activeRequests.delete(requestId); | |
| } | |
| } | |
| // Method to cancel all active requests | |
| cancelAllRequests(): void { | |
| this.activeRequests.forEach((controller) => { | |
| controller.abort(); | |
| }); | |
| this.activeRequests.clear(); | |
| } | |
| } | 

