0

There are 2 react components, both send requests to the server (with componentDidMount) Each request looks to see if the token is too old, if yes, it updates it with the refreshToken.

Problem - the first request will update the token successfully, will return a new token and a new refreshToken. The second request will be sent to the server with the old refreshToken and the server will generate an error.

The solution is to declare a global variable (window.pending). If it is false - we do not update the token, if the token is updated - assign it a promise, wait for its completion and apply the resulting token.

// From component 1

loadClient = async () => {
  const token = await this.props.getToken();
  // other code
};

// From component 2

loadProject = async () => {
  const token = await this.props.getToken();
  // other code
};

// НОС

const tokenProviderHOC = WrappedComponent =>
  class extends PureComponent<Props> {
    request = null;

    componentWillUnmount() {
      if (this.request !== null) this.request.abort(); //Cancel server request
    }

    checkToken = userId => async () => {
      const { history } = this.props;

      // token, refreshToken, expiresIn from localStorage
      const token = methods.getToken("token");
      const refreshToken = methods.getRefreshToken("refreshToken");
      const expiresIn = methods.getExpiresIn("expiresIn");

      if (Date.now() >= expiresIn) {
        if (window.pending === false) {
          console.log("we are first");

          let res;

          this.request = this._updateToken({ refreshToken, userId });

          window.pending = new Promise(async resolve => {
            console.log("request start");

            res = await this.request.ready(); //ready - start server request
            window.pending = false;

            console.log("request end", res ? res.token : false);
            return res ? resolve(res.token) : resolve(false);
          });
        }
        console.log("Token is already looking");
        return window.pending;
      }
      return token;
    };

    render() {
      const { userId, history, ...props } = this.props;

      return <WrappedComponent {...props} getToken={this.checkToken(userId)} />;
    }
  };

const mapStateToProps = state => ({
  userId: userSelectors.getUserInfo(state)?.id || null,
});

export const tokenProvider = compose(
  withRouter,
  connect(mapStateToProps),
  tokenProviderHOC,
);

Must be displayed in the console:

  • 'we are first'
  • 'request start' or 'Token is already looking'
  • 'request end'

Now the console displays:

  • 'we are first'
  • 'request start'
  • 'Token is already looking'
  • 'request end'
  • 'we are first'
  • 'request start'
  • 'Token is already looking'
  • tokenProvider.js:121 PUT http://site.loc/user/1/send net::ERR_ABORTED 400 (Bad Request)
    // because the token is another
  • request end false'

It seems that the second request is waiting for the first one, and then again trying to find out the token from the server passing it the old refreshToken. How to solve a problem?

AKX
  • 152,115
  • 15
  • 115
  • 172

1 Answers1

0

Ignoring the HOC stuff, since it doesn't really matter here, something like this should work. The idea is that you have a singleton of the promise that will eventually resolve to a new token.

Also, you can just as well use a (module-)global variable, no need to stash anything in window unless you especially need to.

tokenIsValid and _updateToken should be more or less self-explanatory, and error handling is elided here.

let fetchTokenPromise = null;
let currentToken = null;

function getToken() {
  if (currentToken && tokenIsValid(currentToken)) {
    // Current token is valid, return it (as a promise).
    return Promise.resolve(currentToken);
  }

  if (!fetchTokenPromise) {
    // We're not fetching a token, start fetching one.
    fetchTokenPromise = new Promise(async (resolve, reject) => {
      const token = await _updateToken(currentToken);
      currentToken = token; // Update saved token.
      fetchTokenPromise = null; // Clear fetching state.
      resolve(token);
    });
  }
  return fetchTokenPromise;
}

// ...

const token = await getToken();  // this will either get the current token, or start refreshing one
AKX
  • 152,115
  • 15
  • 115
  • 172