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?