How to subscribe to an observable from a function that returns observable?

608 Views Asked by At

So I have a put method that returns an observable, inside this method I need to check if the token is valid, if it is not valid, then I will need to call another method that can create a new refresh token and I need to subscribe to this method so that I can update the values of the local refresh token, and then return the observable for the put method.

This is what I have so far:

    public putRequest(endpoint: string, body: any):
     Observable < APIResponseModel < any >> {
      if (this.authService.isValidToken()) {
       //     . . .
      }));
    }
    else {

     // get a new token 
     const refresh = this.authService.refreshToken();

     return refresh.switchMap(response => {

      this.authService.setRefreshToken(response.results.refreshToken);

      return this.httpClient.put < any > (`${endpoint}`, body).pipe(map(result => {
       this.hideLoader();
       return result;
      }));
     }).catch(err => {
      console.log("error occurred! " + err)
      this.authService.redirectToLogin();
      return this.getNullResponse();
     });

    }

AuthService methods:

  isValidToken(): boolean {
        const token = this.getAuthToken();
        if (!token && this.firstload) {

          return true; }
        if (token && !this.firstload) {
          if (this.jwtHelper.isTokenExpired(token)) { 
            console.log("Token is expired  ");

            return false;
          } else {
            return true;
          }
        } else {
          return false;
        }
      }

  refreshToken(): Observable<APIResponseModel<any>> {

    console.log("refresh token:" + this.getRefreshToken());
    const url = `${environment.baseAPIUrl}/${environment.version}/login/token/refresh`;
    const body = {
      refreshToken: `${this.getRefreshToken()}`
    };
    return this.httpClient.post(url, body).map((response: APIResponseModel<any>) => {
      this.setAuthToken(response.results.token);
      this.setRefreshToken(response.results.refreshToken);
      this.tokenBeingRefreshed = false;
      return response;
    }, err => err);
  }

Note that I tried SwitchMap and MergeMap but I am getting server error that the session is expired. Seems I am getting this error before waiting to generate a new token. How can I make sure a new token is created before calling the httpClient.put ?

2

There are 2 best solutions below

0
On

Don't worry to make sure refresh token before any http request in server, but use Http Interceptor when you are making a request in server set token in request header and check it in server-side if token has expire or signature is not valid or anything else is wrong set response header status 401 and use catchError to catch error and check if status is 401 then call refresh token method that is going to send a request in server to refresh token using switchMap as example below:

here is httpInterceptor

export class HttpInterceptorService implements HttpInterceptor {

constructor(private auth : AuthService ) { }

isRefreshingToken: boolean = false;

tokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

intercept(request: HttpRequest<any>, next: HttpHandler) : Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | any> {

  if( this.auth.token ) {

      if( this.isRefreshingToken ){ //  next handle when make request to refresh token .........

          return next.handle(this.setRequestHeaders( request ));

      }else{ // request and catch Error when token is expired or any else Error ...........

          return next.handle(this.setRequestHeaders( request ))
              .pipe(
                  catchError(err => { // catch response error from server
                      if (err instanceof HttpErrorResponse) {
                          switch ((<HttpErrorResponse>err).status) {
                              case 401: // if is 401 error
                                  return this.handle401Error(request, next);  // return handle401Error method
                          }
                      } else {
                          return throwError(err);
                      }
                  })
              );

      }

  }else{
      return next.handle(this.setRequestHeaders( request ))
  }

  private setRequestHeaders(request: HttpRequest<any> ) : HttpRequest<any> { // set request headers ......

     if( this.isRefreshingToken ){

         return request.clone({headers :request.headers.set('Refresh-Token',this.auth.refresh_token || '')});

     } 

     else if( this.auth.token ){

           return request.clone({headers :request.headers.set('Token', this.auth.token || '' ) });

     }
  }

  private handle401Error(request: HttpRequest<any>, next: HttpHandler) { // 401 error from server when token is expired ..

  if (!this.isRefreshingToken) {

      this.isRefreshingToken = true;
      // Reset here so that the following requests wait until the token
      // comes back from the refreshToken call.

      this.tokenSubject.next(null);

      return this.auth.refreshToken(clientSignature)  // make request to refresh token return false or new token and new refresh token

          .pipe(  // get result from refresh token ............................

              switchMap((result: any) => {

                  if ( result ) {

                      this.isRefreshingToken = false;

                      this.auth.refresh_tokens( result );

                      this.tokenSubject.next( result );

                      return next.handle(this.setRequestHeaders( request ) );
                  }

                  this.isRefreshingToken = false;

                  this.auth.logout();

                  this.tokenSubject.next(false);

                  return next.handle(request);
              }),
              catchError( err => {

                  this.isRefreshingToken = false;

                  this.auth.logout();

                  this.tokenSubject.next(false);

                  return next.handle(request);

              } ),

              finalize(() => {
                  this.isRefreshingToken = false;
              }),
          );

  } else {
      return this.tokenSubject
          .pipe(filter(token => token != null),
              take(1),
              switchMap( token => {

                  if( token ){

                      return next.handle(this.setRequestHeaders( request ));

                  }else{
                      return next.handle(request);
                  }

              })
        );
  }
 }
} 

provide http interceptor in app.module:

providers: [

       {
          provide:HTTP_INTERCEPTORS,

          useClass:HttpInterceptorService,

          multi:true

      }
]
0
On

So if i got your issue correctly it is

  • PUT
  • if (!token) → GET(token)
  • retry PUT

In this case, the correct flow would be

getData() {
  return this.http.put(...);
}

getToken() {
  return this.http.get(...);
}

isValidToken() {
  return ...;
}

securedCall() {
  const data$ = this.isValidToken ? 
    this.getData() : 
    this.getToken().pipe(
      switchMap(token => this.getData())
    );

  return data$;
}

Way cleanier and easier to read than using operators !