urql-graphql / urql

The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
https://urql.dev/goto/docs
MIT License
8.55k stars 443 forks source link

[Network] Load failed. Requests are failing on mobile and safari (Safari on both desktop and mobile, and on mobile chrome) #3474

Closed vholik closed 6 months ago

vholik commented 6 months ago

Describe the bug

The application, built using Next.js version 13.5 and utilizing the App router with URQL following this tutorial is encountering errors specifically on Safari (both desktop and mobile) and Chrome on iPhones.

Network requests are consistently failing when the application attempts to make them. This issue is reproducible on mobile devices, including Safari on both desktop and mobile, as well as Chrome on iPhones.

This is my URQL wrapper:

// Wrapper.tsx
"use client"

export const fetchPolyfill = (...args: any) => {
  return fetch(...args).then((response) => {
    const newSession = response.headers.get("woocommerce-session");
    const existingSession = !isServer
      ? localStorage.getItem("woo-session")
      : null;

    if (!isServer && newSession && !existingSession) {
      localStorage.setItem("woo-session", newSession);
    }

    return response;
  });
};

export const AppProvider = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  const [client, ssr] = useMemo(() => {
    const ssr = ssrExchange();
    const client = createClient({
      url: process.env.NEXT_PUBLIC_WORDPRESS_API_URL!,
      exchanges: [cacheExchange, ssr, fetchExchange],
      suspense: true,
      fetch: fetchPolyfill,
      fetchOptions() {
        const session = !isServer ? localStorage.getItem("woo-session") : null;

        return {
          headers: {
            "woocommerce-session": `Session ${session}`,
          },
        };
      },
    });

    return [client, ssr];
  }, []);

  return (
      <UrqlProvider client={client} ssr={ssr}>
        {children}
      </UrqlProvider>
  );
};
// AddToCartButton.tsx
"use client"
import { useMutation, gql } from "@urql/next";

const ADD_TO_CART = gql`
  mutation ADD_TO_CART($input: AddToCartInput!) {
    addToCart(input: $input) {
      cartItem {
        key
        product {
          node {
            id
            productId: databaseId
            name
            description
            type
            onSale
            slug
            averageRating
            reviewCount
            terms {
              edges {
                node {
                  id
                  slug
                  name
                  taxonomyName
                }
              }
            }
            image {
              id
              sourceUrl
              altText
            }
            galleryImages {
              nodes {
                id
                sourceUrl
                altText
              }
            }
          }
        }
        variation {
          node {
            id
            variationId: databaseId
            name
            description
            type
            onSale
            price
            regularPrice
            salePrice
            image {
              id
              sourceUrl
              altText
            }
          }
          attributes {
            id
            attributeId
            name
            value
          }
        }
        quantity
        total
        subtotal
        subtotalTax
      }
    }
  }
`;

const addToCartButton = () => {
    const [{ fetching: addToCartLoading }, addToCart] = useMutation(ADD_TO_CART);

    const add = () => addToCart({ input: productQryInput }).then((result) => {
           console.log(result)
     });

     // Aditional ui goes here...

Here is result console: Zrzut ekranu 2024-01-4 o 16 29 51

And here is response in network: image

We were trying to change CORS settings on the server and make it so it will handle from every domain but it does not help.

Thanks for help in advance.

Reproduction

Reproduction example is in the description

Urql version

"@urql/next": "^1.1.0", "urql": "^4.0.6",

Validations

kitten commented 6 months ago

This is likely a usage issue, and if I had to guess based on “Load Failed” being a very ambiguous error, it's indeed likely to be a CORS issue.

Note that I wouldn't recommend a fetch wrapper just to add a session header as there's basically three alternatives that are a better practice for this, given that you don't have to actually modify the behaviour of fetch:

I'm unsure why fetch is being wrapped here since you're already using the fetchOptions option, but it's not a good idea to separate and rely on timing there.

To get back to the original issue, since there's no reproduction here I can actually look at, if you check this with a different browser, or check the console in Safari, you'll likely get more information about the CORS issue, since the details of this issue are withheld from the JS runtime error.

If I had to guess Access-Control-Allow-Headers may be missing or allowing domains as a wildcard in Access-Control-Allow-Origin isn't possible in your case, i.e. is configured incorrectly on the API. There's not much advice I can give though straight off this.

I'll leave this open as an issue for now, but will convert this into a discussion, as needed, if we have more of an idea what you're dealing with and the conversation here goes on ✌️

vholik commented 6 months ago

Thanks for the response. I will try to try use your suggestions tomorrow and give my feedback on this.

vholik commented 6 months ago

@kitten I'm using fetch wrapper to access http response in order to get header that is generated on the server.

export const fetchPolyfill = (...args: any) => {
  return fetch(...args).then((response) => {
    const newSession = response.headers.get("woocommerce-session"); // Get header
    const existingSession = !isServer
      ? localStorage.getItem("woo-session")
      : null;

    if (!isServer && newSession && !existingSession) {
      localStorage.setItem("woo-session", newSession); // Store header
    }

    return response;
  });
};

and then later provide it in the request header.

Could you provide an example how to properly get the response header? Thanks

kitten commented 6 months ago

Fair enough. Basically, the response headers are restricted, since it's expected that behaviour like this is handled differently, since state in the response headers is a little conflicting with other assumptions in GraphQL.

Basically, I would've expected this to either be handled using cookies or a proper auth mechanism. I'd assume that the session you're creating is ephemeral and cookie-like though. Ultimately, I have no opinions and quarrels about how you'd want to implement thi.

I'd just note that I don't find using random headers on a GraphQL response to be particularly idiomatic if it represents auth-like or other state/inputs. If you do decide to create a fetch wrapper though I'd keep this separate from fetchOptions.

To keep this on topic though, I'll convert this to a discussion, since I do still think the original issue is most likely a CORS issue (making this more of a candidate for a QnA thread) ✌️