I'm trying to make these RXJS observables work properly with Angular 7 but I can't figure out what's wrong.
What I want to do is check is there is verify token state every 10 seconds for auth.
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {interval, Observable, of, timer} from 'rxjs';
import {concatMap, delay, flatMap, takeWhile, tap} from 'rxjs/operators';
import {Token} from './Token';
import {User} from './User';
import {TokenFactory} from './factory/tokenfactory';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable({
providedIn: 'root'
})
export class TokenService {
private user;
private token;
private tokenLoginUrl = 'http://localhost:8080/login_check';
private refreshTokenUrl = 'http://localhost:8080/token/refresh';
constructor(private http: HttpClient) { }
private verifyTokenState(): Observable<any> {
if (!localStorage.getItem('token')) {
this.getLoginInfo().subscribe(token => {
this.token = TokenFactory.createToken(token.token, token.refresh_token, Date.now(), true);
return of(this.token);
});
this.token = {};
return of(this.token);
}
this.token = TokenFactory.getTokenFromLocalStorage();
const timeElapsedInMinutes = (Date.now() - this.token.date_token) / 1000 / 60;
console.log(timeElapsedInMinutes);
if (timeElapsedInMinutes >= 59) {
this.getRefreshToken(this.token).subscribe(jsonToken => {
this.token = TokenFactory.createToken(jsonToken.token, jsonToken.refresh_token, Date.now(), true);
return of(this.token);
});
}
return of(this.token);
}
private getLoginInfo(): Observable<any> {
return this.http.post<Token>(this.tokenLoginUrl, this.user, httpOptions);
}
private getRefreshToken(token: Token): Observable<any> {
return this.http.post<Token>(this.refreshTokenUrl, token, httpOptions);
}
public getToken() {
return timer(0, 10000).pipe(
flatMap(() => this.verifyTokenState()),
takeWhile(() => this.token !== undefined && !this.token.isValid)
);
}
public setUser(user: User): void {
this.user = user;
}
}
The issue I have is that when I call get TokenService.getToken() from inside another component (let's say in a login component after a user clicks the submit button) and I use timer(0,10000)
the getLoginInfo() and
getRefreshToken() methods get called in an infinite loop and there are hundred of ajax calls fired.
public getToken() {
return timer(0, 10000).pipe(
flatMap(() => this.verifyTokenState()),
takeWhile(() => this.token !== undefined && !this.token.isValid)
);
}
The thing that I don't understand is that if I use 2000 instead of 0 as first parameter, everything works fine but it's not what I want.
public getToken() {
return timer(2000, 10000).pipe(
flatMap(() => this.verifyTokenState()),
takeWhile(() => this.token !== undefined && !this.token.isValid)
);
}
What I want exactly is for the observable to have intervals of 10 seconds between calls but I want it to start immediately. I also want to get an observable Token returned from the getToken() after its being returned from the verifyTokenSate() method. So what's wrong exactly? Is there a better way to do it? I want to do this in a reactive way without unsubscribe.
The issues seem to happen in the AuthInterceptor after further invertigation. When I remove in from the module providers, everything is ok. Here is the AuthInterceptor:
import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {TokenService} from './token.service';
import {Observable} from 'rxjs';
import {map, mergeMap} from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private token;
constructor(private auth: TokenService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let request = req.clone();
this.auth.getToken().subscribe(t => {
console.log(t);
if (t.token) {
console.log('token valid');
request = req.clone({
setHeaders: { Authorization: `Bearer ${t.token}` }
});
}
});
return next.handle(request);
}
}
So what's wrong inside the AuthInterceptor?
AuthInterceptor maybe intercepting http requests to login/refresh urls, and in turn triggering these calls itself (via this.auth.getToken()), leading to cyclic nature. A possible solution would be to not intercept (or skip) these http requests
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// whitelisting certain urls that don't need token check
if (req.url.includes('/login_check')
|| req.url.includes('/token/refresh')) {
return next.handle(req);
}
// remaining code
}