Using RxJS for various scenarios with Angular HttpInterceptor

Take a look at the following code block containing AuthInterceptor:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        const Token = this.authService.getToken();

        if (!Token) {
            return next.handle(req);
        }
 
        // Handling Refresh Token first
        if (Token.expiresRefreshToken && Number(Token.expiresRefreshToken) < Date.now()) {
            this.authService.refreshTokenRefresh(Token.tokenref)
            .subscribe((response) => {
                localStorage.setItem('tokenref', response.tokenref);
                localStorage.setItem('tokenrefexp', response.tokenrefexp);
            });
        }
        // Proceeding with Access Token refresh
        if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
            this.authService.refreshToken(Token.tokenref)
            .subscribe((response) => {
                localStorage.setItem('token', response.token);
                localStorage.setItem('tokenexp', response.tokenexp);
            });
        }
        // Sending original request with updated custom headers
        return next.handle(req.clone({
            headers: req.headers
            .set('Authorization', 'Bearer ' + localStorage.getItem('token'))
            .set('X-Auth-Provider', localStorage.getItem('provider'))
        }));
    }

}

I am looking for a way to evaluate specific conditions within a RxJS operator to handle the changes in custom headers that occur after invoking the methods refreshToken and refreshTokenRefresh. Is there a way to achieve this?

Update: I encountered the following error:

RangeError: Maximum call stack size exceeded.
How can this be resolved?

Answer №1

Our strategy is to wait for certain requests to be completed before moving on to the next one. The order of evaluation does not matter in this case.

const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([]));
- here, we will list all the requests that need to be executed before calling next.handle. We will use forkJoin to ensure that all requests in the queue are completed before mapping to another Observable using mergeMap.

Additionally, we can consider moving handleRefreshTokenRefresh and handleRefreshToke to separate HttpInterceptor for better organization.

UPDATE: To avoid a recursive call of interceptors, we need to skip interceptors for refreshTokens calls.

export const InterceptorSkipHeader = 'X-Skip-Interceptor';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) { }

    handleRefreshTokenRefresh(queue: Observable<void>[]) {
        const Token = this.authService.getToken();
        if (Token.expiresRefreshToken && 
            const req = this.authService.refreshTokenRefresh(Token.tokenref)
              .pipe(tap((response) => {
                localStorage.setItem('tokenref', response.tokenref);
                localStorage.setItem('tokenrefexp', response.tokenrefexp);
            }));
            return [...queue, req];
        }
        return queue;
    }

    handleRefreshToke(queue: Observable<void>[]) {
        const Token = this.authService.getToken();
        if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
            const req = this.authService.refreshToken(Token.tokenref)
              .subscribe((response) => {
                localStorage.setItem('token', response.token);
                localStorage.setItem('tokenexp', response.tokenexp);
            });
            return [...queue, req];
        }
        return queue;
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
       if (req.headers.has(InterceptorSkipHeader)) {
            const headers = req.headers.delete(InterceptorSkipHeader);
            return next.handle(req.clone({ headers }));
        }

        const Token = this.authService.getToken();

        if (!Token) {
            return next.handle(req);
        }

        const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([]));

        return forkJoin(queue).pipe(
            mergeMap(()=>{
                return next.handle(req.clone({
                    headers: req.headers
                        .set('Authorization', 'Bearer ' + localStorage.getItem('token'))
                        .set('X-Auth-Provider', localStorage.getItem('provider')),
                }));
            })
        );
    }

}

Add InterceptorSkipHeader to refreshTokens to avoid invoking interceptors.

// AuthService

refreshTokenRefresh(token){
   const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
   return this.httpClient
    .get(someUrl, { headers })
}

