@shopify/hydrogen 2023.7.13 → 2023.10.0

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.
@@ -109,13 +109,9 @@ function hashKey(queryKey) {
109
109
  for (const key of rawKeys) {
110
110
  if (key != null) {
111
111
  if (typeof key === "object") {
112
- if (!!key.body && typeof key.body === "string") {
113
- hash += key.body;
114
- } else {
115
- hash += JSON.stringify(key);
116
- }
112
+ hash += JSON.stringify(key);
117
113
  } else {
118
- hash += key;
114
+ hash += key.toString();
119
115
  }
120
116
  }
121
117
  }
@@ -175,6 +171,16 @@ function CacheLong(overrideOptions) {
175
171
  ...overrideOptions
176
172
  };
177
173
  }
174
+ function CacheDefault(overrideOptions) {
175
+ guardExpirableModeType(overrideOptions);
176
+ return {
177
+ mode: PUBLIC,
178
+ maxAge: 1,
179
+ staleWhileRevalidate: 86399,
180
+ // 1 second less than 24 hours
181
+ ...overrideOptions
182
+ };
183
+ }
178
184
  function CacheCustom(overrideOptions) {
179
185
  return overrideOptions;
180
186
  }
@@ -196,7 +202,7 @@ function getCacheControlSetting(userCacheOptions, options) {
196
202
  ...options
197
203
  };
198
204
  } else {
199
- return userCacheOptions || CacheShort();
205
+ return userCacheOptions || CacheDefault();
200
206
  }
201
207
  }
