@shopify/hydrogen 2025.7.0 → 2025.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/dev/{get-virtual-routes-KWSSQH4L.js → get-virtual-routes-ZEUPNZWL.js} +0 -36
  2. package/dist/dev/hydrogen-routes.js +1 -1
  3. package/dist/development/{get-virtual-routes-6PVSMJPH.js → get-virtual-routes-XE7G57DS.js} +3 -36
  4. package/dist/development/get-virtual-routes-XE7G57DS.js.map +1 -0
  5. package/dist/development/index.cjs +406 -124
  6. package/dist/development/index.cjs.map +1 -1
  7. package/dist/development/index.js +395 -81
  8. package/dist/development/index.js.map +1 -1
  9. package/dist/development/react-router-preset.d.ts +5 -5
  10. package/dist/development/react-router-preset.js +4 -4
  11. package/dist/development/react-router-preset.js.map +1 -1
  12. package/dist/oxygen/index.d.ts +120 -3
  13. package/dist/oxygen/index.js +111 -5
  14. package/dist/production/get-virtual-routes-MYYLGSAS.js +3 -0
  15. package/dist/production/get-virtual-routes-MYYLGSAS.js.map +1 -0
  16. package/dist/production/index.cjs +76 -76
  17. package/dist/production/index.cjs.map +1 -1
  18. package/dist/production/index.d.cts +76 -15
  19. package/dist/production/index.d.ts +76 -15
  20. package/dist/production/index.js +76 -76
  21. package/dist/production/index.js.map +1 -1
  22. package/dist/production/react-router-preset.d.ts +5 -5
  23. package/dist/production/react-router-preset.js +2 -2
  24. package/dist/vite/get-virtual-routes.d.ts +1 -17
  25. package/dist/vite/get-virtual-routes.js +0 -36
  26. package/package.json +6 -6
  27. package/dist/development/get-virtual-routes-6PVSMJPH.js.map +0 -1
  28. package/dist/oxygen/chunk-RVXKHOUX.js +0 -39
  29. package/dist/oxygen/chunk-T4YWBSCF.js +0 -14
  30. package/dist/oxygen/createRequestHandler.d.ts +0 -10
  31. package/dist/oxygen/createRequestHandler.js +0 -6
  32. package/dist/oxygen/getStorefrontHeaders-BqPh5S1b.d.ts +0 -69
  33. package/dist/oxygen/getStorefrontHeaders.d.ts +0 -1
  34. package/dist/oxygen/getStorefrontHeaders.js +0 -6
  35. package/dist/production/get-virtual-routes-JVKSNI4M.js +0 -3
  36. package/dist/production/get-virtual-routes-JVKSNI4M.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import { createContext, forwardRef, useContext, lazy, useMemo, useEffect, useRef, useState, createElement, Fragment as Fragment$1, Suspense } from 'react';
2
- import { createContext as createContext$1, useFetcher, useFetchers, RouterContextProvider, useNavigation, useLocation, useNavigate, Link, useMatches } from 'react-router';
2
+ import { createContext as createContext$1, useRevalidator, useFetcher, useFetchers, RouterContextProvider, createRequestHandler as createRequestHandler$1, useNavigation, useLocation, useNavigate, Link, useMatches } from 'react-router';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { useLoadScript, createStorefrontClient as createStorefrontClient$1, SHOPIFY_STOREFRONT_ID_HEADER, getShopifyCookies, SHOPIFY_Y, SHOPIFY_STOREFRONT_Y_HEADER, SHOPIFY_S, SHOPIFY_STOREFRONT_S_HEADER, flattenConnection, RichText as RichText$1, ShopPayButton as ShopPayButton$1, useShopifyCookies, parseGid, sendShopifyAnalytics, AnalyticsEventName, AnalyticsPageType, getClientBrowserParameters } from '@shopify/hydrogen-react';
5
- export { AnalyticsEventName, AnalyticsPageType, ExternalVideo, IMAGE_FRAGMENT, Image, MediaFile, ModelViewer, Money, ShopifySalesChannel, Video, customerAccountApiCustomScalars, decodeEncodedVariant, flattenConnection, getAdjacentAndFirstAvailableVariants, getClientBrowserParameters, getProductOptions, getShopifyCookies, isOptionValueCombinationInEncodedVariant, mapSelectedProductOptionToObject, parseGid, parseMetafield, sendShopifyAnalytics, storefrontApiCustomScalars, useLoadScript, useMoney, useSelectedOptionInUrlParam, useShopifyCookies } from '@shopify/hydrogen-react';
4
+ import { useLoadScript, useShopifyCookies, getTrackingValues, createStorefrontClient as createStorefrontClient$1, SHOPIFY_STOREFRONT_ID_HEADER, SHOPIFY_STOREFRONT_Y_HEADER, SHOPIFY_STOREFRONT_S_HEADER, SHOPIFY_UNIQUE_TOKEN_HEADER, SHOPIFY_VISIT_TOKEN_HEADER, flattenConnection, RichText as RichText$1, ShopPayButton as ShopPayButton$1, parseGid, sendShopifyAnalytics, AnalyticsEventName, AnalyticsPageType, getClientBrowserParameters } from '@shopify/hydrogen-react';
5
+ export { AnalyticsEventName, AnalyticsPageType, ExternalVideo, IMAGE_FRAGMENT, Image, MediaFile, ModelViewer, Money, ShopifySalesChannel, Video, customerAccountApiCustomScalars, decodeEncodedVariant, flattenConnection, getAdjacentAndFirstAvailableVariants, getClientBrowserParameters, getProductOptions, getShopifyCookies, getTrackingValues, isOptionValueCombinationInEncodedVariant, mapSelectedProductOptionToObject, parseGid, parseMetafield, sendShopifyAnalytics, storefrontApiCustomScalars, useLoadScript, useMoney, useSelectedOptionInUrlParam, useShopifyCookies } from '@shopify/hydrogen-react';
6
+ import { useLoadScript as useLoadScript$1 } from '@shopify/hydrogen-react/load-script';
6
7
  import { createGraphQLClient } from '@shopify/graphql-client';