refreshToken(token){
   const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
   return this.httpClient
    .get(someUrl, { headers })
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to create a generic that can handle readonly types efficiently?

When performing an action on a list type PerformActionOn<L extends any[]> = L The approach I am taking is as follows: const keys = { a: ['a', 'b', 'c'] as ['a', 'b', 'c'], d: ['d ...

Testing React components using the React Testing Library

I've been working on creating a test unit for my React Application using React Testing Library, but I've hit a roadblock. I've gone through all the documentation but I'm still stuck. The API was set up using create React app. One of th ...

concerning a snippet of programming code

I'm new to coding and I'd like some help understanding this piece of code, particularly the red colored fonts. Which page value are they referring to? $(function() { $("#title").blur(function() { QuestionSuggestions(); }); }); funct ...

Why Are My JavaScript GET Request Parameters Displaying as Strings Rather Than Numbers?

Currently, I am in the process of developing a REST API where one of the defined routes looks like this: router.get("/objects/:id?/:val1?/:val2?", getObject); Specifically, my request from Postman appears as follows: http://localhost:8000/objects?val1= ...

What is the method for accessing the value of variable "a" in the following code?

request(target, function (err, resp, body) { $ = cheerio.load(body); links = $('a'); $(links).each(function (i, link) { if ($(link).text().match('worker')) { var a = $(link).attr('href').toStri ...

My SF2 app is experiencing issues with AngularJS integration

I am currently developing a straightforward API using Symfony2 and now I am experimenting with integrating AngularJS into my bundle to visualize the results of my API calls. How can I effectively implement AngularJS? I initiated a bundle via app/console ...

TypeError: Unable to find TextEncoder in mongoose and jest when using TypeScript

Currently, I am working on a project using Node 14 along with Express v4.16.3 and Typescript (v4.7.4). Recently, I added Mongoose (v6.5.2) to the project, and while the logic code seems fine, most of the tests executed by Jest (v26.4.2) are failing with th ...

Discovering the central point within an SVG path

I am currently working with a path that is part of a group and using Jquery to locate the specific path. My goal is to find the midpoint of that path. I came across an example here. However, when attempting to use .getTotalLength(); or .getPointAtLength(), ...

Ways to confirm if a video has sound

There is a video that has an audio track, yet the volume is not zero or muted, resulting in the video being silent. I am looking for a way to determine if a video is audible so that I can perform specific functions accordingly. I came across a method to c ...

window.onresize = function() { // code here

Here's an example of code I've been working on: $(document).ready(function (e) { adjustSize(); $(window).resize(adjustSize); function adjustSize() { var windowWidth = parseInt($(window).width()); if (windowWidth > ...

Tips on causing JavaScript execution to halt until an element has finished rendering

test.view.js timeDBox = new sap.ui.commons.DropdownBox({layoutData: new sap.ui.layout.GridData({linebreak: true}), change: function(oEvent){ oController.getKeyEqChart(); }, }), new sap ...

JavaScript doesn't automatically redirect after receiving a response

Hey there, I'm attempting to implement a redirect upon receiving a response from an ajax request. However, it seems that the windows.href.location doesn't execute the redirect until the PHP background process finishes processing. Check out my cod ...

Changing the color of dots in a React Material-UI timeline

Hey there, I'm trying to change the default color of the MUI TimelineDot component. I attempted to do so by adding this code: <TimelineDot sx={{ '& .MuiTimelineDot-root': { backgroundColor: '#00FF00' }, }} /> Unfortunate ...

How can I utilize the Redux store outside of a component in a React application with ASP.NET Core and TypeScript?

While I have some experience with React, I am new to using Redux. Luckily, Visual Studio 2017 comes with a built-in template for React + Redux under .NET Core 2.0. About my environment: Visual Studio 2017 React 15.6.1 TypeScript 2.4.1 Redux 3.7.1 Rea ...

Troubleshooting Compatibility Issues: JavaScript Function Works in Chrome but not in Internet

After collaborating with fellow coders to develop info boxes using HTML, CSS, and JavaScript, I have come across an issue with the hover functionality not working properly in Internet Explorer. Interestingly, everything functions flawlessly on Google Chrom ...

How to use Express Validator to validate both email and username within a single field?

I am currently developing an application using the Express (Node.js framework) and I want to allow users to log in with either their email address or username. My question is, how can I implement validation for both types of input on the same field using e ...

Error encountered while rendering content in an Angular template

I'm currently integrating ngx-carousel into my application. Interestingly, the carousel works perfectly when I manually input the data. However, when trying to fetch the data from the server, it fails to work as expected. Take a look at my code snip ...

When trying to access a certain class property, I was met with the following error message: TypeError: Unable to read/set property 'x' of

Lately, I've delved into the realm of JavaScript / TypeScript and decided to create a basic React App using TypeScript. Within one of my components, I aim to switch between different components using a "state" (where each component will follow the pre ...

Steps for linking HTTP requests in Angular 2 depending on the type of response

My attempt to create an api call from a remote server and then, if an error occurs, make another request from my local server is not working as expected. I am encountering errors and need help to determine if my approach is feasible. Here is the code snip ...

Using AngularJS to add external scripts to partials with ng-include

Why won't my main javascript files (located in index.html) work in the partials (such as page1.html)? For example, jQuery and syntax highlighting scripts are not functioning properly when I click on my menu items. HTML CODE: <div data-ng-controll ...