@shopify/hydrogen 2023.7.13 → 2023.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.1";
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
  };
@@ -1059,14 +1101,15 @@ function generateSeoTags(seoInput) {
1059
1101
  if (!content) {
1060
1102
  break;
1061
1103
  }
1104
+ const urlWithoutParams = content.split("?")[0];
1062
1105
  tagResults.push(
1063
1106
  generateTag("link", {
1064
1107
  rel: "canonical",
1065
- href: content
1108
+ href: urlWithoutParams
1066
1109
  }),
1067
1110
  generateTag("meta", {
1068
1111
  property: "og:url",
1069
- content
1112
+ content: urlWithoutParams
1070
1113
  })
1071
1114
  );
1072
1115
  break;
@@ -1297,7 +1340,7 @@ function Seo({ debug }) {
1297
1340
  return [];
1298
1341
  }
1299
1342
  if (handleSeo) {
1300
- return recursivelyInvokeOrReturn(handle.seo, routeData);
1343
+ return recursivelyInvokeOrReturn(handleSeo, routeData);
1301
1344
  } else {
1302
1345
  return [loaderSeo];
1303
1346
  }
@@ -1563,6 +1606,456 @@ function getPaginationVariables(request, options = { pageBy: 20 }) {
1563
1606
  const variables = isPrevious ? prevPage : nextPage;
1564
1607
  return variables;
1565
1608
  }
1609
+
1610
+ // src/customer/BadRequest.ts
1611
+ var BadRequest = class extends Response {
1612
+ constructor(message, helpMessage) {
1613
+ if (helpMessage && true) {
1614
+ console.error("Customer Account API Error: " + helpMessage);
1615
+ }
1616
+ super(`Bad request: ${message}`, { status: 400 });
1617
+ }
1618
+ };
1619
+
1620
+ // src/customer/auth.helpers.ts
1621
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
1622
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
1623
+ function redirect2(path, options = {}) {
1624
+ const headers = options.headers ? new Headers(options.headers) : new Headers({});
1625
+ headers.set("location", path);
1626
+ return new Response(null, { status: options.status || 302, headers });
1627
+ }
1628
+ async function refreshToken({
1629
+ session,
1630
+ customerAccountId,
1631
+ customerAccountUrl,
1632
+ origin
1633
+ }) {
1634
+ const newBody = new URLSearchParams();
1635
+ const refreshToken2 = session.get("refresh_token");
1636
+ if (!refreshToken2)
1637
+ throw new BadRequest(
1638
+ "Unauthorized",
1639
+ "No refresh_token in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1640
+ );
1641
+ newBody.append("grant_type", "refresh_token");
1642
+ newBody.append("refresh_token", refreshToken2);
1643
+ newBody.append("client_id", customerAccountId);
1644
+ const headers = {
1645
+ "content-type": "application/x-www-form-urlencoded",
1646
+ "User-Agent": USER_AGENT,
1647
+ Origin: origin
1648
+ };
1649
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1650
+ method: "POST",
1651
+ headers,
1652
+ body: newBody
1653
+ });
1654
+ if (!response.ok) {
1655
+ const text = await response.text();
1656
+ throw new Response(text, {
1657
+ status: response.status,
1658
+ headers: {
1659
+ "Content-Type": "text/html; charset=utf-8"
1660
+ }
1661
+ });
1662
+ }
1663
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
1664
+ session.set("customer_authorization_code_token", access_token);
1665
+ session.set(
1666
+ "expires_at",
1667
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
1668
+ );
1669
+ session.set("id_token", id_token);
1670
+ session.set("refresh_token", refresh_token);
1671
+ const customerAccessToken = await exchangeAccessToken(
1672
+ session,
1673
+ customerAccountId,
1674
+ customerAccountUrl,
1675
+ origin
1676
+ );
1677
+ session.set("customer_access_token", customerAccessToken);
1678
+ }
1679
+ function clearSession(session) {
1680
+ session.unset("code-verifier");
1681
+ session.unset("customer_authorization_code_token");
1682
+ session.unset("expires_at");
1683
+ session.unset("id_token");
1684
+ session.unset("refresh_token");
1685
+ session.unset("customer_access_token");
1686
+ session.unset("state");
1687
+ session.unset("nonce");
1688
+ }
1689
+ async function checkExpires({
1690
+ locks,
1691
+ expiresAt,
1692
+ session,
1693
+ customerAccountId,
1694
+ customerAccountUrl,
1695
+ origin
1696
+ }) {
1697
+ if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
1698
+ try {
1699
+ if (!locks.refresh)
1700
+ locks.refresh = refreshToken({
1701
+ session,
1702
+ customerAccountId,
1703
+ customerAccountUrl,
1704
+ origin
1705
+ });
1706
+ await locks.refresh;
1707
+ delete locks.refresh;
1708
+ } catch (error) {
1709
+ clearSession(session);
1710
+ if (error && error.status !== 401) {
1711
+ throw error;
1712
+ } else {
1713
+ throw new BadRequest(
1714
+ "Unauthorized",
1715
+ "Login before querying the Customer Account API."
1716
+ );
1717
+ }
1718
+ }
1719
+ }
1720
+ }
1721
+ async function generateCodeVerifier() {
1722
+ const rando = generateRandomCode();
1723
+ return base64UrlEncode(rando);
1724
+ }
1725
+ async function generateCodeChallenge(codeVerifier) {
1726
+ const digestOp = await crypto.subtle.digest(
1727
+ { name: "SHA-256" },
1728
+ new TextEncoder().encode(codeVerifier)
1729
+ );
1730
+ const hash = convertBufferToString(digestOp);
1731
+ return base64UrlEncode(hash);
1732
+ }
1733
+ function generateRandomCode() {
1734
+ const array = new Uint8Array(32);
1735
+ crypto.getRandomValues(array);
1736
+ return String.fromCharCode.apply(null, Array.from(array));
1737
+ }
1738
+ function base64UrlEncode(str) {
1739
+ const base64 = btoa(str);
1740
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1741
+ }
1742
+ function convertBufferToString(hash) {
1743
+ const uintArray = new Uint8Array(hash);
1744
+ const numberArray = Array.from(uintArray);
1745
+ return String.fromCharCode(...numberArray);
1746
+ }
1747
+ async function generateState() {
1748
+ const timestamp = Date.now().toString();
1749
+ const randomString = Math.random().toString(36).substring(2);
1750
+ return timestamp + randomString;
1751
+ }
1752
+ async function exchangeAccessToken(session, customerAccountId, customerAccountUrl, origin) {
1753
+ const clientId = customerAccountId;
1754
+ const accessToken = session.get("customer_authorization_code_token");
1755
+ if (!accessToken)
1756
+ throw new BadRequest(
1757
+ "Unauthorized",
1758
+ "No access token found in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1759
+ );
1760
+ const body = new URLSearchParams();
1761
+ body.append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
1762
+ body.append("client_id", clientId);
1763
+ body.append("audience", CUSTOMER_API_CLIENT_ID);
1764
+ body.append("subject_token", accessToken);
1765
+ body.append(
1766
+ "subject_token_type",
1767
+ "urn:ietf:params:oauth:token-type:access_token"
1768
+ );
1769
+ body.append("scopes", "https://api.customers.com/auth/customer.graphql");
1770
+ const headers = {
1771
+ "content-type": "application/x-www-form-urlencoded",
1772
+ "User-Agent": USER_AGENT,
1773
+ Origin: origin
1774
+ };
1775
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1776
+ method: "POST",
1777
+ headers,
1778
+ body
1779
+ });
1780
+ const data = await response.json();
1781
+ if (data.error) {
1782
+ throw new BadRequest(data.error_description);
1783
+ }
1784
+ return data.access_token;
1785
+ }
1786
+ function getNonce(token) {
1787
+ return decodeJwt(token).payload.nonce;
1788
+ }
1789
+ function decodeJwt(token) {
1790
+ const [header, payload, signature] = token.split(".");
1791
+ const decodedHeader = JSON.parse(atob(header));
1792
+ const decodedPayload = JSON.parse(atob(payload));
1793
+ return {
1794
+ header: decodedHeader,
1795
+ payload: decodedPayload,
1796
+ signature
1797
+ };
1798
+ }
1799
+
1800
+ // src/csp/nonce.ts
1801
+ function generateNonce() {
1802
+ return toHexString(randomUint8Array());
1803
+ }
1804
+ function randomUint8Array() {
1805
+ try {
1806
+ return crypto.getRandomValues(new Uint8Array(16));
1807
+ } catch (e) {
1808
+ return new Uint8Array(16).map(() => Math.random() * 255 | 0);
1809
+ }
1810
+ }
1811
+ function toHexString(byteArray) {
1812
+ return Array.from(byteArray, function(byte) {
1813
+ return ("0" + (byte & 255).toString(16)).slice(-2);
1814
+ }).join("");
1815
+ }
1816
+
1817
+ // src/customer/customer.ts
1818
+ function createCustomerClient({
1819
+ session,
1820
+ customerAccountId,
1821
+ customerAccountUrl,
1822
+ customerApiVersion = "2023-10",
1823
+ request,
1824
+ waitUntil
1825
+ }) {
1826
+ if (!request?.url) {
1827
+ throw new Error(
1828
+ "[h2:error:createCustomerClient] The request object does not contain a URL."
1829
+ );
1830
+ }
1831
+ const url = new URL(request.url);
1832
+ const origin = url.protocol === "http:" ? url.origin.replace("http", "https") : url.origin;
1833
+ const locks = {};
1834
+ const logSubRequestEvent = (query, startTime) => {
1835
+ globalThis.__H2O_LOG_EVENT?.({
1836
+ eventType: "subrequest",
1837
+ url: `https://shopify.dev/?${hashKey([
1838
+ `Customer Account `,
1839
+ /((query|mutation) [^\s\(]+)/g.exec(query)?.[0] || query.substring(0, 10)
1840
+ ])}`,
1841
+ startTime,
1842
+ waitUntil,
1843
+ ...getDebugHeaders(request)
1844
+ });
1845
+ } ;
1846
+ async function fetchCustomerAPI({
1847
+ query,
1848
+ type,
1849
+ variables = {}
1850
+ }) {
1851
+ const accessToken = session.get("customer_access_token");
1852
+ const expiresAt = session.get("expires_at");
1853
+ if (!accessToken || !expiresAt)
1854
+ throw new BadRequest(
1855
+ "Unauthorized",
1856
+ "Login before querying the Customer Account API."
1857
+ );
1858
+ await checkExpires({
1859
+ locks,
1860
+ expiresAt,
1861
+ session,
1862
+ customerAccountId,
1863
+ customerAccountUrl,
1864
+ origin
1865
+ });
1866
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1867
+ const response = await fetch(
1868
+ `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`,
1869
+ {
1870
+ method: "POST",
1871
+ headers: {
1872
+ "Content-Type": "application/json",
1873
+ "User-Agent": USER_AGENT,
1874
+ Origin: origin,
1875
+ Authorization: accessToken
1876
+ },
1877
+ body: JSON.stringify({
1878
+ operationName: "SomeQuery",
1879
+ query,
1880
+ variables
1881
+ })
1882
+ }
1883
+ );
1884
+ logSubRequestEvent?.(query, startTime);
1885
+ const body = await response.text();
1886
+ const errorOptions = {
1887
+ response,
1888
+ type,
1889
+ query,
1890
+ queryVariables: variables,
1891
+ errors: void 0,
1892
+ client: "customer"
1893
+ };
1894
+ if (!response.ok) {
1895
+ let errors;
1896
+ try {
1897
+ errors = parseJSON(body);
1898
+ } catch (_e) {
1899
+ errors = [{ message: body }];
1900
+ }
1901
+ throwGraphQLError({ ...errorOptions, errors });
1902
+ }
1903
+ try {
1904
+ return parseJSON(body).data;
1905
+ } catch (e) {
1906
+ throwGraphQLError({ ...errorOptions, errors: [{ message: body }] });
1907
+ }
1908
+ }
1909
+ return {
1910
+ login: async () => {
1911
+ const loginUrl = new URL(customerAccountUrl + "/auth/oauth/authorize");
1912
+ const state = await generateState();
1913
+ const nonce = await generateNonce();
1914
+ loginUrl.searchParams.set("client_id", customerAccountId);
1915
+ loginUrl.searchParams.set("scope", "openid email");
1916
+ loginUrl.searchParams.append("response_type", "code");
1917
+ loginUrl.searchParams.append("redirect_uri", origin + "/authorize");
1918
+ loginUrl.searchParams.set(
1919
+ "scope",
1920
+ "openid email https://api.customers.com/auth/customer.graphql"
1921
+ );
1922
+ loginUrl.searchParams.append("state", state);
1923
+ loginUrl.searchParams.append("nonce", nonce);
1924
+ const verifier = await generateCodeVerifier();
1925
+ const challenge = await generateCodeChallenge(verifier);
1926
+ session.set("code-verifier", verifier);
1927
+ session.set("state", state);
1928
+ session.set("nonce", nonce);
1929
+ loginUrl.searchParams.append("code_challenge", challenge);
1930
+ loginUrl.searchParams.append("code_challenge_method", "S256");
1931
+ return redirect2(loginUrl.toString(), {
1932
+ headers: {
1933
+ "Set-Cookie": await session.commit()
1934
+ }
1935
+ });
1936
+ },
1937
+ logout: async () => {
1938
+ const idToken = session.get("id_token");
1939
+ clearSession(session);
1940
+ return redirect2(
1941
+ `${customerAccountUrl}/auth/logout?id_token_hint=${idToken}`,
1942
+ {
1943
+ status: 302,
1944
+ headers: {
1945
+ "Set-Cookie": await session.commit()
1946
+ }
1947
+ }
1948
+ );
1949
+ },
1950
+ isLoggedIn: async () => {
1951
+ const expiresAt = session.get("expires_at");
1952
+ if (!session.get("customer_access_token") || !expiresAt)
1953
+ return false;
1954
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1955
+ try {
1956
+ await checkExpires({
1957
+ locks,
1958
+ expiresAt,
1959
+ session,
1960
+ customerAccountId,
1961
+ customerAccountUrl,
1962
+ origin
1963
+ });
1964
+ logSubRequestEvent?.(" check expires", startTime);
1965
+ } catch {
1966
+ return false;
1967
+ }
1968
+ return true;
1969
+ },
1970
+ mutate(mutation, options) {
1971
+ mutation = minifyQuery(mutation);
1972
+ assertMutation(mutation, "customer.mutate");
1973
+ return fetchCustomerAPI({ query: mutation, type: "mutation", ...options });
1974
+ },
1975
+ query(query, options) {
1976
+ query = minifyQuery(query);
1977
+ assertQuery(query, "customer.query");
1978
+ return fetchCustomerAPI({ query, type: "query", ...options });
1979
+ },
1980
+ authorize: async (redirectPath = "/") => {
1981
+ const code = url.searchParams.get("code");
1982
+ const state = url.searchParams.get("state");
1983
+ if (!code || !state) {
1984
+ clearSession(session);
1985
+ throw new BadRequest(
1986
+ "Unauthorized",
1987
+ "No code or state parameter found in the redirect URL."
1988
+ );
1989
+ }
1990
+ if (session.get("state") !== state) {
1991
+ clearSession(session);
1992
+ throw new BadRequest(
1993
+ "Unauthorized",
1994
+ "The session state does not match the state parameter. Make sure that the session is configured correctly and passed to `createCustomerClient`."
1995
+ );
1996
+ }
1997
+ const clientId = customerAccountId;
1998
+ const body = new URLSearchParams();
1999
+ body.append("grant_type", "authorization_code");
2000
+ body.append("client_id", clientId);
2001
+ body.append("redirect_uri", origin + "/authorize");
2002
+ body.append("code", code);
2003
+ const codeVerifier = session.get("code-verifier");
2004
+ if (!codeVerifier)
2005
+ throw new BadRequest(
2006
+ "Unauthorized",
2007
+ "No code verifier found in the session. Make sure that the session is configured correctly and passed to `createCustomerClient`."
2008
+ );
2009
+ body.append("code_verifier", codeVerifier);
2010
+ const headers = {
2011
+ "content-type": "application/x-www-form-urlencoded",
2012
+ "User-Agent": USER_AGENT,
2013
+ Origin: origin
2014
+ };
2015
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
2016
+ method: "POST",
2017
+ headers,
2018
+ body
2019
+ });
2020
+ if (!response.ok) {
2021
+ throw new Response(await response.text(), {
2022
+ status: response.status,
2023
+ headers: {
2024
+ "Content-Type": "text/html; charset=utf-8"
2025
+ }
2026
+ });
2027
+ }
2028
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
2029
+ const sessionNonce = session.get("nonce");
2030
+ const responseNonce = await getNonce(id_token);
2031
+ if (sessionNonce !== responseNonce) {
2032
+ throw new BadRequest(
2033
+ "Unauthorized",
2034
+ `Returned nonce does not match: ${sessionNonce} !== ${responseNonce}`
2035
+ );
2036
+ }
2037
+ session.set("customer_authorization_code_token", access_token);
2038
+ session.set(
2039
+ "expires_at",
2040
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
2041
+ );
2042
+ session.set("id_token", id_token);
2043
+ session.set("refresh_token", refresh_token);
2044
+ const customerAccessToken = await exchangeAccessToken(
2045
+ session,
2046
+ customerAccountId,
2047
+ customerAccountUrl,
2048
+ origin
2049
+ );
2050
+ session.set("customer_access_token", customerAccessToken);
2051
+ return redirect2(redirectPath, {
2052
+ headers: {
2053
+ "Set-Cookie": await session.commit()
2054
+ }
2055
+ });
2056
+ }
2057
+ };
2058
+ }
1566
2059
  var INPUT_NAME = "cartFormInput";
