All checks were successful
Build and Deploy Angular App / build (push) Successful in 1m20s
Signed-off-by: Polianin Nikita <wesser@noreply.git.winsomnia.net>
210 lines
7.2 KiB
TypeScript
210 lines
7.2 KiB
TypeScript
import {
|
||
BehaviorSubject,
|
||
catchError,
|
||
distinctUntilChanged,
|
||
filter,
|
||
first,
|
||
Observable,
|
||
of,
|
||
ReplaySubject,
|
||
switchMap
|
||
} from "rxjs";
|
||
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
|
||
import {environment} from "@environment";
|
||
import {Router} from "@angular/router";
|
||
import {Injectable} from "@angular/core";
|
||
import {RequestBuilder, RequestData} from "@api/RequestBuilder";
|
||
import {ToastrService} from "ngx-toastr";
|
||
import {AuthRoles} from "@model/authRoles";
|
||
|
||
export enum AvailableVersion {
|
||
v1
|
||
}
|
||
|
||
@Injectable()
|
||
export default abstract class ApiService {
|
||
constructor(protected http: HttpClient, protected notify: ToastrService, private router: Router) {
|
||
}
|
||
|
||
private apiUrl = environment.apiUrl;
|
||
private static isRefreshingToken: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||
private static refreshTokenSubject: ReplaySubject<any> = new ReplaySubject(1);
|
||
|
||
protected abstract basePath: string;
|
||
protected abstract version: AvailableVersion;
|
||
|
||
private static addQuery(endpoint: string, queryParams?: Record<string, string | number | boolean | Array<any> | null> | null): string {
|
||
const url = new URL(endpoint);
|
||
|
||
if (queryParams) {
|
||
Object.keys(queryParams).forEach(key => {
|
||
const value = queryParams[key];
|
||
if (value !== null && value !== undefined) {
|
||
if (typeof (value) === typeof (Array)) {
|
||
(value as Array<any>).forEach(x => url.searchParams.append(key, x.toString()));
|
||
} else
|
||
url.searchParams.append(key, value.toString());
|
||
}
|
||
});
|
||
}
|
||
|
||
return url.href;
|
||
}
|
||
|
||
private static combineUrls(...parts: string[]): string {
|
||
return parts.map(part => part.replace(/(^\/+|\/+$)/g, '')).join('/');
|
||
}
|
||
|
||
protected combinedUrl(request: RequestData) {
|
||
return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, request.endpoint), request.queryParams);
|
||
}
|
||
|
||
private sendHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData, secondTry: boolean = false): Observable<Type> {
|
||
const doneEndpoint = this.combinedUrl(request);
|
||
|
||
return this.http.request<Type>(method, doneEndpoint, {
|
||
withCredentials: request.withCredentials,
|
||
headers: request.httpHeaders,
|
||
body: request.data,
|
||
}).pipe(
|
||
catchError(error => {
|
||
if (request.needAuth && !secondTry && error.status === 401)
|
||
return this.handle401Error(error).pipe(
|
||
switchMap(() => this.sendHttpRequest<Type>(method, request, true))
|
||
);
|
||
else {
|
||
if (!request.silenceMode)
|
||
this.handleError(error);
|
||
|
||
throw error;
|
||
}
|
||
})
|
||
);
|
||
}
|
||
|
||
private refreshToken(): Observable<AuthRoles> {
|
||
return this.http.get<AuthRoles>(ApiService.combineUrls(this.apiUrl, AvailableVersion[AvailableVersion.v1], 'Auth', 'ReLogin'), {
|
||
withCredentials: true
|
||
});
|
||
}
|
||
|
||
private handle401Error(error: any): Observable<any> {
|
||
if (ApiService.isRefreshingToken.value)
|
||
return ApiService.refreshTokenSubject.asObservable();
|
||
|
||
ApiService.isRefreshingToken.next(true);
|
||
return this.refreshToken().pipe(
|
||
switchMap(_ => {
|
||
ApiService.isRefreshingToken.next(false);
|
||
ApiService.refreshTokenSubject.next(null);
|
||
return of(null);
|
||
}),
|
||
catchError(err => {
|
||
ApiService.isRefreshingToken.next(false);
|
||
ApiService.refreshTokenSubject.error(err);
|
||
ApiService.refreshTokenSubject = new ReplaySubject(1);
|
||
throw error;
|
||
})
|
||
);
|
||
}
|
||
|
||
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData): Observable<Type> {
|
||
if (request.needAuth) {
|
||
return ApiService.isRefreshingToken.pipe(
|
||
distinctUntilChanged(),
|
||
filter(isRefreshing => !isRefreshing),
|
||
first(),
|
||
switchMap(() => this.sendHttpRequest<Type>(method, request))
|
||
);
|
||
} else {
|
||
return this.sendHttpRequest<Type>(method, request);
|
||
}
|
||
}
|
||
|
||
private getRequest(request: RequestData | string | null): RequestData {
|
||
if (request === null)
|
||
return this.createRequestBuilder().build;
|
||
if (typeof request === 'string')
|
||
return this.createRequestBuilder().setEndpoint(request as string).build;
|
||
|
||
return request as RequestData;
|
||
}
|
||
|
||
public createRequestBuilder() {
|
||
return new RequestBuilder();
|
||
}
|
||
|
||
public get<Type>(request: RequestData | string | null = null): Observable<Type> {
|
||
return this.makeHttpRequest<Type>('get', this.getRequest(request));
|
||
}
|
||
|
||
public post<Type>(request: RequestData | string | null = null): Observable<Type> {
|
||
return this.makeHttpRequest<Type>('post', this.getRequest(request));
|
||
}
|
||
|
||
public put<Type>(request: RequestData | string | null = null): Observable<Type> {
|
||
return this.makeHttpRequest<Type>('put', this.getRequest(request));
|
||
}
|
||
|
||
public delete<Type>(request: RequestData | string | null = null): Observable<Type> {
|
||
return this.makeHttpRequest<Type>('delete', this.getRequest(request));
|
||
}
|
||
|
||
public addAuth(request: RequestData) {
|
||
request.needAuth = true;
|
||
request.withCredentials = true;
|
||
return this;
|
||
}
|
||
|
||
private handleError(error: HttpErrorResponse): void {
|
||
// todo: change to Retry-After condition
|
||
if (error.error && error.error.detail.includes("setup")) {
|
||
this.router.navigate(['/setup/']).then();
|
||
return;
|
||
}
|
||
|
||
let title: string;
|
||
let message: string | undefined = undefined;
|
||
if (error.error instanceof ErrorEvent) {
|
||
title = `Произошла ошибка: ${error.error.message}`;
|
||
} else {
|
||
if (error.error && error.error.type && error.error.title) {
|
||
title = error.error.title || `Ошибка с кодом ${error.status}`;
|
||
message = error.error.detail || 'Неизвестная ошибка';
|
||
} else {
|
||
switch (error.status) {
|
||
case 0:
|
||
title = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.';
|
||
break;
|
||
case 400:
|
||
title = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
|
||
break;
|
||
case 401:
|
||
this.router.navigate(['/login/']).then();
|
||
title = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
|
||
break;
|
||
case 403:
|
||
title = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.';
|
||
break;
|
||
case 404:
|
||
title = 'Запрашиваемый ресурс не найден.';
|
||
break;
|
||
case 500:
|
||
title = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.';
|
||
break;
|
||
case 503:
|
||
title = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.';
|
||
break;
|
||
default:
|
||
title = `Сервер вернул код ошибки: ${error.status}`;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!message)
|
||
message = error.error.statusMessage;
|
||
}
|
||
this.notify.error(message == '' ? undefined : message, title);
|
||
}
|
||
}
|