Skip to content

Instantly share code, notes, and snippets.

@MariMax
Created January 17, 2019 01:57
Show Gist options
  • Select an option

  • Save MariMax/0127f0e90173ff75e8a21c033f427579 to your computer and use it in GitHub Desktop.

Select an option

Save MariMax/0127f0e90173ff75e8a21c033f427579 to your computer and use it in GitHub Desktop.
import {Inject, Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {from as fromPromise, Observable, Subject, throwError} from 'rxjs';
import {catchError, map, switchMap, tap} from 'rxjs/operators';
import {ToasterService} from '../../../shared/toaster/toaster.service';
import {ToasterTypes} from '../../../shared/toaster/toaster-types.enum';
import {XsrfHolderService} from '../../xsrf-holder/xsrf-holder.service';
import {readFileAsText} from '../../../shared/utils/read-file';
import {CustomEncoder} from '../../custom-encoder/custom-encoder';
import {of} from 'rxjs/internal/observable/of';
import {PerformanceWrapper} from '../performance-wrapper.service';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private _logoutTrigger = new Subject();
private _logoutTrigger$ = this._logoutTrigger.asObservable();
public get logoutTrigger() {
return this._logoutTrigger$;
}
private headers = {
'Content-Type': 'application/json',
Accept: 'application/json'
};
private apiUrl = '';
private importantResponseStatuses = new Set<number>();
constructor(private http: HttpClient,
private toasterService: ToasterService,
private xsrfHolderService: XsrfHolderService,
@Inject(PerformanceWrapper) private performance: Performance) {
this.importantResponseStatuses.add(204);
this.importantResponseStatuses.add(400);
this.importantResponseStatuses.add(404);
this.importantResponseStatuses.add(500);
}
private static updateMessage(response, message: string) {
if (response && response.error && !response.error.msg) response.error.msg = message;
}
private static updateData(response, message: string) {
if (!response) return Promise.resolve();
if (!response.error) {
response.error = {};
} else if (response.error instanceof Blob) {
return readFileAsText(response.error, Promise).then(data => {
try {
response.error = JSON.parse(data);
} catch (e) {
}
finally {
ApiService.updateMessage(response, message);
}
});
}
ApiService.updateMessage(response, message);
return Promise.resolve();
}
private static checkIfErrorWorthNotification(doWeHandleIt: boolean, options): boolean {
return !!(doWeHandleIt && options && options.errMessage && options.errMessage.title);
}
private static mapResponse(response: { url: string, error: { msg: string } }, options: any) {
return {
data: response.error,
config: {
url: response.url,
errMessage: options.errMessage
},
};
}
private invalidateResponse(response, allowedStatus?: number) {
if (this.importantResponseStatuses.has(response.status) && allowedStatus !== response.status) {
return throwError(response);
}
return of(response);
}
private checkForError(response, requestOptions: any = {}) {
if (!response) return throwError(new Error('response does not exist'));
if (response.status === 401) {
this._logoutTrigger.next();
return throwError(new Error('access denied'));
}
if (response.error && typeof response.error === 'string') {
try {
response.error = JSON.parse(response.error);
} catch (e) {
response.error = {};
}
}
const isHandledWarning = response.error && response.error.informational;
if (ApiService.checkIfErrorWorthNotification(isHandledWarning, requestOptions)) {
ApiService.updateData(response, requestOptions.errMessage.title).then(() => {
this.toasterService.showErrorToast(ToasterTypes.Warning, ApiService.mapResponse(response, requestOptions));
});
}
const isHandledError = !isHandledWarning && this.importantResponseStatuses.has(response.status);
if (ApiService.checkIfErrorWorthNotification(isHandledError, requestOptions)) {
ApiService.updateData(response, requestOptions.errMessage.title).then(() => {
this.toasterService.showErrorToast(ToasterTypes.Error, ApiService.mapResponse(response, requestOptions));
});
}
if (ApiService.checkIfErrorWorthNotification(response.status === 415, requestOptions)) {
requestOptions.errMessage.title = 'This document type is not allowed for upload.';
ApiService.updateData(response, requestOptions.errMessage.title).then(() => {
this.toasterService.showErrorToast(ToasterTypes.Warning, ApiService.mapResponse(response, requestOptions));
});
}
if (ApiService.checkIfErrorWorthNotification(response.status === 490, requestOptions)) {
requestOptions.errMessage.title = 'Document is not allowed, it contains viruses.';
ApiService.updateData(response, requestOptions.errMessage.title).then(() => {
this.toasterService.showErrorToast(ToasterTypes.Warning, ApiService.mapResponse(response, requestOptions));
});
}
return throwError(response.error);
}
private waitForXsrf(options = {skipInterceptor: false}): Observable<void> {
if (options.skipInterceptor) return of(null);
return fromPromise(this.xsrfHolderService.ready());
}
private performRequest(method: string, path: string, measure: boolean, nativeResponse: boolean, allowedStatus: number, ...args) {
let httpObservable = this.http[method](`${this.apiUrl}${path}`, ...args);
if (measure) {
httpObservable = httpObservable
.pipe(
tap((response: Response) => {
const correlationId = response.headers.get('X-CorrelationId');
const performanceTimingForRequest = this.performance.getEntriesByType('resource')
.find((i: PerformanceResourceTiming) => {
const url = new URL(i.name);
return i.initiatorType === 'xmlhttprequest' && url.pathname === path;
});
if (performanceTimingForRequest) {
//when we measure performance, we report results(if there are any) to backend
this.sendStatsToBackend(performanceTimingForRequest, correlationId);
}
}),
);
}
return httpObservable
.pipe(
switchMap((response: Response) => this.invalidateResponse(response, allowedStatus)),
map((response: Response) => nativeResponse ? response : response.body)
);
}
public head(path: string, measure = false): Observable<any> {
return this.waitForXsrf()
.pipe(
map(() => this.prepareOptions()),
switchMap(_options => this.performRequest('head', path, measure, false, -1, _options)),
catchError(response => this.checkForError(response))
);
}
public get(path: string, options: any = {}, measure = false, nativeResponse = false): Observable<any> {
return this.waitForXsrf(options)
.pipe(
map(() => this.prepareOptions(options)),
switchMap(_options => this.performRequest('get', path, measure, nativeResponse, options.allowedStatus, _options)),
catchError(response => this.checkForError(response, options))
);
}
public post(path: string, body: any, options: any = {}, measure = false): Observable<any> {
return this.waitForXsrf(options).pipe(
map(() => this.prepareOptions(options)),
switchMap(_options => this.performRequest('post', path, measure, false, options.allowedStatus, body, _options)),
catchError(response => this.checkForError(response, options))
);
}
public delete(path: string, options, measure = false): Observable<any> {
return this.waitForXsrf(options).pipe(
map(() => this.prepareOptions(options)),
switchMap(_options => this.performRequest('delete', path, measure, false, options.allowedStatus, _options)),
catchError(response => this.checkForError(response, options))
);
}
public put(path: string, body: any, options: any = {}, measure = false): Observable<any> {
return this.waitForXsrf(options).pipe(
map(() => this.prepareOptions(options)),
switchMap(_options => this.performRequest('put', path, measure, false, options.allowedStatus, body, _options)),
catchError(response => this.checkForError(response, options))
);
}
private prepareOptions(options: any = {headers: {}, params: null}) {
const headers = {...this.headers, ...options.headers};
Object.keys(headers).forEach(key => {
if (!headers[key]) delete headers[key];
});
if (options.params) {
let params = new HttpParams({encoder: new CustomEncoder()});
Object.keys(options.params).forEach(key => params = params.set(key, options.params[key]));
options.params = params;
}
return {...options, headers: new HttpHeaders(headers), observe: 'response'};
};
public sendStatsToBackend(performanceTimingForRequest: Partial<PerformanceResourceTiming>, correlationId: string) {
return this.http.post(`${this.apiUrl}/service/processes/LogResourceTimingRequests`,
{
info: performanceTimingForRequest,
correlationId,
},
this.prepareOptions())
.toPromise()
.catch(() => null); // we don't care if logging failed
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment