elbywan / wretch

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

[Question] Abort requests on global catcher #250

Closed donnes closed 1 month ago

donnes commented 1 month 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 1 month 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);
  });;
donnes commented 1 month ago

Hey @elbywan! I really appreciate your help. That’s worked just fine. ❤️