@shopify/hydrogen-react
Version:
React components, hooks, and utilities for creating custom Shopify storefronts
1,528 lines (1,516 loc) • 160 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react")) : typeof define === "function" && define.amd ? define(["exports", "react"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.hydrogenreact = {}, global.React));
})(this, (function(exports2, React$1) {
"use strict";
function _interopNamespaceDefault(e2) {
const n2 = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
if (e2) {
for (const k in e2) {
if (k !== "default") {
const d = Object.getOwnPropertyDescriptor(e2, k);
Object.defineProperty(n2, k, d.get ? d : {
enumerable: true,
get: () => e2[k]
});
}
}
}
n2.default = e2;
return Object.freeze(n2);
}
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React$1);
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function t(t2, n2) {
var e2 = "function" == typeof Symbol && t2[Symbol.iterator];
if (!e2) return t2;
var r2, i2, o2 = e2.call(t2), a2 = [];
try {
for (; (void 0 === n2 || n2-- > 0) && !(r2 = o2.next()).done; ) a2.push(r2.value);
} catch (t3) {
i2 = { error: t3 };
} finally {
try {
r2 && !r2.done && (e2 = o2.return) && e2.call(o2);
} finally {
if (i2) throw i2.error;
}
}
return a2;
}
var n;
!(function(t2) {
t2[t2.NotStarted = 0] = "NotStarted", t2[t2.Running = 1] = "Running", t2[t2.Stopped = 2] = "Stopped";
})(n || (n = {}));
var e = { type: "xstate.init" };
function r(t2) {
return void 0 === t2 ? [] : [].concat(t2);
}
function i(t2) {
return { type: "xstate.assign", assignment: t2 };
}
function o(t2, n2) {
return "string" == typeof (t2 = "string" == typeof t2 && n2 && n2[t2] ? n2[t2] : t2) ? { type: t2 } : "function" == typeof t2 ? { type: t2.name, exec: t2 } : t2;
}
function a(t2) {
return function(n2) {
return t2 === n2;
};
}
function u(t2) {
return "string" == typeof t2 ? { type: t2 } : t2;
}
function c(t2, n2) {
return { value: t2, context: n2, actions: [], changed: false, matches: a(t2) };
}
function f(t2, n2, e2) {
var r2 = n2, i2 = false;
return [t2.filter((function(t3) {
if ("xstate.assign" === t3.type) {
i2 = true;
var n3 = Object.assign({}, r2);
return "function" == typeof t3.assignment ? n3 = t3.assignment(r2, e2) : Object.keys(t3.assignment).forEach((function(i3) {
n3[i3] = "function" == typeof t3.assignment[i3] ? t3.assignment[i3](r2, e2) : t3.assignment[i3];
})), r2 = n3, false;
}
return true;
})), r2, i2];
}
function s(n2, i2) {
void 0 === i2 && (i2 = {});
var s2 = t(f(r(n2.states[n2.initial].entry).map((function(t2) {
return o(t2, i2.actions);
})), n2.context, e), 2), l2 = s2[0], v2 = s2[1], y = { config: n2, _options: i2, initialState: { value: n2.initial, actions: l2, context: v2, matches: a(n2.initial) }, transition: function(e2, i3) {
var s3, l3, v3 = "string" == typeof e2 ? { value: e2, context: n2.context } : e2, p = v3.value, g = v3.context, d = u(i3), x = n2.states[p];
if (x.on) {
var m = r(x.on[d.type]);
try {
for (var h = (function(t2) {
var n3 = "function" == typeof Symbol && Symbol.iterator, e3 = n3 && t2[n3], r2 = 0;
if (e3) return e3.call(t2);
if (t2 && "number" == typeof t2.length) return { next: function() {
return t2 && r2 >= t2.length && (t2 = void 0), { value: t2 && t2[r2++], done: !t2 };
} };
throw new TypeError(n3 ? "Object is not iterable." : "Symbol.iterator is not defined.");
})(m), b = h.next(); !b.done; b = h.next()) {
var S = b.value;
if (void 0 === S) return c(p, g);
var w = "string" == typeof S ? { target: S } : S, j = w.target, E = w.actions, R = void 0 === E ? [] : E, N = w.cond, O = void 0 === N ? function() {
return true;
} : N, _ = void 0 === j, k = null != j ? j : p, T = n2.states[k];
if (O(g, d)) {
var q = t(f((_ ? r(R) : [].concat(x.exit, R, T.entry).filter((function(t2) {
return t2;
}))).map((function(t2) {
return o(t2, y._options.actions);
})), g, d), 3), z = q[0], A = q[1], B = q[2], C = null != j ? j : p;
return { value: C, context: A, actions: z, changed: j !== p || z.length > 0 || B, matches: a(C) };
}
}
} catch (t2) {
s3 = { error: t2 };
} finally {
try {
b && !b.done && (l3 = h.return) && l3.call(h);
} finally {
if (s3) throw s3.error;
}
}
}
return c(p, g);
} };
return y;
}
var l$1 = function(t2, n2) {
return t2.actions.forEach((function(e2) {
var r2 = e2.exec;
return r2 && r2(t2.context, n2);
}));
};
function v(t2) {
var r2 = t2.initialState, i2 = n.NotStarted, o2 = /* @__PURE__ */ new Set(), c2 = { _machine: t2, send: function(e2) {
i2 === n.Running && (r2 = t2.transition(r2, e2), l$1(r2, u(e2)), o2.forEach((function(t3) {
return t3(r2);
})));
}, subscribe: function(t3) {
return o2.add(t3), t3(r2), { unsubscribe: function() {
return o2.delete(t3);
} };
}, start: function(o3) {
if (o3) {
var u2 = "object" == typeof o3 ? o3 : { context: t2.config.context, value: o3 };
r2 = { value: u2.value, actions: [], context: u2.context, matches: a(u2.value) };
} else r2 = t2.initialState;
return i2 = n.Running, l$1(r2, e), c2;
}, stop: function() {
return i2 = n.Stopped, o2.clear(), c2;
}, get state() {
return r2;
}, get status() {
return i2;
} };
return c2;
}
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React$1.useLayoutEffect : React$1.useEffect;
function useConstant(fn) {
const ref = React$1.useRef();
if (!ref.current) {
ref.current = { v: fn() };
}
return ref.current.v;
}
function getServiceState(service) {
let currentValue;
service.subscribe((state) => currentValue = state).unsubscribe();
return currentValue;
}
function useMachine(stateMachine, options) {
const persistedStateRef = React$1.useRef();
const [service, queue] = useConstant(() => {
const eventQueue = [];
const svc = v(
s(
stateMachine.config,
options ? options : stateMachine._options
)
);
const originalSend = svc.send;
svc.send = (event) => {
if (svc.status === n.NotStarted) {
eventQueue.push(event);
return;
}
originalSend(event);
persistedStateRef.current = svc.state;
};
return [svc, eventQueue];
});
useIsomorphicLayoutEffect(() => {
if (options) {
service._machine._options = options;
}
});
const getSnapshot = React$1.useCallback(() => getServiceState(service), [service]);
const subscribe = React$1.useCallback(
(handleStoreChange) => {
const { unsubscribe } = service.subscribe(handleStoreChange);
return unsubscribe;
},
[service]
);
const storeSnapshot = React$1.useSyncExternalStore(
subscribe,
getSnapshot,
getSnapshot
);
React$1.useEffect(() => {
service.start(persistedStateRef.current);
queue.forEach(service.send);
persistedStateRef.current = service.state;
return () => {
service.stop();
};
}, []);
return [storeSnapshot, service.send, service];
}
function flattenConnection(connection) {
if (!connection) {
const noConnectionErr = `flattenConnection(): needs a 'connection' to flatten, but received '${connection ?? ""}' instead.`;
{
throw new Error(noConnectionErr);
}
}
if ("nodes" in connection) {
return connection.nodes;
}
if ("edges" in connection && Array.isArray(connection.edges)) {
return connection.edges.map((edge) => {
if (!(edge == null ? void 0 : edge.node)) {
throw new Error(
"flattenConnection(): Connection edges must contain nodes"
);
}
return edge.node;
});
}
{
console.warn(
`flattenConnection(): The connection did not contain either "nodes" or "edges.node". Returning an empty array.`
);
}
return [];
}
const CartLineAdd = (cartFragment) => (
/* GraphQL */
`
mutation CartLineAdd(
$cartId: ID!
$lines: [CartLineInput!]!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartCreate = (cartFragment) => (
/* GraphQL */
`
mutation CartCreate(
$input: CartInput!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartCreate(input: $input) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartLineRemove = (cartFragment) => (
/* GraphQL */
`
mutation CartLineRemove(
$cartId: ID!
$lines: [ID!]!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartLinesRemove(cartId: $cartId, lineIds: $lines) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartLineUpdate = (cartFragment) => (
/* GraphQL */
`
mutation CartLineUpdate(
$cartId: ID!
$lines: [CartLineUpdateInput!]!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartLinesUpdate(cartId: $cartId, lines: $lines) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartNoteUpdate = (cartFragment) => (
/* GraphQL */
`
mutation CartNoteUpdate(
$cartId: ID!
$note: String!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartNoteUpdate(cartId: $cartId, note: $note) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartBuyerIdentityUpdate = (cartFragment) => (
/* GraphQL */
`
mutation CartBuyerIdentityUpdate(
$cartId: ID!
$buyerIdentity: CartBuyerIdentityInput!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartAttributesUpdate = (cartFragment) => (
/* GraphQL */
`
mutation CartAttributesUpdate(
$attributes: [AttributeInput!]!
$cartId: ID!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartAttributesUpdate(attributes: $attributes, cartId: $cartId) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartDiscountCodesUpdate = (cartFragment) => (
/* GraphQL */
`
mutation CartDiscountCodesUpdate(
$cartId: ID!
$discountCodes: [String!]!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
cart {
...CartFragment
}
}
}
${cartFragment}
`
);
const CartQuery = (cartFragment) => (
/* GraphQL */
`
query CartQuery(
$id: ID!
$numCartLines: Int = 250
$country: CountryCode = ZZ
$language: LanguageCode
$visitorConsent: VisitorConsent
)
@inContext(
country: $country
language: $language
visitorConsent: $visitorConsent
) {
cart(id: $id) {
...CartFragment
}
}
${cartFragment}
`
);
const defaultCartFragment = (
/* GraphQL */
`
fragment CartFragment on Cart {
id
checkoutUrl
totalQuantity
buyerIdentity {
countryCode
customer {
id
email
firstName
lastName
displayName
}
email
phone
}
lines(first: $numCartLines) {
edges {
node {
id
quantity
attributes {
key
value
}
cost {
totalAmount {
amount
currencyCode
}
compareAtAmountPerQuantity {
amount
currencyCode
}
}
merchandise {
... on ProductVariant {
id
availableForSale
compareAtPrice {
...MoneyFragment
}
price {
...MoneyFragment
}
requiresShipping
title
image {
...ImageFragment
}
product {
handle
title
id
}
selectedOptions {
name
value
}
}
}
}
}
}
cost {
subtotalAmount {
...MoneyFragment
}
totalAmount {
...MoneyFragment
}
totalDutyAmount {
...MoneyFragment
}
totalTaxAmount {
...MoneyFragment
}
}
note
attributes {
key
value
}
discountCodes {
code
applicable
}
}
fragment MoneyFragment on MoneyV2 {
currencyCode
amount
}
fragment ImageFragment on Image {
id
url
altText
width
height
}
`
);
const SFAPI_VERSION = "2026-01";
const MOCK_SHOP_DOMAIN = "mock.shop";
const isMockShop = (domain) => domain.includes(MOCK_SHOP_DOMAIN);
function createStorefrontClient({
storeDomain,
privateStorefrontToken,
publicStorefrontToken,
storefrontApiVersion = SFAPI_VERSION,
contentType
}) {
if (!storeDomain) {
{
storeDomain = MOCK_SHOP_DOMAIN;
warnOnce(
`storeDomain missing, defaulting to ${MOCK_SHOP_DOMAIN}`,
"info"
);
}
}
if (storefrontApiVersion !== SFAPI_VERSION) {
warnOnce(
`The Storefront API version that you're using is different than the version this build of Hydrogen React is targeting.
You may run into unexpected errors if these versions don't match. Received version: "${storefrontApiVersion}"; expected version "${SFAPI_VERSION}"`
);
}
if (!privateStorefrontToken && !globalThis.document && !isMockShop(storeDomain)) {
warnOnce(
`Using a private storefront token is recommended for server environments.
Refer to the authentication https://shopify.dev/api/storefront#authentication documentation for more details.`
);
}
if (privateStorefrontToken && globalThis.document) {
warnOnce(
"You are attempting to use a private token in an environment where it can be easily accessed by anyone.\nThis is a security risk; please use the public token and the `publicStorefrontToken` prop"
);
}
const getShopifyDomain = (overrideProps) => {
const domain = (overrideProps == null ? void 0 : overrideProps.storeDomain) ?? storeDomain;
return domain.includes("://") ? domain : `https://${domain}`;
};
return {
getShopifyDomain,
getStorefrontApiUrl(overrideProps) {
const domain = getShopifyDomain(overrideProps);
const apiUrl = domain + (domain.endsWith("/") ? "api" : "/api");
return `${apiUrl}/${(overrideProps == null ? void 0 : overrideProps.storefrontApiVersion) ?? storefrontApiVersion}/graphql.json`;
},
getPrivateTokenHeaders(overrideProps) {
if (!privateStorefrontToken && !(overrideProps == null ? void 0 : overrideProps.privateStorefrontToken) && !isMockShop(storeDomain)) {
throw new Error(
H2_PREFIX_ERROR + "You did not pass in a `privateStorefrontToken` while using `createStorefrontClient()` or `getPrivateTokenHeaders()`"
);
}
if (!(overrideProps == null ? void 0 : overrideProps.buyerIp)) {
warnOnce(
"It is recommended to pass in the `buyerIp` property which improves analytics and data in the admin."
);
}
const finalContentType = (overrideProps == null ? void 0 : overrideProps.contentType) ?? contentType;
return {
// default to json
"content-type": finalContentType === "graphql" ? "application/graphql" : "application/json",
"X-SDK-Variant": "hydrogen-react",
"X-SDK-Variant-Source": "react",
"X-SDK-Version": storefrontApiVersion,
"Shopify-Storefront-Private-Token": (overrideProps == null ? void 0 : overrideProps.privateStorefrontToken) ?? privateStorefrontToken ?? "",
...(overrideProps == null ? void 0 : overrideProps.buyerIp) ? { "Shopify-Storefront-Buyer-IP": overrideProps.buyerIp } : {}
};
},
getPublicTokenHeaders(overrideProps) {
if (!publicStorefrontToken && !(overrideProps == null ? void 0 : overrideProps.publicStorefrontToken) && !isMockShop(storeDomain)) {
throw new Error(
H2_PREFIX_ERROR + "You did not pass in a `publicStorefrontToken` while using `createStorefrontClient()` or `getPublicTokenHeaders()`"
);
}
const finalContentType = (overrideProps == null ? void 0 : overrideProps.contentType) ?? contentType ?? "json";
return getPublicTokenHeadersRaw(
finalContentType,
storefrontApiVersion,
(overrideProps == null ? void 0 : overrideProps.publicStorefrontToken) ?? publicStorefrontToken ?? ""
);
}
};
}
function getPublicTokenHeadersRaw(contentType, storefrontApiVersion, accessToken) {
return {
// default to json
"content-type": contentType === "graphql" ? "application/graphql" : "application/json",
"X-SDK-Variant": "hydrogen-react",
"X-SDK-Variant-Source": "react",
"X-SDK-Version": storefrontApiVersion,
"X-Shopify-Storefront-Access-Token": accessToken
};
}
const warnings = /* @__PURE__ */ new Set();
const H2_PREFIX_ERROR = "[h2:error:createStorefrontClient] ";
const warnOnce = (string, type = "warn") => {
if (!warnings.has(string)) {
console[type](`[h2:${type}:createStorefrontClient] ` + string);
warnings.add(string);
}
};
const defaultShopifyContext = {
storeDomain: "test",
storefrontToken: "abc123",
storefrontApiVersion: SFAPI_VERSION,
countryIsoCode: "US",
languageIsoCode: "EN",
getStorefrontApiUrl() {
return "";
},
getPublicTokenHeaders() {
return {};
},
getShopifyDomain() {
return "";
}
};
const ShopifyContext = React$1.createContext(
defaultShopifyContext
);
function isSfapiProxyEnabled() {
var _a, _b, _c;
if (typeof window === "undefined") return false;
try {
const navigationEntry = (_b = (_a = window.performance) == null ? void 0 : _a.getEntriesByType) == null ? void 0 : _b.call(
_a,
"navigation"
)[0];
return !!((_c = navigationEntry == null ? void 0 : navigationEntry.serverTiming) == null ? void 0 : _c.some(
(entry) => entry.name === "_sfapi_proxy"
));
} catch (e2) {
return false;
}
}
function ShopifyProvider({
children,
...shopifyConfig
}) {
if (!shopifyConfig.countryIsoCode || !shopifyConfig.languageIsoCode || !shopifyConfig.storeDomain || !shopifyConfig.storefrontToken || !shopifyConfig.storefrontApiVersion) {
throw new Error(
`Please provide the necessary props to '<ShopifyProvider/>'`
);
}
if (shopifyConfig.storefrontApiVersion !== SFAPI_VERSION) {
console.warn(
`<ShopifyProvider/>: This version of Hydrogen React is built for Shopify's Storefront API version ${SFAPI_VERSION}, but it looks like you're using version ${shopifyConfig.storefrontApiVersion}. There may be issues or bugs if you use a mismatched version of Hydrogen React and the Storefront API.`
);
}
const finalConfig = React$1.useMemo(() => {
const sameDomainForStorefrontApi = shopifyConfig.sameDomainForStorefrontApi ?? isSfapiProxyEnabled();
function getShopifyDomain(overrideProps) {
const domain = (overrideProps == null ? void 0 : overrideProps.storeDomain) ?? shopifyConfig.storeDomain;
return domain.includes("://") ? domain : `https://${domain}`;
}
return {
...shopifyConfig,
sameDomainForStorefrontApi,
getPublicTokenHeaders(overrideProps) {
return getPublicTokenHeadersRaw(
overrideProps.contentType,
shopifyConfig.storefrontApiVersion,
overrideProps.storefrontToken ?? shopifyConfig.storefrontToken
);
},
getShopifyDomain,
getStorefrontApiUrl(overrideProps) {
const finalDomainUrl = sameDomainForStorefrontApi && typeof window !== "undefined" ? window.location.origin : getShopifyDomain({
storeDomain: (overrideProps == null ? void 0 : overrideProps.storeDomain) ?? shopifyConfig.storeDomain
});
return `${finalDomainUrl}${finalDomainUrl.endsWith("/") ? "" : "/"}api/${(overrideProps == null ? void 0 : overrideProps.storefrontApiVersion) ?? shopifyConfig.storefrontApiVersion}/graphql.json`;
}
};
}, [shopifyConfig]);
return /* @__PURE__ */ React.createElement(ShopifyContext.Provider, { value: finalConfig }, children);
}
function useShop() {
const shopContext = React$1.useContext(ShopifyContext);
if (!shopContext) {
throw new Error(`'useShop()' must be a descendent of <ShopifyProvider/>`);
}
return shopContext;
}
const CART_ID_STORAGE_KEY = "shopifyCartId";
const SHOPIFY_STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
const SHOPIFY_STOREFRONT_Y_HEADER = "Shopify-Storefront-Y";
const SHOPIFY_STOREFRONT_S_HEADER = "Shopify-Storefront-S";
const SHOPIFY_Y = "_shopify_y";
const SHOPIFY_S = "_shopify_s";
const SHOPIFY_VISIT_TOKEN_HEADER = "X-Shopify-VisitToken";
const SHOPIFY_UNIQUE_TOKEN_HEADER = "X-Shopify-UniqueToken";
const cachedTrackingValues = { current: null };
function getTrackingValues() {
var _a, _b, _c;
let trackingValues;
if (typeof window !== "undefined" && typeof window.performance !== "undefined") {
try {
const resourceRE = /^https?:\/\/([^/]+)(\/api\/(?:unstable|2\d{3}-\d{2})\/graphql\.json(?=$|\?))?/;
const entries = performance.getEntriesByType(
"resource"
);
let matchedValues;
for (let i2 = entries.length - 1; i2 >= 0; i2--) {
const entry = entries[i2];
if (entry.initiatorType !== "fetch") continue;
const currentHost = window.location.host;
const match = entry.name.match(resourceRE);
if (!match) continue;
const [, matchedHost, sfapiPath] = match;
const isMatch = (
// Same origin (exact host match)
matchedHost === currentHost || // Subdomain with SFAPI path
sfapiPath && (matchedHost == null ? void 0 : matchedHost.endsWith(`.${currentHost}`))
);
if (isMatch) {
const values = extractFromPerformanceEntry(entry);
if (values) {
matchedValues = values;
break;
}
}
}
if (matchedValues) {
trackingValues = matchedValues;
}
if (trackingValues) {
cachedTrackingValues.current = trackingValues;
} else if (cachedTrackingValues.current) {
trackingValues = cachedTrackingValues.current;
}
if (!trackingValues) {
const navigationEntries = performance.getEntriesByType(
"navigation"
)[0];
trackingValues = extractFromPerformanceEntry(navigationEntries, false);
}
} catch {
}
}
if (!trackingValues) {
const cookie = (
// Read from arguments to avoid declaring parameters in this function signature.
// This logic is only used internally from `getShopifyCookies` and will be deprecated.
typeof arguments[0] === "string" ? arguments[0] : typeof document !== "undefined" ? document.cookie : ""
);
trackingValues = {
uniqueToken: ((_a = cookie.match(/\b_shopify_y=([^;]+)/)) == null ? void 0 : _a[1]) || "",
visitToken: ((_b = cookie.match(/\b_shopify_s=([^;]+)/)) == null ? void 0 : _b[1]) || "",
consent: ((_c = cookie.match(/\b_tracking_consent=([^;]+)/)) == null ? void 0 : _c[1]) || ""
};
}
return trackingValues;
}
function extractFromPerformanceEntry(entry, isConsentRequired = true) {
let uniqueToken = "";
let visitToken = "";
let consent = "";
const serverTiming = entry.serverTiming;
if (serverTiming && serverTiming.length >= 3) {
for (let i2 = serverTiming.length - 1; i2 >= 0; i2--) {
const { name, description } = serverTiming[i2];
if (!name || !description) continue;
if (name === "_y") {
uniqueToken = description;
} else if (name === "_s") {
visitToken = description;
} else if (name === "_cmp") {
consent = description;
}
if (uniqueToken && visitToken && consent) break;
}
}
return uniqueToken && visitToken && (isConsentRequired ? consent : true) ? { uniqueToken, visitToken, consent } : void 0;
}
function useCartFetch() {
const {
storefrontId,
getPublicTokenHeaders,
getStorefrontApiUrl,
sameDomainForStorefrontApi
} = useShop();
return React$1.useCallback(
({
query,
variables
}) => {
const headers = getPublicTokenHeaders({ contentType: "json" });
if (storefrontId) {
headers[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
}
if (!sameDomainForStorefrontApi) {
const { uniqueToken, visitToken } = getTrackingValues();
if (uniqueToken) {
headers[SHOPIFY_STOREFRONT_Y_HEADER] = uniqueToken;
headers[SHOPIFY_UNIQUE_TOKEN_HEADER] = uniqueToken;
}
if (visitToken) {
headers[SHOPIFY_STOREFRONT_S_HEADER] = visitToken;
headers[SHOPIFY_VISIT_TOKEN_HEADER] = visitToken;
}
}
return fetch(getStorefrontApiUrl(), {
method: "POST",
headers,
body: JSON.stringify({
query: query.toString(),
variables
})
}).then(
(res) => res.json()
).catch((error) => {
return {
data: void 0,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
errors: error == null ? void 0 : error.toString()
};
});
},
[
getPublicTokenHeaders,
storefrontId,
getStorefrontApiUrl,
sameDomainForStorefrontApi
]
);
}
function useCartActions({
numCartLines,
cartFragment,
countryCode = "US",
languageCode = "EN"
}) {
const fetchCart = useCartFetch();
const cartFetch = React$1.useCallback(
(cartId) => {
return fetchCart({
query: CartQuery(cartFragment),
variables: {
id: cartId,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[fetchCart, cartFragment, numCartLines, countryCode, languageCode]
);
const cartCreate = React$1.useCallback(
(cart) => {
return fetchCart({
query: CartCreate(cartFragment),
variables: {
input: cart,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const cartLineAdd = React$1.useCallback(
(cartId, lines) => {
return fetchCart({
query: CartLineAdd(cartFragment),
variables: {
cartId,
lines,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const cartLineUpdate = React$1.useCallback(
(cartId, lines) => {
return fetchCart({
query: CartLineUpdate(cartFragment),
variables: {
cartId,
lines,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const cartLineRemove = React$1.useCallback(
(cartId, lines) => {
return fetchCart({
query: CartLineRemove(cartFragment),
variables: {
cartId,
lines,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const noteUpdate = React$1.useCallback(
(cartId, note) => {
return fetchCart({
query: CartNoteUpdate(cartFragment),
variables: {
cartId,
note,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[fetchCart, cartFragment, numCartLines, countryCode, languageCode]
);
const buyerIdentityUpdate = React$1.useCallback(
(cartId, buyerIdentity) => {
return fetchCart({
query: CartBuyerIdentityUpdate(cartFragment),
variables: {
cartId,
buyerIdentity,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const cartAttributesUpdate = React$1.useCallback(
(cartId, attributes) => {
return fetchCart({
query: CartAttributesUpdate(cartFragment),
variables: {
cartId,
attributes,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
const discountCodesUpdate = React$1.useCallback(
(cartId, discountCodes) => {
return fetchCart({
query: CartDiscountCodesUpdate(cartFragment),
variables: {
cartId,
discountCodes,
numCartLines,
country: countryCode,
language: languageCode
}
});
},
[cartFragment, countryCode, fetchCart, numCartLines, languageCode]
);
return React$1.useMemo(
() => ({
cartFetch,
cartCreate,
cartLineAdd,
cartLineUpdate,
cartLineRemove,
noteUpdate,
buyerIdentityUpdate,
cartAttributesUpdate,
discountCodesUpdate,
cartFragment
}),
[
cartFetch,
cartCreate,
cartLineAdd,
cartLineUpdate,
cartLineRemove,
noteUpdate,
buyerIdentityUpdate,
cartAttributesUpdate,
discountCodesUpdate,
cartFragment
]
);
}
function invokeCart(action, options) {
return {
entry: [
...(options == null ? void 0 : options.entryActions) || [],
i({
lastValidCart: (context) => context == null ? void 0 : context.cart
}),
"onCartActionEntry",
"onCartActionOptimisticUI",
action
],
on: {
RESOLVE: {
target: (options == null ? void 0 : options.resolveTarget) || "idle",
actions: [
i({
prevCart: (context) => context == null ? void 0 : context.lastValidCart,
cart: (_, event) => {
var _a;
return (_a = event == null ? void 0 : event.payload) == null ? void 0 : _a.cart;
},
rawCartResult: (_, event) => {
var _a;
return (_a = event == null ? void 0 : event.payload) == null ? void 0 : _a.rawCartResult;
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
errors: (_) => void 0
})
]
},
ERROR: {
target: (options == null ? void 0 : options.errorTarget) || "error",
actions: [
i({
prevCart: (context) => context == null ? void 0 : context.lastValidCart,
cart: (context) => context == null ? void 0 : context.lastValidCart,
errors: (_, event) => {
var _a;
return (_a = event == null ? void 0 : event.payload) == null ? void 0 : _a.errors;
}
})
]
},
CART_COMPLETED: {
target: "cartCompleted",
actions: i({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
prevCart: (_) => void 0,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
cart: (_) => void 0,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
lastValidCart: (_) => void 0,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
rawCartResult: (_) => void 0,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
errors: (_) => void 0
})
}
},
exit: ["onCartActionComplete", ...(options == null ? void 0 : options.exitActions) || []]
};
}
const INITIALIZING_CART_EVENTS = {
CART_FETCH: {
target: "cartFetching"
},
CART_CREATE: {
target: "cartCreating"
},
CART_SET: {
target: "idle",
actions: [
i({
rawCartResult: (_, event) => event.payload.cart,
cart: (_, event) => cartFromGraphQL(event.payload.cart)
})
]
}
};
const UPDATING_CART_EVENTS = {
CARTLINE_ADD: {
target: "cartLineAdding"
},
CARTLINE_UPDATE: {
target: "cartLineUpdating"
},
CARTLINE_REMOVE: {
target: "cartLineRemoving"
},
NOTE_UPDATE: {
target: "noteUpdating"
},
BUYER_IDENTITY_UPDATE: {
target: "buyerIdentityUpdating"
},
CART_ATTRIBUTES_UPDATE: {
target: "cartAttributesUpdating"
},
DISCOUNT_CODES_UPDATE: {
target: "discountCodesUpdating"
}
};
function createCartMachine(initialCart) {
return s({
id: "Cart",
initial: initialCart ? "idle" : "uninitialized",
context: {
cart: initialCart && cartFromGraphQL(initialCart)
},
states: {
uninitialized: {
on: INITIALIZING_CART_EVENTS
},
cartCompleted: {
on: INITIALIZING_CART_EVENTS
},
initializationError: {
on: INITIALIZING_CART_EVENTS
},
idle: {
on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS }
},
error: {
on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS }
},
cartFetching: invokeCart("cartFetchAction", {
errorTarget: "initializationError"
}),
cartCreating: invokeCart("cartCreateAction", {
errorTarget: "initializationError"
}),
cartLineRemoving: invokeCart("cartLineRemoveAction"),
cartLineUpdating: invokeCart("cartLineUpdateAction"),
cartLineAdding: invokeCart("cartLineAddAction"),
noteUpdating: invokeCart("noteUpdateAction"),
buyerIdentityUpdating: invokeCart("buyerIdentityUpdateAction"),
cartAttributesUpdating: invokeCart("cartAttributesUpdateAction"),
discountCodesUpdating: invokeCart("discountCodesUpdateAction")
}
});
}
function useCartAPIStateMachine({
numCartLines,
onCartActionEntry,
onCartActionOptimisticUI,
onCartActionComplete,
data: cart,
cartFragment,
countryCode,
languageCode
}) {
const {
cartFetch,
cartCreate,
cartLineAdd,
cartLineUpdate,
cartLineRemove,
noteUpdate,
buyerIdentityUpdate,
cartAttributesUpdate,
discountCodesUpdate
} = useCartActions({
numCartLines,
cartFragment,
countryCode,
languageCode
});
const cartMachine = React$1.useMemo(() => createCartMachine(cart), [cart]);
const [state, send, service] = useMachine(cartMachine, {
actions: {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartFetchAction: async (_, event) => {
var _a;
if (event.type !== "CART_FETCH") return;
const { data, errors } = await cartFetch((_a = event == null ? void 0 : event.payload) == null ? void 0 : _a.cartId);
const resultEvent = eventFromFetchResult(event, data == null ? void 0 : data.cart, errors);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartCreateAction: async (_, event) => {
var _a;
if (event.type !== "CART_CREATE") return;
const { data, errors } = await cartCreate(event == null ? void 0 : event.payload);
const resultEvent = eventFromFetchResult(
event,
(_a = data == null ? void 0 : data.cartCreate) == null ? void 0 : _a.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartLineAddAction: async (context, event) => {
var _a, _b;
if (event.type !== "CARTLINE_ADD" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id)) return;
const { data, errors } = await cartLineAdd(
context.cart.id,
event.payload.lines
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartLinesAdd) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartLineUpdateAction: async (context, event) => {
var _a, _b;
if (event.type !== "CARTLINE_UPDATE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id)) return;
const { data, errors } = await cartLineUpdate(
context.cart.id,
event.payload.lines
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartLinesUpdate) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartLineRemoveAction: async (context, event) => {
var _a, _b;
if (event.type !== "CARTLINE_REMOVE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id)) return;
const { data, errors } = await cartLineRemove(
context.cart.id,
event.payload.lines
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartLinesRemove) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
noteUpdateAction: async (context, event) => {
var _a, _b;
if (event.type !== "NOTE_UPDATE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id)) return;
const { data, errors } = await noteUpdate(
context.cart.id,
event.payload.note
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartNoteUpdate) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buyerIdentityUpdateAction: async (context, event) => {
var _a, _b;
if (event.type !== "BUYER_IDENTITY_UPDATE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id))
return;
const { data, errors } = await buyerIdentityUpdate(
context.cart.id,
event.payload.buyerIdentity
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartBuyerIdentityUpdate) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
cartAttributesUpdateAction: async (context, event) => {
var _a, _b;
if (event.type !== "CART_ATTRIBUTES_UPDATE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id))
return;
const { data, errors } = await cartAttributesUpdate(
context.cart.id,
event.payload.attributes
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartAttributesUpdate) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
discountCodesUpdateAction: async (context, event) => {
var _a, _b;
if (event.type !== "DISCOUNT_CODES_UPDATE" || !((_a = context == null ? void 0 : context.cart) == null ? void 0 : _a.id))
return;
const { data, errors } = await discountCodesUpdate(
context.cart.id,
event.payload.discountCodes
);
const resultEvent = eventFromFetchResult(
event,
(_b = data == null ? void 0 : data.cartDiscountCodesUpdate) == null ? void 0 : _b.cart,
errors
);
send(resultEvent);
},
...onCartActionEntry && {
onCartActionEntry: (context, event) => {
if (isCartActionEvent(event)) {
onCartActionEntry(context, event);
}
}
},
...onCartActionOptimisticUI && {
onCartActionOptimisticUI: i((context, event) => {
return onCartActionOptimisticUI(context, event);
})
},
...onCartActionComplete && {
onCartActionComplete: (context, event) => {
if (isCartFetchResultEvent(event)) {
onCartActionComplete(context, event);
}
}
}
}
});
return React$1.useMemo(() => [state, send, service], [state, send, service]);
}
function cartFromGraphQL(cart) {
return {
...cart,
lines: flattenConnection(cart == null ? void 0 : cart.lines),
note: cart.note ?? void 0
};
}
function eventFromFetchResult(cartActionEvent, cart, errors) {
if (errors) {
return { type: "ERROR", payload: { errors, cartActionEvent } };
}
if (!cart) {
return {
type: "CART_COMPLETED",
payload: {
cartActionEvent
}
};
}
return {
type: "RESOLVE",
payload: {
cart: cartFromGraphQL(cart),
rawCartResult: cart,
cartActionEvent
}
};
}
function isCartActionEvent(event) {
return event.type === "CART_CREATE" || event.type === "CARTLINE_ADD" || event.type === "CARTLINE_UPDATE" || event.type === "CARTLINE_REMOVE" || event.type === "NOTE_UPDATE" || event.type === "BUYER_IDENTITY_UPDATE" || event.type === "CART_ATTRIBUTES_UPDATE" || event.type === "DISCOUNT_CODES_UPDATE";
}
function isCartFetchResultEvent(event) {
return event.type === "RESOLVE" || event.type === "ERROR" || event.type === "CART_COMPLETED";
}
const CartContext = React$1.createContext(null);
function useCart() {
const context = React$1.useContext(CartContext);
if (!context) {
throw new Error("Expected a Cart Context, but no Cart Context was found");
}
return context;
}
function CartProvider({
children,
numCartLines,
onCreate,
onLineAdd,
onLineRemove,
onLineUpdate,
onNoteUpdate,
onBuyerIdentityUpdate,
onAttributesUpdate,
onDiscountCodesUpdate,
onCreateComplete,
onLineAddComplete,
onLineRemoveComplete,
onLineUpdateComplete,
onNoteUpdateComplete,
onBuyerIdentityUpdateComplete,
onAttributesUpdateComplete,
onDiscountCodesUpdateComplete,
data: cart,
cartFragment = defaultCartFragment,
customerAccessToken,
countryCode,
languageCode
}) {
var _a, _b, _c, _d, _e, _f, _g;
const shop = useShop();
if (!shop)
throw new Error(
"<CartProvider> needs to be a descendant of <ShopifyProvider>"
);
countryCode = (countryCode ?? shop.countryIsoCode ?? "US").toUpperCase();
languageCode = (languageCode ?? shop.languageIsoCode ?? "EN").toUpperCase();
if (countryCode) countryCode = countryCode.toUpperCase();
const [prevCountryCode, setPrevCountryCode] = React$1.useState(countryCode);
const [prevCustomerAccessToken, setPrevCustomerAccessToken] = React$1.useState(customerAccessToken);
const customerOverridesCountryCode = React$1.useRef(false);
if (prevCountryCode !== countryCode || prevCustomerAccessToken !== customerAccessToken) {
setPrevCountryCode(countryCode);
setPrevCustomerAccessToken(customerAccessToken);
customerOverridesCountryCode.current = false;
}
const [cartState, cartSend] = useCartAPIStateMachine({
numCartLines,
data: cart,
cartFragment,
countryCode,
languageCode,
onCartActionEntry(_, event) {
try {
switch (event.type) {
case "CART_CREATE":
return onCreate == null ? void 0 : onCreate();
case "CARTLINE_ADD":
return onLineAdd == null ? void 0 : onLineAdd();
case "CARTLINE_REMOVE":
return onLineRemove == null ? void 0 : onLineRemove();
case "CARTLINE_UPDATE":
return onLineUpdate == null ? void 0 : onLineUpdate();
case "NOTE_UPDATE":
return onNoteUpdate == null ? void 0 : onNoteUpdate();
case "BUYER_IDENTITY_UPDATE":
return onBuyerIdentityUpdate == null ? void 0 : onBuyerIdentityUpdate();
case "CART_ATTRIBUTES_UPDATE":
return onAttributesUpdate == null ? void 0 : onAttributesUpdate();
case "DISCOUNT_CODES_UPDATE":
return onDiscountCodesUpdate == null ? void 0 : onDiscountCodesUpdate();
}
} catch (error) {
console.error("Cart entry action failed", error);
}
},
onCartActionOptimisticUI(context, event) {
var _a2, _b2, _c2, _d2;
if (!context.cart) return { ...context };
switch (event.type) {
case "CARTLINE_REMOVE":
return {
...context,
cart: {
...context.cart,
lines: (_b2 = (_a2 = c