import { Injectable , Injector } from '@angular/core' ; import { HttpClient , HttpEvent , HttpInterceptor , HttpHandler , HttpRequest , HttpErrorResponse } from '@angular/common/http' ; import { Subject , Observable , throwError } from 'rxjs' ; import { map , first , switchMap , catchError } from 'rxjs/operators' ; import { AuthService } from './auth.service' ; import { AUTH_SERVICE } from './tokens' ; @ Injectable ( ) export class AuthInterceptor implements HttpInterceptor { /**
     * Is refresh token is being executed
     */ private refreshInProgress = false ; /**
     * Notify all outstanding requests through this subject
     */ private refreshSubject : Subject < boolean > = new Subject < boolean > ( ) ; constructor ( private injector ) { } /**
     * Intercept an outgoing `HttpRequest`
     */ public intercept ( req , delegate ) { if ( this . skipRequest ( req ) ) { return delegate . handle ( req ) ; } return this . processIntercept ( req , delegate ) ; } /**
     * Process all the requests via custom interceptors.
     */ private processIntercept ( original , delegate ) { const clone = original . clone ( ) ; return this . request ( clone ) . pipe ( switchMap ( ( req ) => delegate . handle ( req ) ) , catchError ( ( res ) => this . responseError ( clone , res ) ) ) ; } /**
     * Request interceptor. Delays request if refresh is in progress
     * otherwise adds token to the headers
     */ private request ( req ) { if ( this . refreshInProgress ) { return this . delayRequest ( req ) ; } return this . addToken ( req ) ; } /**
     * Failed request interceptor, check if it has to be processed with refresh
     */ private responseError ( req , res ) { const authService = this . injector . get < AuthService > ( AUTH_SERVICE ) ; const refreshShouldHappen = authService . refreshShouldHappen ( res , req ) ; if ( refreshShouldHappen && ! this . refreshInProgress ) { this . refreshInProgress = true ; authService . refreshToken ( ) . subscribe ( ( ) => { this . refreshInProgress = false ; this . refreshSubject . next ( true ) ; } , ( ) => { this . refreshInProgress = false ; this . refreshSubject . next ( false ) ; } ) ; } if ( refreshShouldHappen && this . refreshInProgress ) { return this . retryRequest ( req , res ) ; } return throwError ( res ) ; } /**
     * Add access token to headers or the request
     */ private addToken ( req ) { const authService = this . injector . get < AuthService > ( AUTH_SERVICE ) ; return authService . getAccessToken ( ) . pipe ( map ( ( token ) => { if ( token ) { let setHeaders ; if ( typeof authService . getHeaders === 'function' ) { setHeaders = authService . getHeaders ( token ) ; } else { setHeaders = { Authorization : `Bearer ${ token }` } ; } return req . clone ( { setHeaders } ) ; } return req ; } ) , first ( ) ) ; } /**
     * Delay request, by subscribing on refresh event, once it finished, process it
     * otherwise throw error
     */ private delayRequest ( req ) { return this . refreshSubject . pipe ( first ( ) , switchMap ( ( status ) => status ? this . addToken ( req ) : throwError ( req ) ) ) ; } /**
     * Retry request, by subscribing on refresh event, once it finished, process it
     * otherwise throw error
     */ private retryRequest ( req , res ) { const http = this . injector . get < HttpClient > ( HttpClient ) ; return this . refreshSubject . pipe ( first ( ) , switchMap ( ( status ) => status ? http . request ( req ) : throwError ( res || req ) ) ) ; } /**
     * Checks if request must be skipped by interceptor.
     */ private skipRequest ( req ) { const skipRequest = this . exec ( 'skipRequest' , req ) ; const verifyRefreshToken = this . exec ( 'verifyRefreshToken' , req ) ; const verifyTokenRequest = this . exec ( 'verifyTokenRequest' , req . url ) ; return skipRequest || verifyRefreshToken || verifyTokenRequest ; } /**
     * Exec optional method, will be removed in upcoming updates.
     * Temp method until `verifyTokenRequest` will be completely replaced with skipRequest
     */ private exec ( method , ... args ) { const authService = this . injector . get < AuthService > ( AUTH_SERVICE ) ; if ( typeof authService [ method ] === 'function' ) { return authService [ method ] ( ... args ) ; } } }