7
8
  import { parse, stringify } from 'worktop/cookie';
8
9
  import cspBuilder from 'content-security-policy-builder';
@@ -73,7 +74,62 @@ var AnalyticsEvent = {
73
74
  // Custom
74
75
  CUSTOM_EVENT: `custom_`
75
76
  };
76
- var CONSENT_API = "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.1/consent-tracking-api.js";
77
+
78
+ // src/constants.ts
79
+ var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
80
+ var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
81
+ var SDK_VARIANT_HEADER = "X-SDK-Variant";
82
+ var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
83
+ var SDK_VERSION_HEADER = "X-SDK-Version";
84
+ var SHOPIFY_CLIENT_IP_HEADER = "X-Shopify-Client-IP";
85
+ var SHOPIFY_CLIENT_IP_SIG_HEADER = "X-Shopify-Client-IP-Sig";
86
+ var HYDROGEN_SFAPI_PROXY_KEY = "_sfapi_proxy";
87
+ var HYDROGEN_SERVER_TRACKING_KEY = "_server_tracking";
88
+
89
+ // src/utils/server-timing.ts
90
+ function buildServerTimingHeader(values) {
91
+ return Object.entries(values).map(([key, value]) => value ? `${key};desc=${value}` : void 0).filter(Boolean).join(", ");
92
+ }
93
+ function appendServerTimingHeader(response, values) {
94
+ const header = typeof values === "string" ? values : buildServerTimingHeader(values);
95
+ if (header) {
96
+ response.headers.append("Server-Timing", header);
97
+ }
98
+ }
99
+ var trackedTimings = ["_y", "_s", "_cmp"];
100
+ function extractServerTimingHeader(serverTimingHeader) {
101
+ const values = {};
102
+ if (!serverTimingHeader) return values;
103
+ const re = new RegExp(
104
+ `\\b(${trackedTimings.join("|")});desc="?([^",]+)"?`,
105
+ "g"
106
+ );
107
+ let match;
108
+ while ((match = re.exec(serverTimingHeader)) !== null) {
109
+ values[match[1]] = match[2];
110
+ }
111
+ return values;
112
+ }
113
+ function hasServerTimingInNavigationEntry(key) {
114
+ if (typeof window === "undefined") return false;
115
+ try {
116
+ const navigationEntry = window.performance.getEntriesByType(
117
+ "navigation"
118
+ )[0];
119
+ return !!navigationEntry?.serverTiming?.some((entry) => entry.name === key);
120
+ } catch (e) {
121
+ return false;
122
+ }
123
+ }
124
+ function isSfapiProxyEnabled() {
125
+ return hasServerTimingInNavigationEntry(HYDROGEN_SFAPI_PROXY_KEY);
126
+ }
127
+ function hasServerReturnedTrackingValues() {
128
+ return hasServerTimingInNavigationEntry(HYDROGEN_SERVER_TRACKING_KEY);
129
+ }
130
+
131
+ // src/customer-privacy/ShopifyCustomerPrivacy.tsx
132
+ var CONSENT_API = "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.js";
77
133
  var CONSENT_API_WITH_BANNER = "https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js";
