diff --git a/.changeset/spicy-zebras-build.md b/.changeset/spicy-zebras-build.md new file mode 100644 index 0000000000..9e6d0745a1 --- /dev/null +++ b/.changeset/spicy-zebras-build.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen': patch +--- + +Fix infinite loop when checkoutDomain isn't supplied diff --git a/examples/gtm/README.md b/examples/gtm/README.md index 2e7121845a..f1251e7dbf 100644 --- a/examples/gtm/README.md +++ b/examples/gtm/README.md @@ -1,4 +1,4 @@ -# Hydrogen example: Shopify Analytics & Consent (unstable) +# Hydrogen example: Shopify Analytics & Consent This folder contains an end-to-end example of how to implement Google Tag Manager for Hydrogen. Hydrogen supports both Shopify analytics, as well as third-party services. diff --git a/package-lock.json b/package-lock.json index e7dd89e7d0..38f63bfe0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,8 +115,8 @@ "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "compression": "^1.7.4", "cross-env": "^7.0.3", "express": "^4.19.2", @@ -175,8 +175,8 @@ "dependencies": { "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/remix-oxygen": "^2.0.4", "crypto-js": "^4.2.0", "graphql": "^16.6.0", @@ -189,7 +189,7 @@ "devDependencies": { "@remix-run/dev": "^2.9.2", "@remix-run/eslint-config": "^2.9.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@total-typescript/ts-reset": "^0.4.2", @@ -219,8 +219,8 @@ "@builder.io/partytown": "^0.8.1", "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/remix-oxygen": "^2.0.4", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -231,7 +231,7 @@ "devDependencies": { "@remix-run/dev": "^2.9.2", "@remix-run/eslint-config": "^2.9.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@total-typescript/ts-reset": "^0.4.2", @@ -253,8 +253,8 @@ "dependencies": { "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/remix-oxygen": "^2.0.4", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -265,7 +265,7 @@ "devDependencies": { "@remix-run/dev": "^2.9.2", "@remix-run/eslint-config": "^2.9.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@tailwindcss/forms": "^0.5.3", @@ -33140,7 +33140,7 @@ }, "packages/cli": { "name": "@shopify/cli-hydrogen", - "version": "8.0.4", + "version": "8.1.0", "license": "MIT", "dependencies": { "@ast-grep/napi": "0.11.0", @@ -33193,7 +33193,7 @@ "@graphql-codegen/cli": "^5.0.2", "@remix-run/dev": "^2.1.0", "@shopify/hydrogen-codegen": "^0.3.1", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "graphql-config": "^5.0.3", "vite": "^5.1.0" }, @@ -33366,10 +33366,10 @@ }, "packages/create-hydrogen": { "name": "@shopify/create-hydrogen", - "version": "4.3.9", + "version": "4.3.10", "license": "MIT", "dependencies": { - "@shopify/cli-hydrogen": "^8.0.4" + "@shopify/cli-hydrogen": "^8.1.0" }, "bin": { "create-hydrogen": "dist/create-app.js" @@ -33377,13 +33377,12 @@ }, "packages/hydrogen": { "name": "@shopify/hydrogen", - "version": "2024.4.2", + "version": "2024.4.3", "license": "MIT", "dependencies": { - "@shopify/hydrogen-react": "2024.4.2", + "@shopify/hydrogen-react": "2024.4.3", "content-security-policy-builder": "^2.2.0", "source-map-support": "^0.5.21", - "tiny-invariant": "^1.3.1", "type-fest": "^4.5.0", "use-resize-observer": "^9.1.0", "worktop": "^0.7.3" @@ -33444,7 +33443,7 @@ }, "packages/hydrogen-react": { "name": "@shopify/hydrogen-react", - "version": "2024.4.2", + "version": "2024.4.3", "license": "MIT", "dependencies": { "@google/model-viewer": "^1.12.1", @@ -35678,7 +35677,7 @@ }, "packages/mini-oxygen": { "name": "@shopify/mini-oxygen", - "version": "3.0.2", + "version": "3.0.3", "license": "MIT", "dependencies": { "@miniflare/cache": "^2.14.2", @@ -35797,8 +35796,8 @@ "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/remix-oxygen": "^2.0.4", "@total-typescript/ts-reset": "^0.4.2", "graphql": "^16.6.0", @@ -35809,7 +35808,7 @@ }, "devDependencies": { "@remix-run/dev": "^2.9.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@types/eslint": "^8.4.10", @@ -35827,13 +35826,13 @@ } }, "templates/skeleton": { - "version": "2024.4.4", + "version": "2024.4.5", "dependencies": { "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/remix-oxygen": "^2.0.4", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -35846,7 +35845,7 @@ "@remix-run/dev": "^2.9.2", "@remix-run/eslint-config": "^2.9.2", "@shopify/hydrogen-codegen": "^0.3.1", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@total-typescript/ts-reset": "^0.4.2", @@ -41690,7 +41689,7 @@ "@shopify/create-hydrogen": { "version": "file:packages/create-hydrogen", "requires": { - "@shopify/cli-hydrogen": "^8.0.4" + "@shopify/cli-hydrogen": "^8.1.0" } }, "@shopify/generate-docs": { @@ -41738,7 +41737,7 @@ "@remix-run/server-runtime": "^2.9.2", "@shopify/generate-docs": "0.11.1", "@shopify/hydrogen-codegen": "*", - "@shopify/hydrogen-react": "2024.4.2", + "@shopify/hydrogen-react": "2024.4.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", "@types/source-map-support": "^0.5.10", @@ -41748,7 +41747,6 @@ "react": "^18.2.0", "schema-dts": "^1.1.0", "source-map-support": "^0.5.21", - "tiny-invariant": "^1.3.1", "type-fest": "^4.5.0", "use-resize-observer": "^9.1.0", "vitest": "^1.0.4", @@ -47453,8 +47451,8 @@ "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", "@types/morgan": "^1.9.4", @@ -47502,9 +47500,9 @@ "@remix-run/eslint-config": "^2.9.2", "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@shopify/remix-oxygen": "^2.0.4", @@ -47540,9 +47538,9 @@ "@remix-run/eslint-config": "^2.9.2", "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@shopify/remix-oxygen": "^2.0.4", @@ -47569,9 +47567,9 @@ "@remix-run/eslint-config": "^2.9.2", "@remix-run/react": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@shopify/remix-oxygen": "^2.0.4", @@ -49122,9 +49120,9 @@ "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@shopify/remix-oxygen": "^2.0.4", @@ -56901,10 +56899,10 @@ "@remix-run/react": "^2.9.2", "@remix-run/server-runtime": "^2.9.2", "@shopify/cli": "3.60.0", - "@shopify/cli-hydrogen": "^8.0.4", - "@shopify/hydrogen": "2024.4.2", + "@shopify/cli-hydrogen": "^8.1.0", + "@shopify/hydrogen": "2024.4.3", "@shopify/hydrogen-codegen": "^0.3.1", - "@shopify/mini-oxygen": "^3.0.2", + "@shopify/mini-oxygen": "^3.0.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", "@shopify/remix-oxygen": "^2.0.4", diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index d56974f13c..628055cbf7 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -67,7 +67,6 @@ "content-security-policy-builder": "^2.2.0", "type-fest": "^4.5.0", "source-map-support": "^0.5.21", - "tiny-invariant": "^1.3.1", "use-resize-observer": "^9.1.0", "worktop": "^0.7.3" }, diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.tsx index 0b3692e159..cc8f59788e 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.tsx @@ -37,6 +37,7 @@ import {ShopifyAnalytics} from './ShopifyAnalytics'; import {CartAnalytics} from './CartAnalytics'; import type {CustomerPrivacyApiProps} from '../customer-privacy/ShopifyCustomerPrivacy'; import type {Storefront} from '../storefront'; +import {errorOnce, warnOnce} from '../utils/warning'; export type ShopAnalytics = { /** The shop ID. */ @@ -67,7 +68,7 @@ export type AnalyticsProviderProps = { 'checkoutDomain' | 'storefrontAccessToken' | 'withPrivacyBanner' > >; - /** Disable throwing errors when required props are missing. */ + /** @deprecated Disable throwing errors when required props are missing. */ disableThrowOnError?: boolean; /** The domain scope of the cookie set with `useShopifyCookies`. **/ cookieDomain?: string; @@ -265,6 +266,10 @@ function shopifyCanTrack(): boolean { return false; } +function messageOnError(field: string, envVar: string) { + return `[h2:error:Analytics.Provider] - ${field} is required. Make sure ${envVar} is defined in your environment variables. See https://h2o.fyi/analytics/consent to learn how to setup environment variables in the Shopify admin.`; +} + function AnalyticsProvider({ canTrack: customCanTrack, cart: currentCart, @@ -285,6 +290,31 @@ function AnalyticsProvider({ customCanTrack ? () => customCanTrack : () => shopifyCanTrack, ); + if (!!shop) { + // If mock shop is used, log error instead of throwing + if (/\/68817551382$/.test(shop.shopId)) { + warnOnce( + '[h2:error:Analytics.Provider] - Mock shop is used. Analytics will not work properly.', + ); + } else { + if (!consent.checkoutDomain) { + const errorMsg = messageOnError( + 'consent.checkoutDomain', + 'PUBLIC_CHECKOUT_DOMAIN', + ); + errorOnce(errorMsg); + } + + if (!consent.storefrontAccessToken) { + const errorMsg = messageOnError( + 'consent.storefrontAccessToken', + 'PUBLIC_STOREFRONT_API_TOKEN', + ); + errorOnce(errorMsg); + } + } + } + const value = useMemo(() => { return { canTrack, @@ -318,7 +348,7 @@ function AnalyticsProvider({ {!!shop && !!currentCart && ( )} - {!!shop && ( + {!!shop && consent.checkoutDomain && ( { @@ -327,8 +357,6 @@ function AnalyticsProvider({ setCanTrack(() => shopifyCanTrack); }} domain={cookieDomain} - disableThrowOnError={disableThrowOnError} - isMockShop={/\/68817551382$/.test(shop.shopId)} /> )} diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsView.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsView.tsx index 585c546f33..47aeaacbae 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsView.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsView.tsx @@ -147,12 +147,21 @@ function AnalyticsView(props: CustomViewProps): null; function AnalyticsView(props: any) { const {type, data = {}, customData} = props; const location = useLocation(); - const {publish, cart, prevCart, shop} = useAnalytics(); + const { + publish, + cart, + prevCart, + shop, + customData: analyticProviderCustomData, + } = useAnalytics(); const url = location.pathname + location.search; let viewPayload: ViewPayload = { ...data, - customData, + customData: { + ...analyticProviderCustomData, + ...customData, + }, cart, prevCart, shop, diff --git a/packages/hydrogen/src/analytics-manager/ShopifyAnalytics.tsx b/packages/hydrogen/src/analytics-manager/ShopifyAnalytics.tsx index 5c0c0e1882..e1c32d0eb4 100644 --- a/packages/hydrogen/src/analytics-manager/ShopifyAnalytics.tsx +++ b/packages/hydrogen/src/analytics-manager/ShopifyAnalytics.tsx @@ -29,8 +29,6 @@ import { ComponentizableCartLine, Maybe, } from '@shopify/hydrogen-react/storefront-api-types'; -import invariant from 'tiny-invariant'; -import {warnOnce} from '../utils/warning'; function getCustomerPrivacyRequired() { const customerPrivacy = getCustomerPrivacy(); @@ -44,10 +42,6 @@ function getCustomerPrivacyRequired() { return customerPrivacy; } -function messageOnError(field: string) { - return `[h2:error:Analytics.Provider] - ${field} is required`; -} - /** * This component is responsible for sending analytics events to Shopify. * It emits the following events: @@ -61,42 +55,11 @@ export function ShopifyAnalytics({ consent, onReady, domain, - disableThrowOnError, - isMockShop, }: { consent: AnalyticsProviderProps['consent']; onReady: () => void; domain?: string; - disableThrowOnError: boolean; - isMockShop: boolean; }) { - // If mock shop is used, log error instead of throwing - if (isMockShop) { - warnOnce( - '[h2:error:Analytics.Provider] - Mock shop is used. Analytics will not work properly.', - ); - } else { - if (!consent.checkoutDomain) { - const errorMsg = messageOnError('consent.checkoutDomain'); - if (disableThrowOnError) { - // eslint-disable-next-line no-console - console.error(errorMsg); - } else { - invariant(false, errorMsg); - } - } - - if (!consent.storefrontAccessToken) { - const errorMsg = messageOnError('consent.storefrontAccessToken'); - if (disableThrowOnError) { - // eslint-disable-next-line no-console - console.error(errorMsg); - } else { - invariant(false, errorMsg); - } - } - } - const {subscribe, register, canTrack} = useAnalytics(); const [shopifyReady, setShopifyReady] = useState(false); const [privacyReady, setPrivacyReady] = useState(false); @@ -117,13 +80,11 @@ export function ShopifyAnalytics({ const {checkoutDomain, storefrontAccessToken, withPrivacyBanner} = consent; useCustomerPrivacy({ - checkoutDomain: - isMockShop || !checkoutDomain ? 'mock.shop' : checkoutDomain, - storefrontAccessToken: - isMockShop || !storefrontAccessToken - ? 'abcdefghijklmnopqrstuvwxyz123456' - : storefrontAccessToken, - withPrivacyBanner: isMockShop ? false : withPrivacyBanner, + checkoutDomain: !checkoutDomain ? 'mock.shop' : checkoutDomain, + storefrontAccessToken: !storefrontAccessToken + ? 'abcdefghijklmnopqrstuvwxyz123456' + : storefrontAccessToken, + withPrivacyBanner, onVisitorConsentCollected: setCustomerPrivacyReady, onReady: () => { // Set customer privacy ready 3 seconds after load diff --git a/packages/hydrogen/src/utils/warning.ts b/packages/hydrogen/src/utils/warning.ts index f1687393bb..993b8c3658 100644 --- a/packages/hydrogen/src/utils/warning.ts +++ b/packages/hydrogen/src/utils/warning.ts @@ -5,3 +5,11 @@ export const warnOnce = (string: string) => { warnings.add(string); } }; + +const errors = new Set(); +export const errorOnce = (string: string) => { + if (!errors.has(string)) { + console.error(new Error(string)); + errors.add(string); + } +}; diff --git a/packages/hydrogen/src/vite/plugin.ts b/packages/hydrogen/src/vite/plugin.ts index a834363982..c98f5cb70d 100644 --- a/packages/hydrogen/src/vite/plugin.ts +++ b/packages/hydrogen/src/vite/plugin.ts @@ -73,11 +73,7 @@ export function hydrogen(pluginOptions: HydrogenPluginOptions = {}): Plugin[] { // Avoid optimizing Hydrogen itself in the monorepo // to prevent caching source code changes: include: isHydrogenMonorepo - ? [ - 'content-security-policy-builder', - 'tiny-invariant', - 'worktop/cookie', - ] + ? ['content-security-policy-builder', 'worktop/cookie'] : ['@shopify/hydrogen'], }, };