I have the following interceptor that works as a retry wrapper in case the access token expires:
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpRequest,
HttpHandler,
HttpInterceptor,
HttpHeaders,
} from '@angular/common/http';
import { Observable, EMPTY, throwError } from 'rxjs';
import { catchError, switchMap, take } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from '../services';
@Injectable()
export class UnauthorizedInterceptor implements HttpInterceptor {
constructor(
private router: Router,
private readonly authService: AuthService
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error, caught) => {
if (error.status === 401) {
const newToken = this.authService.getNewToken();
const updatedReq = req.clone({
setHeaders: {
Authorization: `Bearer ${newToken}`,
},
});
return next.handle(updatedReq);
}
return EMPTY;
})
);
}
}
witch works as expected.
However, the function getNewToken
is an async function and if I update the code to
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError(async (error, caught) => {
if (error.status === 401) {
const newToken = await this.authService.getNewToken();
const updatedReq = req.clone({
setHeaders: {
Authorization: `Bearer ${newToken}`,
},
});
return next.handle(updatedReq);
}
return EMPTY;
})
);
}
What is the solution to this use case, when an async operation needs to be performed inside catchError
?
P.S: The getNewToken
function can be rewritten to return an observable instead of promise if this would help, but I'm not able to figure it out
Update
The error thrown by TypeScript is
Type 'Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | Observable<...>>' is not assignable to type 'Observable<HttpEvent<any>>'.
Type 'HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | Observable<...>' is not assignable to type 'HttpEvent<any>'.
Type 'Observable<HttpEvent<any>>' is not assignable to type 'HttpEvent<any>'.ts(2322)
and if I do a @ts-ignore
then in the browser the error thrown is
EmptyError: no elements in sequence
CodePudding user response:
Try this approach:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
return this.handleUnauthorized(request, next);
} else {
return throwError(error);
}
})
);
}
And the "handleUnauthorized" method implementation
handleUnauthorized(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.authService.setToken(null);
const body = new HttpParams()
.set('grant_type', 'refresh_token')
.set('refresh_token', localStorage.getItem('refresh_token'));
return this.http.post('YOUR_URL', body.toString(), {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
}).pipe(flatMap((el: any) => {
if (el && el.access_token) {
req = req.clone({ setHeaders: { Authorization: 'Bearer ' el.access_token } });
this.authService.setToken(el);
return next.handle(req);
} else {
this.authService.unAuthorize();
return throwError('refreshToken');
}
}), finalize(() => {
this.isRefreshingToken = false;
}), catchError(error => {
return throwError(error);
}));
} else {
return this.authService.tokenSubject.pipe(
filter(token => token)
, take(1)
, switchMap(token => {
req = req.clone({ setHeaders: { Authorization: 'Bearer ' token.access_token } });
this.authService.setToken(token);
return next.handle(req);
}));
}
}
And inside authService, you should have this functionality
tokenSubject = new Subject<any>();
.
.
.
setToken(newToken: any) {
this.tokenSubject.next(newToken)
}
getToken() {
return this.tokenSubject.asObservable();
}
I hope this approach helps you, Feel free to ask me any questions in the comment section.