uchaindb/UClient

View on GitHub
src/ClientApp/app/services/endpoint-factory.service.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { Injectable, Injector } from '@angular/core';
import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';

import { AuthService } from './auth.service';
import { ConfigurationService } from './configuration.service';


@Injectable()
export class EndpointFactory {

    static readonly apiVersion: string = "1";

    private readonly _loginUrl: string = "/connect/token";
    private readonly _codeUrl: string = "/connect/code";

    private get loginUrl() { return this.configurations.baseUrl + this._loginUrl; }
    private get codeUrl() { return this.configurations.baseUrl + this._codeUrl; }



    private taskPauser: Subject<any>;
    private isRefreshingLogin: boolean;

    private _authService: AuthService;

    private get authService() {
        if (!this._authService)
            this._authService = this.injector.get(AuthService);

        return this._authService;
    }



    constructor(protected http: Http, protected configurations: ConfigurationService, private injector: Injector) {

    }


    getAuthRequestEndpoint(phoneNumber: string): Observable<Response> {

        let header = new Headers();
        header.append("Content-Type", "application/x-www-form-urlencoded");

        let searchParams = new URLSearchParams();
        searchParams.append('type', 'code');
        searchParams.append('phonenumber', phoneNumber);

        let requestBody = searchParams.toString();

        return this.http.post(this.codeUrl, requestBody, { headers: header });
    }

    getLoginEndpoint(phoneNumber: string, code: string): Observable<Response> {
        let header = new Headers();
        header.append("Content-Type", "application/x-www-form-urlencoded");

        let searchParams = new URLSearchParams();
        searchParams.append('username', phoneNumber);
        searchParams.append('password', code);
        searchParams.append('grant_type', 'password');
        searchParams.append('scope', 'openid email profile offline_access roles');
        searchParams.append('resource', window.location.origin);

        let requestBody = searchParams.toString();

        return this.http.post(this.loginUrl, requestBody, { headers: header });    }

    getCodeLoginEndpoint(code: string): Observable<Response> {
        // as I don't fully understand oauth, this is the tricky code
        return this.getLoginEndpoint("<SPECIAL_CODE>", code);
    }


    getRefreshLoginEndpoint(): Observable<Response> {

        let header = new Headers();
        header.append("Content-Type", "application/x-www-form-urlencoded");

        let searchParams = new URLSearchParams();
        searchParams.append('refresh_token', this.authService.refreshToken);
        searchParams.append('grant_type', 'refresh_token');
        searchParams.append('scope', 'openid email profile offline_access roles');

        let requestBody = searchParams.toString();

        return this.http.post(this.loginUrl, requestBody, { headers: header })
            .map((response: Response) => {
                return response;
            })
            .catch(error => {
                return this.handleError(error, () => this.getRefreshLoginEndpoint());
            });
    }







    protected getAuthHeader(otherOptions?: RequestOptions, includeJsonContentType?: boolean): RequestOptions {
        let options = otherOptions == null ? new RequestOptions() : otherOptions;
        options.headers = options.headers == null ? new Headers() : options.headers;
        let headers = options.headers;

        headers.append("Authorization", 'Bearer ' + this.authService.accessToken);

        if (includeJsonContentType) headers.append("Content-Type", "application/json");

        headers.append("Accept", `application/vnd.iman.v${EndpointFactory.apiVersion}+json, application/json, text/plain, */*`);
        headers.append("App-Version", ConfigurationService.appVersion);

        return options;
    }



    protected handleError(error, continuation: () => Observable<any>) {

        if (error.status == 401) {
            if (this.isRefreshingLogin) {
                return this.pauseTask(continuation);
            }

            this.isRefreshingLogin = true;

            return this.authService.refreshLogin()
                .delay(200)
                .mergeMap(data => {
                    this.isRefreshingLogin = false;
                    this.resumeTasks(true);

                    return continuation();
                })
                .catch(refreshLoginError => {
                    this.isRefreshingLogin = false;
                    this.resumeTasks(false);

                    if (refreshLoginError.status == 401 || (refreshLoginError.url && refreshLoginError.url.toLowerCase().includes(this.loginUrl.toLowerCase()))) {
                        this.authService.reLogin();
                        return Observable.throw('session expired');
                    }
                    else {
                        return Observable.throw(refreshLoginError || 'server error');
                    }
                });
        }

        if (error.url && error.url.toLowerCase().includes(this.loginUrl.toLowerCase())) {
            this.authService.logout();
            this.authService.reLogin();
            return Observable.throw('session expired');
        }
        else {
            return Observable.throw(error || 'server error');
        }
    }

    protected getFormData(file: File): FormData {
        let formData: FormData = new FormData();
        formData.append("file", file);

        //let headers = new Headers();
        //headers.append('Accept', 'application/json');
        //// DON'T SET THE Content-Type to multipart/form-data, You'll get the Missing content-type boundary error
        //let options = new RequestOptions({ headers: headers });

        return formData;
    }


    private pauseTask(continuation: () => Observable<any>) {
        if (!this.taskPauser)
            this.taskPauser = new Subject();

        return this.taskPauser.switchMap(continueOp => {
            return continueOp ? continuation() : Observable.throw('session expired');
        });
    }


    private resumeTasks(continueOp: boolean) {
        setTimeout(() => {
            if (this.taskPauser) {
                this.taskPauser.next(continueOp);
                this.taskPauser.complete();
                this.taskPauser = null;
            }
        });
    }
}