Home > OS >  How to prevent API calls on a minute timer loop when in the process of logging out of a web app?
How to prevent API calls on a minute timer loop when in the process of logging out of a web app?

Time:11-03

In my Ionic/Angular app, I have a 60 second timer observable which just emits the current time synced with server time. Each minute I fetch permissions, settings, etc. I pass a token with each request. On logout I revoke the token. Here's a sample of what my logic looks like.

Side note: There's also a feature where a user can "change login type" where they can "become" an administrator, for example, and this process may also trigger a similar circumstance.

this.clientTimeSub = this.timeService.clientTime
      .pipe(takeUntil(this.logoutService.isLoggingOut$))
      .subscribe(async (latestClientTime) => {
          this.clientTime = { ...latestClientTime };
          // if client time just rolled over to a new minute, update settings
          if (
            this.clientTime?.time?.length === 7 &&
            this.clientTime?.time?.slice(-1) === '0'
          ) {
            await updateSettings();
            await updatePermissions();
            // etc

            // These functions will:
            // (1) make an api call (using the login token!)
            // (2) update app state
            // (3) save to app storage

          }
      });

When I am logging out of the app, there's a small time window where I could be in the middle of sending multiple api requests and the token is no longer valid, due to the timer rolling to a new minute just as I was logging out, or close to it. I am then presented with a 401: Unauthorized in the middle of logging out.

My naive solution was to tell this observable to stop propagation when a Subject or BehaviorSubject fires a value telling this observable that it is logging out, you can see this here .pipe(takeUntil(this.logoutService.isLoggingOut$)).

Then, in any of my logout methods, I would use:

logout() {
  this.isLoggingOut.next(true);
  
  ...
  // Logout logic here, token becomes invalidated somewhere here
  // then token is deleted from state, etc, navigate back to login...
  ...
  
 this.isLoggingOut.next(false);
}

In that small time window of logging out, the client timer should stop firing and checking if it's rolled to a new minute, preventing any further api calls that may be unauthenticated.

Is there a way I can easily prevent this issue from happening or is there a flaw in my logic that may be causing this issue?

I appreciate any help, thank you!

CodePudding user response:

First of all, it is not the best way to use async-await along with RXJS. Its because RXJS as a reactive way of functional programming, have its "pipeable" operators so you can kinda "chain" everything.

So instead of having a logic of calculating time in your subscribe callback function you should rather use, for example filter() RXJ operator, and instead of using await-async you can use switchMap operator and inside it, use forkJoin or concat operator.

this.timeService.clientTime
  .pipe(
    // Filter stream (according to your calculation)
    filter((time) => {
      // here is your logic to calculate if time has passed or whatever else you are doing
      // const isValid = ...
      return isValid;
    }),
    // Switch to another stream so you can call api calls
    // Here with "from" we are converting promises to observables in order to be able to use magic of RXJS
    switchMap(_ => forkJoin([from(updateSettings), from(updatePermissions)])),
    // Take until your logout
    takeUntil(this.logoutService.isLoggingOut$)
  ).subcribe(([updateSettings, updatePermissions]) => {
    
   // Basically your promises should just call API services, and other logic should be here
    
  // Here you can use
  // (2) update app state
  // (3) save to app storage
})

If you split actions like in my example, in your promises you just call api calls to update whatever you are doing, then when its done, in subscribe callback you can update app state, save to app storage etc. So you can have 2 scenarios here:

  1. Api calls from promises, are still in progress. If you trigger logout in the meanwhile takeUntil will do the thing and you will not update app state etc.
  2. If both Api calls from promises are done, you are in a subscribe callback block and if its just a synchronous code (hopefully) it will be done. And then async code can be executed (your timer can now emit next value, its all about Event Loop in javascript)
  • Related