import { Injectable } from '@angular/core'; import { HttpClient, HttpHandler, HttpRequest, HttpHeaders, HttpErrorResponse, HttpResponse, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { map, retry } from "rxjs/operators"; import { NotifService } from '../notif'; import { StorageService } from '../storage'; import { UtilsService } from '../utils'; import { CleverQueryService } from './clever-query.service'; export interface HttpOptions{ body?: any; headers?: HttpHeaders | { [header: string]: string | string[]; }; reportProgress?: boolean; observe: 'response'; params?: HttpParams | { [param: string]: string | string[]; }; responseType?: 'json'; withCredentials?: boolean; } @Injectable() export class Agent extends HttpClient{ config: any; urls: any; headers: HttpHeaders; baseDomain: string; accessToken: string = null; constructor( protected nextRequest: HttpHandler, protected req: HttpRequest, protected notif: NotifService, protected store: StorageService, protected cleverQuery: CleverQueryService, protected _: UtilsService ){ super(nextRequest); this.store = this.store.local(); let baseHeaders = new HttpHeaders(); baseHeaders.append('Authorization', `Bearer ${this.store.get('authToken')}`); nextRequest.handle(req.clone({ headers: baseHeaders })); } /* * high-level features */ go(){ this.aPost(this.urls.client.token, { security_key: this.config.agent.key, security_token: this.config.agent.token }) .map(() => { // return Observable. }); } aRequest(method: string, url: string, moreOptions?: HttpOptions): Observable{ // visually notify user of start of transaction this.onStart(); // start request and hope things goes as planned return this.request( method, url, moreOptions ) // things don't always work as planned // if things go wrong, try again three times // however, delay trial in an incremental manner (multipl of 1 second) .retryWhen((attempts) => { return attempts.zip(Observable.range(1, 3), (_, i) => i) .flatMap((i) => { this.notif.toast('NETWORK ERROR', 'retrying in ' + i + ' second(s)'); return Observable.timer(i * 1000); }); }) // if things works fine at first attempt // or before an error is thrown by retryWhen // and the server returns a good object // inspect the object for any non-http error status and message // and throw the error (then catch it and turn to an observable of undefined) // if no error, just return and observable of the response body .switchMap((response) => { return Observable.of(response) // check response body for error .map((response) => { // has errors if(response.body['status']){ return Observable.throw(new HttpErrorResponse({ status: response.body['code'], statusText: response.body['message'], url: response.url })); } // no errors return response.body; }) //non-http errors and turn to observable of undefined .catch(this.onError); }) // catch http errors and turn to observable of undefined .catch(this.onError) // filter out all observables of error (tagged as undefined) .filter((response) => { return response != undefined; }) // after all is said and done // let me know .finally(this.onFinish); } aGet(url: string, moreOptions?: HttpOptions): Observable{ return this.aRequest('GET', url, Object.assign({}, { observe: 'response' }, moreOptions)); } aPost(url: string, payload: Object, moreOptions?: HttpOptions): Observable{ return this.aRequest('GET', url, Object.assign({}, { observe: 'response', body: `payload=${JSON.stringify(payload)}` }, moreOptions)); } aPut(url: string, payload: Object, moreOptions?: HttpOptions): Observable{ return this.aRequest('GET', url, Object.assign({}, { observe: 'response', body: `payload=${JSON.stringify(payload)}` }, moreOptions)); } aDelete(url: string, moreOptions?: HttpOptions): Observable{ return this.aRequest('DELETE', url, Object.assign({}, { observe: 'response' }, moreOptions)); } aPatch(url: string, payload: Object, moreOptions?: HttpOptions): Observable{ return this.aRequest('PATCH', url, Object.assign({}, { observe: 'response', body: `payload=${JSON.stringify(payload)}` }, moreOptions)); } addAuthToken(url: string): string{ let authToken = this.store.get('authToken'); return (url.includes('?')) ? `${url}&auth_token=${authToken}` : `${url}?auth_token=${authToken}`; } onError (e: HttpErrorResponse){ let errMsg: string; let status = (e.status) ? '' + e.status : ''; if (e.error instanceof Error) { errMsg = `${e.error.name} - ${e.error.message || ''}`; } else { errMsg = e.error ? e.error : e.toString(); } this.notif.toast('HTTP Error - ' + status, errMsg); this.notif.complete(); return Observable.of(undefined); } onStart(){ this.notif.start(); this.notif.toast('HTTP Action', 'Connecting...'); } onFinish(){ this.notif.toast('HTTP Action', 'Connecting...Finished.'); this.notif.complete(); } }