Created
January 17, 2019 01:57
-
-
Save MariMax/0127f0e90173ff75e8a21c033f427579 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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