refactor: refresh token

The service did not update tokens well, so it was rewritten
This commit is contained in:
Polianin Nikita 2024-08-24 04:27:13 +03:00
parent 48a74ecbf5
commit 1f03c2a9c3
3 changed files with 57 additions and 53 deletions

View File

@ -4,7 +4,6 @@ import {FooterComponent} from "@component/common/footer/footer.component";
import localeRu from '@angular/common/locales/ru'; import localeRu from '@angular/common/locales/ru';
import {registerLocaleData} from '@angular/common'; import {registerLocaleData} from '@angular/common';
import {FocusNextDirective} from "@/directives/focus-next.directive"; import {FocusNextDirective} from "@/directives/focus-next.directive";
import {TokenRefreshService} from "@service/token-refresh.service";
import {HeaderComponent} from "@component/common/header/header.component"; import {HeaderComponent} from "@component/common/header/header.component";
@Component({ @Component({
@ -17,8 +16,7 @@ import {HeaderComponent} from "@component/common/header/header.component";
<app-footer/>` <app-footer/>`
}) })
export class AppComponent { export class AppComponent {
constructor(tokenRefreshService: TokenRefreshService) { constructor() {
registerLocaleData(localeRu); registerLocaleData(localeRu);
tokenRefreshService.startTokenRefresh();
} }
} }

View File

@ -1,6 +1,6 @@
import {EventEmitter, Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http"; import {HttpClient, HttpHeaders} from "@angular/common/http";
import {Observable, tap} from "rxjs"; import {map, Observable, throwError} from "rxjs";
import {TokenResponse} from "@api/v1/tokenResponse"; import {TokenResponse} from "@api/v1/tokenResponse";
import ApiService from "@api/api.service"; import ApiService from "@api/api.service";
@ -21,7 +21,7 @@ export class AuthToken {
this.endpoint = refreshEndpoint; this.endpoint = refreshEndpoint;
} }
static httpHeader(token: AuthToken): HttpHeaders { public static httpHeader(token: AuthToken): HttpHeaders {
let header = new HttpHeaders(); let header = new HttpHeaders();
if (token.authProvider === AvailableAuthenticationProvider.Bearer) if (token.authProvider === AvailableAuthenticationProvider.Bearer)
@ -35,8 +35,6 @@ export class AuthToken {
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
public expireTokenChange = new EventEmitter<Date>();
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
} }
@ -56,12 +54,12 @@ export class AuthService {
return result <= new Date() ? new Date() : result; return result <= new Date() ? new Date() : result;
} }
public refreshToken(): Observable<TokenResponse> { public refreshToken(): Observable<Date> {
const token = localStorage.getItem(ApiService.tokenKey); const token = localStorage.getItem(ApiService.tokenKey);
console.log(token); console.log(token);
if (!token) if (!token)
throw new Error("token is not found"); return throwError(() => new Error("Token is not found"));
const authToken = JSON.parse(token) as AuthToken; const authToken = JSON.parse(token) as AuthToken;
@ -69,14 +67,16 @@ export class AuthService {
case AvailableAuthenticationProvider.Bearer: case AvailableAuthenticationProvider.Bearer:
return this.http.get<TokenResponse>(authToken.endpoint, {withCredentials: true}) return this.http.get<TokenResponse>(authToken.endpoint, {withCredentials: true})
.pipe( .pipe(
tap(response => { map(response => {
const newExpireDate = new Date(response.expiresIn); const newExpireDate = new Date(response.expiresIn);
const oldExpireDate = new Date(authToken.expiresIn); const oldExpireDate = new Date(authToken.expiresIn);
if (newExpireDate.getTime() !== oldExpireDate.getTime()) { if (newExpireDate.getTime() !== oldExpireDate.getTime()) {
AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, authToken.endpoint); AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, authToken.endpoint);
this.expireTokenChange.emit(newExpireDate); return newExpireDate;
} }
return newExpireDate;
}) })
); );
} }

View File

