@gadgetinc/react-shopify-app-bridge
Advanced tools
Comparing version
@@ -14,2 +14,5 @@ "use strict"; | ||
})(AppType || (exports.AppType = AppType = {})); | ||
// mutation which passes the session token back to Gadget to see if this shop is already installed and if not, install it | ||
// for managed installations where there is no OAuth callback sent to Gadget, this is the piece that first discovers a new shop and informs the backend | ||
// for normal OAuth installs, this also checks that the right scopes have been granted by this shop in order to trigger re-authentication | ||
const FetchOrInstallShopMutation = ` | ||
@@ -38,10 +41,2 @@ mutation ShopifyFetchOrInstallShop($shopifySessionToken: String!) { | ||
const forceRedirect = isReady && typeof host !== "undefined" && !inDestinationContext; | ||
const [context, setContext] = (0, react_2.useState)({ | ||
isAuthenticated: false, | ||
isEmbedded: false, | ||
canAuth: false, | ||
loading: true, | ||
appBridge, | ||
isRootFrameRequest: false, | ||
}); | ||
const [idTokenError, setIdTokenError] = (0, react_2.useState)(); | ||
@@ -78,5 +73,5 @@ (0, react_2.useEffect)(() => { | ||
const hasStartedFetchingOrInstallingShop = (0, react_2.useRef)(false); | ||
let hasInstallStateHint = false; | ||
const [{ data: fetchOrInstallShopData, fetching: fetchingOrInstallingShop, error: fetchingOrInstallingShopError }, fetchOrInstallShop] = (0, react_1.useMutation)(FetchOrInstallShopMutation); | ||
if (fetchOrInstallShopData) { | ||
console.debug({ fetchOrInstallShopData }, "[gadget-rsab] fetched or installed shop data"); | ||
redirectToOauth = fetchOrInstallShopData.shopifyConnection.fetchOrInstallShop.redirectToOauth; | ||
@@ -86,2 +81,9 @@ isAuthenticated = fetchOrInstallShopData.shopifyConnection.fetchOrInstallShop.isAuthenticated; | ||
} | ||
else if (typeof window != "undefined" && window.gadgetConfig?.shopifyInstallState) { | ||
const hint = window.gadgetConfig.shopifyInstallState; | ||
hasInstallStateHint = true; | ||
redirectToOauth = hint.redirectToOauth; | ||
isAuthenticated = hint.isAuthenticated; | ||
missingScopes = hint.missingScopes ?? []; | ||
} | ||
// we want to show the loading state until we've started fetching or installing the shop | ||
@@ -93,3 +95,6 @@ // or the mutation to fetch or install the shop has completed | ||
} | ||
}, [fetchingOrInstallingShop]); | ||
if (hasInstallStateHint) { | ||
console.debug("[gadget-rsab] shopifyInstallState hint used", window.gadgetConfig.shopifyInstallState); | ||
} | ||
}, [fetchingOrInstallingShop, hasInstallStateHint]); | ||
// always run one fetch to the gadget backend on boot to discover if this app is installed | ||
@@ -131,5 +136,24 @@ (0, react_2.useEffect)(() => { | ||
}, [gadgetAppUrl, isRootFrameRequest, originalQueryParams, redirectToOauth]); | ||
const loading = forceRedirect || redirectToOauth || fetchingOrInstallingShop || !hasStartedFetchingOrInstallingShop.current; | ||
// we're loading we need to redirect somewhere so we dont render the app while a redirect is underway | ||
let loading = forceRedirect || redirectToOauth; | ||
// we're also loading if we're in the middle of checking installation status in the backend | ||
if (!hasStartedFetchingOrInstallingShop.current || fetchingOrInstallingShop) { | ||
if (hasInstallStateHint) { | ||
// if the backend request is outstanding, but we have an installation status hint that says we're already authenticated, assume that's what the backend will say too, and stop being loading earlier to improve LCP | ||
loading || (loading = !isAuthenticated); | ||
} | ||
else { | ||
loading = true; | ||
} | ||
} | ||
const [contextValue, setContextValue] = (0, react_2.useState)({ | ||
isAuthenticated, | ||
isEmbedded, | ||
canAuth: !!appBridge, | ||
loading, | ||
appBridge, | ||
isRootFrameRequest, | ||
}); | ||
(0, react_2.useEffect)(() => { | ||
const context = { | ||
const newContext = { | ||
isAuthenticated, | ||
@@ -143,4 +167,4 @@ isEmbedded, | ||
}; | ||
console.debug("[gadget-rsab] context changed", context); | ||
return setContext(context); | ||
console.debug("[gadget-rsab] context changed", newContext); | ||
return setContextValue(newContext); | ||
}, [loading, isEmbedded, appBridge, isAuthenticated, fetchingOrInstallingShopError, idTokenError, isRootFrameRequest]); | ||
@@ -157,3 +181,3 @@ (0, react_2.useEffect)(() => { | ||
}, [redirectToOauth, missingScopes]); | ||
return react_2.default.createElement(index_js_1.GadgetAuthContext.Provider, { value: context }, children); | ||
return react_2.default.createElement(index_js_1.GadgetAuthContext.Provider, { value: contextValue }, children); | ||
}); | ||
@@ -193,3 +217,6 @@ const StandaloneInnerProvider = ({ isRootFrameRequest, children, query, gadgetAppUrl, type, }) => { | ||
const shopifyGlobalDefined = !!(globalThis && globalThis.shopify); | ||
const [location, setLocation] = (0, react_2.useState)(null); | ||
const location = (0, react_2.useMemo)(() => ({ | ||
asPath: `${window.location.pathname}${window.location.search}`, | ||
query: new URLSearchParams(window.location.search), | ||
}), []); | ||
const isReady = !!location; | ||
@@ -205,8 +232,2 @@ const { query } = location ?? {}; | ||
const gadgetAppUrl = new URL(api.connection.options.endpoint).origin; | ||
(0, react_2.useEffect)(() => { | ||
setLocation({ | ||
asPath: `${window.location.pathname}${window.location.search}`, | ||
query: new URLSearchParams(window.location.search), | ||
}); | ||
}, []); | ||
console.debug("[gadget-rsab] provider rendering", { | ||
@@ -213,0 +234,0 @@ host, |
@@ -10,2 +10,5 @@ import { Provider as GadgetUrqlProvider, useMutation } from "@gadgetinc/react"; | ||
})(AppType || (AppType = {})); | ||
// mutation which passes the session token back to Gadget to see if this shop is already installed and if not, install it | ||
// for managed installations where there is no OAuth callback sent to Gadget, this is the piece that first discovers a new shop and informs the backend | ||
// for normal OAuth installs, this also checks that the right scopes have been granted by this shop in order to trigger re-authentication | ||
const FetchOrInstallShopMutation = ` | ||
@@ -34,10 +37,2 @@ mutation ShopifyFetchOrInstallShop($shopifySessionToken: String!) { | ||
const forceRedirect = isReady && typeof host !== "undefined" && !inDestinationContext; | ||
const [context, setContext] = useState({ | ||
isAuthenticated: false, | ||
isEmbedded: false, | ||
canAuth: false, | ||
loading: true, | ||
appBridge, | ||
isRootFrameRequest: false, | ||
}); | ||
const [idTokenError, setIdTokenError] = useState(); | ||
@@ -74,5 +69,5 @@ useEffect(() => { | ||
const hasStartedFetchingOrInstallingShop = useRef(false); | ||
let hasInstallStateHint = false; | ||
const [{ data: fetchOrInstallShopData, fetching: fetchingOrInstallingShop, error: fetchingOrInstallingShopError }, fetchOrInstallShop] = useMutation(FetchOrInstallShopMutation); | ||
if (fetchOrInstallShopData) { | ||
console.debug({ fetchOrInstallShopData }, "[gadget-rsab] fetched or installed shop data"); | ||
redirectToOauth = fetchOrInstallShopData.shopifyConnection.fetchOrInstallShop.redirectToOauth; | ||
@@ -82,2 +77,9 @@ isAuthenticated = fetchOrInstallShopData.shopifyConnection.fetchOrInstallShop.isAuthenticated; | ||
} | ||
else if (typeof window != "undefined" && window.gadgetConfig?.shopifyInstallState) { | ||
const hint = window.gadgetConfig.shopifyInstallState; | ||
hasInstallStateHint = true; | ||
redirectToOauth = hint.redirectToOauth; | ||
isAuthenticated = hint.isAuthenticated; | ||
missingScopes = hint.missingScopes ?? []; | ||
} | ||
// we want to show the loading state until we've started fetching or installing the shop | ||
@@ -89,3 +91,6 @@ // or the mutation to fetch or install the shop has completed | ||
} | ||
}, [fetchingOrInstallingShop]); | ||
if (hasInstallStateHint) { | ||
console.debug("[gadget-rsab] shopifyInstallState hint used", window.gadgetConfig.shopifyInstallState); | ||
} | ||
}, [fetchingOrInstallingShop, hasInstallStateHint]); | ||
// always run one fetch to the gadget backend on boot to discover if this app is installed | ||
@@ -127,5 +132,24 @@ useEffect(() => { | ||
}, [gadgetAppUrl, isRootFrameRequest, originalQueryParams, redirectToOauth]); | ||
const loading = forceRedirect || redirectToOauth || fetchingOrInstallingShop || !hasStartedFetchingOrInstallingShop.current; | ||
// we're loading we need to redirect somewhere so we dont render the app while a redirect is underway | ||
let loading = forceRedirect || redirectToOauth; | ||
// we're also loading if we're in the middle of checking installation status in the backend | ||
if (!hasStartedFetchingOrInstallingShop.current || fetchingOrInstallingShop) { | ||
if (hasInstallStateHint) { | ||
// if the backend request is outstanding, but we have an installation status hint that says we're already authenticated, assume that's what the backend will say too, and stop being loading earlier to improve LCP | ||
loading || (loading = !isAuthenticated); | ||
} | ||
else { | ||
loading = true; | ||
} | ||
} | ||
const [contextValue, setContextValue] = useState({ | ||
isAuthenticated, | ||
isEmbedded, | ||
canAuth: !!appBridge, | ||
loading, | ||
appBridge, | ||
isRootFrameRequest, | ||
}); | ||
useEffect(() => { | ||
const context = { | ||
const newContext = { | ||
isAuthenticated, | ||
@@ -139,4 +163,4 @@ isEmbedded, | ||
}; | ||
console.debug("[gadget-rsab] context changed", context); | ||
return setContext(context); | ||
console.debug("[gadget-rsab] context changed", newContext); | ||
return setContextValue(newContext); | ||
}, [loading, isEmbedded, appBridge, isAuthenticated, fetchingOrInstallingShopError, idTokenError, isRootFrameRequest]); | ||
@@ -153,3 +177,3 @@ useEffect(() => { | ||
}, [redirectToOauth, missingScopes]); | ||
return React.createElement(GadgetAuthContext.Provider, { value: context }, children); | ||
return React.createElement(GadgetAuthContext.Provider, { value: contextValue }, children); | ||
}); | ||
@@ -189,3 +213,6 @@ const StandaloneInnerProvider = ({ isRootFrameRequest, children, query, gadgetAppUrl, type, }) => { | ||
const shopifyGlobalDefined = !!(globalThis && globalThis.shopify); | ||
const [location, setLocation] = useState(null); | ||
const location = useMemo(() => ({ | ||
asPath: `${window.location.pathname}${window.location.search}`, | ||
query: new URLSearchParams(window.location.search), | ||
}), []); | ||
const isReady = !!location; | ||
@@ -201,8 +228,2 @@ const { query } = location ?? {}; | ||
const gadgetAppUrl = new URL(api.connection.options.endpoint).origin; | ||
useEffect(() => { | ||
setLocation({ | ||
asPath: `${window.location.pathname}${window.location.search}`, | ||
query: new URLSearchParams(window.location.search), | ||
}); | ||
}, []); | ||
console.debug("[gadget-rsab] provider rendering", { | ||
@@ -209,0 +230,0 @@ host, |
{ | ||
"name": "@gadgetinc/react-shopify-app-bridge", | ||
"version": "0.15.2", | ||
"version": "0.15.3", | ||
"files": [ | ||
@@ -5,0 +5,0 @@ "README.md", |
@@ -7,3 +7,3 @@ <div align="center"> | ||
<a href=""> | ||
<img alt="GitHub CI status" src="https://badgen.net/github/checks/gadget-inc/js-clients/main/Test?label=CI" /> | ||
<img alt="GitHub CI status" src="https://badgen.net/github/checks/gadget-inc/js-clients/main/test?label=CI" /> | ||
</a> | ||
@@ -43,3 +43,3 @@ <a href="https://www.npmjs.com/package/@gadgetinc/react-shopify-app-bridge"> | ||
Full installation instructions can be found in the Gadget docs at `https://docs.gadget.dev/api/<my-app-slug>/installing`. | ||
Full installation instructions can be found in the Gadget docs at `https://docs.gadget.dev/api/<my-app-slug>/external-api-calls/installing`. | ||
@@ -86,3 +86,2 @@ Once you have your JS client installed, you can install the React hooks library and the Shopify App bridge library with `yarn` or `npm`: | ||
import { Button, Redirect, TitleBar } from "@shopify/app-bridge/actions"; | ||
import React from "react"; | ||
// import the instance of the Gadget API client for this app constructed in the other file | ||
@@ -151,11 +150,9 @@ import { api } from "./api"; | ||
// import Gadget's react hooks for accessing data from your Gadget app | ||
import { useAction, useFindMany } from "@gadgetinc/react"; | ||
// import the Gadget<->Shopify bindings that manage the auth process with Shopify | ||
import { AppType, Provider as GadgetProvider, useGadget } from "@gadgetinc/react-shopify-app-bridge"; | ||
import { AppType, Provider as GadgetProvider } from "@gadgetinc/react-shopify-app-bridge"; | ||
// import and use Shopify's react components like you might in other Shopify app | ||
import { Button, Redirect, TitleBar } from "@shopify/app-bridge/actions"; | ||
import React, { useMemo } from "react"; | ||
import { useMemo } from "react"; | ||
// import the instance of the Gadget API client for this app constructed in the other file | ||
import { BrowserRouter, useLocation, useNavigate } from "react-router-dom"; | ||
import { api } from "./api"; | ||
import { useLocation, useNavigate, BrowserRouter } from "react-router-dom"; | ||
// import your app's custom routes | ||
@@ -162,0 +159,0 @@ import Routes from "./Routes"; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
91528
7.51%614
7.34%192
-1.54%