flare / flare-ui /src /app /services /error-handler.service.ts
ciyidogan's picture
Upload 118 files
9f79da5 verified
// 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;
}
@Injectable({
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';
@Injectable()
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();
}
}