78
134
  function logMissingConfig(fieldName) {
79
135
  console.error(
@@ -85,19 +141,34 @@ function useCustomerPrivacy(props) {
85
141
  withPrivacyBanner = false,
86
142
  onVisitorConsentCollected,
87
143
  onReady,
88
- ...consentConfig
144
+ checkoutDomain,
145
+ storefrontAccessToken,
146
+ country,
147
+ locale,
148
+ sameDomainForStorefrontApi
89
149
  } = props;
90
- useLoadScript(withPrivacyBanner ? CONSENT_API_WITH_BANNER : CONSENT_API, {
150
+ const hasSfapiProxy = useMemo(
151
+ () => sameDomainForStorefrontApi ?? isSfapiProxyEnabled(),
152
+ [sameDomainForStorefrontApi]
153
+ );
154
+ const fetchTrackingValuesFromBrowser = useMemo(
155
+ () => hasSfapiProxy && !hasServerReturnedTrackingValues(),
156
+ [hasSfapiProxy]
157
+ );
158
+ const cookiesReady = useShopifyCookies({
159
+ fetchTrackingValues: fetchTrackingValuesFromBrowser,
160
+ storefrontAccessToken,
161
+ ignoreDeprecatedCookies: true
162
+ });
163
+ const initialTrackingValues = useMemo(getTrackingValues, [cookiesReady]);
164
+ const { revalidate } = useRevalidator();
165
+ useLoadScript$1(withPrivacyBanner ? CONSENT_API_WITH_BANNER : CONSENT_API, {
91
166
  attributes: {
92
167
  id: "customer-privacy-api"
93
168
  }
94
169
  });
95
- const { observing, setLoaded } = useApisLoaded({
96
- withPrivacyBanner,
97
- onLoaded: onReady
98
- });
170
+ const { observing, setLoaded, apisLoaded } = useApisLoaded({ withPrivacyBanner });
99
171
  const config = useMemo(() => {
100
- const { checkoutDomain, storefrontAccessToken } = consentConfig;
101
172
  if (!checkoutDomain) logMissingConfig("checkoutDomain");
102
173
  if (!storefrontAccessToken) logMissingConfig("storefrontAccessToken");
103
174
  if (storefrontAccessToken.startsWith("shpat_") || storefrontAccessToken.length !== 32) {
@@ -105,18 +176,54 @@ function useCustomerPrivacy(props) {
105
176
  `[h2:error:useCustomerPrivacy] It looks like you passed a private access token, make sure to use the public token`
106
177
  );
107
178
  }
179
+ const commonAncestorDomain = parseStoreDomain(checkoutDomain);
180
+ const sfapiDomain = (
181
+ // Check if standard route proxy is enabled in Hydrogen server
182
+ // to use it instead of doing a cross-origin request to checkout.
183
+ hasSfapiProxy && typeof window !== "undefined" ? window.location.host : checkoutDomain
184
+ );
108
185
  const config2 = {
109
- checkoutRootDomain: checkoutDomain,
186
+ // This domain is used to send requests to SFAPI for setting and getting consent.
187
+ checkoutRootDomain: sfapiDomain,
188
+ // Prefix with a dot to ensure this domain is different from checkoutRootDomain.
189
+ // This will ensure old cookies are set for a cross-subdomain checkout setup
190
+ // so that we keep backward compatibility until new cookies are rolled out.
191
+ // Once consent-tracking-api is updated to not rely on cookies anymore, we can remove this.
192
+ storefrontRootDomain: commonAncestorDomain ? "." + commonAncestorDomain : void 0,
110
193
  storefrontAccessToken,
111
- storefrontRootDomain: parseStoreDomain(checkoutDomain),
112
- country: consentConfig.country,
113
- locale: consentConfig.locale
194
+ country,
195
+ locale
114
196
  };
115
197
  return config2;
116
- }, [consentConfig, parseStoreDomain, logMissingConfig]);
198
+ }, [
199
+ logMissingConfig,
200
+ checkoutDomain,
201
+ storefrontAccessToken,
202
+ country,
203
+ locale
204
+ ]);
117
205
  useEffect(() => {
118
206
  const consentCollectedHandler = (event) => {
207
+ const latestTrackingValues = getTrackingValues();
208
+ if (initialTrackingValues.visitToken !== latestTrackingValues.visitToken || initialTrackingValues.uniqueToken !== latestTrackingValues.uniqueToken) {
209
+ revalidate().catch(() => {
210
+ console.warn(
211
+ "[h2:warn:useCustomerPrivacy] Revalidation failed after consent change."
212
+ );
213
+ });
214
+ }
119
215
  if (onVisitorConsentCollected) {
216
+ const customerPrivacy = getCustomerPrivacy();
217
+ if (customerPrivacy?.shouldShowBanner()) {
218
+ const consentValues = customerPrivacy.currentVisitorConsent();
219
+ if (consentValues) {
220
+ const NO_VALUE = "";
221
+ const noInteraction = consentValues.marketing === NO_VALUE && consentValues.analytics === NO_VALUE && consentValues.preferences === NO_VALUE;
222
+ if (noInteraction) {
223
+ return;
224
+ }
225
+ }
226
+ }
120
227
  onVisitorConsentCollected(event.detail);
121
228
  }
122
229
  };
@@ -142,14 +249,11 @@ function useCustomerPrivacy(props) {
142
249
  },
143
250
  set(value) {
144
251
  if (typeof value === "object" && value !== null && "showPreferences" in value && "loadBanner" in value) {
145
- const privacyBanner = value;
146
- privacyBanner.loadBanner(config);
147
252
  customPrivacyBanner = overridePrivacyBannerMethods({
148
- privacyBanner,
253
+ privacyBanner: value,
149
254
  config
150
255
  });
151
256
  setLoaded.privacyBanner();
152
- emitCustomerPrivacyApiLoaded();
153
257
  }
154
258
  }
155
259
  };
@@ -183,6 +287,8 @@ function useCustomerPrivacy(props) {
183
287
  const customerPrivacy = value2;
184
288
  customCustomerPrivacy = {
185
289
  ...customerPrivacy,
290
+ // Note: this method is not used by the privacy-banner,
291
+ // it bundles its own setTrackingConsent.
186
292
  setTrackingConsent: overrideCustomerPrivacySetTrackingConsent(
187
293
  { customerPrivacy, config }
188
294
  )
@@ -192,7 +298,6 @@ function useCustomerPrivacy(props) {
192
298
  customerPrivacy: customCustomerPrivacy
193
299
  };
194
300
  setLoaded.customerPrivacy();
195
- emitCustomerPrivacyApiLoaded();
196
301
  }
197
302
  }
198
303
  });
@@ -204,6 +309,24 @@ function useCustomerPrivacy(props) {
204
309
  overrideCustomerPrivacySetTrackingConsent,
205
310
  setLoaded.customerPrivacy
206
311
  ]);
312
+ useEffect(() => {
313
+ if (!apisLoaded || !cookiesReady) return;
314
+ const customerPrivacy = getCustomerPrivacy();
315
+ if (customerPrivacy && !customerPrivacy.cachedConsent) {
316
+ const trackingValues = getTrackingValues();
317
+ if (trackingValues.consent) {
318
+ customerPrivacy.cachedConsent = trackingValues.consent;
319
+ }
320
+ }
321
+ if (withPrivacyBanner) {
322
+ const privacyBanner = getPrivacyBanner();
323
+ if (privacyBanner) {
324
+ privacyBanner.loadBanner(config);
325
+ }
326
+ }
327
+ emitCustomerPrivacyApiLoaded();
328
+ onReady?.();
329
+ }, [apisLoaded, cookiesReady]);
207
330
  const result = {
208
331
  customerPrivacy: getCustomerPrivacy()
209
332
  };
@@ -219,15 +342,12 @@ function emitCustomerPrivacyApiLoaded() {
219
342
  const event = new CustomEvent("shopifyCustomerPrivacyApiLoaded");
220
343
  document.dispatchEvent(event);
221
344
  }
