diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | hooks/useAppQuery.js | 26 | ||||
-rw-r--r-- | hooks/useAuthenticatedFetch.js | 38 | ||||
-rw-r--r-- | index.html | 17 |
4 files changed, 83 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b72f9be --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +*.swp diff --git a/hooks/useAppQuery.js b/hooks/useAppQuery.js new file mode 100644 index 0000000..7218274 --- /dev/null +++ b/hooks/useAppQuery.js @@ -0,0 +1,26 @@ +/** + * A hook for querying your custom app data. + * @desc A thin wrapper around useAuthenticatedFetch and react-query's useQuery. + * + * @param {Object} options - The options for your query. Accepts 3 keys: + * + * 1. url: The URL to query. E.g: /api/widgets/1` + * 2. fetchInit: The init options for fetch. See: https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters + * 3. reactQueryOptions: The options for `useQuery`. See: https://react-query.tanstack.com/reference/useQuery + * + * @returns Return value of useQuery. See: https://react-query.tanstack.com/reference/useQuery. + */ +const useAppQuery = ({ url, fetchInit = {}, reactQueryOptions }) => { + const authenticatedFetch = useAuthenticatedFetch(); + const fetch = useMemo(() => { + return async () => { + const response = await authenticatedFetch(url, fetchInit); + return response.json(); + }; + }, [url, JSON.stringify(fetchInit)]); + + return useQuery(url, fetch, { + ...reactQueryOptions, + refetchOnWindowFocus: false, + }); +}; diff --git a/hooks/useAuthenticatedFetch.js b/hooks/useAuthenticatedFetch.js new file mode 100644 index 0000000..e5f36a8 --- /dev/null +++ b/hooks/useAuthenticatedFetch.js @@ -0,0 +1,38 @@ +/** + * A hook that returns an auth-aware fetch function. + * @desc The returned fetch function that matches the browser's fetch API + * See: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API + * It will provide the following functionality: + * + * 1. Add a `X-Shopify-Access-Token` header to the request. + * 2. Check response for `X-Shopify-API-Request-Failure-Reauthorize` header. + * 3. Redirect the user to the reauthorization URL if the header is present. + * + * @returns {Function} fetch function + */ +function useAuthenticatedFetch() { + const app = useAppBridge(); + const fetchFunction = authenticatedFetch(app); + + return async (uri, options) => { + const response = await fetchFunction(uri, options); + checkHeadersForReauthorization(response.headers, app); + return response; + }; +} + +function checkHeadersForReauthorization(headers, app) { + if (headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1") { + const authUrlHeader = + headers.get("X-Shopify-API-Request-Failure-Reauthorize-Url") || + `/api/auth`; + + const redirect = Redirect.create(app); + redirect.dispatch( + Redirect.Action.REMOTE, + authUrlHeader.startsWith("/") + ? `https://${window.location.host}${authUrlHeader}` + : authUrlHeader + ); + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..70c4936 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang=en> + <head> + <meta charset=utf-8/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <link rel="stylesheet" href="https://unpkg.com/@shopify/polaris@10.2.0/build/esm/styles.css"/> + </head> + <body> + <script src="https://unpkg.com/react@18"></script> + <script src="https://unpkg.com/react-query@3"></script> + <script src="https://unpkg.com/@shopify/app-bridge@3"></script> + <script src="https://unpkg.com/@shopify/app-bridge-utils@3"></script> + <script src="https://unpkg.com/@shopify/app-bridge-react@3"></script> + <script async type="text/javascript" src="js/useAuthenticatedFetch.js"></script> + <script async type="text/javascript" src="js/useAppQuery.js"></script> + </body> +</html> |