MireaFrontend/src/api/api.service.ts

208 lines
7.0 KiB
TypeScript
Raw Normal View History

import {
BehaviorSubject,
catchError,
distinctUntilChanged,
filter,
first,
Observable,
of,
ReplaySubject,
switchMap
} from "rxjs";
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
2024-06-15 00:37:12 +03:00
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 (!secondTry && error.status === 401)
return this.handle401Error().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(): 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 err;
})
);
}
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));
}
2024-07-02 00:54:21 +03:00
public put<Type>(request: RequestData | string | null = null): Observable<Type> {
return this.makeHttpRequest<Type>('put', this.getRequest(request));
}
2024-07-02 00:54:21 +03:00
public delete<Type>(request: RequestData | string | null = null): Observable<Type> {
return this.makeHttpRequest<Type>('delete', this.getRequest(request));
}
2024-07-02 00:54:21 +03:00
public addAuth(request: RequestData) {
request.needAuth = true;
request.withCredentials = true;
2024-07-02 00:54:21 +03:00
return this;
}
private handleError(error: HttpErrorResponse): void {
// todo: change to Retry-After condition
2024-08-23 23:21:12 +03:00
if (error.error && error.error.toString().includes("setup")) {
this.router.navigate(['/setup/']).then();
return;
}
2024-10-07 03:25:13 +03:00
let title: string;
let message: string | undefined = undefined;
if (error.error instanceof ErrorEvent) {
2024-10-07 03:25:13 +03:00
title = `Произошла ошибка: ${error.error.message}`;
} else {
switch (error.status) {
case 0:
2024-10-07 03:25:13 +03:00
title = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.';
break;
case 400:
2024-10-07 03:25:13 +03:00
title = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
break;
case 401:
2024-07-02 00:54:21 +03:00
this.router.navigate(['/login/']).then();
2024-10-07 03:25:13 +03:00
title = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
break;
case 403:
2024-10-07 03:25:13 +03:00
title = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.';
break;
case 404:
2024-10-07 03:25:13 +03:00
title = 'Запрашиваемый ресурс не найден.';
break;
case 500:
2024-10-07 03:25:13 +03:00
title = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.';
break;
case 503:
2024-10-07 03:25:13 +03:00
title = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.';
break;
default:
2024-10-07 03:25:13 +03:00
title = `Сервер вернул код ошибки: ${error.status}`;
break;
}
2024-10-07 03:25:13 +03:00
if (error.error?.Error)
message = error.error.Error;
else if (error.error instanceof String)
message = error.error.toString();
2024-10-07 03:25:13 +03:00
else
message = error.error.statusMessage;
}
2024-10-07 03:25:13 +03:00
this.notify.error(message == '' ? undefined : message, title);
}
}