common-ui/src/lib/error/server-error-interceptor.ts
2023-04-03 16:03:42 +03:00

84 lines
3.3 KiB
TypeScript

import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpStatusCode } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens';
import { ErrorService } from './error.service';
import { KeycloakService } from 'keycloak-angular';
import { IqserConfigService } from '../services';
import { KeycloakStatusService } from '../users';
function updateSeconds(seconds: number) {
if (seconds === 0 || seconds === 1) {
return seconds + 1;
}
return seconds * seconds;
}
function isNotServerError(error: HttpErrorResponse) {
return error.status < HttpStatusCode.InternalServerError && error.status !== 0;
}
function isPathSkipped(path: string, skippedPaths: string[]) {
const sanitized = path.replace('https://', '').replace('http://', '');
const pathWithoutHost = sanitized.split(/\/(.*)/s)[1];
return skippedPaths.some(skippedPath => pathWithoutHost === skippedPath);
}
function backoffOnServerError(maxRetries = 3, skippedPaths: string[] = []): MonoTypeOperatorFunction<HttpEvent<unknown>> {
let seconds = 0;
function delay(error: HttpErrorResponse) {
seconds = updateSeconds(seconds);
if (isNotServerError(error) || (error.url && isPathSkipped(error.url, skippedPaths))) {
return throwError(() => error);
}
console.error('An error occurred: ', error);
console.error(`Retrying in ${seconds} seconds...`);
return timer(seconds * 1000);
}
return retry({ count: maxRetries, delay });
}
@Injectable()
export class ServerErrorInterceptor implements HttpInterceptor {
private readonly _urlsWithError = new Set();
constructor(
private readonly _errorService: ErrorService,
private readonly _keycloakService: KeycloakService,
private readonly _configService: IqserConfigService,
private readonly _keycloakStatusService: KeycloakStatusService,
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number,
@Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[],
) {}
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
// token expired
if (error.status === HttpStatusCode.Unauthorized) {
this._keycloakStatusService.createLoginUrlAndExecute();
}
// server error
if (error.status >= HttpStatusCode.InternalServerError && error.url && !isPathSkipped(error.url, this._skippedPaths)) {
this._errorService.set(error);
this._urlsWithError.add(req.url);
}
return throwError(() => error);
}),
backoffOnServerError(this._maxRetries, this._skippedPaths),
tap(() => {
if (this._urlsWithError.has(req.url)) {
this._errorService.setOnline();
this._urlsWithError.delete(req.url);
}
}),
);
}
}