From 1f03c2a9c3cb2166a1f06138c7b4def1bb63ed18 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 24 Aug 2024 04:27:13 +0300 Subject: [PATCH] refactor: refresh token The service did not update tokens well, so it was rewritten --- src/app/app.component.ts | 4 +- src/services/auth.service.ts | 18 +++--- src/services/token-refresh.service.ts | 88 ++++++++++++++------------- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6545c5f..012f122 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -4,7 +4,6 @@ import {FooterComponent} from "@component/common/footer/footer.component"; import localeRu from '@angular/common/locales/ru'; import {registerLocaleData} from '@angular/common'; import {FocusNextDirective} from "@/directives/focus-next.directive"; -import {TokenRefreshService} from "@service/token-refresh.service"; import {HeaderComponent} from "@component/common/header/header.component"; @Component({ @@ -17,8 +16,7 @@ import {HeaderComponent} from "@component/common/header/header.component"; ` }) export class AppComponent { - constructor(tokenRefreshService: TokenRefreshService) { + constructor() { registerLocaleData(localeRu); - tokenRefreshService.startTokenRefresh(); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 0b870a4..709cfa3 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,6 +1,6 @@ -import {EventEmitter, Injectable} from '@angular/core'; +import {Injectable} from '@angular/core'; 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 ApiService from "@api/api.service"; @@ -21,7 +21,7 @@ export class AuthToken { this.endpoint = refreshEndpoint; } - static httpHeader(token: AuthToken): HttpHeaders { + public static httpHeader(token: AuthToken): HttpHeaders { let header = new HttpHeaders(); if (token.authProvider === AvailableAuthenticationProvider.Bearer) @@ -35,8 +35,6 @@ export class AuthToken { providedIn: 'root', }) export class AuthService { - public expireTokenChange = new EventEmitter(); - constructor(private http: HttpClient) { } @@ -56,12 +54,12 @@ export class AuthService { return result <= new Date() ? new Date() : result; } - public refreshToken(): Observable { + public refreshToken(): Observable { const token = localStorage.getItem(ApiService.tokenKey); console.log(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; @@ -69,14 +67,16 @@ export class AuthService { case AvailableAuthenticationProvider.Bearer: return this.http.get(authToken.endpoint, {withCredentials: true}) .pipe( - tap(response => { + map(response => { const newExpireDate = new Date(response.expiresIn); const oldExpireDate = new Date(authToken.expiresIn); if (newExpireDate.getTime() !== oldExpireDate.getTime()) { AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, authToken.endpoint); - this.expireTokenChange.emit(newExpireDate); + return newExpireDate; } + + return newExpireDate; }) ); } diff --git a/src/services/token-refresh.service.ts b/src/services/token-refresh.service.ts index 957f86d..2672d52 100644 --- a/src/services/token-refresh.service.ts +++ b/src/services/token-refresh.service.ts @@ -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 {AuthService} from "@service/auth.service"; import {environment} from "@environment"; @@ -8,64 +8,70 @@ import ApiService from "@api/api.service"; providedIn: 'root', }) export class TokenRefreshService { - private tokenRefreshSubscription: Subscription | undefined; private tokenRefreshing$ = new BehaviorSubject(false); + private refreshTokenTimeout: any; private refreshTokenExpireMs: number = environment.retryDelay; constructor(private authService: AuthService) { - this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn.getTime() - 1000 - Date.now()); - - authService.expireTokenChange.subscribe(date => { - console.debug('Expire token change event received:', date); - this.setRefreshTokenExpireMs(date.getTime() - 1000 - Date.now()); - }); + this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn); } - public startTokenRefresh(date: Date | null = null): void { - if (date) - this.refreshTokenExpireMs = new Date(date).getTime() - 1000 - Date.now(); + private startTokenRefresh(): void { + this.refreshTokenTimeout = setTimeout(() => { + this.refreshToken(); - console.debug(this.tokenRefreshSubscription); - if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed) + if (this.refreshTokenExpireMs > 0) + this.startTokenRefresh(); + }, this.refreshTokenExpireMs); + } + + private refreshToken(): void { + if (this.tokenRefreshing$.value) { + console.warn('Refreshing token was started...'); 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: (_) => { + 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); - }, - error: error => { - this.tokenRefreshing$.next(false); - localStorage.removeItem(ApiService.tokenKey); - } - }); + console.log('Token refresh process completed'); + }); } - public getTokenRefreshing$(): BehaviorSubject { + public getTokenRefreshing$(): Subject { return this.tokenRefreshing$; } - public stopTokenRefresh(): void { - if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed) { - this.tokenRefreshSubscription.unsubscribe(); - this.tokenRefreshSubscription = undefined; - } - } + public setRefreshTokenExpireMs(expireMs: number | string | Date | null = null): void { + let expireMsNumber: number; + if (expireMs === null) + 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 (expireMs < environment.retryDelay) - expireMs = environment.retryDelay; + if (expireMsNumber < environment.retryDelay) + expireMsNumber = environment.retryDelay; - this.refreshTokenExpireMs = expireMs; + this.refreshTokenExpireMs = expireMsNumber; + console.log('New refresh token interval:', this.refreshTokenExpireMs); - console.log(expireMs); - this.stopTokenRefresh(); + clearTimeout(this.refreshTokenTimeout); this.startTokenRefresh(); } }