Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change header on HTTP when retrying an Observable

I am trying to implement refresh tokens on my Angular2 app. I am taking an optimistic approach and instead of checking if the access token expired before making the request, I am making the request and if it returns a 401 code, I will refresh the access token by requesting a new one and saving it to local storage.

Here is my code snippet:

   getWithParams<T>(serviceUrl: string, params: URLSearchParams): Observable<T> {
    return super.getWithParams<T>(serviceUrl, params)
        .retryWhen((error) => {
            return error
                .filter((e) => e.status === 401)
                .scan((acc, value) => {
                    return acc + 1;
                }, 0)
                .takeWhile(acc => acc < 3)
                .flatMap(() => this.tokenRefreshService.refreshToken())
                .delay(1000);
        });
}

It is important to mention that super.getWithParams setsthe access token on the request headers by retrieving it from the local storage.

The method call tokenRefreshService.refreshToken() gets a new access token and saves it to local storage.

The problem that I am facing is that when the request is retried it is using the old access token, that is, it is no calling super.getWithParams to rebuild the request again. It just retries the exsiting observable.

Is there a way of building the request again? or chainging the request header of the observable that failed?

like image 557
Radu Cojocari Avatar asked Jan 16 '17 10:01

Radu Cojocari


1 Answers

Actually retryWhen() resubscribes to its source so you can use it to your advantage. This example should simulate your situation:

let token = 'token';
let counter = 0;

const source$ = Rx.Observable.defer(() => {
    console.log('Observable.defer(), token: ' + token);
    return Rx.Observable.of(token);
  })
  .map(token => {
    if (counter++ < 3) {
      throw new Error('invalid token');
    }
    return token;
  })
  .retryWhen((error) => {
    return error
      .filter(() => true) // or whatever...
      .do(() => token = token + 'bla'); // update the token
  })
  .map(token => { // create the request
    return "I'm a request with token: " + token;
  });


source$.subscribe(
  res => console.log(res),
  err => console.log('error: ' + err),
  () => console.log('complete')
);

See live demo: https://jsbin.com/roduqi/5/edit?js,console

This three times throws an error with invalid token and updates it every time.

Notice, that on every error I'm creating a new source Observable with Observable.defer.

This example print to console:

Observable.defer(), token: token
Observable.defer(), token: tokenbla
Observable.defer(), token: tokenblabla
Observable.defer(), token: tokenblablabla
I'm a request with token: tokenblablabla
complete
like image 178
martin Avatar answered Dec 04 '22 13:12

martin