@ -1,4 +1,4 @@
import {BehaviorSubject, filter, interval, Subscription, switchMap} from "rxjs"; import {BehaviorSubject, catchError, of, Subject, Subscription} from "rxjs";
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {AuthService} from "@service/auth.service"; import {AuthService} from "@service/auth.service";
import {environment} from "@environment"; import {environment} from "@environment";
@ -8,64 +8,70 @@ import ApiService from "@api/api.service";
providedIn: 'root', providedIn: 'root',
}) })
export class TokenRefreshService { export class TokenRefreshService {
private tokenRefreshSubscription: Subscription | undefined;
private tokenRefreshing$ = new BehaviorSubject<boolean>(false); private tokenRefreshing$ = new BehaviorSubject<boolean>(false);
private refreshTokenTimeout: any;
private refreshTokenExpireMs: number = environment.retryDelay; private refreshTokenExpireMs: number = environment.retryDelay;
constructor(private authService: AuthService) { constructor(private authService: AuthService) {
this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn.getTime() - 1000 - Date.now()); this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn);
authService.expireTokenChange.subscribe(date => {
console.debug('Expire token change event received:', date);
this.setRefreshTokenExpireMs(date.getTime() - 1000 - Date.now());
});
} }
public startTokenRefresh(date: Date | null = null): void { private startTokenRefresh(): void {
if (date) this.refreshTokenTimeout = setTimeout(() => {
this.refreshTokenExpireMs = new Date(date).getTime() - 1000 - Date.now(); this.refreshToken();
console.debug(this.tokenRefreshSubscription); if (this.refreshTokenExpireMs > 0)
if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed) this.startTokenRefresh();
}, this.refreshTokenExpireMs);
}
private refreshToken(): void {
if (this.tokenRefreshing$.value) {
console.warn('Refreshing token was started...');
return; return;
this.tokenRefreshSubscription = interval(this.refreshTokenExpireMs).pipe(
filter(isRefreshing => !isRefreshing),
switchMap(() => {
this.tokenRefreshing$.next(true);
console.debug('Send query to refresh token');
return this.authService.refreshToken();
})
).subscribe({
next: (_) => {
this.tokenRefreshing$.next(false);
},
error: error => {
this.tokenRefreshing$.next(false);
localStorage.removeItem(ApiService.tokenKey);
} }
console.log('Start Refreshing token...');
this.tokenRefreshing$.next(true);
this.authService.refreshToken()
.pipe(
catchError(error => {
console.warn('Error refreshing token', error);
localStorage.removeItem(ApiService.tokenKey);
this.refreshTokenExpireMs = -1;
return of(undefined);
}))
.subscribe(data => {
if (data) {
console.log('Token refreshed');
this.setRefreshTokenExpireMs(data);
}
this.tokenRefreshing$.next(false);
console.log('Token refresh process completed');
}); });
} }
public getTokenRefreshing$(): BehaviorSubject<boolean> { public getTokenRefreshing$(): Subject<boolean> {
return this.tokenRefreshing$; return this.tokenRefreshing$;
} }
public stopTokenRefresh(): void { public setRefreshTokenExpireMs(expireMs: number | string | Date | null = null): void {
if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed) { let expireMsNumber: number;
this.tokenRefreshSubscription.unsubscribe(); if (expireMs === null)
this.tokenRefreshSubscription = undefined; expireMsNumber = -1;
} else if (expireMs instanceof Date || typeof expireMs === 'string')
} expireMsNumber = new Date(expireMs).getTime() - 1000 - Date.now();
else
expireMsNumber = expireMs;
public setRefreshTokenExpireMs(expireMs: number): void { if (expireMsNumber < environment.retryDelay)
if (expireMs < environment.retryDelay) expireMsNumber = environment.retryDelay;
expireMs = environment.retryDelay;
this.refreshTokenExpireMs = expireMs; this.refreshTokenExpireMs = expireMsNumber;
console.log('New refresh token interval:', this.refreshTokenExpireMs);
console.log(expireMs); clearTimeout(this.refreshTokenTimeout);
this.stopTokenRefresh();
this.startTokenRefresh(); this.startTokenRefresh();
} }
} }