Reputation: 3151
I'm building a small function that will refresh the access token after a specific time (base on calculation).
public buildRefreshTokenTimerAsync(): Observable<LoginResultViewModel> {
let accessToken = '';
let refreshToken = '';
let lastRefreshTokenTime: number;
let issuedTokenTime: number;
let accessTokenLifeTime = ACCESS_TOKEN_LIFE_TIME;
let user: ProfileViewModel;
let retriedTime = 0;
// Load the last time the application refresh the access token.
const loadLastRefreshTokenTimeObservable = this.storageMap
.get<number>(LocalStorageKeyConstant.lastRefreshTokenAt)
.pipe(
tap((time: number) => {
lastRefreshTokenTime = time;
})
);
// Load login result from storage map observable.
const loadLoginResultObservable = this.storageMap
.get<LoginResultViewModel>(LocalStorageKeyConstant.loginResult)
.pipe(
tap((loginResult: LoginResultViewModel) => {
if (!loginResult || !loginResult.accessToken || !loginResult.accessToken.trim()) {
throw new Error(ExceptionCodeConstant.accessTokenNotFound);
}
if (!loginResult.refreshToken || !loginResult.refreshToken.trim()) {
throw new Error(ExceptionCodeConstant.refreshTokenNotFound);
}
accessToken = loginResult.accessToken;
refreshToken = loginResult.refreshToken;
issuedTokenTime = loginResult.issuedAt;
accessTokenLifeTime = loginResult.lifeTime;
user = loginResult.user;
})
);
return of(null)
.pipe(
flatMap(_ => {
return forkJoin([
loadLastRefreshTokenTimeObservable,
loadLoginResultObservable]);
}),
flatMap(() => {
// Get the current time in the system.
const currentTime = new Date().getTime();
// Calculate the time to refresh the access token.
const timeToRefreshToken = this.calculateAccessTokenRefreshTime(issuedTokenTime, ACCESS_TOKEN_LIFE_TIME);
// Access token hasn't been refreshed before.
if (lastRefreshTokenTime === undefined || lastRefreshTokenTime === null) {
// Refresh time was in the past. Refresh the token now.
if (currentTime >= timeToRefreshToken) {
return timer(0);
}
}
// Access token has refreshed before, however, another thread is handling the operation.
// Retry after the specific time.
if (currentTime - lastRefreshTokenTime < REFRESH_TIME_GAP) {
throw new Error(ExceptionCodeConstant.accessTokenIsRefreshing);
}
// Refresh time is in the future.
if (currentTime < timeToRefreshToken) {
const delayTime = timeToRefreshToken - currentTime;
return timer(delayTime);
}
return timer(0);
}),
flatMap(_ => this.loadRefreshTokenAsync(refreshToken)),
flatMap((loginResult: LoginResultViewModel) => {
loginResult.user = user;
return this.storageMap
.set(LocalStorageKeyConstant.loginResult, loginResult)
.pipe(
flatMap(_ => {
return this.storageMap.set(LocalStorageKeyConstant.lastRefreshTokenAt, new Date().getTime());
}),
map(() => {
return loginResult;
})
);
}),
retryWhen(errors => {
return errors
.pipe(
flatMap(error => {
retriedTime++;
if (retriedTime > 2) {
throw error;
}
if (!(error instanceof Error)) {
return throwError(error);
}
if (ExceptionCodeConstant.accessTokenIsRefreshing === (error as Error).message) {
return timer(2000);
}
return throwError(error);
})
);
})
);
}
What I expect is when ACCESS_TOKEN_IS_REFRESHING
exception is thrown, the function will retry after 2 seconds.
If I use retry
instead of retryWhen
, the function is OK, but when I changed to retryWhen
and return a timer
, function does not retry after 2 second.
Am I doing anything wrong ?
Upvotes: 0
Views: 128
Reputation: 71911
First:
I'm building a small function
Posts a +100 line function with major indentations :D
Second:
I think you shouldn't use flatMap
but a delayWhen
:
retryWhen((errors) => errors.pipe(
delayWhen((error) => {
retriedTime++;
if (retriedTime > 2) {
throw error;
}
if (!(error instanceof Error)) {
return throwError(error);
}
if (ExceptionCodeConstant.accessTokenIsRefreshing === (error as Error).message) {
return timer(2000);
}
return throwError(error);
})
))
Simplified stackblitz showing it works
Upvotes: 1