222
- function useApisLoaded({
223
- withPrivacyBanner,
224
- onLoaded
225
- }) {
345
+ function useApisLoaded({ withPrivacyBanner }) {
226
346
  const observing = useRef({ customerPrivacy: false, privacyBanner: false });
227
- const [apisLoaded, setApisLoaded] = useState(
347
+ const [apisLoadedArray, setApisLoaded] = useState(
228
348
  withPrivacyBanner ? [false, false] : [false]
229
349
  );
230
- const loaded = apisLoaded.every(Boolean);
350
+ const apisLoaded = apisLoadedArray.every(Boolean);
231
351
  const setLoaded = {
232
352
  customerPrivacy: () => {
233
353
  if (withPrivacyBanner) {
@@ -243,16 +363,11 @@ function useApisLoaded({
243
363
  setApisLoaded((prev) => [prev[0], true]);
244
364
  }
245
365
  };
246
- useEffect(() => {
247
- if (loaded && onLoaded) {
248
- onLoaded();
249
- }
250
- }, [loaded, onLoaded]);
251
- return { observing, setLoaded };
366
+ return { observing, setLoaded, apisLoaded };
252
367
  }
253
368
  function parseStoreDomain(checkoutDomain) {
254
369
  if (typeof window === "undefined") return;
255
- const host = window.document.location.host;
370
+ const host = window.location.host;
256
371
  const checkoutDomainParts = checkoutDomain.split(".").reverse();
257
372
  const currentDomainParts = host.split(".").reverse();
258
373
  const sameDomainParts = [];
@@ -261,7 +376,7 @@ function parseStoreDomain(checkoutDomain) {
261
376
  sameDomainParts.push(part);
262
377
  }
263
378
  });
264
- return sameDomainParts.reverse().join(".");
379
+ return sameDomainParts.reverse().join(".") || void 0;
265
380
  }
266
381
  function overrideCustomerPrivacySetTrackingConsent({
267
382
  customerPrivacy,
@@ -319,7 +434,7 @@ function getPrivacyBanner() {
319
434
  }
320
435
 
321
436
  // package.json
322
- var version = "2025.7.0";
437
+ var version = "2025.7.2";
323
438
 
324
439
  // src/analytics-manager/ShopifyAnalytics.tsx
325
440
  function getCustomerPrivacyRequired() {
@@ -339,6 +454,7 @@ function ShopifyAnalytics({
339
454
  const { subscribe: subscribe2, register: register2, canTrack } = useAnalytics();
340
455
  const [shopifyReady, setShopifyReady] = useState(false);
341
456
  const [privacyReady, setPrivacyReady] = useState(false);
457
+ const [collectedConsent, setCollectedConsent] = useState("");
342
458
  const init = useRef(false);
343
459
  const { checkoutDomain, storefrontAccessToken, language } = consent;
344
460
  const { ready: shopifyAnalyticsReady } = register2("Internal_Shopify_Analytics");
@@ -347,14 +463,31 @@ function ShopifyAnalytics({
347
463
  locale: language,
348
464
  checkoutDomain: !checkoutDomain ? "mock.shop" : checkoutDomain,
349
465
  storefrontAccessToken: !storefrontAccessToken ? "abcdefghijklmnopqrstuvwxyz123456" : storefrontAccessToken,
350
- onVisitorConsentCollected: () => setPrivacyReady(true),
351
- onReady: () => setPrivacyReady(true)
466
+ // If we use privacy banner, we should wait until consent is collected.
467
+ // Otherwise, we can consider privacy ready immediately:
468
+ onReady: () => !consent.withPrivacyBanner && setPrivacyReady(true),
469
+ onVisitorConsentCollected: (consent2) => {
470
+ try {
471
+ setCollectedConsent(JSON.stringify(consent2));
472
+ } catch (e) {
473
+ }
474
+ setPrivacyReady(true);
475
+ }
352
476
  });
477
+ const hasUserConsent = useMemo(
478
+ // must be initialized with true to avoid removing cookies too early
479
+ () => privacyReady ? canTrack() : true,
480
+ // Make this value depend on collectedConsent to re-run `canTrack()` when consent changes
481
+ [privacyReady, canTrack, collectedConsent]
482
+ );
353
483
  useShopifyCookies({
354
- hasUserConsent: privacyReady ? canTrack() : true,
355
- // must be initialized with true
484
+ hasUserConsent,
356
485
  domain,
357
- checkoutDomain
486
+ checkoutDomain,
487
+ // Already done inside useCustomerPrivacy
488
+ fetchTrackingValues: false,
489
+ // Avoid creating local cookies too early
490
+ ignoreDeprecatedCookies: !privacyReady
358
491
  });
359
492
  useEffect(() => {
360
493
  if (init.current) return;
@@ -404,11 +537,11 @@ function prepareBasePageViewPayload(payload) {
404
537
  ...payload.shop,
405
538
  hasUserConsent,
406
539
  ...getClientBrowserParameters(),
407
- ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
408
- gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed()),
409
540
  analyticsAllowed: customerPrivacy.analyticsProcessingAllowed(),
410
541
  marketingAllowed: customerPrivacy.marketingAllowed(),
411
- saleOfDataAllowed: customerPrivacy.saleOfDataAllowed()
542
+ saleOfDataAllowed: customerPrivacy.saleOfDataAllowed(),
543
+ ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
544
+ gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed())
412
545
  };
413
546
  return eventPayload;
414
547
  }
@@ -841,11 +974,11 @@ function AnalyticsProvider({
841
974
  shop: shopProp = null,
842
975
  cookieDomain
843
976
  }) {
844
- const listenerSet = useRef(false);
845
977
  const { shop } = useShopAnalytics(shopProp);
846
978
  const [analyticsLoaded, setAnalyticsLoaded] = useState(
847
979
  customCanTrack ? true : false
848
980
  );
981
+ const [consentCollected, setConsentCollected] = useState(false);
849
982
  const [carts, setCarts] = useState({ cart: null, prevCart: null });
850
983
  const [canTrack, setCanTrack] = useState(
851
984
  customCanTrack ? () => customCanTrack : () => shopifyCanTrack
@@ -913,21 +1046,21 @@ function AnalyticsProvider({
913
1046
  children,
914
1047
  !!shop && /* @__PURE__ */ jsx(AnalyticsPageView, {}),
915
1048
  !!shop && !!currentCart && /* @__PURE__ */ jsx(CartAnalytics, { cart: currentCart, setCarts }),
916
- !!shop && consent.checkoutDomain && /* @__PURE__ */ jsx(
1049
+ !!shop && /* @__PURE__ */ jsx(
917
1050
  ShopifyAnalytics,
918
1051
  {
919
1052
  consent,
920
1053
  onReady: () => {
921
- listenerSet.current = true;
922
1054
  setAnalyticsLoaded(true);
923
1055
  setCanTrack(
924
1056
  customCanTrack ? () => customCanTrack : () => shopifyCanTrack
925
1057
  );
1058
+ setConsentCollected(true);
926
1059
  },
927
1060
  domain: cookieDomain
928
1061
  }
929
1062
  ),
930
- !!shop && /* @__PURE__ */ jsx(PerfKit, { shop })
1063
+ !!shop && consentCollected && /* @__PURE__ */ jsx(PerfKit, { shop })
931
1064
  ] });
932
1065
  }
933
1066
  function useAnalytics() {
@@ -1006,6 +1139,31 @@ function getDebugHeaders(request) {
1006
1139
  purpose: request ? getHeader(request, "purpose") : void 0
1007
1140
  };
1008
1141
  }
1142
+ function getStorefrontHeaders(request) {
1143
+ return {
1144
+ requestGroupId: getHeader(request, "request-id"),
1145
+ buyerIp: getHeader(request, "oxygen-buyer-ip"),
1146
+ buyerIpSig: getHeader(request, SHOPIFY_CLIENT_IP_SIG_HEADER),
1147
+ cookie: getHeader(request, "cookie"),
1148
+ // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules
1149
+ purpose: getHeader(request, "sec-purpose") || getHeader(request, "purpose")
1150
+ };
1151
+ }
1152
+ var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
1153
+ var getSafePathname = (url) => {
1154
+ try {
1155
+ return new URL(url, "http://e.c").pathname;
1156
+ } catch {
1157
+ return "/";
1158
+ }
1159
+ };
1160
+ function extractHeaders(extract, keys) {
1161
+ return keys.reduce((acc, key) => {
1162
+ const forwardedValue = extract(key);
1163
+ if (forwardedValue) acc.push([key, forwardedValue]);
1164
+ return acc;
1165
+ }, []);
1166
+ }
1009
1167
 
1010
1168
  // src/utils/callsites.ts
1011
1169
  function withSyncStack(promise, options = {}) {
@@ -1373,13 +1531,16 @@ async function runWithCache(cacheKey, actionFn, {
1373
1531
  }
1374
1532
  return result;
1375
1533
  }
1534
+ var excludedHeaders = ["set-cookie", "server-timing"];
1376
1535
  function toSerializableResponse(body, response) {
1377
1536
  return [
1378
1537
  body,
1379
1538
  {
1380
1539
  status: response.status,
1381
1540
  statusText: response.statusText,
1382
- headers: Array.from(response.headers.entries())
1541
+ headers: [...response.headers].filter(
1542
+ ([key]) => !excludedHeaders.includes(key.toLowerCase())
1543
+ )
1383
1544
  }
1384
1545
  ];
1385
1546
  }
@@ -1393,7 +1554,8 @@ async function fetchWithServerCache(url, requestInit, {
1393
1554
  shouldCacheResponse,
1394
1555
  waitUntil,
1395
1556
  debugInfo,
1396
- streamConfig
1557
+ streamConfig,
1558
+ onRawHeaders
1397
1559
  }) {
1398
1560
  if (!cacheOptions && (!requestInit.method || requestInit.method === "GET")) {
1399
1561
  cacheOptions = CacheShort();
@@ -1407,6 +1569,7 @@ async function fetchWithServerCache(url, requestInit, {
1407
1569
  url,
1408
1570
  customFetchApi: async (url2, options) => {
1409
1571
  rawResponse = await fetch(url2, options);
1572
+ onRawHeaders?.(rawResponse.headers);
1410
1573
  return rawResponse;
1411
1574
  },
1412
1575
  headers: requestInit.headers
@@ -1430,6 +1593,7 @@ async function fetchWithServerCache(url, requestInit, {
1430
1593
  );
1431
1594
  }
1432
1595
  const response = await fetch(url, requestInit);
1596
+ onRawHeaders?.(response.headers);
1433
1597
  if (!response.ok) {
1434
1598
  return response;
1435
1599
  }
@@ -1653,13 +1817,6 @@ var cartSetIdDefault = (cookieOptions) => {
1653
1817
  };
1654
1818
  };
1655
1819
 
1656
- // src/constants.ts
1657
- var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
1658
- var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
1659
- var SDK_VARIANT_HEADER = "X-SDK-Variant";
1660
- var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
1661
- var SDK_VERSION_HEADER = "X-SDK-Version";
1662
-
1663
1820
  // src/utils/uuid.ts
1664
1821
  function generateUUID() {
1665
1822
  if (typeof crypto !== "undefined" && !!crypto.randomUUID) {
@@ -1670,7 +1827,7 @@ function generateUUID() {
1670
1827
  }
1671
1828
 
1672
1829
  // src/version.ts
1673
- var LIB_VERSION = "2025.7.0";
1830
+ var LIB_VERSION = "2025.7.2";
1674
1831
 
1675
1832
  // src/utils/graphql.ts
1676
1833
  function minifyQuery(string) {
@@ -1834,16 +1991,34 @@ function createStorefrontClient(options) {
1834
1991
  contentType: "json",
1835
1992
  buyerIp: storefrontHeaders?.buyerIp || ""
1836
1993
  });
1994
+ if (storefrontHeaders?.buyerIp) {
1995
+ defaultHeaders[SHOPIFY_CLIENT_IP_HEADER] = storefrontHeaders.buyerIp;
1996
+ }
1997
+ if (storefrontHeaders?.buyerIpSig) {
1998
+ defaultHeaders[SHOPIFY_CLIENT_IP_SIG_HEADER] = storefrontHeaders.buyerIpSig;
1999
+ }
1837
2000
  defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
1838
2001
  if (storefrontId) defaultHeaders[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
1839
2002
  defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
1840
- if (storefrontHeaders && storefrontHeaders.cookie) {
1841
- const cookies = getShopifyCookies(storefrontHeaders.cookie ?? "");
1842
- if (cookies[SHOPIFY_Y])
1843
- defaultHeaders[SHOPIFY_STOREFRONT_Y_HEADER] = cookies[SHOPIFY_Y];
1844
- if (cookies[SHOPIFY_S])
1845
- defaultHeaders[SHOPIFY_STOREFRONT_S_HEADER] = cookies[SHOPIFY_S];
2003
+ const requestCookie = storefrontHeaders?.cookie ?? "";
2004
+ if (requestCookie) defaultHeaders["cookie"] = requestCookie;
2005
+ let uniqueToken;
2006
+ let visitToken;
2007
+ if (!/\b_shopify_(analytics|marketing)=/.test(requestCookie)) {
2008
+ const legacyUniqueToken = requestCookie.match(/\b_shopify_y=([^;]+)/)?.[1];
2009
+ const legacyVisitToken = requestCookie.match(/\b_shopify_s=([^;]+)/)?.[1];
2010
+ if (legacyUniqueToken) {
2011
+ defaultHeaders[SHOPIFY_STOREFRONT_Y_HEADER] = legacyUniqueToken;
2012
+ }
2013
+ if (legacyVisitToken) {
2014
+ defaultHeaders[SHOPIFY_STOREFRONT_S_HEADER] = legacyVisitToken;
2015
+ }
2016
+ uniqueToken = legacyUniqueToken ?? generateUUID();
2017
+ visitToken = legacyVisitToken ?? generateUUID();
2018
+ defaultHeaders[SHOPIFY_UNIQUE_TOKEN_HEADER] = uniqueToken;
2019
+ defaultHeaders[SHOPIFY_VISIT_TOKEN_HEADER] = visitToken;
1846
2020
  }
2021
+ let collectedSubrequestHeaders;
1847
2022
  const cacheKeyHeader = JSON.stringify({
1848
2023
  "content-type": defaultHeaders["content-type"],
1849
2024
  "user-agent": defaultHeaders["user-agent"],
@@ -1910,7 +2085,13 @@ function createStorefrontClient(options) {
1910
2085
  graphql: graphqlData,
1911
2086
  purpose: storefrontHeaders?.purpose
1912
2087
  },
1913
- streamConfig
2088
+ streamConfig,
2089
+ onRawHeaders: (headers2) => {
2090
+ collectedSubrequestHeaders ??= {
2091
+ setCookie: headers2.getSetCookie(),
2092
+ serverTiming: headers2.get("server-timing") ?? ""
2093
+ };
2094
+ }
1914
2095
  });
1915
2096
  const errorOptions = {
1916
2097
  url,
@@ -2009,9 +2190,90 @@ function createStorefrontClient(options) {
2009
2190
  generateCacheControlHeader,
2010
2191
  getPublicTokenHeaders,
2011
2192
  getPrivateTokenHeaders,
2193
+ getHeaders: () => ({ ...defaultHeaders }),
2012
2194
  getShopifyDomain,
2013
2195
  getApiUrl: getStorefrontApiUrl,
2014
- i18n: i18n ?? defaultI18n
2196
+ i18n: i18n ?? defaultI18n,
2197
+ /**
2198
+ * Checks if the request is targeting the Storefront API endpoint.
2199
+ */
2200
+ isStorefrontApiUrl(request) {
2201
+ return SFAPI_RE.test(getSafePathname(request.url ?? ""));
2202
+ },
2203
+ /**
2204
+ * Forwards the request to the Storefront API.
2205
+ */
2206
+ async forward(request, options2) {
2207
+ const forwardedHeaders = new Headers([
2208
+ // Forward only a selected set of headers to the Storefront API
2209
+ // to avoid getting 403 errors due to unexpected headers.
2210
+ ...extractHeaders(
2211
+ (key) => request.headers.get(key),
2212
+ [
2213
+ "accept",
2214
+ "accept-encoding",
2215
+ "accept-language",
2216
+ // Access-Control headers are used for CORS preflight requests.
2217
+ "access-control-request-headers",
2218
+ "access-control-request-method",
2219
+ "content-type",
2220
+ "content-length",
2221
+ "cookie",
2222
+ "origin",
2223
+ "referer",
2224
+ "user-agent",
2225
+ STOREFRONT_ACCESS_TOKEN_HEADER,
2226
+ SHOPIFY_UNIQUE_TOKEN_HEADER,
2227
+ SHOPIFY_VISIT_TOKEN_HEADER
2228
+ ]
2229
+ ),
2230
+ // Add some headers to help with geolocalization and debugging
2231
+ ...extractHeaders(
2232
+ (key) => defaultHeaders[key],
2233
+ [
2234
+ SHOPIFY_CLIENT_IP_HEADER,
2235
+ SHOPIFY_CLIENT_IP_SIG_HEADER,
2236
+ SHOPIFY_STOREFRONT_ID_HEADER,
2237
+ STOREFRONT_REQUEST_GROUP_ID_HEADER
2238
+ ]
2239
+ )
2240
+ ]);
2241
+ if (storefrontHeaders?.buyerIp) {
2242
+ forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
2243
+ }
2244
+ const storefrontApiVersion = options2?.storefrontApiVersion ?? getSafePathname(request.url).match(SFAPI_RE)?.[1];
2245
+ const sfapiResponse = await fetch(
2246
+ getStorefrontApiUrl({ storefrontApiVersion }),
2247
+ {
2248
+ method: request.method,
2249
+ body: request.body,
2250
+ headers: forwardedHeaders
2251
+ }
2252
+ );
2253
+ return new Response(sfapiResponse.body, sfapiResponse);
2254
+ },
2255
+ setCollectedSubrequestHeaders: (response) => {
2256
+ if (collectedSubrequestHeaders) {
2257
+ for (const value of collectedSubrequestHeaders.setCookie) {
2258
+ response.headers.append("Set-Cookie", value);
2259
+ }
2260
+ }
2261
+ const serverTiming = extractServerTimingHeader(
2262
+ collectedSubrequestHeaders?.serverTiming
2263
+ );
2264
+ const isDocumentResponse = response.headers.get("content-type")?.startsWith("text/html");
2265
+ const fallbackValues = isDocumentResponse ? { _y: uniqueToken, _s: visitToken } : void 0;
2266
+ appendServerTimingHeader(response, {
2267
+ ...fallbackValues,
2268
+ ...serverTiming
2269
+ });
2270
+ if (isDocumentResponse && collectedSubrequestHeaders && // _shopify_essential cookie is always set, but we need more than that
2271
+ collectedSubrequestHeaders.setCookie.length > 1 && serverTiming?._y && serverTiming?._s && serverTiming?._cmp) {
2272
+ appendServerTimingHeader(response, {
2273
+ [HYDROGEN_SERVER_TRACKING_KEY]: "1"
2274
+ });
2275
+ }
2276
+ }
2015
2277
  }
2016
2278
  };
2017
2279
  }
@@ -3607,6 +3869,12 @@ function createCustomerAccountClient({
3607
3869
  if (options?.countryCode) {
3608
3870
  loginUrl.searchParams.append("region_country", options.countryCode);
3609
3871
  }
3872
+ if (options?.acrValues) {
3873
+ loginUrl.searchParams.append("acr_values", options.acrValues);
3874
+ }
3875
+ if (options?.loginHint) {
3876
+ loginUrl.searchParams.append("login_hint", options.loginHint);
3877
+ }
3610
3878
  const verifier = generateCodeVerifier();
3611
3879
  const challenge = await generateCodeChallenge(verifier);
3612
3880
  session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
@@ -3937,12 +4205,58 @@ function createHydrogenContext(options, additionalContext) {
3937
4205
  });
3938
4206
  return hybridProvider;
3939
4207
  }
3940
- function getStorefrontHeaders(request) {
3941
- return {
3942
- requestGroupId: getHeader(request, "request-id"),
3943
- buyerIp: getHeader(request, "oxygen-buyer-ip"),
3944
- cookie: getHeader(request, "cookie"),
3945
- purpose: getHeader(request, "purpose")
4208
+ function createRequestHandler({
4209
+ build,
4210
+ mode,
4211
+ poweredByHeader = true,
4212
+ getLoadContext,
4213
+ collectTrackingInformation = true,
4214
+ proxyStandardRoutes = true
4215
+ }) {
4216
+ const handleRequest = createRequestHandler$1(build, mode);
4217
+ const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
4218
+ return async (request) => {
4219
+ const method = request.method;
4220
+ if ((method === "GET" || method === "HEAD") && request.body) {
4221
+ return new Response(`${method} requests cannot have a body`, {
4222
+ status: 400
4223
+ });
4224
+ }
4225
+ const url = new URL(request.url);
4226
+ if (url.pathname.includes("//")) {
4227
+ return new Response(null, {
4228
+ status: 301,
4229
+ headers: {
4230
+ location: url.pathname.replace(/\/+/g, "/")
4231
+ }
4232
+ });
4233
+ }
4234
+ const context = await getLoadContext?.(request);
4235
+ const storefront = context?.storefront || context?.get?.(storefrontContext);
4236
+ if (proxyStandardRoutes) {
4237
+ if (!storefront) {
4238
+ warnOnce(
4239
+ "[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
4240
+ );
4241
+ }
4242
+ if (storefront?.isStorefrontApiUrl(request)) {
4243
+ const response2 = await storefront.forward(request);
4244
+ appendPoweredByHeader?.(response2);
4245
+ return response2;
4246
+ }
4247
+ }
4248
+ const response = await handleRequest(request, context);
4249
+ if (storefront && proxyStandardRoutes) {
4250
+ if (collectTrackingInformation) {
4251
+ storefront.setCollectedSubrequestHeaders(response);
4252
+ }
4253
+ const fetchDest = request.headers.get("sec-fetch-dest");
4254
+ if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
4255
+ appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
4256
+ }
4257
+ }
4258
+ appendPoweredByHeader?.(response);
4259
+ return response;
3946
4260
  };
3947
4261
  }
3948
4262
  var NonceContext = createContext(void 0);
@@ -4069,7 +4383,7 @@ function LazyScript({
4069
4383
 
4070
4384
  // src/dev/hydrogen-routes.ts
4071
4385
  async function hydrogenRoutes(currentRoutes) {
4072
- const { getVirtualRoutesV3 } = await import('./get-virtual-routes-6PVSMJPH.js');
4386
+ const { getVirtualRoutesV3 } = await import('./get-virtual-routes-XE7G57DS.js');
4073
4387
  const { layout, routes: virtualRoutes } = await getVirtualRoutesV3();
4074
4388
  const childVirtualRoutes = virtualRoutes.map(({ path, file, index, id }) => {
4075
4389
  return {
@@ -4556,10 +4870,10 @@ function hydrogenPreset() {
4556
4870
  ssr: true,
4557
4871
  future: {
4558
4872
  v8_middleware: true,
4873
+ v8_splitRouteModules: true,
4874
+ v8_viteEnvironmentApi: false,
4559
4875
  unstable_optimizeDeps: true,
4560
- unstable_splitRouteModules: true,
4561
- unstable_subResourceIntegrity: false,
4562
- unstable_viteEnvironmentApi: false
4876
+ unstable_subResourceIntegrity: false
4563
4877
  }
4564
4878
  }),
4565
4879
  reactRouterConfigResolved: ({ reactRouterConfig }) => {
@@ -4575,7 +4889,7 @@ function hydrogenPreset() {
4575
4889
  }
4576
4890
  if (reactRouterConfig.serverBundles) {
4577
4891
  throw new Error(
4578
- "[Hydrogen Preset] serverBundles is not supported in Hydrogen 2025.7.0.\nReason: React Router plugin manifest incompatibility with Hydrogen CLI.\nAlternative: Route-level code splitting via unstable_splitRouteModules is enabled."
4892
+ "[Hydrogen Preset] serverBundles is not supported in Hydrogen 2025.7.0.\nReason: React Router plugin manifest incompatibility with Hydrogen CLI.\nAlternative: Route-level code splitting via v8_splitRouteModules is enabled."
4579
4893
  );
4580
4894
  }
4581
4895
  if (reactRouterConfig.buildEnd) {
@@ -5090,10 +5404,10 @@ var schema = {
5090
5404
  if (typeof value !== "string") {
5091
5405
  throw new Error(ERROR_PREFIX.concat("`title` should be a string"));
5092
5406
  }
5093
- if (typeof value === "string" && value.length > 120) {
5407
+ if (typeof value === "string" && value.length > 70) {
5094
5408
  throw new Error(
5095
5409
  ERROR_PREFIX.concat(
5096
- "`title` should not be longer than 120 characters"
5410
+ "`title` should not be longer than 70 characters"
5097
5411
  )
5098
5412
  );
5099
5413
  }
@@ -5110,7 +5424,7 @@ var schema = {
5110
5424
  if (typeof value === "string" && value.length > 155) {
5111
5425
  throw new Error(
5112
5426
  ERROR_PREFIX.concat(
5113
- "`description` should not be longer than 155 characters"
5427
+ "`description` should not be longer than 160 characters"
5114
5428
  )
5115
5429
  );
5116
5430
  }
@@ -5986,6 +6300,6 @@ var QUERIES = {
5986
6300
  //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartDeliveryAddressesRemove
5987
6301
  //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartDeliveryAddressesUpdate
5988
6302
 
5989
- export { Analytics, AnalyticsEvent, CacheCustom, CacheLong, CacheNone, CacheShort, CartForm, InMemoryCache, NonceProvider, OptimisticInput, Pagination, RichText, Script, Seo, ShopPayButton, VariantSelector, cartAttributesUpdateDefault, cartBuyerIdentityUpdateDefault, cartCreateDefault, cartDiscountCodesUpdateDefault, cartGetDefault, cartGetIdDefault, cartGiftCardCodesRemoveDefault, cartGiftCardCodesUpdateDefault, cartLinesAddDefault, cartLinesRemoveDefault, cartLinesUpdateDefault, cartMetafieldDeleteDefault, cartMetafieldsSetDefault, cartNoteUpdateDefault, cartSelectedDeliveryOptionsUpdateDefault, cartSetIdDefault, changelogHandler, createCartHandler, createContentSecurityPolicy, createCustomerAccountClient, createHydrogenContext, createStorefrontClient, createWithCache, formatAPIResult, generateCacheControlHeader, getPaginationVariables, getSelectedProductOptions, getSeoMeta, getShopAnalytics, getSitemap, getSitemapIndex, graphiqlLoader, hydrogenContext, hydrogenPreset, hydrogenRoutes, storefrontRedirect, useAnalytics, useCustomerPrivacy, useNonce, useOptimisticCart, useOptimisticData, useOptimisticVariant };
6303
+ export { Analytics, AnalyticsEvent, CacheCustom, CacheLong, CacheNone, CacheShort, CartForm, InMemoryCache, NonceProvider, OptimisticInput, Pagination, RichText, Script, Seo, ShopPayButton, VariantSelector, cartAttributesUpdateDefault, cartBuyerIdentityUpdateDefault, cartCreateDefault, cartDiscountCodesUpdateDefault, cartGetDefault, cartGetIdDefault, cartGiftCardCodesRemoveDefault, cartGiftCardCodesUpdateDefault, cartLinesAddDefault, cartLinesRemoveDefault, cartLinesUpdateDefault, cartMetafieldDeleteDefault, cartMetafieldsSetDefault, cartNoteUpdateDefault, cartSelectedDeliveryOptionsUpdateDefault, cartSetIdDefault, changelogHandler, createCartHandler, createContentSecurityPolicy, createCustomerAccountClient, createHydrogenContext, createRequestHandler, createStorefrontClient, createWithCache, formatAPIResult, generateCacheControlHeader, getPaginationVariables, getSelectedProductOptions, getSeoMeta, getShopAnalytics, getSitemap, getSitemapIndex, graphiqlLoader, hydrogenContext, hydrogenPreset, hydrogenRoutes, storefrontRedirect, useAnalytics, useCustomerPrivacy, useNonce, useOptimisticCart, useOptimisticData, useOptimisticVariant };
5990
6304
  //# sourceMappingURL=index.js.map
5991
6305
  //# sourceMappingURL=index.js.map