elbywan / wretch

A tiny wrapper built around fetch with an intuitive syntax. :candy:
MIT License
4.79k stars 96 forks source link

[Question] Abort requests on global catcher #250

Open donnes opened 3 days ago

donnes commented 3 days ago

I'm trying to implement a global catcher for 403 error, and I want a way to abort subsequent/parallel requests when a 403 error occurs. For compliance reasons, our backend has a one-time access token duration (no refresh token is allowed), so users need to log in again when the token expires.

Below is how I'm trying to implement this behavior, but doesn't seems to be working as expected. The redirect method is called for each request that fails.

// Reusable API Client

import wretch from "wretch";
import AbortAddon from "wretch/addons/abort";
import FormDataAddon from "wretch/addons/formData";
import ProgressAddon from "wretch/addons/progress";
import QueryStringAddon from "wretch/addons/queryString";
import { dedupe } from "wretch/middlewares";

export function createApiClient(prefixUrl: string) {
  return wretch(prefixUrl)
    .middlewares([dedupe()])
    .addon(FormDataAddon)
    .addon(QueryStringAddon)
    .addon(AbortAddon())
    .addon(ProgressAddon())
    .errorType("json");
}

// App-specific API client instance

const controller = new AbortController();

export const api = createApiClient('https://httpstat.us/403')
  .signal(controller)
  .catcher(403, (error) => {
    controller.abort();
    logger("403 Forbidden ->", error.message);
    redirect("/api/logout"); // Next.JS redirect function
  });

Great library, btw! First time using and already loving it <3

elbywan commented 9 hours ago

Hey @donnes :wave:

Great library, btw! First time using and already loving it <3

Thanks a bunch :bow:

The redirect method is called for each request that fails.

I think the reason is because you are using the dedupe middleware which actually sends only a single request at a time. The 403 response is then cloned and shared between the wretch instances which triggers all the catchers.

One thing you could do would be to flag cloned responses to prevent calling the redirect method for them. Something like:

function createApiClient(prefixUrl) {
      return wretch(prefixUrl)
        .middlewares([dedupe({
          resolver: response => {
            // Flag cloned responses
            // see: https://elbywan.github.io/wretch/api/types/middlewares_dedupe.DedupeMiddleware.html
            const clonedResponse = response.clone()
            clonedResponse.cloned = true
            return clonedResponse
          }
        })])
        .addon(FormDataAddon)
        .addon(QueryStringAddon)
        .addon(AbortAddon())
        .addon(ProgressAddon())
        .errorType("text");
    }

export const api = createApiClient('https://httpstat.us/403')
  .signal(controller)
  .catcher(403, (error) => {
    controller.abort();
    logger("403 Forbidden ->", error.message);
     // error.response.cloned === true if the response was cloned
    if(!error.response.cloned) {
        redirect("/api/logout"); // Next.JS redirect function
    }
  })
  .catcher("AbortError", (error) => {
    console.log("Request aborted ->", error.message);
  });;