All files / http error-notification.interceptor.ts

93.75% Statements 30/32
91.66% Branches 11/12
100% Functions 6/6
93.1% Lines 27/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 1071x 1x 1x 1x               1x 1x 1x                                                   1x 12x 12x     12x   11x 11x       11x           11x 1x       10x 1x       9x 9x 1x       8x 8x   8x       11x 11x                                       1x          
import { SKIP_ERROR_NOTIFICATION, CUSTOM_ERROR_MESSAGE, SKIP_ERROR_FOR_STATUSES } from './http-context-tokens';
import { HttpErrorMapperService } from './http-error-mapper.service';
import { NotificationService } from '../services/notification.service';
import {
    HttpInterceptor,
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpErrorResponse,
    HTTP_INTERCEPTORS,
} from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
 
/**
 * HTTP interceptor that automatically shows toast notifications for HTTP errors.
 *
 * Behavior can be controlled per-request using HTTP context tokens:
 *
 * @example
 * // Skip error notification entirely
 * this.http.get('/api/data', {
 *   context: new HttpContext().set(SKIP_ERROR_NOTIFICATION, true)
 * });
 *
 * @example
 * // Skip only for specific status codes
 * this.http.get('/api/data', {
 *   context: new HttpContext().set(SKIP_ERROR_FOR_STATUSES, [404])
 * });
 *
 * @example
 * // Custom error message
 * this.http.post('/api/action', body, {
 *   context: new HttpContext().set(CUSTOM_ERROR_MESSAGE, 'Failed to save')
 * });
 */
@Injectable()
export class ErrorNotificationInterceptor implements HttpInterceptor {
    private readonly notification = inject(NotificationService);
    private readonly errorMapper = inject(HttpErrorMapperService);
 
    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        return next.handle(request).pipe(
            catchError((error: unknown) => {
                if (error instanceof HttpErrorResponse) {
                    this.handleError(request, error);
                } else E{
                    this.notification.error('Произошла неожиданная ошибка.');
                }
                return throwError(() => error);
            })
        );
    }
 
    private handleError(request: HttpRequest<unknown>, error: HttpErrorResponse): void {
        if (!this.isApiRequest(request.url)) {
            return;
        }
 
        // Check if notifications are completely skipped for this request
        if (request.context.get(SKIP_ERROR_NOTIFICATION)) {
            return;
        }
 
        // Check if this specific status code should be skipped
        const skipStatuses = request.context.get(SKIP_ERROR_FOR_STATUSES);
        if (skipStatuses.length > 0 && skipStatuses.includes(error.status)) {
            return;
        }
 
        // Determine the message to show
        const customMessage = request.context.get(CUSTOM_ERROR_MESSAGE);
        const message = customMessage || this.errorMapper.mapError(error).message;
 
        this.notification.error(message);
    }
 
    private isApiRequest(url: string): boolean {
        try {
            return new URL(url, 'http://dummy').pathname.startsWith('/api/');
        } catch {
            return url.startsWith('/api/');
        }
    }
}
 
/**
 * Provider for ErrorNotificationInterceptor.
 * Add this to your app providers to enable automatic error notifications.
 *
 * @example
 * // app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideHttpClient(withInterceptorsFromDi()),
 *     errorNotificationInterceptorProvider,
 *   ],
 * };
 */
export const errorNotificationInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorNotificationInterceptor,
    multi: true,
};