1567
2060
  function CartForm({
1568
2061
  children,
@@ -2256,10 +2749,10 @@ function createCartHandler(options) {
2256
2749
  },
2257
2750
  deleteMetafield: cartMetafieldDeleteDefault(mutateOptions)
2258
2751
  };
2259
- if ("customMethods__unstable" in options) {
2752
+ if ("customMethods" in options) {
2260
2753
  return {
2261
2754
  ...methods,
2262
- ...options.customMethods__unstable ?? {}
2755
+ ...options.customMethods ?? {}
2263
2756
  };
2264
2757
  } else {
2265
2758
  return methods;
@@ -2356,25 +2849,6 @@ function useVariantPath(handle, productPath) {
2356
2849
  };
2357
2850
  }, [pathname, search, handle, productPath]);
2358
2851
  }
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
2852
  var NonceContext = react.createContext(void 0);
2379
2853
  var NonceProvider = NonceContext.Provider;
2380
2854
  var useNonce = () => react.useContext(NonceContext);
@@ -2427,9 +2901,8 @@ var Script = react.forwardRef(
2427
2901
  function useOptimisticData(identifier) {
2428
2902
  const fetchers = react$1.useFetchers();
2429
2903
  const data = {};
2430
- for (const fetcher of fetchers) {
2431
- const formData = fetcher.submission?.formData;
2432
- if (formData && formData.get("optimistic-identifier") === identifier) {
2904
+ for (const { formData } of fetchers) {
2905
+ if (formData?.get("optimistic-identifier") === identifier) {
2433
2906
  try {
2434
2907
  if (formData.has("optimistic-data")) {
2435
2908
  const dataInForm = JSON.parse(
@@ -2466,7 +2939,7 @@ function OptimisticInput({ id, data }) {
2466
2939
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
2467
2940
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
2468
2941
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
2469
- //! @see https://shopify.dev/docs/api/storefront/2023-07/mutations/cartMetafieldDelete
2942
+ //! @see https://shopify.dev/docs/api/storefront/2023-10/mutations/cartMetafieldDelete
2470
2943
 
2471
2944
  Object.defineProperty(exports, 'AnalyticsEventName', {
2472
2945
  enumerable: true,
@@ -2580,6 +3053,7 @@ exports.cartSelectedDeliveryOptionsUpdateDefault = cartSelectedDeliveryOptionsUp
2580
3053
  exports.cartSetIdDefault = cartSetIdDefault;
2581
3054
  exports.createCartHandler = createCartHandler;
2582
3055
  exports.createContentSecurityPolicy = createContentSecurityPolicy;
3056
+ exports.createCustomerClient__unstable = createCustomerClient;
2583
3057
  exports.createStorefrontClient = createStorefrontClient;
2584
3058
  exports.createWithCache = createWithCache;
2585
3059
  exports.generateCacheControlHeader = generateCacheControlHeader;