@shopify/hydrogen 2024.7.9 → 2024.7.10
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 +376 -70
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +375 -74
- package/dist/development/index.js.map +1 -1
- package/dist/production/index.cjs +116 -110
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.d.cts +69 -10
- package/dist/production/index.d.ts +69 -10
- package/dist/production/index.js +60 -59
- package/dist/production/index.js.map +1 -1
- package/package.json +7 -2
|
@@ -6,6 +6,7 @@ var react$1 = require('@remix-run/react');
|
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var cookie = require('worktop/cookie');
|
|
8
8
|
var cspBuilder = require('content-security-policy-builder');
|
|
9
|
+
var serverRuntime = require('@remix-run/server-runtime');
|
|
9
10
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
@@ -426,13 +427,16 @@ async function runWithCache(cacheKey, actionFn, {
|
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
// src/cache/server-fetch.ts
|
|
430
|
+
var excludedHeaders = ["set-cookie", "server-timing"];
|
|
429
431
|
function toSerializableResponse(body, response) {
|
|
430
432
|
return [
|
|
431
433
|
body,
|
|
432
434
|
{
|
|
433
435
|
status: response.status,
|
|
434
436
|
statusText: response.statusText,
|
|
435
|
-
headers:
|
|
437
|
+
headers: [...response.headers].filter(
|
|
438
|
+
([key]) => !excludedHeaders.includes(key.toLowerCase())
|
|
439
|
+
)
|
|
436
440
|
}
|
|
437
441
|
];
|
|
438
442
|
}
|
|
@@ -447,7 +451,8 @@ async function fetchWithServerCache(url, requestInit, {
|
|
|
447
451
|
shouldCacheResponse = () => true,
|
|
448
452
|
waitUntil,
|
|
449
453
|
returnType = "json",
|
|
450
|
-
debugInfo
|
|
454
|
+
debugInfo,
|
|
455
|
+
onRawHeaders
|
|
451
456
|
} = {}) {
|
|
452
457
|
if (!cacheOptions && (!requestInit.method || requestInit.method === "GET")) {
|
|
453
458
|
cacheOptions = CacheShort();
|
|
@@ -456,6 +461,7 @@ async function fetchWithServerCache(url, requestInit, {
|
|
|
456
461
|
cacheKey,
|
|
457
462
|
async () => {
|
|
458
463
|
const response = await fetch(url, requestInit);
|
|
464
|
+
onRawHeaders?.(response.headers);
|
|
459
465
|
let data;
|
|
460
466
|
try {
|
|
461
467
|
data = await response[returnType]();
|
|
@@ -484,6 +490,10 @@ var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
|
|
|
484
490
|
var SDK_VARIANT_HEADER = "X-SDK-Variant";
|
|
485
491
|
var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
|
|
486
492
|
var SDK_VERSION_HEADER = "X-SDK-Version";
|
|
493
|
+
var SHOPIFY_CLIENT_IP_HEADER = "X-Shopify-Client-IP";
|
|
494
|
+
var SHOPIFY_CLIENT_IP_SIG_HEADER = "X-Shopify-Client-IP-Sig";
|
|
495
|
+
var HYDROGEN_SFAPI_PROXY_KEY = "_sfapi_proxy";
|
|
496
|
+
var HYDROGEN_SERVER_TRACKING_KEY = "_server_tracking";
|
|
487
497
|
|
|
488
498
|
// src/utils/uuid.ts
|
|
489
499
|
function generateUUID() {
|
|
@@ -511,7 +521,7 @@ var errorOnce = (string) => {
|
|
|
511
521
|
};
|
|
512
522
|
|
|
513
523
|
// src/version.ts
|
|
514
|
-
var LIB_VERSION = "2024.7.
|
|
524
|
+
var LIB_VERSION = "2024.7.10";
|
|
515
525
|
|
|
516
526
|
// src/utils/graphql.ts
|
|
517
527
|
function minifyQuery(string) {
|
|
@@ -687,6 +697,78 @@ var getCallerStackLine = (stackOffset = 0) => {
|
|
|
687
697
|
return stackInfo;
|
|
688
698
|
} ;
|
|
689
699
|
|
|
700
|
+
// src/utils/request.ts
|
|
701
|
+
function getHeader(request, key) {
|
|
702
|
+
return getHeaderValue(request.headers, key);
|
|
703
|
+
}
|
|
704
|
+
function getHeaderValue(headers, key) {
|
|
705
|
+
const value = headers?.get?.(key) ?? headers?.[key];
|
|
706
|
+
return typeof value === "string" ? value : null;
|
|
707
|
+
}
|
|
708
|
+
function getDebugHeaders(request) {
|
|
709
|
+
return {
|
|
710
|
+
requestId: request ? getHeader(request, "request-id") : void 0,
|
|
711
|
+
purpose: request ? getHeader(request, "purpose") : void 0
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
|
|
715
|
+
var getSafePathname = (url) => {
|
|
716
|
+
try {
|
|
717
|
+
return new URL(url, "http://e.c").pathname;
|
|
718
|
+
} catch {
|
|
719
|
+
return "/";
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
function extractHeaders(extract, keys) {
|
|
723
|
+
return keys.reduce((acc, key) => {
|
|
724
|
+
const forwardedValue = extract(key);
|
|
725
|
+
if (forwardedValue) acc.push([key, forwardedValue]);
|
|
726
|
+
return acc;
|
|
727
|
+
}, []);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/utils/server-timing.ts
|
|
731
|
+
function buildServerTimingHeader(values) {
|
|
732
|
+
return Object.entries(values).map(([key, value]) => value ? `${key};desc=${value}` : void 0).filter(Boolean).join(", ");
|
|
733
|
+
}
|
|
734
|
+
function appendServerTimingHeader(response, values) {
|
|
735
|
+
const header = typeof values === "string" ? values : buildServerTimingHeader(values);
|
|
736
|
+
if (header) {
|
|
737
|
+
response.headers.append("Server-Timing", header);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
var trackedTimings = ["_y", "_s", "_cmp"];
|
|
741
|
+
function extractServerTimingHeader(serverTimingHeader) {
|
|
742
|
+
const values = {};
|
|
743
|
+
if (!serverTimingHeader) return values;
|
|
744
|
+
const re = new RegExp(
|
|
745
|
+
`\\b(${trackedTimings.join("|")});desc="?([^",]+)"?`,
|
|
746
|
+
"g"
|
|
747
|
+
);
|
|
748
|
+
let match;
|
|
749
|
+
while ((match = re.exec(serverTimingHeader)) !== null) {
|
|
750
|
+
values[match[1]] = match[2];
|
|
751
|
+
}
|
|
752
|
+
return values;
|
|
753
|
+
}
|
|
754
|
+
function hasServerTimingInNavigationEntry(key) {
|
|
755
|
+
if (typeof window === "undefined") return false;
|
|
756
|
+
try {
|
|
757
|
+
const navigationEntry = window.performance.getEntriesByType(
|
|
758
|
+
"navigation"
|
|
759
|
+
)[0];
|
|
760
|
+
return !!navigationEntry?.serverTiming?.some((entry) => entry.name === key);
|
|
761
|
+
} catch (e) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function isSfapiProxyEnabled() {
|
|
766
|
+
return hasServerTimingInNavigationEntry(HYDROGEN_SFAPI_PROXY_KEY);
|
|
767
|
+
}
|
|
768
|
+
function hasServerReturnedTrackingValues() {
|
|
769
|
+
return hasServerTimingInNavigationEntry(HYDROGEN_SERVER_TRACKING_KEY);
|
|
770
|
+
}
|
|
771
|
+
|
|
690
772
|
// src/storefront.ts
|
|
691
773
|
var defaultI18n = { language: "EN", country: "US" };
|
|
692
774
|
function createStorefrontClient(options) {
|
|
@@ -716,16 +798,34 @@ function createStorefrontClient(options) {
|
|
|
716
798
|
contentType: "json",
|
|
717
799
|
buyerIp: storefrontHeaders?.buyerIp || ""
|
|
718
800
|
});
|
|
801
|
+
if (storefrontHeaders?.buyerIp) {
|
|
802
|
+
defaultHeaders[SHOPIFY_CLIENT_IP_HEADER] = storefrontHeaders.buyerIp;
|
|
803
|
+
}
|
|
804
|
+
if (storefrontHeaders?.buyerIpSig) {
|
|
805
|
+
defaultHeaders[SHOPIFY_CLIENT_IP_SIG_HEADER] = storefrontHeaders.buyerIpSig;
|
|
806
|
+
}
|
|
719
807
|
defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
|
|
720
808
|
if (storefrontId) defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
|
|
721
809
|
defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
810
|
+
const requestCookie = storefrontHeaders?.cookie ?? "";
|
|
811
|
+
if (requestCookie) defaultHeaders["cookie"] = requestCookie;
|
|
812
|
+
let uniqueToken;
|
|
813
|
+
let visitToken;
|
|
814
|
+
if (!/\b_shopify_(analytics|marketing)=/.test(requestCookie)) {
|
|
815
|
+
const legacyUniqueToken = requestCookie.match(/\b_shopify_y=([^;]+)/)?.[1];
|
|
816
|
+
const legacyVisitToken = requestCookie.match(/\b_shopify_s=([^;]+)/)?.[1];
|
|
817
|
+
if (legacyUniqueToken) {
|
|
818
|
+
defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_Y_HEADER] = legacyUniqueToken;
|
|
819
|
+
}
|
|
820
|
+
if (legacyVisitToken) {
|
|
821
|
+
defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_S_HEADER] = legacyVisitToken;
|
|
822
|
+
}
|
|
823
|
+
uniqueToken = legacyUniqueToken ?? generateUUID();
|
|
824
|
+
visitToken = legacyVisitToken ?? generateUUID();
|
|
825
|
+
defaultHeaders[hydrogenReact.SHOPIFY_UNIQUE_TOKEN_HEADER] = uniqueToken;
|
|
826
|
+
defaultHeaders[hydrogenReact.SHOPIFY_VISIT_TOKEN_HEADER] = visitToken;
|
|
728
827
|
}
|
|
828
|
+
let collectedSubrequestHeaders;
|
|
729
829
|
const cacheKeyHeader = JSON.stringify({
|
|
730
830
|
"content-type": defaultHeaders["content-type"],
|
|
731
831
|
"user-agent": defaultHeaders["user-agent"],
|
|
@@ -784,6 +884,13 @@ function createStorefrontClient(options) {
|
|
|
784
884
|
stackInfo,
|
|
785
885
|
graphql: graphqlData,
|
|
786
886
|
purpose: storefrontHeaders?.purpose
|
|
887
|
+
},
|
|
888
|
+
onRawHeaders: (headers2) => {
|
|
889
|
+
collectedSubrequestHeaders ??= {
|
|
890
|
+
// `getSetCookie` may not be available in all environments (e.g., classic Remix compiler)
|
|
891
|
+
setCookie: typeof headers2.getSetCookie === "function" ? headers2.getSetCookie() : [],
|
|
892
|
+
serverTiming: headers2.get("server-timing") ?? ""
|
|
893
|
+
};
|
|
787
894
|
}
|
|
788
895
|
});
|
|
789
896
|
const errorOptions = {
|
|
@@ -878,9 +985,90 @@ function createStorefrontClient(options) {
|
|
|
878
985
|
generateCacheControlHeader,
|
|
879
986
|
getPublicTokenHeaders,
|
|
880
987
|
getPrivateTokenHeaders,
|
|
988
|
+
getHeaders: () => ({ ...defaultHeaders }),
|
|
881
989
|
getShopifyDomain,
|
|
882
990
|
getApiUrl: getStorefrontApiUrl,
|
|
883
|
-
i18n: i18n ?? defaultI18n
|
|
991
|
+
i18n: i18n ?? defaultI18n,
|
|
992
|
+
/**
|
|
993
|
+
* Checks if the request is targeting the Storefront API endpoint.
|
|
994
|
+
*/
|
|
995
|
+
isStorefrontApiUrl(request) {
|
|
996
|
+
return SFAPI_RE.test(getSafePathname(request.url ?? ""));
|
|
997
|
+
},
|
|
998
|
+
/**
|
|
999
|
+
* Forwards the request to the Storefront API.
|
|
1000
|
+
*/
|
|
1001
|
+
async forward(request, options2) {
|
|
1002
|
+
const forwardedHeaders = new Headers([
|
|
1003
|
+
// Forward only a selected set of headers to the Storefront API
|
|
1004
|
+
// to avoid getting 403 errors due to unexpected headers.
|
|
1005
|
+
...extractHeaders(
|
|
1006
|
+
(key) => request.headers.get(key),
|
|
1007
|
+
[
|
|
1008
|
+
"accept",
|
|
1009
|
+
"accept-encoding",
|
|
1010
|
+
"accept-language",
|
|
1011
|
+
// Access-Control headers are used for CORS preflight requests.
|
|
1012
|
+
"access-control-request-headers",
|
|
1013
|
+
"access-control-request-method",
|
|
1014
|
+
"content-type",
|
|
1015
|
+
"content-length",
|
|
1016
|
+
"cookie",
|
|
1017
|
+
"origin",
|
|
1018
|
+
"referer",
|
|
1019
|
+
"user-agent",
|
|
1020
|
+
STOREFRONT_ACCESS_TOKEN_HEADER,
|
|
1021
|
+
hydrogenReact.SHOPIFY_UNIQUE_TOKEN_HEADER,
|
|
1022
|
+
hydrogenReact.SHOPIFY_VISIT_TOKEN_HEADER
|
|
1023
|
+
]
|
|
1024
|
+
),
|
|
1025
|
+
// Add some headers to help with geolocalization and debugging
|
|
1026
|
+
...extractHeaders(
|
|
1027
|
+
(key) => defaultHeaders[key],
|
|
1028
|
+
[
|
|
1029
|
+
SHOPIFY_CLIENT_IP_HEADER,
|
|
1030
|
+
SHOPIFY_CLIENT_IP_SIG_HEADER,
|
|
1031
|
+
hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER,
|
|
1032
|
+
STOREFRONT_REQUEST_GROUP_ID_HEADER
|
|
1033
|
+
]
|
|
1034
|
+
)
|
|
1035
|
+
]);
|
|
1036
|
+
if (storefrontHeaders?.buyerIp) {
|
|
1037
|
+
forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
|
|
1038
|
+
}
|
|
1039
|
+
const storefrontApiVersion = options2?.storefrontApiVersion ?? getSafePathname(request.url).match(SFAPI_RE)?.[1];
|
|
1040
|
+
const sfapiResponse = await fetch(
|
|
1041
|
+
getStorefrontApiUrl({ storefrontApiVersion }),
|
|
1042
|
+
{
|
|
1043
|
+
method: request.method,
|
|
1044
|
+
body: request.body,
|
|
1045
|
+
headers: forwardedHeaders
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
1048
|
+
return new Response(sfapiResponse.body, sfapiResponse);
|
|
1049
|
+
},
|
|
1050
|
+
setCollectedSubrequestHeaders: (response) => {
|
|
1051
|
+
if (collectedSubrequestHeaders) {
|
|
1052
|
+
for (const value of collectedSubrequestHeaders.setCookie) {
|
|
1053
|
+
response.headers.append("Set-Cookie", value);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const serverTiming = extractServerTimingHeader(
|
|
1057
|
+
collectedSubrequestHeaders?.serverTiming
|
|
1058
|
+
);
|
|
1059
|
+
const isDocumentResponse = response.headers.get("content-type")?.startsWith("text/html");
|
|
1060
|
+
const fallbackValues = isDocumentResponse ? { _y: uniqueToken, _s: visitToken } : void 0;
|
|
1061
|
+
appendServerTimingHeader(response, {
|
|
1062
|
+
...fallbackValues,
|
|
1063
|
+
...serverTiming
|
|
1064
|
+
});
|
|
1065
|
+
if (isDocumentResponse && collectedSubrequestHeaders && // _shopify_essential cookie is always set, but we need more than that
|
|
1066
|
+
collectedSubrequestHeaders.setCookie.length > 1 && serverTiming?._y && serverTiming?._s && serverTiming?._cmp) {
|
|
1067
|
+
appendServerTimingHeader(response, {
|
|
1068
|
+
[HYDROGEN_SERVER_TRACKING_KEY]: "1"
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
884
1072
|
}
|
|
885
1073
|
};
|
|
886
1074
|
}
|
|
@@ -898,21 +1086,6 @@ function formatAPIResult(data, errors2) {
|
|
|
898
1086
|
};
|
|
899
1087
|
}
|
|
900
1088
|
|
|
901
|
-
// src/utils/request.ts
|
|
902
|
-
function getHeader(request, key) {
|
|
903
|
-
return getHeaderValue(request.headers, key);
|
|
904
|
-
}
|
|
905
|
-
function getHeaderValue(headers, key) {
|
|
906
|
-
const value = headers?.get?.(key) ?? headers?.[key];
|
|
907
|
-
return typeof value === "string" ? value : null;
|
|
908
|
-
}
|
|
909
|
-
function getDebugHeaders(request) {
|
|
910
|
-
return {
|
|
911
|
-
requestId: request ? getHeader(request, "request-id") : void 0,
|
|
912
|
-
purpose: request ? getHeader(request, "purpose") : void 0
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
|
|
916
1089
|
// src/cache/create-with-cache.ts
|
|
917
1090
|
function createWithCache(cacheOptions) {
|
|
918
1091
|
const { cache, waitUntil, request } = cacheOptions;
|
|
@@ -4222,7 +4395,7 @@ var AnalyticsEvent = {
|
|
|
4222
4395
|
// Custom
|
|
4223
4396
|
CUSTOM_EVENT: `custom_`
|
|
4224
4397
|
};
|
|
4225
|
-
var CONSENT_API = "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.
|
|
4398
|
+
var CONSENT_API = "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.js";
|
|
4226
4399
|
var CONSENT_API_WITH_BANNER = "https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js";
|
|
4227
4400
|
function logMissingConfig(fieldName) {
|
|
4228
4401
|
console.error(
|
|
@@ -4234,19 +4407,34 @@ function useCustomerPrivacy(props) {
|
|
|
4234
4407
|
withPrivacyBanner = true,
|
|
4235
4408
|
onVisitorConsentCollected,
|
|
4236
4409
|
onReady,
|
|
4237
|
-
|
|
4410
|
+
checkoutDomain,
|
|
4411
|
+
storefrontAccessToken,
|
|
4412
|
+
country,
|
|
4413
|
+
locale,
|
|
4414
|
+
sameDomainForStorefrontApi
|
|
4238
4415
|
} = props;
|
|
4416
|
+
const hasSfapiProxy = react.useMemo(
|
|
4417
|
+
() => sameDomainForStorefrontApi ?? isSfapiProxyEnabled(),
|
|
4418
|
+
[sameDomainForStorefrontApi]
|
|
4419
|
+
);
|
|
4420
|
+
const fetchTrackingValuesFromBrowser = react.useMemo(
|
|
4421
|
+
() => hasSfapiProxy && !hasServerReturnedTrackingValues(),
|
|
4422
|
+
[hasSfapiProxy]
|
|
4423
|
+
);
|
|
4424
|
+
const cookiesReady = hydrogenReact.useShopifyCookies({
|
|
4425
|
+
fetchTrackingValues: fetchTrackingValuesFromBrowser,
|
|
4426
|
+
storefrontAccessToken,
|
|
4427
|
+
ignoreDeprecatedCookies: true
|
|
4428
|
+
});
|
|
4429
|
+
const initialTrackingValues = react.useMemo(hydrogenReact.getTrackingValues, [cookiesReady]);
|
|
4430
|
+
const { revalidate } = react$1.useRevalidator();
|
|
4239
4431
|
hydrogenReact.useLoadScript(withPrivacyBanner ? CONSENT_API_WITH_BANNER : CONSENT_API, {
|
|
4240
4432
|
attributes: {
|
|
4241
4433
|
id: "customer-privacy-api"
|
|
4242
4434
|
}
|
|
4243
4435
|
});
|
|
4244
|
-
const { observing, setLoaded } = useApisLoaded({
|
|
4245
|
-
withPrivacyBanner,
|
|
4246
|
-
onLoaded: onReady
|
|
4247
|
-
});
|
|
4436
|
+
const { observing, setLoaded, apisLoaded } = useApisLoaded({ withPrivacyBanner });
|
|
4248
4437
|
const config = react.useMemo(() => {
|
|
4249
|
-
const { checkoutDomain, storefrontAccessToken } = consentConfig;
|
|
4250
4438
|
if (!checkoutDomain) logMissingConfig("checkoutDomain");
|
|
4251
4439
|
if (!storefrontAccessToken) logMissingConfig("storefrontAccessToken");
|
|
4252
4440
|
if (storefrontAccessToken.startsWith("shpat_") || storefrontAccessToken.length !== 32) {
|
|
@@ -4254,18 +4442,50 @@ function useCustomerPrivacy(props) {
|
|
|
4254
4442
|
`[h2:error:useCustomerPrivacy] It looks like you passed a private access token, make sure to use the public token`
|
|
4255
4443
|
);
|
|
4256
4444
|
}
|
|
4445
|
+
const commonAncestorDomain = parseStoreDomain(checkoutDomain);
|
|
4446
|
+
const sfapiDomain = (
|
|
4447
|
+
// Check if standard route proxy is enabled in Hydrogen server
|
|
4448
|
+
// to use it instead of doing a cross-origin request to checkout.
|
|
4449
|
+
hasSfapiProxy && typeof window !== "undefined" ? window.location.host : checkoutDomain
|
|
4450
|
+
);
|
|
4257
4451
|
const config2 = {
|
|
4258
|
-
|
|
4452
|
+
// This domain is used to send requests to SFAPI for setting and getting consent.
|
|
4453
|
+
checkoutRootDomain: sfapiDomain,
|
|
4454
|
+
// Prefix with a dot to ensure this domain is different from checkoutRootDomain.
|
|
4455
|
+
// This will ensure old cookies are set for a cross-subdomain checkout setup
|
|
4456
|
+
// so that we keep backward compatibility until new cookies are rolled out.
|
|
4457
|
+
// Once consent-tracking-api is updated to not rely on cookies anymore, we can remove this.
|
|
4458
|
+
storefrontRootDomain: commonAncestorDomain ? "." + commonAncestorDomain : void 0,
|
|
4259
4459
|
storefrontAccessToken,
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
locale: consentConfig.locale
|
|
4460
|
+
country,
|
|
4461
|
+
locale
|
|
4263
4462
|
};
|
|
4264
4463
|
return config2;
|
|
4265
|
-
}, [
|
|
4464
|
+
}, [
|
|
4465
|
+
logMissingConfig,
|
|
4466
|
+
checkoutDomain,
|
|
4467
|
+
storefrontAccessToken,
|
|
4468
|
+
country,
|
|
4469
|
+
locale
|
|
4470
|
+
]);
|
|
4266
4471
|
react.useEffect(() => {
|
|
4267
4472
|
const consentCollectedHandler = (event) => {
|
|
4473
|
+
const latestTrackingValues = hydrogenReact.getTrackingValues();
|
|
4474
|
+
if (initialTrackingValues.visitToken !== latestTrackingValues.visitToken || initialTrackingValues.uniqueToken !== latestTrackingValues.uniqueToken) {
|
|
4475
|
+
revalidate();
|
|
4476
|
+
}
|
|
4268
4477
|
if (onVisitorConsentCollected) {
|
|
4478
|
+
const customerPrivacy = getCustomerPrivacy();
|
|
4479
|
+
if (customerPrivacy?.shouldShowBanner()) {
|
|
4480
|
+
const consentValues = customerPrivacy.currentVisitorConsent();
|
|
4481
|
+
if (consentValues) {
|
|
4482
|
+
const NO_VALUE = "";
|
|
4483
|
+
const noInteraction = consentValues.marketing === NO_VALUE && consentValues.analytics === NO_VALUE && consentValues.preferences === NO_VALUE;
|
|
4484
|
+
if (noInteraction) {
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4269
4489
|
onVisitorConsentCollected(event.detail);
|
|
4270
4490
|
}
|
|
4271
4491
|
};
|
|
@@ -4291,14 +4511,11 @@ function useCustomerPrivacy(props) {
|
|
|
4291
4511
|
},
|
|
4292
4512
|
set(value) {
|
|
4293
4513
|
if (typeof value === "object" && value !== null && "showPreferences" in value && "loadBanner" in value) {
|
|
4294
|
-
const privacyBanner = value;
|
|
4295
|
-
privacyBanner.loadBanner(config);
|
|
4296
4514
|
customPrivacyBanner = overridePrivacyBannerMethods({
|
|
4297
|
-
privacyBanner,
|
|
4515
|
+
privacyBanner: value,
|
|
4298
4516
|
config
|
|
4299
4517
|
});
|
|
4300
4518
|
setLoaded.privacyBanner();
|
|
4301
|
-
emitCustomerPrivacyApiLoaded();
|
|
4302
4519
|
}
|
|
4303
4520
|
}
|
|
4304
4521
|
};
|
|
@@ -4332,6 +4549,8 @@ function useCustomerPrivacy(props) {
|
|
|
4332
4549
|
const customerPrivacy = value2;
|
|
4333
4550
|
customCustomerPrivacy = {
|
|
4334
4551
|
...customerPrivacy,
|
|
4552
|
+
// Note: this method is not used by the privacy-banner,
|
|
4553
|
+
// it bundles its own setTrackingConsent.
|
|
4335
4554
|
setTrackingConsent: overrideCustomerPrivacySetTrackingConsent(
|
|
4336
4555
|
{ customerPrivacy, config }
|
|
4337
4556
|
)
|
|
@@ -4341,7 +4560,6 @@ function useCustomerPrivacy(props) {
|
|
|
4341
4560
|
customerPrivacy: customCustomerPrivacy
|
|
4342
4561
|
};
|
|
4343
4562
|
setLoaded.customerPrivacy();
|
|
4344
|
-
emitCustomerPrivacyApiLoaded();
|
|
4345
4563
|
}
|
|
4346
4564
|
}
|
|
4347
4565
|
});
|
|
@@ -4353,6 +4571,24 @@ function useCustomerPrivacy(props) {
|
|
|
4353
4571
|
overrideCustomerPrivacySetTrackingConsent,
|
|
4354
4572
|
setLoaded.customerPrivacy
|
|
4355
4573
|
]);
|
|
4574
|
+
react.useEffect(() => {
|
|
4575
|
+
if (!apisLoaded || !cookiesReady) return;
|
|
4576
|
+
const customerPrivacy = getCustomerPrivacy();
|
|
4577
|
+
if (customerPrivacy && !customerPrivacy.cachedConsent) {
|
|
4578
|
+
const trackingValues = hydrogenReact.getTrackingValues();
|
|
4579
|
+
if (trackingValues.consent) {
|
|
4580
|
+
customerPrivacy.cachedConsent = trackingValues.consent;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
if (withPrivacyBanner) {
|
|
4584
|
+
const privacyBanner = getPrivacyBanner();
|
|
4585
|
+
if (privacyBanner) {
|
|
4586
|
+
privacyBanner.loadBanner(config);
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
emitCustomerPrivacyApiLoaded();
|
|
4590
|
+
onReady?.();
|
|
4591
|
+
}, [apisLoaded, cookiesReady]);
|
|
4356
4592
|
const result = {
|
|
4357
4593
|
customerPrivacy: getCustomerPrivacy()
|
|
4358
4594
|
};
|
|
@@ -4368,15 +4604,12 @@ function emitCustomerPrivacyApiLoaded() {
|
|
|
4368
4604
|
const event = new CustomEvent("shopifyCustomerPrivacyApiLoaded");
|
|
4369
4605
|
document.dispatchEvent(event);
|
|
4370
4606
|
}
|
|
4371
|
-
function useApisLoaded({
|
|
4372
|
-
withPrivacyBanner,
|
|
4373
|
-
onLoaded
|
|
4374
|
-
}) {
|
|
4607
|
+
function useApisLoaded({ withPrivacyBanner }) {
|
|
4375
4608
|
const observing = react.useRef({ customerPrivacy: false, privacyBanner: false });
|
|
4376
|
-
const [
|
|
4609
|
+
const [apisLoadedArray, setApisLoaded] = react.useState(
|
|
4377
4610
|
withPrivacyBanner ? [false, false] : [false]
|
|
4378
4611
|
);
|
|
4379
|
-
const
|
|
4612
|
+
const apisLoaded = apisLoadedArray.every(Boolean);
|
|
4380
4613
|
const setLoaded = {
|
|
4381
4614
|
customerPrivacy: () => {
|
|
4382
4615
|
if (withPrivacyBanner) {
|
|
@@ -4392,16 +4625,11 @@ function useApisLoaded({
|
|
|
4392
4625
|
setApisLoaded((prev) => [prev[0], true]);
|
|
4393
4626
|
}
|
|
4394
4627
|
};
|
|
4395
|
-
|
|
4396
|
-
if (loaded && onLoaded) {
|
|
4397
|
-
onLoaded();
|
|
4398
|
-
}
|
|
4399
|
-
}, [loaded, onLoaded]);
|
|
4400
|
-
return { observing, setLoaded };
|
|
4628
|
+
return { observing, setLoaded, apisLoaded };
|
|
4401
4629
|
}
|
|
4402
4630
|
function parseStoreDomain(checkoutDomain) {
|
|
4403
4631
|
if (typeof window === "undefined") return;
|
|
4404
|
-
const host = window.
|
|
4632
|
+
const host = window.location.host;
|
|
4405
4633
|
const checkoutDomainParts = checkoutDomain.split(".").reverse();
|
|
4406
4634
|
const currentDomainParts = host.split(".").reverse();
|
|
4407
4635
|
const sameDomainParts = [];
|
|
@@ -4410,7 +4638,7 @@ function parseStoreDomain(checkoutDomain) {
|
|
|
4410
4638
|
sameDomainParts.push(part);
|
|
4411
4639
|
}
|
|
4412
4640
|
});
|
|
4413
|
-
return sameDomainParts.reverse().join(".");
|
|
4641
|
+
return sameDomainParts.reverse().join(".") || void 0;
|
|
4414
4642
|
}
|
|
4415
4643
|
function overrideCustomerPrivacySetTrackingConsent({
|
|
4416
4644
|
customerPrivacy,
|
|
@@ -4468,7 +4696,7 @@ function getPrivacyBanner() {
|
|
|
4468
4696
|
}
|
|
4469
4697
|
|
|
4470
4698
|
// package.json
|
|
4471
|
-
var version = "2024.7.
|
|
4699
|
+
var version = "2024.7.10";
|
|
4472
4700
|
|
|
4473
4701
|
// src/analytics-manager/ShopifyAnalytics.tsx
|
|
4474
4702
|
function getCustomerPrivacyRequired() {
|
|
@@ -4488,6 +4716,7 @@ function ShopifyAnalytics({
|
|
|
4488
4716
|
const { subscribe: subscribe2, register: register2, canTrack } = useAnalytics();
|
|
4489
4717
|
const [shopifyReady, setShopifyReady] = react.useState(false);
|
|
4490
4718
|
const [privacyReady, setPrivacyReady] = react.useState(false);
|
|
4719
|
+
const [collectedConsent, setCollectedConsent] = react.useState("");
|
|
4491
4720
|
const init = react.useRef(false);
|
|
4492
4721
|
const { checkoutDomain, storefrontAccessToken, language } = consent;
|
|
4493
4722
|
const { ready: shopifyAnalyticsReady } = register2("Internal_Shopify_Analytics");
|
|
@@ -4496,14 +4725,31 @@ function ShopifyAnalytics({
|
|
|
4496
4725
|
locale: language,
|
|
4497
4726
|
checkoutDomain: !checkoutDomain ? "mock.shop" : checkoutDomain,
|
|
4498
4727
|
storefrontAccessToken: !storefrontAccessToken ? "abcdefghijklmnopqrstuvwxyz123456" : storefrontAccessToken,
|
|
4499
|
-
|
|
4500
|
-
|
|
4728
|
+
// If we use privacy banner, we should wait until consent is collected.
|
|
4729
|
+
// Otherwise, we can consider privacy ready immediately:
|
|
4730
|
+
onReady: () => !consent.withPrivacyBanner && setPrivacyReady(true),
|
|
4731
|
+
onVisitorConsentCollected: (consent2) => {
|
|
4732
|
+
try {
|
|
4733
|
+
setCollectedConsent(JSON.stringify(consent2));
|
|
4734
|
+
} catch (e) {
|
|
4735
|
+
}
|
|
4736
|
+
setPrivacyReady(true);
|
|
4737
|
+
}
|
|
4501
4738
|
});
|
|
4739
|
+
const hasUserConsent = react.useMemo(
|
|
4740
|
+
// must be initialized with true to avoid removing cookies too early
|
|
4741
|
+
() => privacyReady ? canTrack() : true,
|
|
4742
|
+
// Make this value depend on collectedConsent to re-run `canTrack()` when consent changes
|
|
4743
|
+
[privacyReady, canTrack, collectedConsent]
|
|
4744
|
+
);
|
|
4502
4745
|
hydrogenReact.useShopifyCookies({
|
|
4503
|
-
hasUserConsent
|
|
4504
|
-
// must be initialized with true
|
|
4746
|
+
hasUserConsent,
|
|
4505
4747
|
domain,
|
|
4506
|
-
checkoutDomain
|
|
4748
|
+
checkoutDomain,
|
|
4749
|
+
// Already done inside useCustomerPrivacy
|
|
4750
|
+
fetchTrackingValues: false,
|
|
4751
|
+
// Avoid creating local cookies too early
|
|
4752
|
+
ignoreDeprecatedCookies: !privacyReady
|
|
4507
4753
|
});
|
|
4508
4754
|
react.useEffect(() => {
|
|
4509
4755
|
if (init.current) return;
|
|
@@ -4553,11 +4799,11 @@ function prepareBasePageViewPayload(payload) {
|
|
|
4553
4799
|
...payload.shop,
|
|
4554
4800
|
hasUserConsent,
|
|
4555
4801
|
...hydrogenReact.getClientBrowserParameters(),
|
|
4556
|
-
ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
|
|
4557
|
-
gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed()),
|
|
4558
4802
|
analyticsAllowed: customerPrivacy.analyticsProcessingAllowed(),
|
|
4559
4803
|
marketingAllowed: customerPrivacy.marketingAllowed(),
|
|
4560
|
-
saleOfDataAllowed: customerPrivacy.saleOfDataAllowed()
|
|
4804
|
+
saleOfDataAllowed: customerPrivacy.saleOfDataAllowed(),
|
|
4805
|
+
ccpaEnforced: !customerPrivacy.saleOfDataAllowed(),
|
|
4806
|
+
gdprEnforced: !(customerPrivacy.marketingAllowed() && customerPrivacy.analyticsProcessingAllowed())
|
|
4561
4807
|
};
|
|
4562
4808
|
return eventPayload;
|
|
4563
4809
|
}
|
|
@@ -4974,11 +5220,11 @@ function AnalyticsProvider({
|
|
|
4974
5220
|
shop: shopProp = null,
|
|
4975
5221
|
cookieDomain
|
|
4976
5222
|
}) {
|
|
4977
|
-
const listenerSet = react.useRef(false);
|
|
4978
5223
|
const { shop } = useShopAnalytics(shopProp);
|
|
4979
5224
|
const [analyticsLoaded, setAnalyticsLoaded] = react.useState(
|
|
4980
5225
|
customCanTrack ? true : false
|
|
4981
5226
|
);
|
|
5227
|
+
const [consentCollected, setConsentCollected] = react.useState(false);
|
|
4982
5228
|
const [carts, setCarts] = react.useState({ cart: null, prevCart: null });
|
|
4983
5229
|
const [canTrack, setCanTrack] = react.useState(
|
|
4984
5230
|
customCanTrack ? () => customCanTrack : () => shopifyCanTrack
|
|
@@ -5046,21 +5292,21 @@ function AnalyticsProvider({
|
|
|
5046
5292
|
children,
|
|
5047
5293
|
!!shop && /* @__PURE__ */ jsxRuntime.jsx(AnalyticsPageView, {}),
|
|
5048
5294
|
!!shop && !!currentCart && /* @__PURE__ */ jsxRuntime.jsx(CartAnalytics, { cart: currentCart, setCarts }),
|
|
5049
|
-
!!shop &&
|
|
5295
|
+
!!shop && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5050
5296
|
ShopifyAnalytics,
|
|
5051
5297
|
{
|
|
5052
5298
|
consent,
|
|
5053
5299
|
onReady: () => {
|
|
5054
|
-
listenerSet.current = true;
|
|
5055
5300
|
setAnalyticsLoaded(true);
|
|
5056
5301
|
setCanTrack(
|
|
5057
5302
|
customCanTrack ? () => customCanTrack : () => shopifyCanTrack
|
|
5058
5303
|
);
|
|
5304
|
+
setConsentCollected(true);
|
|
5059
5305
|
},
|
|
5060
5306
|
domain: cookieDomain
|
|
5061
5307
|
}
|
|
5062
5308
|
),
|
|
5063
|
-
!!shop && /* @__PURE__ */ jsxRuntime.jsx(PerfKit, { shop })
|
|
5309
|
+
!!shop && consentCollected && /* @__PURE__ */ jsxRuntime.jsx(PerfKit, { shop })
|
|
5064
5310
|
] });
|
|
5065
5311
|
}
|
|
5066
5312
|
function useAnalytics() {
|
|
@@ -5221,8 +5467,63 @@ function getStorefrontHeaders(request) {
|
|
|
5221
5467
|
return {
|
|
5222
5468
|
requestGroupId: getHeader(request, "request-id"),
|
|
5223
5469
|
buyerIp: getHeader(request, "oxygen-buyer-ip"),
|
|
5470
|
+
buyerIpSig: getHeader(request, "X-Shopify-Client-IP-Sig"),
|
|
5224
5471
|
cookie: getHeader(request, "cookie"),
|
|
5225
|
-
purpose: getHeader(request, "purpose")
|
|
5472
|
+
purpose: getHeader(request, "sec-purpose") || getHeader(request, "purpose")
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
function createRequestHandler({
|
|
5476
|
+
build,
|
|
5477
|
+
mode,
|
|
5478
|
+
poweredByHeader = true,
|
|
5479
|
+
getLoadContext,
|
|
5480
|
+
collectTrackingInformation = true,
|
|
5481
|
+
proxyStandardRoutes = true
|
|
5482
|
+
}) {
|
|
5483
|
+
const handleRequest = serverRuntime.createRequestHandler(build, mode);
|
|
5484
|
+
const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
|
|
5485
|
+
return async (request) => {
|
|
5486
|
+
const method = request.method;
|
|
5487
|
+
if ((method === "GET" || method === "HEAD") && request.body) {
|
|
5488
|
+
return new Response(`${method} requests cannot have a body`, {
|
|
5489
|
+
status: 400
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5492
|
+
const url = new URL(request.url);
|
|
5493
|
+
if (url.pathname.includes("//")) {
|
|
5494
|
+
return new Response(null, {
|
|
5495
|
+
status: 301,
|
|
5496
|
+
headers: {
|
|
5497
|
+
location: url.pathname.replace(/\/+/g, "/")
|
|
5498
|
+
}
|
|
5499
|
+
});
|
|
5500
|
+
}
|
|
5501
|
+
const context = getLoadContext ? await getLoadContext(request) : void 0;
|
|
5502
|
+
const storefront = context?.storefront;
|
|
5503
|
+
if (proxyStandardRoutes) {
|
|
5504
|
+
if (!storefront) {
|
|
5505
|
+
warnOnce(
|
|
5506
|
+
"[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
|
|
5507
|
+
);
|
|
5508
|
+
}
|
|
5509
|
+
if (storefront?.isStorefrontApiUrl(request)) {
|
|
5510
|
+
const response2 = await storefront.forward(request);
|
|
5511
|
+
appendPoweredByHeader?.(response2);
|
|
5512
|
+
return response2;
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
const response = await handleRequest(request, context);
|
|
5516
|
+
if (storefront && proxyStandardRoutes) {
|
|
5517
|
+
if (collectTrackingInformation) {
|
|
5518
|
+
storefront.setCollectedSubrequestHeaders(response);
|
|
5519
|
+
}
|
|
5520
|
+
const fetchDest = request.headers.get("sec-fetch-dest");
|
|
5521
|
+
if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
|
|
5522
|
+
appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
appendPoweredByHeader?.(response);
|
|
5526
|
+
return response;
|
|
5226
5527
|
};
|
|
5227
5528
|
}
|
|
5228
5529
|
|
|
@@ -5565,6 +5866,10 @@ Object.defineProperty(exports, "getShopifyCookies", {
|
|
|
5565
5866
|
enumerable: true,
|
|
5566
5867
|
get: function () { return hydrogenReact.getShopifyCookies; }
|
|
5567
5868
|
});
|
|
5869
|
+
Object.defineProperty(exports, "getTrackingValues", {
|
|
5870
|
+
enumerable: true,
|
|
5871
|
+
get: function () { return hydrogenReact.getTrackingValues; }
|
|
5872
|
+
});
|
|
5568
5873
|
Object.defineProperty(exports, "parseGid", {
|
|
5569
5874
|
enumerable: true,
|
|
5570
5875
|
get: function () { return hydrogenReact.parseGid; }
|
|
@@ -5628,6 +5933,7 @@ exports.createCartHandler = createCartHandler;
|
|
|
5628
5933
|
exports.createContentSecurityPolicy = createContentSecurityPolicy;
|
|
5629
5934
|
exports.createCustomerAccountClient = createCustomerAccountClient;
|
|
5630
5935
|
exports.createHydrogenContext = createHydrogenContext;
|
|
5936
|
+
exports.createRequestHandler = createRequestHandler;
|
|
5631
5937
|
exports.createStorefrontClient = createStorefrontClient;
|
|
5632
5938
|
exports.createWithCache = createWithCache;
|
|
5633
5939
|
exports.formatAPIResult = formatAPIResult;
|