@shopify/hydrogen 2025.7.0 → 2025.7.1
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.
- package/dist/development/index.cjs +390 -77
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +388 -80
- package/dist/development/index.js.map +1 -1
- package/dist/development/react-router-preset.d.ts +5 -5
- package/dist/development/react-router-preset.js +4 -4
- package/dist/development/react-router-preset.js.map +1 -1
- package/dist/oxygen/index.d.ts +120 -3
- package/dist/oxygen/index.js +111 -5
- package/dist/production/index.cjs +76 -76
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.d.cts +74 -15
- package/dist/production/index.d.ts +74 -15
- package/dist/production/index.js +76 -76
- package/dist/production/index.js.map +1 -1
- package/dist/production/react-router-preset.d.ts +5 -5
- package/dist/production/react-router-preset.js +2 -2
- package/package.json +6 -6
- package/dist/oxygen/chunk-RVXKHOUX.js +0 -39
- package/dist/oxygen/chunk-T4YWBSCF.js +0 -14
- package/dist/oxygen/createRequestHandler.d.ts +0 -10
- package/dist/oxygen/createRequestHandler.js +0 -6
- package/dist/oxygen/getStorefrontHeaders-BqPh5S1b.d.ts +0 -69
- package/dist/oxygen/getStorefrontHeaders.d.ts +0 -1
- package/dist/oxygen/getStorefrontHeaders.js +0 -6
|
@@ -7,6 +7,7 @@ var react = require('react');
|
|
|
7
7
|
var reactRouter = require('react-router');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
9
|
var hydrogenReact = require('@shopify/hydrogen-react');
|
|
10
|
+
var loadScript = require('@shopify/hydrogen-react/load-script');
|
|
10
11
|
var graphqlClient = require('@shopify/graphql-client');
|
|
11
12
|
var cookie = require('worktop/cookie');
|
|
12
13
|
var cspBuilder = require('content-security-policy-builder');
|
|
@@ -281,7 +282,62 @@ var AnalyticsEvent = {
|
|
|
281
282
|
// Custom
|
|
282
283
|
CUSTOM_EVENT: `custom_`
|
|
283
284
|
};
|
|
284
|
-
|
|
285
|
+
|
|
286
|
+
// src/constants.ts
|
|
287
|
+
var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
|
|
288
|
+
var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
|
|
289
|
+
var SDK_VARIANT_HEADER = "X-SDK-Variant";
|
|
290
|
+
var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
|
|
291
|
+
var SDK_VERSION_HEADER = "X-SDK-Version";
|
|
292
|
+
var SHOPIFY_CLIENT_IP_HEADER = "X-Shopify-Client-IP";
|
|
293
|
+
var SHOPIFY_CLIENT_IP_SIG_HEADER = "X-Shopify-Client-IP-Sig";
|
|
294
|
+
var HYDROGEN_SFAPI_PROXY_KEY = "_sfapi_proxy";
|
|
295
|
+
var HYDROGEN_SERVER_TRACKING_KEY = "_server_tracking";
|
|
296
|
+
|
|
297
|
+
// src/utils/server-timing.ts
|
|
298
|
+
function buildServerTimingHeader(values) {
|
|
299
|
+
return Object.entries(values).map(([key, value]) => value ? `${key};desc=${value}` : void 0).filter(Boolean).join(", ");
|
|
300
|
+
}
|
|
301
|
+
function appendServerTimingHeader(response, values) {
|
|
302
|
+
const header = typeof values === "string" ? values : buildServerTimingHeader(values);
|
|
303
|
+
if (header) {
|
|
304
|
+
response.headers.append("Server-Timing", header);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
var trackedTimings = ["_y", "_s", "_cmp"];
|
|
308
|
+
function extractServerTimingHeader(serverTimingHeader) {
|
|
309
|
+
const values = {};
|
|
310
|
+
if (!serverTimingHeader) return values;
|
|
311
|
+
const re = new RegExp(
|
|
312
|
+
`\\b(${trackedTimings.join("|")});desc="?([^",]+)"?`,
|
|
313
|
+
"g"
|
|
314
|
+
);
|
|
315
|
+
let match;
|
|
316
|
+
while ((match = re.exec(serverTimingHeader)) !== null) {
|
|
317
|
+
values[match[1]] = match[2];
|
|
318
|
+
}
|
|
319
|
+
return values;
|
|
320
|
+
}
|
|
321
|
+
function hasServerTimingInNavigationEntry(key) {
|
|
322
|
+
if (typeof window === "undefined") return false;
|
|
323
|
+
try {
|
|
324
|
+
const navigationEntry = window.performance.getEntriesByType(
|
|
325
|
+
"navigation"
|
|
326
|
+
)[0];
|
|
327
|
+
return !!navigationEntry?.serverTiming?.some((entry) => entry.name === key);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function isSfapiProxyEnabled() {
|
|
333
|
+
return hasServerTimingInNavigationEntry(HYDROGEN_SFAPI_PROXY_KEY);
|
|
334
|
+
}
|
|
335
|
+
function hasServerReturnedTrackingValues() {
|
|
336
|
+
return hasServerTimingInNavigationEntry(HYDROGEN_SERVER_TRACKING_KEY);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/customer-privacy/ShopifyCustomerPrivacy.tsx
|
|
340
|
+
var CONSENT_API = "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.js";
|
|
285
341
|
var CONSENT_API_WITH_BANNER = "https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js";
|
|
286
342
|
function logMissingConfig(fieldName) {
|
|
287
343
|
console.error(
|
|
@@ -293,19 +349,34 @@ function useCustomerPrivacy(props) {
|
|
|
293
349
|
withPrivacyBanner = false,
|
|
294
350
|
onVisitorConsentCollected,
|
|
295
351
|
onReady,
|
|
296
|
-
|
|
352
|
+
checkoutDomain,
|
|
353
|
+
storefrontAccessToken,
|
|
354
|
+
country,
|
|
355
|
+
locale,
|
|
356
|
+
sameDomainForStorefrontApi
|
|
297
357
|
} = props;
|
|
298
|
-
|
|
358
|
+
const hasSfapiProxy = react.useMemo(
|
|
359
|
+
() => sameDomainForStorefrontApi ?? isSfapiProxyEnabled(),
|
|
360
|
+
[sameDomainForStorefrontApi]
|
|
361
|
+
);
|
|
362
|
+
const fetchTrackingValuesFromBrowser = react.useMemo(
|
|
363
|
+
() => hasSfapiProxy && !hasServerReturnedTrackingValues(),
|
|
364
|
+
[hasSfapiProxy]
|
|
365
|
+
);
|
|
366
|
+
const cookiesReady = hydrogenReact.useShopifyCookies({
|
|
367
|
+
fetchTrackingValues: fetchTrackingValuesFromBrowser,
|
|
368
|
+
storefrontAccessToken,
|
|
369
|
+
ignoreDeprecatedCookies: true
|
|
370
|
+
});
|
|
371
|
+
const initialTrackingValues = react.useMemo(hydrogenReact.getTrackingValues, [cookiesReady]);
|
|
372
|
+
const { revalidate } = reactRouter.useRevalidator();
|
|
373
|
+
loadScript.useLoadScript(withPrivacyBanner ? CONSENT_API_WITH_BANNER : CONSENT_API, {
|
|
299
374
|
attributes: {
|
|
300
375
|
id: "customer-privacy-api"
|
|
301
376
|
}
|
|
302
377
|
});
|
|
303
|
-
const { observing, setLoaded } = useApisLoaded({
|
|
304
|
-
withPrivacyBanner,
|
|
305
|
-
onLoaded: onReady
|
|
306
|
-
});
|
|
378
|
+
const { observing, setLoaded, apisLoaded } = useApisLoaded({ withPrivacyBanner });
|
|
307
379
|
const config = react.useMemo(() => {
|
|
308
|
-
const { checkoutDomain, storefrontAccessToken } = consentConfig;
|
|
309
380
|
if (!checkoutDomain) logMissingConfig("checkoutDomain");
|
|
310
381
|
if (!storefrontAccessToken) logMissingConfig("storefrontAccessToken");
|
|
311
382
|
if (storefrontAccessToken.startsWith("shpat_") || storefrontAccessToken.length !== 32) {
|
|
@@ -313,18 +384,54 @@ function useCustomerPrivacy(props) {
|
|
|
313
384
|
`[h2:error:useCustomerPrivacy] It looks like you passed a private access token, make sure to use the public token`
|
|
314
385
|
);
|
|
315
386
|
}
|
|
387
|
+
const commonAncestorDomain = parseStoreDomain(checkoutDomain);
|
|
388
|
+
const sfapiDomain = (
|
|
389
|
+
// Check if standard route proxy is enabled in Hydrogen server
|
|
390
|
+
// to use it instead of doing a cross-origin request to checkout.
|
|
391
|
+
hasSfapiProxy && typeof window !== "undefined" ? window.location.host : checkoutDomain
|
|
392
|
+
);
|
|
316
393
|
const config2 = {
|
|
317
|
-
|
|
394
|
+
// This domain is used to send requests to SFAPI for setting and getting consent.
|
|
395
|
+
checkoutRootDomain: sfapiDomain,
|
|
396
|
+
// Prefix with a dot to ensure this domain is different from checkoutRootDomain.
|
|
397
|
+
// This will ensure old cookies are set for a cross-subdomain checkout setup
|
|
398
|
+
// so that we keep backward compatibility until new cookies are rolled out.
|
|
399
|
+
// Once consent-tracking-api is updated to not rely on cookies anymore, we can remove this.
|
|
400
|
+
storefrontRootDomain: commonAncestorDomain ? "." + commonAncestorDomain : void 0,
|
|
318
401
|
storefrontAccessToken,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
locale: consentConfig.locale
|
|
402
|
+
country,
|
|
403
|
+
locale
|
|
322
404
|
};
|
|
323
405
|
return config2;
|
|
324
|
-
}, [
|
|
406
|
+
}, [
|
|
407
|
+
logMissingConfig,
|
|
408
|
+
checkoutDomain,
|
|
409
|
+
storefrontAccessToken,
|
|
410
|
+
country,
|
|
411
|
+
locale
|
|
412
|
+
]);
|
|
325
413
|
react.useEffect(() => {
|
|
326
414
|
const consentCollectedHandler = (event) => {
|
|
415
|
+
const latestTrackingValues = hydrogenReact.getTrackingValues();
|
|
416
|
+
if (initialTrackingValues.visitToken !== latestTrackingValues.visitToken || initialTrackingValues.uniqueToken !== latestTrackingValues.uniqueToken) {
|
|
417
|
+
revalidate().catch(() => {
|
|
418
|
+
console.warn(
|
|
419
|
+
"[h2:warn:useCustomerPrivacy] Revalidation failed after consent change."
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
327
423
|
if (onVisitorConsentCollected) {
|
|
424
|
+
const customerPrivacy = getCustomerPrivacy();
|
|
425
|
+
if (customerPrivacy?.shouldShowBanner()) {
|
|
426
|
+
const consentValues = customerPrivacy.currentVisitorConsent();
|
|
427
|
+
if (consentValues) {
|
|
428
|
+
const NO_VALUE = "";
|
|
429
|
+
const noInteraction = consentValues.marketing === NO_VALUE && consentValues.analytics === NO_VALUE && consentValues.preferences === NO_VALUE;
|
|
430
|
+
if (noInteraction) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
328
435
|
onVisitorConsentCollected(event.detail);
|
|
329
436
|
}
|
|
330
437
|
};
|
|
@@ -350,14 +457,11 @@ function useCustomerPrivacy(props) {
|
|
|
350
457
|
},
|
|
351
458
|
set(value) {
|
|
352
459
|
if (typeof value === "object" && value !== null && "showPreferences" in value && "loadBanner" in value) {
|
|
353
|
-
const privacyBanner = value;
|
|
354
|
-
privacyBanner.loadBanner(config);
|
|
355
460
|
customPrivacyBanner = overridePrivacyBannerMethods({
|
|
356
|
-
privacyBanner,
|
|
461
|
+
privacyBanner: value,
|
|
357
462
|
config
|
|
358
463
|
});
|
|
359
464
|
setLoaded.privacyBanner();
|
|
360
|
-
emitCustomerPrivacyApiLoaded();
|
|
361
465
|
}
|
|
362
466
|
}
|
|
363
467
|
};
|
|
@@ -391,6 +495,8 @@ function useCustomerPrivacy(props) {
|
|
|
391
495
|
const customerPrivacy = value2;
|
|
392
496
|
customCustomerPrivacy = {
|
|
393
497
|
...customerPrivacy,
|
|
498
|
+
// Note: this method is not used by the privacy-banner,
|
|
499
|
+
// it bundles its own setTrackingConsent.
|
|
394
500
|
setTrackingConsent: overrideCustomerPrivacySetTrackingConsent(
|
|
395
501
|
{ customerPrivacy, config }
|
|
396
502
|
)
|
|
@@ -400,7 +506,6 @@ function useCustomerPrivacy(props) {
|
|
|
400
506
|
customerPrivacy: customCustomerPrivacy
|
|
401
507
|
};
|
|
402
508
|
setLoaded.customerPrivacy();
|
|
403
|
-
emitCustomerPrivacyApiLoaded();
|
|
404
509
|
}
|
|
405
510
|
}
|
|
406
511
|
});
|
|
@@ -412,6 +517,24 @@ function useCustomerPrivacy(props) {
|
|
|
412
517
|
overrideCustomerPrivacySetTrackingConsent,
|
|
413
518
|
setLoaded.customerPrivacy
|
|
414
519
|
]);
|
|
520
|
+
react.useEffect(() => {
|
|
521
|
+
if (!apisLoaded || !cookiesReady) return;
|
|
522
|
+
const customerPrivacy = getCustomerPrivacy();
|
|
523
|
+
if (customerPrivacy && !customerPrivacy.cachedConsent) {
|
|
524
|
+
const trackingValues = hydrogenReact.getTrackingValues();
|
|
525
|
+
if (trackingValues.consent) {
|
|
526
|
+
customerPrivacy.cachedConsent = trackingValues.consent;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (withPrivacyBanner) {
|
|
530
|
+
const privacyBanner = getPrivacyBanner();
|
|
531
|
+
if (privacyBanner) {
|
|
532
|
+
privacyBanner.loadBanner(config);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
emitCustomerPrivacyApiLoaded();
|
|
536
|
+
onReady?.();
|
|
537
|
+
}, [apisLoaded, cookiesReady]);
|
|
415
538
|
const result = {
|
|
416
539
|
customerPrivacy: getCustomerPrivacy()
|
|
417
540
|
};
|
|
@@ -427,15 +550,12 @@ function emitCustomerPrivacyApiLoaded() {
|
|
|
427
550
|
const event = new CustomEvent("shopifyCustomerPrivacyApiLoaded");
|
|
428
551
|
document.dispatchEvent(event);
|
|
429
552
|
}
|
|
430
|
-
function useApisLoaded({
|
|
431
|
-
withPrivacyBanner,
|
|
432
|
-
onLoaded
|
|
433
|
-
}) {
|
|
553
|
+
function useApisLoaded({ withPrivacyBanner }) {
|
|
434
554
|
const observing = react.useRef({ customerPrivacy: false, privacyBanner: false });
|
|
435
|
-
const [
|
|
555
|
+
const [apisLoadedArray, setApisLoaded] = react.useState(
|
|
436
556
|
withPrivacyBanner ? [false, false] : [false]
|
|
437
557
|
);
|
|
438
|
-
const
|
|
558
|
+
const apisLoaded = apisLoadedArray.every(Boolean);
|
|
439
559
|
const setLoaded = {
|
|
440
560
|
customerPrivacy: () => {
|
|
441
561
|
if (withPrivacyBanner) {
|
|
@@ -451,16 +571,11 @@ function useApisLoaded({
|
|
|
451
571
|
setApisLoaded((prev) => [prev[0], true]);
|
|
452
572
|
}
|
|
453
573
|
};
|
|
454
|
-
|
|
455
|
-
if (loaded && onLoaded) {
|
|
456
|
-
onLoaded();
|
|
457
|
-
}
|
|
458
|
-
}, [loaded, onLoaded]);
|
|
459
|
-
return { observing, setLoaded };
|
|
574
|
+
return { observing, setLoaded, apisLoaded };
|
|
460
575
|
}
|
|
461
576
|
function parseStoreDomain(checkoutDomain) {
|
|
462
577
|
if (typeof window === "undefined") return;
|
|
463
|
-
const host = window.
|
|
578
|
+
const host = window.location.host;
|
|
464
579
|
const checkoutDomainParts = checkoutDomain.split(".").reverse();
|
|
465
580
|
const currentDomainParts = host.split(".").reverse();
|
|
466
581
|
const sameDomainParts = [];
|
|
@@ -469,7 +584,7 @@ function parseStoreDomain(checkoutDomain) {
|
|
|
469
584
|
sameDomainParts.push(part);
|
|
470
585
|
}
|
|
471
586
|
});
|
|
472
|
-
return sameDomainParts.reverse().join(".");
|
|
587
|
+
return sameDomainParts.reverse().join(".") || void 0;
|
|
473
588
|
}
|
|
474
589
|
function overrideCustomerPrivacySetTrackingConsent({
|
|
475
590
|
customerPrivacy,
|
|
@@ -527,7 +642,7 @@ function getPrivacyBanner() {
|
|
|
527
642
|
}
|
|
528
643
|
|
|
529
644
|
// package.json
|
|
530
|
-
var version = "2025.7.
|
|
645
|
+
var version = "2025.7.1";
|
|
531
646
|
|
|
532
647
|
// src/analytics-manager/ShopifyAnalytics.tsx
|
|
533
648
|
function getCustomerPrivacyRequired() {
|
|
@@ -547,6 +662,7 @@ function ShopifyAnalytics({
|
|
|
547
662
|
const { subscribe: subscribe2, register: register2, canTrack } = useAnalytics();
|
|
548
663
|
const [shopifyReady, setShopifyReady] = react.useState(false);
|
|
549
664
|
const [privacyReady, setPrivacyReady] = react.useState(false);
|
|
665
|
+
const [collectedConsent, setCollectedConsent] = react.useState("");
|
|
550
666
|
const init = react.useRef(false);
|
|
551
667
|
const { checkoutDomain, storefrontAccessToken, language } = consent;
|
|
552
668
|
const { ready: shopifyAnalyticsReady } = register2("Internal_Shopify_Analytics");
|
|
@@ -555,14 +671,31 @@ function ShopifyAnalytics({
|
|
|
555
671
|
locale: language,
|
|
556
672
|
checkoutDomain: !checkoutDomain ? "mock.shop" : checkoutDomain,
|
|
557
673
|
storefrontAccessToken: !storefrontAccessToken ? "abcdefghijklmnopqrstuvwxyz123456" : storefrontAccessToken,
|
|
558
|
-
|
|
559
|
-
|
|
674
|
+
// If we use privacy banner, we should wait until consent is collected.
|
|
675
|
+
// Otherwise, we can consider privacy ready immediately:
|
|
676
|
+
onReady: () => !consent.withPrivacyBanner && setPrivacyReady(true),
|
|
677
|
+
onVisitorConsentCollected: (consent2) => {
|
|
678
|
+
try {
|
|
679
|
+
setCollectedConsent(JSON.stringify(consent2));
|
|
680
|
+
} catch (e) {
|
|
681
|
+
}
|
|
682
|
+
setPrivacyReady(true);
|
|
683
|
+
}
|
|
560
684
|
});
|
|
685
|
+
const hasUserConsent = react.useMemo(
|
|
686
|
+
// must be initialized with true to avoid removing cookies too early
|
|
687
|
+
() => privacyReady ? canTrack() : true,
|
|
688
|
+
// Make this value depend on collectedConsent to re-run `canTrack()` when consent changes
|
|
689
|
+
[privacyReady, canTrack, collectedConsent]
|
|
690
|
+
);
|
|
561
691
|
hydrogenReact.useShopifyCookies({
|
|
562
|
-
hasUserConsent
|
|
563
|
-
// must be initialized with true
|
|
692
|
+
hasUserConsent,
|
|
564
693
|
domain,
|
|
565
|
-
checkoutDomain
|
|
694
|
+
checkoutDomain,
|
|
695
|
+
// Already done inside useCustomerPrivacy
|
|
696
|
+
fetchTrackingValues: false,
|
|
697
|
+
// Avoid creating local cookies too early
|
|
698
|
+
ignoreDeprecatedCookies: !privacyReady
|
|
566
699
|
});
|
|
567
700
|
react.useEffect(() => {
|
|
568
701
|
if (init.current) return;
|
|
@@ -612,11 +745,11 @@ function prepareBasePageViewPayload(payload) {
|
|
|
612
745
|
...payload.shop,
|
|
613
746
|
hasUserConsent,
|
|
614
747
|
...hydrogenReact.getClientBrowserParameters(),
|
|
615
|
-
ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
|
|
616
|
-
gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed()),
|
|
617
748
|
analyticsAllowed: customerPrivacy.analyticsProcessingAllowed(),
|
|
618
749
|
marketingAllowed: customerPrivacy.marketingAllowed(),
|
|
619
|
-
saleOfDataAllowed: customerPrivacy.saleOfDataAllowed()
|
|
750
|
+
saleOfDataAllowed: customerPrivacy.saleOfDataAllowed(),
|
|
751
|
+
ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
|
|
752
|
+
gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed())
|
|
620
753
|
};
|
|
621
754
|
return eventPayload;
|
|
622
755
|
}
|
|
@@ -1049,11 +1182,11 @@ function AnalyticsProvider({
|
|
|
1049
1182
|
shop: shopProp = null,
|
|
1050
1183
|
cookieDomain
|
|
1051
1184
|
}) {
|
|
1052
|
-
const listenerSet = react.useRef(false);
|
|
1053
1185
|
const { shop } = useShopAnalytics(shopProp);
|
|
1054
1186
|
const [analyticsLoaded, setAnalyticsLoaded] = react.useState(
|
|
1055
1187
|
customCanTrack ? true : false
|
|
1056
1188
|
);
|
|
1189
|
+
const [consentCollected, setConsentCollected] = react.useState(false);
|
|
1057
1190
|
const [carts, setCarts] = react.useState({ cart: null, prevCart: null });
|
|
1058
1191
|
const [canTrack, setCanTrack] = react.useState(
|
|
1059
1192
|
customCanTrack ? () => customCanTrack : () => shopifyCanTrack
|
|
@@ -1121,21 +1254,21 @@ function AnalyticsProvider({
|
|
|
1121
1254
|
children,
|
|
1122
1255
|
!!shop && /* @__PURE__ */ jsxRuntime.jsx(AnalyticsPageView, {}),
|
|
1123
1256
|
!!shop && !!currentCart && /* @__PURE__ */ jsxRuntime.jsx(CartAnalytics, { cart: currentCart, setCarts }),
|
|
1124
|
-
!!shop &&
|
|
1257
|
+
!!shop && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1125
1258
|
ShopifyAnalytics,
|
|
1126
1259
|
{
|
|
1127
1260
|
consent,
|
|
1128
1261
|
onReady: () => {
|
|
1129
|
-
listenerSet.current = true;
|
|
1130
1262
|
setAnalyticsLoaded(true);
|
|
1131
1263
|
setCanTrack(
|
|
1132
1264
|
customCanTrack ? () => customCanTrack : () => shopifyCanTrack
|
|
1133
1265
|
);
|
|
1266
|
+
setConsentCollected(true);
|
|
1134
1267
|
},
|
|
1135
1268
|
domain: cookieDomain
|
|
1136
1269
|
}
|
|
1137
1270
|
),
|
|
1138
|
-
!!shop && /* @__PURE__ */ jsxRuntime.jsx(PerfKit, { shop })
|
|
1271
|
+
!!shop && consentCollected && /* @__PURE__ */ jsxRuntime.jsx(PerfKit, { shop })
|
|
1139
1272
|
] });
|
|
1140
1273
|
}
|
|
1141
1274
|
function useAnalytics() {
|
|
@@ -1214,6 +1347,31 @@ function getDebugHeaders(request) {
|
|
|
1214
1347
|
purpose: request ? getHeader(request, "purpose") : void 0
|
|
1215
1348
|
};
|
|
1216
1349
|
}
|
|
1350
|
+
function getStorefrontHeaders(request) {
|
|
1351
|
+
return {
|
|
1352
|
+
requestGroupId: getHeader(request, "request-id"),
|
|
1353
|
+
buyerIp: getHeader(request, "oxygen-buyer-ip"),
|
|
1354
|
+
buyerIpSig: getHeader(request, SHOPIFY_CLIENT_IP_SIG_HEADER),
|
|
1355
|
+
cookie: getHeader(request, "cookie"),
|
|
1356
|
+
// sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules
|
|
1357
|
+
purpose: getHeader(request, "sec-purpose") || getHeader(request, "purpose")
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
|
|
1361
|
+
var getSafePathname = (url) => {
|
|
1362
|
+
try {
|
|
1363
|
+
return new URL(url, "http://e.c").pathname;
|
|
1364
|
+
} catch {
|
|
1365
|
+
return "/";
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
function extractHeaders(extract, keys) {
|
|
1369
|
+
return keys.reduce((acc, key) => {
|
|
1370
|
+
const forwardedValue = extract(key);
|
|
1371
|
+
if (forwardedValue) acc.push([key, forwardedValue]);
|
|
1372
|
+
return acc;
|
|
1373
|
+
}, []);
|
|
1374
|
+
}
|
|
1217
1375
|
|
|
1218
1376
|
// src/utils/callsites.ts
|
|
1219
1377
|
function withSyncStack(promise, options = {}) {
|
|
@@ -1581,13 +1739,16 @@ async function runWithCache(cacheKey, actionFn, {
|
|
|
1581
1739
|
}
|
|
1582
1740
|
return result;
|
|
1583
1741
|
}
|
|
1742
|
+
var excludedHeaders = ["set-cookie", "server-timing"];
|
|
1584
1743
|
function toSerializableResponse(body, response) {
|
|
1585
1744
|
return [
|
|
1586
1745
|
body,
|
|
1587
1746
|
{
|
|
1588
1747
|
status: response.status,
|
|
1589
1748
|
statusText: response.statusText,
|
|
1590
|
-
headers:
|
|
1749
|
+
headers: [...response.headers].filter(
|
|
1750
|
+
([key]) => !excludedHeaders.includes(key.toLowerCase())
|
|
1751
|
+
)
|
|
1591
1752
|
}
|
|
1592
1753
|
];
|
|
1593
1754
|
}
|
|
@@ -1601,7 +1762,8 @@ async function fetchWithServerCache(url, requestInit, {
|
|
|
1601
1762
|
shouldCacheResponse,
|
|
1602
1763
|
waitUntil,
|
|
1603
1764
|
debugInfo,
|
|
1604
|
-
streamConfig
|
|
1765
|
+
streamConfig,
|
|
1766
|
+
onRawHeaders
|
|
1605
1767
|
}) {
|
|
1606
1768
|
if (!cacheOptions && (!requestInit.method || requestInit.method === "GET")) {
|
|
1607
1769
|
cacheOptions = CacheShort();
|
|
@@ -1615,6 +1777,7 @@ async function fetchWithServerCache(url, requestInit, {
|
|
|
1615
1777
|
url,
|
|
1616
1778
|
customFetchApi: async (url2, options) => {
|
|
1617
1779
|
rawResponse = await fetch(url2, options);
|
|
1780
|
+
onRawHeaders?.(rawResponse.headers);
|
|
1618
1781
|
return rawResponse;
|
|
1619
1782
|
},
|
|
1620
1783
|
headers: requestInit.headers
|
|
@@ -1638,6 +1801,7 @@ async function fetchWithServerCache(url, requestInit, {
|
|
|
1638
1801
|
);
|
|
1639
1802
|
}
|
|
1640
1803
|
const response = await fetch(url, requestInit);
|
|
1804
|
+
onRawHeaders?.(response.headers);
|
|
1641
1805
|
if (!response.ok) {
|
|
1642
1806
|
return response;
|
|
1643
1807
|
}
|
|
@@ -1861,13 +2025,6 @@ var cartSetIdDefault = (cookieOptions) => {
|
|
|
1861
2025
|
};
|
|
1862
2026
|
};
|
|
1863
2027
|
|
|
1864
|
-
// src/constants.ts
|
|
1865
|
-
var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
|
|
1866
|
-
var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
|
|
1867
|
-
var SDK_VARIANT_HEADER = "X-SDK-Variant";
|
|
1868
|
-
var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
|
|
1869
|
-
var SDK_VERSION_HEADER = "X-SDK-Version";
|
|
1870
|
-
|
|
1871
2028
|
// src/utils/uuid.ts
|
|
1872
2029
|
function generateUUID() {
|
|
1873
2030
|
if (typeof crypto !== "undefined" && !!crypto.randomUUID) {
|
|
@@ -1878,7 +2035,7 @@ function generateUUID() {
|
|
|
1878
2035
|
}
|
|
1879
2036
|
|
|
1880
2037
|
// src/version.ts
|
|
1881
|
-
var LIB_VERSION = "2025.7.
|
|
2038
|
+
var LIB_VERSION = "2025.7.1";
|
|
1882
2039
|
|
|
1883
2040
|
// src/utils/graphql.ts
|
|
1884
2041
|
function minifyQuery(string) {
|
|
@@ -2042,16 +2199,34 @@ function createStorefrontClient(options) {
|
|
|
2042
2199
|
contentType: "json",
|
|
2043
2200
|
buyerIp: storefrontHeaders?.buyerIp || ""
|
|
2044
2201
|
});
|
|
2202
|
+
if (storefrontHeaders?.buyerIp) {
|
|
2203
|
+
defaultHeaders[SHOPIFY_CLIENT_IP_HEADER] = storefrontHeaders.buyerIp;
|
|
2204
|
+
}
|
|
2205
|
+
if (storefrontHeaders?.buyerIpSig) {
|
|
2206
|
+
defaultHeaders[SHOPIFY_CLIENT_IP_SIG_HEADER] = storefrontHeaders.buyerIpSig;
|
|
2207
|
+
}
|
|
2045
2208
|
defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
|
|
2046
2209
|
if (storefrontId) defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
|
|
2047
2210
|
defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2211
|
+
const requestCookie = storefrontHeaders?.cookie ?? "";
|
|
2212
|
+
if (requestCookie) defaultHeaders["cookie"] = requestCookie;
|
|
2213
|
+
let uniqueToken;
|
|
2214
|
+
let visitToken;
|
|
2215
|
+
if (!/\b_shopify_(analytics|marketing)=/.test(requestCookie)) {
|
|
2216
|
+
const legacyUniqueToken = requestCookie.match(/\b_shopify_y=([^;]+)/)?.[1];
|
|
2217
|
+
const legacyVisitToken = requestCookie.match(/\b_shopify_s=([^;]+)/)?.[1];
|
|
2218
|
+
if (legacyUniqueToken) {
|
|
2219
|
+
defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_Y_HEADER] = legacyUniqueToken;
|
|
2220
|
+
}
|
|
2221
|
+
if (legacyVisitToken) {
|
|
2222
|
+
defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_S_HEADER] = legacyVisitToken;
|
|
2223
|
+
}
|
|
2224
|
+
uniqueToken = legacyUniqueToken ?? generateUUID();
|
|
2225
|
+
visitToken = legacyVisitToken ?? generateUUID();
|
|
2226
|
+
defaultHeaders[hydrogenReact.SHOPIFY_UNIQUE_TOKEN_HEADER] = uniqueToken;
|
|
2227
|
+
defaultHeaders[hydrogenReact.SHOPIFY_VISIT_TOKEN_HEADER] = visitToken;
|
|
2228
|
+
}
|
|
2229
|
+
let collectedSubrequestHeaders;
|
|
2055
2230
|
const cacheKeyHeader = JSON.stringify({
|
|
2056
2231
|
"content-type": defaultHeaders["content-type"],
|
|
2057
2232
|
"user-agent": defaultHeaders["user-agent"],
|
|
@@ -2118,7 +2293,13 @@ function createStorefrontClient(options) {
|
|
|
2118
2293
|
graphql: graphqlData,
|
|
2119
2294
|
purpose: storefrontHeaders?.purpose
|
|
2120
2295
|
},
|
|
2121
|
-
streamConfig
|
|
2296
|
+
streamConfig,
|
|
2297
|
+
onRawHeaders: (headers2) => {
|
|
2298
|
+
collectedSubrequestHeaders ??= {
|
|
2299
|
+
setCookie: headers2.getSetCookie(),
|
|
2300
|
+
serverTiming: headers2.get("server-timing") ?? ""
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2122
2303
|
});
|
|
2123
2304
|
const errorOptions = {
|
|
2124
2305
|
url,
|
|
@@ -2217,9 +2398,90 @@ function createStorefrontClient(options) {
|
|
|
2217
2398
|
generateCacheControlHeader,
|
|
2218
2399
|
getPublicTokenHeaders,
|
|
2219
2400
|
getPrivateTokenHeaders,
|
|
2401
|
+
getHeaders: () => ({ ...defaultHeaders }),
|
|
2220
2402
|
getShopifyDomain,
|
|
2221
2403
|
getApiUrl: getStorefrontApiUrl,
|
|
2222
|
-
i18n: i18n ?? defaultI18n
|
|
2404
|
+
i18n: i18n ?? defaultI18n,
|
|
2405
|
+
/**
|
|
2406
|
+
* Checks if the request is targeting the Storefront API endpoint.
|
|
2407
|
+
*/
|
|
2408
|
+
isStorefrontApiUrl(request) {
|
|
2409
|
+
return SFAPI_RE.test(getSafePathname(request.url ?? ""));
|
|
2410
|
+
},
|
|
2411
|
+
/**
|
|
2412
|
+
* Forwards the request to the Storefront API.
|
|
2413
|
+
*/
|
|
2414
|
+
async forward(request, options2) {
|
|
2415
|
+
const forwardedHeaders = new Headers([
|
|
2416
|
+
// Forward only a selected set of headers to the Storefront API
|
|
2417
|
+
// to avoid getting 403 errors due to unexpected headers.
|
|
2418
|
+
...extractHeaders(
|
|
2419
|
+
(key) => request.headers.get(key),
|
|
2420
|
+
[
|
|
2421
|
+
"accept",
|
|
2422
|
+
"accept-encoding",
|
|
2423
|
+
"accept-language",
|
|
2424
|
+
// Access-Control headers are used for CORS preflight requests.
|
|
2425
|
+
"access-control-request-headers",
|
|
2426
|
+
"access-control-request-method",
|
|
2427
|
+
"content-type",
|
|
2428
|
+
"content-length",
|
|
2429
|
+
"cookie",
|
|
2430
|
+
"origin",
|
|
2431
|
+
"referer",
|
|
2432
|
+
"user-agent",
|
|
2433
|
+
STOREFRONT_ACCESS_TOKEN_HEADER,
|
|
2434
|
+
hydrogenReact.SHOPIFY_UNIQUE_TOKEN_HEADER,
|
|
2435
|
+
hydrogenReact.SHOPIFY_VISIT_TOKEN_HEADER
|
|
2436
|
+
]
|
|
2437
|
+
),
|
|
2438
|
+
// Add some headers to help with geolocalization and debugging
|
|
2439
|
+
...extractHeaders(
|
|
2440
|
+
(key) => defaultHeaders[key],
|
|
2441
|
+
[
|
|
2442
|
+
SHOPIFY_CLIENT_IP_HEADER,
|
|
2443
|
+
SHOPIFY_CLIENT_IP_SIG_HEADER,
|
|
2444
|
+
hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER,
|
|
2445
|
+
STOREFRONT_REQUEST_GROUP_ID_HEADER
|
|
2446
|
+
]
|
|
2447
|
+
)
|
|
2448
|
+
]);
|
|
2449
|
+
if (storefrontHeaders?.buyerIp) {
|
|
2450
|
+
forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
|
|
2451
|
+
}
|
|
2452
|
+
const storefrontApiVersion = options2?.storefrontApiVersion ?? getSafePathname(request.url).match(SFAPI_RE)?.[1];
|
|
2453
|
+
const sfapiResponse = await fetch(
|
|
2454
|
+
getStorefrontApiUrl({ storefrontApiVersion }),
|
|
2455
|
+
{
|
|
2456
|
+
method: request.method,
|
|
2457
|
+
body: request.body,
|
|
2458
|
+
headers: forwardedHeaders
|
|
2459
|
+
}
|
|
2460
|
+
);
|
|
2461
|
+
return new Response(sfapiResponse.body, sfapiResponse);
|
|
2462
|
+
},
|
|
2463
|
+
setCollectedSubrequestHeaders: (response) => {
|
|
2464
|
+
if (collectedSubrequestHeaders) {
|
|
2465
|
+
for (const value of collectedSubrequestHeaders.setCookie) {
|
|
2466
|
+
response.headers.append("Set-Cookie", value);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
const serverTiming = extractServerTimingHeader(
|
|
2470
|
+
collectedSubrequestHeaders?.serverTiming
|
|
2471
|
+
);
|
|
2472
|
+
const isDocumentResponse = response.headers.get("content-type")?.startsWith("text/html");
|
|
2473
|
+
const fallbackValues = isDocumentResponse ? { _y: uniqueToken, _s: visitToken } : void 0;
|
|
2474
|
+
appendServerTimingHeader(response, {
|
|
2475
|
+
...fallbackValues,
|
|
2476
|
+
...serverTiming
|
|
2477
|
+
});
|
|
2478
|
+
if (isDocumentResponse && collectedSubrequestHeaders && // _shopify_essential cookie is always set, but we need more than that
|
|
2479
|
+
collectedSubrequestHeaders.setCookie.length > 1 && serverTiming?._y && serverTiming?._s && serverTiming?._cmp) {
|
|
2480
|
+
appendServerTimingHeader(response, {
|
|
2481
|
+
[HYDROGEN_SERVER_TRACKING_KEY]: "1"
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2223
2485
|
}
|
|
2224
2486
|
};
|
|
2225
2487
|
}
|
|
@@ -4145,12 +4407,58 @@ function createHydrogenContext(options, additionalContext) {
|
|
|
4145
4407
|
});
|
|
4146
4408
|
return hybridProvider;
|
|
4147
4409
|
}
|
|
4148
|
-
function
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4410
|
+
function createRequestHandler({
|
|
4411
|
+
build,
|
|
4412
|
+
mode,
|
|
4413
|
+
poweredByHeader = true,
|
|
4414
|
+
getLoadContext,
|
|
4415
|
+
collectTrackingInformation = true,
|
|
4416
|
+
proxyStandardRoutes = true
|
|
4417
|
+
}) {
|
|
4418
|
+
const handleRequest = reactRouter.createRequestHandler(build, mode);
|
|
4419
|
+
const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
|
|
4420
|
+
return async (request) => {
|
|
4421
|
+
const method = request.method;
|
|
4422
|
+
if ((method === "GET" || method === "HEAD") && request.body) {
|
|
4423
|
+
return new Response(`${method} requests cannot have a body`, {
|
|
4424
|
+
status: 400
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
const url = new URL(request.url);
|
|
4428
|
+
if (url.pathname.includes("//")) {
|
|
4429
|
+
return new Response(null, {
|
|
4430
|
+
status: 301,
|
|
4431
|
+
headers: {
|
|
4432
|
+
location: url.pathname.replace(/\/+/g, "/")
|
|
4433
|
+
}
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
4436
|
+
const context = await getLoadContext?.(request);
|
|
4437
|
+
const storefront = context?.storefront || context?.get?.(storefrontContext);
|
|
4438
|
+
if (proxyStandardRoutes) {
|
|
4439
|
+
if (!storefront) {
|
|
4440
|
+
warnOnce(
|
|
4441
|
+
"[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
if (storefront?.isStorefrontApiUrl(request)) {
|
|
4445
|
+
const response2 = await storefront.forward(request);
|
|
4446
|
+
appendPoweredByHeader?.(response2);
|
|
4447
|
+
return response2;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
const response = await handleRequest(request, context);
|
|
4451
|
+
if (storefront && proxyStandardRoutes) {
|
|
4452
|
+
if (collectTrackingInformation) {
|
|
4453
|
+
storefront.setCollectedSubrequestHeaders(response);
|
|
4454
|
+
}
|
|
4455
|
+
const fetchDest = request.headers.get("sec-fetch-dest");
|
|
4456
|
+
if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
|
|
4457
|
+
appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
appendPoweredByHeader?.(response);
|
|
4461
|
+
return response;
|
|
4154
4462
|
};
|
|
4155
4463
|
}
|
|
4156
4464
|
var NonceContext = react.createContext(void 0);
|
|
@@ -4764,10 +5072,10 @@ function hydrogenPreset() {
|
|
|
4764
5072
|
ssr: true,
|
|
4765
5073
|
future: {
|
|
4766
5074
|
v8_middleware: true,
|
|
5075
|
+
v8_splitRouteModules: true,
|
|
5076
|
+
v8_viteEnvironmentApi: false,
|
|
4767
5077
|
unstable_optimizeDeps: true,
|
|
4768
|
-
|
|
4769
|
-
unstable_subResourceIntegrity: false,
|
|
4770
|
-
unstable_viteEnvironmentApi: false
|
|
5078
|
+
unstable_subResourceIntegrity: false
|
|
4771
5079
|
}
|
|
4772
5080
|
}),
|
|
4773
5081
|
reactRouterConfigResolved: ({ reactRouterConfig }) => {
|
|
@@ -4783,7 +5091,7 @@ function hydrogenPreset() {
|
|
|
4783
5091
|
}
|
|
4784
5092
|
if (reactRouterConfig.serverBundles) {
|
|
4785
5093
|
throw new Error(
|
|
4786
|
-
"[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
|
|
5094
|
+
"[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."
|
|
4787
5095
|
);
|
|
4788
5096
|
}
|
|
4789
5097
|
if (reactRouterConfig.buildEnd) {
|
|
@@ -5298,10 +5606,10 @@ var schema = {
|
|
|
5298
5606
|
if (typeof value !== "string") {
|
|
5299
5607
|
throw new Error(ERROR_PREFIX.concat("`title` should be a string"));
|
|
5300
5608
|
}
|
|
5301
|
-
if (typeof value === "string" && value.length >
|
|
5609
|
+
if (typeof value === "string" && value.length > 70) {
|
|
5302
5610
|
throw new Error(
|
|
5303
5611
|
ERROR_PREFIX.concat(
|
|
5304
|
-
"`title` should not be longer than
|
|
5612
|
+
"`title` should not be longer than 70 characters"
|
|
5305
5613
|
)
|
|
5306
5614
|
);
|
|
5307
5615
|
}
|
|
@@ -5318,7 +5626,7 @@ var schema = {
|
|
|
5318
5626
|
if (typeof value === "string" && value.length > 155) {
|
|
5319
5627
|
throw new Error(
|
|
5320
5628
|
ERROR_PREFIX.concat(
|
|
5321
|
-
"`description` should not be longer than
|
|
5629
|
+
"`description` should not be longer than 160 characters"
|
|
5322
5630
|
)
|
|
5323
5631
|
);
|
|
5324
5632
|
}
|
|
@@ -6262,6 +6570,10 @@ Object.defineProperty(exports, "getShopifyCookies", {
|
|
|
6262
6570
|
enumerable: true,
|
|
6263
6571
|
get: function () { return hydrogenReact.getShopifyCookies; }
|
|
6264
6572
|
});
|
|
6573
|
+
Object.defineProperty(exports, "getTrackingValues", {
|
|
6574
|
+
enumerable: true,
|
|
6575
|
+
get: function () { return hydrogenReact.getTrackingValues; }
|
|
6576
|
+
});
|
|
6265
6577
|
Object.defineProperty(exports, "isOptionValueCombinationInEncodedVariant", {
|
|
6266
6578
|
enumerable: true,
|
|
6267
6579
|
get: function () { return hydrogenReact.isOptionValueCombinationInEncodedVariant; }
|
|
@@ -6339,6 +6651,7 @@ exports.createCartHandler = createCartHandler;
|
|
|
6339
6651
|
exports.createContentSecurityPolicy = createContentSecurityPolicy;
|
|
6340
6652
|
exports.createCustomerAccountClient = createCustomerAccountClient;
|
|
6341
6653
|
exports.createHydrogenContext = createHydrogenContext;
|
|
6654
|
+
exports.createRequestHandler = createRequestHandler;
|
|
6342
6655
|
exports.createStorefrontClient = createStorefrontClient;
|
|
6343
6656
|
exports.createWithCache = createWithCache;
|
|
6344
6657
|
exports.formatAPIResult = formatAPIResult;
|