202
208
  function generateDefaultCacheControlHeader(userCacheOptions) {
@@ -267,7 +273,7 @@ function getKeyUrl(key) {
267
273
  return `https://shopify.dev/?${key}`;
268
274
  }
269
275
  function getCacheOption(userCacheOptions) {
270
- return userCacheOptions || CacheShort();
276
+ return userCacheOptions || CacheDefault();
271
277
  }
272
278
  async function getItemFromCache(cache, key) {
273
279
  if (!cache)
@@ -451,26 +457,63 @@ var warnOnce = (string) => {
451
457
  };
452
458
 
453
459
  // src/version.ts
454
- var LIB_VERSION = "2023.7.13";
460
+ var LIB_VERSION = "2023.10.0";
461
+
462
+ // src/utils/graphql.ts
463
+ function minifyQuery(string) {
464
+ return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
465
+ }
466
+ var IS_QUERY_RE = /(^|}\s)query[\s({]/im;
467
+ var IS_MUTATION_RE = /(^|}\s)mutation[\s({]/im;
468
+ function assertQuery(query, callerName) {
469
+ if (!IS_QUERY_RE.test(query)) {
470
+ throw new Error(`[h2:error:${callerName}] Can only execute queries`);
471
+ }
472
+ }
473
+ function assertMutation(query, callerName) {
474
+ if (!IS_MUTATION_RE.test(query)) {
475
+ throw new Error(`[h2:error:${callerName}] Can only execute mutations`);
476
+ }
477
+ }
478
+ function throwGraphQLError({
479
+ response,
480
+ errors,
481
+ type,
482
+ query,
483
+ queryVariables,
484
+ ErrorConstructor = Error,
485
+ client = "storefront"
486
+ }) {
487
+ const requestId = response.headers.get("x-request-id");
488
+ const errorMessage = (typeof errors === "string" ? errors : errors?.map?.((error) => error.message).join("\n")) || `API response error: ${response.status}`;
489
+ throw new ErrorConstructor(
490
+ `[h2:error:${client}.${type}] ` + errorMessage + (requestId ? ` - Request ID: ${requestId}` : ""),
491
+ {
492
+ cause: JSON.stringify({
493
+ errors,
494
+ requestId,
495
+ ...{
496
+ graphql: {
497
+ query,
498
+ variables: JSON.stringify(queryVariables)
499
+ }
500
+ }
501
+ })
502
+ }
503
+ );
504
+ }
455
505
 
456
506
  // src/storefront.ts
457
507
  var StorefrontApiError = class extends Error {
458
508
  };
459
509
  var isStorefrontApiError = (error) => error instanceof StorefrontApiError;
460
- var isQueryRE = /(^|}\s)query[\s({]/im;
461
- var isMutationRE = /(^|}\s)mutation[\s({]/im;
462
- function minifyQuery(string) {
463
- return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
464
- }
465
510
  var defaultI18n = { language: "EN", country: "US" };
466
511
  function createStorefrontClient(options) {
467
512
  const {
468
513
  storefrontHeaders,
469
514
  cache,
470
515
  waitUntil,
471
- buyerIp,
472
516
  i18n,
473
- requestGroupId,
474
517
  storefrontId,
475
518
  ...clientOptions
476
519
  } = options;
@@ -489,9 +532,9 @@ function createStorefrontClient(options) {
489
532
  const getHeaders = clientOptions.privateStorefrontToken ? getPrivateTokenHeaders : getPublicTokenHeaders;
490
533
  const defaultHeaders = getHeaders({
491
534
  contentType: "json",
492
- buyerIp: storefrontHeaders?.buyerIp || buyerIp
535
+ buyerIp: storefrontHeaders?.buyerIp || ""
493
536
  });
494
- defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || requestGroupId || generateUUID();
537
+ defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
495
538
  if (storefrontId)
496
539
  defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
497
540
  defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
@@ -502,11 +545,14 @@ function createStorefrontClient(options) {
502
545
  if (cookies[hydrogenReact.SHOPIFY_S])
503
546
  defaultHeaders[hydrogenReact.SHOPIFY_STOREFRONT_S_HEADER] = cookies[hydrogenReact.SHOPIFY_S];
504
547
  }
505
- if (!storefrontHeaders) {
506
- warnOnce(
507
- H2_PREFIX_WARN + "`requestGroupId` and `buyerIp` will be deprecated in the next calendar release. Please use `getStorefrontHeaders`"
508
- );
509
- }
548
+ const cacheKeyHeader = JSON.stringify({
549
+ "content-type": defaultHeaders["content-type"],
550
+ "user-agent": defaultHeaders["user-agent"],
551
+ [SDK_VARIANT_HEADER]: defaultHeaders[SDK_VARIANT_HEADER],
552
+ [SDK_VARIANT_SOURCE_HEADER]: defaultHeaders[SDK_VARIANT_SOURCE_HEADER],
553
+ [SDK_VERSION_HEADER]: defaultHeaders[SDK_VERSION_HEADER],
554
+ [STOREFRONT_ACCESS_TOKEN_HEADER]: defaultHeaders[STOREFRONT_ACCESS_TOKEN_HEADER]
555
+ });
510
556
  async function fetchStorefrontApi({
511
557
  query,
512
558
  mutation,
@@ -535,22 +581,13 @@ function createStorefrontClient(options) {
535
581
  };
536
582
  const cacheKey = [
537
583
  url,
538
- {
539
- method: requestInit.method,
540
- headers: {
541
- "content-type": defaultHeaders["content-type"],
542
- "user-agent": defaultHeaders["user-agent"],
543
- [SDK_VARIANT_HEADER]: defaultHeaders[SDK_VARIANT_HEADER],
544
- [SDK_VARIANT_SOURCE_HEADER]: defaultHeaders[SDK_VARIANT_SOURCE_HEADER],
545
- [SDK_VERSION_HEADER]: defaultHeaders[SDK_VERSION_HEADER],
546
- [STOREFRONT_ACCESS_TOKEN_HEADER]: defaultHeaders[STOREFRONT_ACCESS_TOKEN_HEADER]
547
- },
548
- body: requestInit.body
549
- }
584
+ requestInit.method,
585
+ cacheKeyHeader,
586
+ requestInit.body
550
587
  ];
551
588
  const [body, response] = await fetchWithServerCache(url, requestInit, {
552
589
  cacheInstance: mutation ? void 0 : cache,
553
- cache: cacheOptions || CacheShort(),
590
+ cache: cacheOptions || CacheDefault(),
554
591
  cacheKey,
555
592
  shouldCacheResponse: checkGraphQLErrors,
556
593
  waitUntil,
@@ -574,11 +611,11 @@ function createStorefrontClient(options) {
574
611
  } catch (_e) {
575
612
  errors2 = [{ message: body }];
576
613
  }
577
- throwError({ ...errorOptions, errors: errors2 });
614
+ throwGraphQLError({ ...errorOptions, errors: errors2 });
578
615
  }
579
616
  const { data, errors } = body;
580
617
  if (errors?.length) {
581
- throwError({
618
+ throwGraphQLError({
582
619
  ...errorOptions,
583
620
  errors,
584
621
  ErrorConstructor: StorefrontApiError
@@ -604,11 +641,7 @@ function createStorefrontClient(options) {
604
641
  */
605
642
  query: (query, payload) => {
606
643
  query = minifyQuery(query);
607
- if (isMutationRE.test(query)) {
608
- throw new Error(
609
- "[h2:error:storefront.query] Cannot execute mutations"
610
- );
611
- }
644
+ assertQuery(query, "storefront.query");
612
645
  const result = fetchStorefrontApi({
613
646
  ...payload,
614
647
  query
@@ -632,11 +665,7 @@ function createStorefrontClient(options) {
632
665
  */
633
666
  mutate: (mutation, payload) => {
634
667
  mutation = minifyQuery(mutation);
635
- if (isQueryRE.test(mutation)) {
636
- throw new Error(
637
- "[h2:error:storefront.mutate] Cannot execute queries"
638
- );
639
- }
668
+ assertMutation(mutation, "storefront.mutate");
640
669
  const result = fetchStorefrontApi({
641
670
  ...payload,
642
671
  mutation
@@ -679,32 +708,6 @@ function createStorefrontClient(options) {
679
708
  }
680
709
  };
681
710
  }
682
- function throwError({
683
- response,
684
- errors,
685
- type,
686
- query,
687
- queryVariables,
688
- ErrorConstructor = Error
689
- }) {
690
- const requestId = response.headers.get("x-request-id");
691
- const errorMessage = (typeof errors === "string" ? errors : errors?.map?.((error) => error.message).join("\n")) || `API response error: ${response.status}`;
692
- throw new ErrorConstructor(
693
- `[h2:error:storefront.${type}] ` + errorMessage + (requestId ? ` - Request ID: ${requestId}` : ""),
694
- {
695
- cause: JSON.stringify({
696
- errors,
697
- requestId,
698
- ...{
699
- graphql: {
700
- query,
701
- variables: JSON.stringify(queryVariables)
702
- }
703
- }
704
- })
705
- }
706
- );
707
- }
708
711
 
709
712
  // src/utils/request.ts
710
713
  function getHeader(request, key) {
@@ -884,76 +887,115 @@ var graphiqlLoader = async function graphiqlLoader2({
884
887
  const url = storefront.getApiUrl();
885
888
  const accessToken = storefront.getPublicTokenHeaders()["X-Shopify-Storefront-Access-Token"];
886
889
  const favicon = `https://avatars.githubusercontent.com/u/12972006?s=48&v=4`;
890
+ const html = String.raw;
887
891
  return new Response(
888
- `
889
- <!DOCTYPE html>
890
- <html lang="en">
891
- <head>
892
- <title>GraphiQL</title>
893
- <link rel="icon" type="image/x-icon" href="${favicon}">
894
- <style>
895
- body {
896
- height: 100%;
897
- margin: 0;
898
- width: 100%;
899
- overflow: hidden;
900
- }
892
+ html`
893
+ <!DOCTYPE html>
894
+ <html lang="en">
895
+ <head>
896
+ <title>GraphiQL</title>
897
+ <link rel="icon" type="image/x-icon" href="${favicon}" />
898
+ <style>
899
+ body {
900
+ height: 100%;
901
+ margin: 0;
902
+ width: 100%;
903
+ overflow: hidden;
904
+ background-color: hsl(219, 29%, 18%);
905
+ }
901
906
 
902
- #graphiql {
903
- height: 100vh;
904
- }
905
- </style>
907
+ #graphiql {
908
+ height: 100vh;
909
+ }
906
910
 
907
- <script
908
- crossorigin
909
- src="https://unpkg.com/react@18/umd/react.development.js"
910
- ></script>
911
- <script
912
- crossorigin
913
- src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
914
- ></script>
915
- <link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
916
- </head>
911
+ #graphiql > .placeholder {
912
+ color: slategray;
913
+ width: fit-content;
914
+ margin: 40px auto;
915
+ font-family: Arial;
916
+ }
917
+ </style>
917
918
 
918
- <body>
919
- <div id="graphiql">Loading...</div>
920
- <script
921
- src="https://unpkg.com/graphiql@3/graphiql.min.js"
922
- type="application/javascript"
923
- ></script>
924
- <script>
925
- const windowUrl = new URL(document.URL);
919
+ <script
920
+ crossorigin
921
+ src="https://unpkg.com/react@18/umd/react.development.js"
922
+ ></script>
923
+ <script
924
+ crossorigin
925
+ src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
926
+ ></script>
927
+ <link
928
+ rel="stylesheet"
929
+ href="https://unpkg.com/graphiql@3/graphiql.min.css"
930
+ />
931
+ <link
932
+ rel="stylesheet"
933
+ href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css"
934
+ />
935
+ </head>
926
936
 
927
- let query = '{\\n shop {\\n name\\n }\\n}';
928
- if (windowUrl.searchParams.has('query')) {
929
- query = decodeURIComponent(windowUrl.searchParams.get('query') ?? '');
930
- // Prettify query
931
- if (query) query = GraphiQL.GraphQL.print(GraphiQL.GraphQL.parse(query));
932
- }
937
+ <body>
938
+ <div id="graphiql">
939
+ <div class="placeholder">Loading GraphiQL...</div>
940
+ </div>
933
941
 
934
- let variables;
935
- if (windowUrl.searchParams.has('variables')) {
936
- variables = decodeURIComponent(windowUrl.searchParams.get('variables') ?? '');
937
- // Prettify variables
938
- if (variables) variables = JSON.stringify(JSON.parse(variables), null, 2);
939
- }
942
+ <script
943
+ src="https://unpkg.com/graphiql@3/graphiql.min.js"
944
+ type="application/javascript"
945
+ crossorigin="anonymous"
946
+ ></script>
947
+ <script
948
+ src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
949
+ type="application/javascript"
950
+ crossorigin="anonymous"
951
+ ></script>
940
952
 
941
- const root = ReactDOM.createRoot(document.getElementById('graphiql'));
942
- root.render(
943
- React.createElement(GraphiQL, {
944
- fetcher: GraphiQL.createFetcher({
945
- url: '${url}',
946
- headers: {'X-Shopify-Storefront-Access-Token': '${accessToken}'}
947
- }),
948
- defaultEditorToolsVisibility: true,
949
- query,
950
- variables
951
- }),
952
- );
953
- </script>
954
- </body>
955
- </html>
956
- `,
953
+ <script>
954
+ const windowUrl = new URL(document.URL);
955
+
956
+ let query = '{ shop { name } }';
957
+ if (windowUrl.searchParams.has('query')) {
958
+ query = decodeURIComponent(
959
+ windowUrl.searchParams.get('query') ?? query,
960
+ );
961
+ }
962
+
963
+ // Prettify query
964
+ query = GraphiQL.GraphQL.print(GraphiQL.GraphQL.parse(query));
965
+
966
+ let variables;
967
+ if (windowUrl.searchParams.has('variables')) {
968
+ variables = decodeURIComponent(
969
+ windowUrl.searchParams.get('variables') ?? '',
970
+ );
971
+ }
972
+
973
+ // Prettify variables
974
+ if (variables) {
975
+ variables = JSON.stringify(JSON.parse(variables), null, 2);
976
+ }
977
+
978
+ const root = ReactDOM.createRoot(
979
+ document.getElementById('graphiql'),
980
+ );
981
+ root.render(
982
+ React.createElement(GraphiQL, {
983
+ fetcher: GraphiQL.createFetcher({
984
+ url: '${url}',
985
+ headers: {
986
+ 'X-Shopify-Storefront-Access-Token': '${accessToken}',
987
+ },
988
+ }),
989
+ defaultEditorToolsVisibility: true,
990
+ query,
991
+ variables,
992
+ plugins: [GraphiQLPluginExplorer.explorerPlugin()],
993
+ }),
994
+ );
995
+ </script>
996
+ </body>
997
+ </html>
998
+ `,
957
999
  { status: 200, headers: { "content-type": "text/html" } }
958
1000
  );
959
1001
  };
@@ -1297,7 +1339,7 @@ function Seo({ debug }) {
1297
1339
  return [];
1298
1340
  }
1299
1341
  if (handleSeo) {
1300
- return recursivelyInvokeOrReturn(handle.seo, routeData);
1342
+ return recursivelyInvokeOrReturn(handleSeo, routeData);
1301
1343
  } else {
1302
1344
  return [loaderSeo];
1303
1345
  }
@@ -1563,6 +1605,456 @@ function getPaginationVariables(request, options = { pageBy: 20 }) {
1563
1605
  const variables = isPrevious ? prevPage : nextPage;
1564
1606
  return variables;
1565
1607
  }
1608
+
1609
+ // src/customer/BadRequest.ts
1610
+ var BadRequest = class extends Response {
1611
+ constructor(message, helpMessage) {
1612
+ if (helpMessage && true) {
1613
+ console.error("Customer Account API Error: " + helpMessage);
1614
+ }
1615
+ super(`Bad request: ${message}`, { status: 400 });
1616
+ }
1617
+ };
1618
+
1619
+ // src/customer/auth.helpers.ts
1620
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
1621
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
1622
+ function redirect2(path, options = {}) {
1623
+ const headers = options.headers ? new Headers(options.headers) : new Headers({});
1624
+ headers.set("location", path);
1625
+ return new Response(null, { status: options.status || 302, headers });
1626
+ }
1627
+ async function refreshToken({
1628
+ session,
1629
+ customerAccountId,
1630
+ customerAccountUrl,
1631
+ origin
1632
+ }) {
1633
+ const newBody = new URLSearchParams();
1634
+ const refreshToken2 = session.get("refresh_token");
1635
+ if (!refreshToken2)
1636
+ throw new BadRequest(
1637
+ "Unauthorized",
1638
+ "No refresh_token in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1639
+ );
1640
+ newBody.append("grant_type", "refresh_token");
1641
+ newBody.append("refresh_token", refreshToken2);
1642
+ newBody.append("client_id", customerAccountId);
1643
+ const headers = {
1644
+ "content-type": "application/x-www-form-urlencoded",
1645
+ "User-Agent": USER_AGENT,
1646
+ Origin: origin
1647
+ };
1648
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1649
+ method: "POST",
1650
+ headers,
1651
+ body: newBody
1652
+ });
1653
+ if (!response.ok) {
1654
+ const text = await response.text();
1655
+ throw new Response(text, {
1656
+ status: response.status,
1657
+ headers: {
1658
+ "Content-Type": "text/html; charset=utf-8"
1659
+ }
1660
+ });
1661
+ }
1662
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
1663
+ session.set("customer_authorization_code_token", access_token);
1664
+ session.set(
1665
+ "expires_at",
1666
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
1667
+ );
1668
+ session.set("id_token", id_token);
1669
+ session.set("refresh_token", refresh_token);
1670
+ const customerAccessToken = await exchangeAccessToken(
1671
+ session,
1672
+ customerAccountId,
1673
+ customerAccountUrl,
1674
+ origin
1675
+ );
1676
+ session.set("customer_access_token", customerAccessToken);
1677
+ }
1678
+ function clearSession(session) {
1679
+ session.unset("code-verifier");
1680
+ session.unset("customer_authorization_code_token");
1681
+ session.unset("expires_at");
1682
+ session.unset("id_token");
1683
+ session.unset("refresh_token");
1684
+ session.unset("customer_access_token");
1685
+ session.unset("state");
1686
+ session.unset("nonce");
1687
+ }
1688
+ async function checkExpires({
1689
+ locks,
1690
+ expiresAt,
1691
+ session,
1692
+ customerAccountId,
1693
+ customerAccountUrl,
1694
+ origin
1695
+ }) {
1696
+ if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
1697
+ try {
1698
+ if (!locks.refresh)
1699
+ locks.refresh = refreshToken({
1700
+ session,
1701
+ customerAccountId,
1702
+ customerAccountUrl,
1703
+ origin
1704
+ });
1705
+ await locks.refresh;
1706
+ delete locks.refresh;
1707
+ } catch (error) {
1708
+ clearSession(session);
1709
+ if (error && error.status !== 401) {
1710
+ throw error;
1711
+ } else {
1712
+ throw new BadRequest(
1713
+ "Unauthorized",
1714
+ "Login before querying the Customer Account API."
1715
+ );
1716
+ }
1717
+ }
1718
+ }
1719
+ }
1720
+ async function generateCodeVerifier() {
1721
+ const rando = generateRandomCode();
1722
+ return base64UrlEncode(rando);
1723
+ }
1724
+ async function generateCodeChallenge(codeVerifier) {
1725
+ const digestOp = await crypto.subtle.digest(
1726
+ { name: "SHA-256" },
1727
+ new TextEncoder().encode(codeVerifier)
1728
+ );
1729
+ const hash = convertBufferToString(digestOp);
1730
+ return base64UrlEncode(hash);
1731
+ }
1732
+ function generateRandomCode() {
1733
+ const array = new Uint8Array(32);
1734
+ crypto.getRandomValues(array);
1735
+ return String.fromCharCode.apply(null, Array.from(array));
1736
+ }
1737
+ function base64UrlEncode(str) {
1738
+ const base64 = btoa(str);
1739
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1740
+ }
1741
+ function convertBufferToString(hash) {
1742
+ const uintArray = new Uint8Array(hash);
1743
+ const numberArray = Array.from(uintArray);
1744
+ return String.fromCharCode(...numberArray);
1745
+ }
1746
+ async function generateState() {
1747
+ const timestamp = Date.now().toString();
1748
+ const randomString = Math.random().toString(36).substring(2);
1749
+ return timestamp + randomString;
1750
+ }
1751
+ async function exchangeAccessToken(session, customerAccountId, customerAccountUrl, origin) {
1752
+ const clientId = customerAccountId;
1753
+ const accessToken = session.get("customer_authorization_code_token");
1754
+ if (!accessToken)
1755
+ throw new BadRequest(
1756
+ "Unauthorized",
1757
+ "No access token found in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1758
+ );
1759
+ const body = new URLSearchParams();
1760
+ body.append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
1761
+ body.append("client_id", clientId);
1762
+ body.append("audience", CUSTOMER_API_CLIENT_ID);
1763
+ body.append("subject_token", accessToken);
1764
+ body.append(
1765
+ "subject_token_type",
1766
+ "urn:ietf:params:oauth:token-type:access_token"
1767
+ );
1768
+ body.append("scopes", "https://api.customers.com/auth/customer.graphql");
1769
+ const headers = {
1770
+ "content-type": "application/x-www-form-urlencoded",
1771
+ "User-Agent": USER_AGENT,
1772
+ Origin: origin
1773
+ };
1774
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1775
+ method: "POST",
1776
+ headers,
1777
+ body
1778
+ });
1779
+ const data = await response.json();
1780
+ if (data.error) {
1781
+ throw new BadRequest(data.error_description);
1782
+ }
1783
+ return data.access_token;
1784
+ }
1785
+ function getNonce(token) {
1786
+ return decodeJwt(token).payload.nonce;
1787
+ }
1788
+ function decodeJwt(token) {
1789
+ const [header, payload, signature] = token.split(".");
1790
+ const decodedHeader = JSON.parse(atob(header));
1791
+ const decodedPayload = JSON.parse(atob(payload));
1792
+ return {
1793
+ header: decodedHeader,
1794
+ payload: decodedPayload,
1795
+ signature
1796
+ };
1797
+ }
1798
+
1799
+ // src/csp/nonce.ts
1800
+ function generateNonce() {
1801
+ return toHexString(randomUint8Array());
1802
+ }
1803
+ function randomUint8Array() {
1804
+ try {
1805
+ return crypto.getRandomValues(new Uint8Array(16));
1806
+ } catch (e) {
1807
+ return new Uint8Array(16).map(() => Math.random() * 255 | 0);
1808
+ }
1809
+ }
1810
+ function toHexString(byteArray) {
1811
+ return Array.from(byteArray, function(byte) {
1812
+ return ("0" + (byte & 255).toString(16)).slice(-2);
1813
+ }).join("");
1814
+ }
1815
+
1816
+ // src/customer/customer.ts
1817
+ function createCustomerClient({
1818
+ session,
1819
+ customerAccountId,
1820
+ customerAccountUrl,
1821
+ customerApiVersion = "2023-10",
1822
+ request,
1823
+ waitUntil
1824
+ }) {
1825
+ if (!request?.url) {
1826
+ throw new Error(
1827
+ "[h2:error:createCustomerClient] The request object does not contain a URL."
1828
+ );
1829
+ }
1830
+ const url = new URL(request.url);
1831
+ const origin = url.protocol === "http:" ? url.origin.replace("http", "https") : url.origin;
1832
+ const locks = {};
1833
+ const logSubRequestEvent = (query, startTime) => {
1834
+ globalThis.__H2O_LOG_EVENT?.({
1835
+ eventType: "subrequest",
1836
+ url: `https://shopify.dev/?${hashKey([
1837
+ `Customer Account `,
1838
+ /((query|mutation) [^\s\(]+)/g.exec(query)?.[0] || query.substring(0, 10)
1839
+ ])}`,
1840
+ startTime,
1841
+ waitUntil,
1842
+ ...getDebugHeaders(request)
1843
+ });
1844
+ } ;
1845
+ async function fetchCustomerAPI({
1846
+ query,
1847
+ type,
1848
+ variables = {}
1849
+ }) {
1850
+ const accessToken = session.get("customer_access_token");
1851
+ const expiresAt = session.get("expires_at");
1852
+ if (!accessToken || !expiresAt)
1853
+ throw new BadRequest(
1854
+ "Unauthorized",
1855
+ "Login before querying the Customer Account API."
1856
+ );
1857
+ await checkExpires({
1858
+ locks,
1859
+ expiresAt,
1860
+ session,
1861
+ customerAccountId,
1862
+ customerAccountUrl,
1863
+ origin
1864
+ });
1865
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1866
+ const response = await fetch(
1867
+ `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`,
1868
+ {
1869
+ method: "POST",
1870
+ headers: {
1871
+ "Content-Type": "application/json",
1872
+ "User-Agent": USER_AGENT,
1873
+ Origin: origin,
1874
+ Authorization: accessToken
1875
+ },
1876
+ body: JSON.stringify({
1877
+ operationName: "SomeQuery",
1878
+ query,
1879
+ variables
1880
+ })
1881
+ }
1882
+ );
1883
+ logSubRequestEvent?.(query, startTime);
1884
+ const body = await response.text();
1885
+ const errorOptions = {
1886
+ response,
1887
+ type,
1888
+ query,
1889
+ queryVariables: variables,
1890
+ errors: void 0,
1891
+ client: "customer"
1892
+ };
1893
+ if (!response.ok) {
1894
+ let errors;
1895
+ try {
1896
+ errors = parseJSON(body);
1897
+ } catch (_e) {
1898
+ errors = [{ message: body }];
1899
+ }
1900
+ throwGraphQLError({ ...errorOptions, errors });
1901
+ }
1902
+ try {
1903
+ return parseJSON(body).data;
1904
+ } catch (e) {
1905
+ throwGraphQLError({ ...errorOptions, errors: [{ message: body }] });
1906
+ }
1907
+ }
1908
+ return {
1909
+ login: async () => {
1910
+ const loginUrl = new URL(customerAccountUrl + "/auth/oauth/authorize");
1911
+ const state = await generateState();
1912
+ const nonce = await generateNonce();
1913
+ loginUrl.searchParams.set("client_id", customerAccountId);
1914
+ loginUrl.searchParams.set("scope", "openid email");
1915
+ loginUrl.searchParams.append("response_type", "code");
1916
+ loginUrl.searchParams.append("redirect_uri", origin + "/authorize");
1917
+ loginUrl.searchParams.set(
1918
+ "scope",
1919
+ "openid email https://api.customers.com/auth/customer.graphql"
1920
+ );
1921
+ loginUrl.searchParams.append("state", state);
1922
+ loginUrl.searchParams.append("nonce", nonce);
1923
+ const verifier = await generateCodeVerifier();
1924
+ const challenge = await generateCodeChallenge(verifier);
1925
+ session.set("code-verifier", verifier);
1926
+ session.set("state", state);
1927
+ session.set("nonce", nonce);
1928
+ loginUrl.searchParams.append("code_challenge", challenge);
1929
+ loginUrl.searchParams.append("code_challenge_method", "S256");
1930
+ return redirect2(loginUrl.toString(), {
1931
+ headers: {
1932
+ "Set-Cookie": await session.commit()
1933
+ }
1934
+ });
1935
+ },
1936
+ logout: async () => {
1937
+ const idToken = session.get("id_token");
1938
+ clearSession(session);
1939
+ return redirect2(
1940
+ `${customerAccountUrl}/auth/logout?id_token_hint=${idToken}`,
1941
+ {
1942
+ status: 302,
1943
+ headers: {
1944
+ "Set-Cookie": await session.commit()
1945
+ }
1946
+ }
1947
+ );
1948
+ },
1949
+ isLoggedIn: async () => {
1950
+ const expiresAt = session.get("expires_at");
1951
+ if (!session.get("customer_access_token") || !expiresAt)
1952
+ return false;
1953
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1954
+ try {
1955
+ await checkExpires({
1956
+ locks,
1957
+ expiresAt,
1958
+ session,
1959
+ customerAccountId,
1960
+ customerAccountUrl,
1961
+ origin
1962
+ });
1963
+ logSubRequestEvent?.(" check expires", startTime);
1964
+ } catch {
1965
+ return false;
1966
+ }
1967
+ return true;
1968
+ },
1969
+ mutate(mutation, options) {
1970
+ mutation = minifyQuery(mutation);
1971
+ assertMutation(mutation, "customer.mutate");
1972
+ return fetchCustomerAPI({ query: mutation, type: "mutation", ...options });
1973
+ },
1974
+ query(query, options) {
1975
+ query = minifyQuery(query);
1976
+ assertQuery(query, "customer.query");
1977
+ return fetchCustomerAPI({ query, type: "query", ...options });
1978
+ },
1979
+ authorize: async (redirectPath = "/") => {
1980
+ const code = url.searchParams.get("code");
1981
+ const state = url.searchParams.get("state");
1982
+ if (!code || !state) {
1983
+ clearSession(session);
1984
+ throw new BadRequest(
1985
+ "Unauthorized",
1986
+ "No code or state parameter found in the redirect URL."
1987
+ );
1988
+ }
1989
+ if (session.get("state") !== state) {
1990
+ clearSession(session);
1991
+ throw new BadRequest(
1992
+ "Unauthorized",
1993
+ "The session state does not match the state parameter. Make sure that the session is configured correctly and passed to `createCustomerClient`."
1994
+ );
1995
+ }
1996
+ const clientId = customerAccountId;
1997
+ const body = new URLSearchParams();
1998
+ body.append("grant_type", "authorization_code");
1999
+ body.append("client_id", clientId);
2000
+ body.append("redirect_uri", origin + "/authorize");
2001
+ body.append("code", code);
2002
+ const codeVerifier = session.get("code-verifier");
2003
+ if (!codeVerifier)
2004
+ throw new BadRequest(
2005
+ "Unauthorized",
2006
+ "No code verifier found in the session. Make sure that the session is configured correctly and passed to `createCustomerClient`."
2007
+ );
2008
+ body.append("code_verifier", codeVerifier);
2009
+ const headers = {
2010
+ "content-type": "application/x-www-form-urlencoded",
2011
+ "User-Agent": USER_AGENT,
2012
+ Origin: origin
2013
+ };
2014
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
2015
+ method: "POST",
2016
+ headers,
2017
+ body
2018
+ });
2019
+ if (!response.ok) {
2020
+ throw new Response(await response.text(), {
2021
+ status: response.status,
2022
+ headers: {
2023
+ "Content-Type": "text/html; charset=utf-8"
2024
+ }
2025
+ });
2026
+ }
2027
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
2028
+ const sessionNonce = session.get("nonce");
2029
+ const responseNonce = await getNonce(id_token);
2030
+ if (sessionNonce !== responseNonce) {
2031
+ throw new BadRequest(
2032
+ "Unauthorized",
2033
+ `Returned nonce does not match: ${sessionNonce} !== ${responseNonce}`
2034
+ );
2035
+ }
2036
+ session.set("customer_authorization_code_token", access_token);
2037
+ session.set(
2038
+ "expires_at",
2039
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
2040
+ );
2041
+ session.set("id_token", id_token);
2042
+ session.set("refresh_token", refresh_token);
2043
+ const customerAccessToken = await exchangeAccessToken(
2044
+ session,
2045
+ customerAccountId,
2046
+ customerAccountUrl,
2047
+ origin
2048
+ );
2049
+ session.set("customer_access_token", customerAccessToken);
2050
+ return redirect2(redirectPath, {
2051
+ headers: {
2052
+ "Set-Cookie": await session.commit()
2053
+ }
2054
+ });
2055
+ }
2056
+ };
2057
+ }
1566
2058
  var INPUT_NAME = "cartFormInput";
1567
2059
  function CartForm({
1568
2060
  children,
@@ -2256,10 +2748,10 @@ function createCartHandler(options) {
2256
2748
  },
2257
2749
  deleteMetafield: cartMetafieldDeleteDefault(mutateOptions)
2258
2750
  };
2259
- if ("customMethods__unstable" in options) {
2751
+ if ("customMethods" in options) {
2260
2752
  return {
2261
2753
  ...methods,
2262
- ...options.customMethods__unstable ?? {}
2754
+ ...options.customMethods ?? {}
2263
2755
  };
2264
2756
  } else {
2265
2757
  return methods;
@@ -2356,25 +2848,6 @@ function useVariantPath(handle, productPath) {
2356
2848
  };
2357
2849
  }, [pathname, search, handle, productPath]);
2358
2850
  }
2359
-
2360
- // src/csp/nonce.ts
2361
- function generateNonce() {
2362
- return toHexString(randomUint8Array());
2363
- }
2364
- function randomUint8Array() {
2365
- try {
2366
- return crypto.getRandomValues(new Uint8Array(16));
2367
- } catch (e) {
2368
- return new Uint8Array(16).map(() => Math.random() * 255 | 0);
2369
- }
2370
- }
2371
- function toHexString(byteArray) {
2372
- return Array.from(byteArray, function(byte) {
2373
- return ("0" + (byte & 255).toString(16)).slice(-2);
2374
- }).join("");
2375
- }
2376
-
2377
- // src/csp/csp.ts
2378
2851
  var NonceContext = react.createContext(void 0);
2379
2852
  var NonceProvider = NonceContext.Provider;
2380
2853
  var useNonce = () => react.useContext(NonceContext);
@@ -2427,9 +2900,8 @@ var Script = react.forwardRef(
2427
2900
  function useOptimisticData(identifier) {
2428
2901
  const fetchers = react$1.useFetchers();
2429
2902
  const data = {};
2430
- for (const fetcher of fetchers) {
2431
- const formData = fetcher.submission?.formData;
2432
- if (formData && formData.get("optimistic-identifier") === identifier) {
2903
+ for (const { formData } of fetchers) {
2904
+ if (formData?.get("optimistic-identifier") === identifier) {
2433
2905
  try {
2434
2906
  if (formData.has("optimistic-data")) {
2435
2907
  const dataInForm = JSON.parse(
@@ -2466,7 +2938,7 @@ function OptimisticInput({ id, data }) {
2466
2938
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
2467
2939
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
2468
2940
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
2469
- //! @see https://shopify.dev/docs/api/storefront/2023-07/mutations/cartMetafieldDelete
2941
+ //! @see https://shopify.dev/docs/api/storefront/2023-10/mutations/cartMetafieldDelete
2470
2942
 
2471
2943
  Object.defineProperty(exports, 'AnalyticsEventName', {
2472
2944
  enumerable: true,
@@ -2580,6 +3052,7 @@ exports.cartSelectedDeliveryOptionsUpdateDefault = cartSelectedDeliveryOptionsUp
2580
3052
  exports.cartSetIdDefault = cartSetIdDefault;
2581
3053
  exports.createCartHandler = createCartHandler;
2582
3054
  exports.createContentSecurityPolicy = createContentSecurityPolicy;
3055
+ exports.createCustomerClient__unstable = createCustomerClient;
2583
3056
  exports.createStorefrontClient = createStorefrontClient;
2584
3057
  exports.createWithCache = createWithCache;
2585
3058
  exports.generateCacheControlHeader = generateCacheControlHeader;