@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.
@@ -15,13 +15,9 @@ function hashKey(queryKey) {
15
15
  for (const key of rawKeys) {
16
16
  if (key != null) {
17
17
  if (typeof key === "object") {
18
- if (!!key.body && typeof key.body === "string") {
19
- hash += key.body;
20
- } else {
21
- hash += JSON.stringify(key);
22
- }
18
+ hash += JSON.stringify(key);
23
19
  } else {
24
- hash += key;
20
+ hash += key.toString();
25
21
  }
26
22
  }
27
23
  }
@@ -81,6 +77,16 @@ function CacheLong(overrideOptions) {
81
77
  ...overrideOptions
82
78
  };
83
79
  }
80
+ function CacheDefault(overrideOptions) {
81
+ guardExpirableModeType(overrideOptions);
82
+ return {
83
+ mode: PUBLIC,
84
+ maxAge: 1,
85
+ staleWhileRevalidate: 86399,
86
+ // 1 second less than 24 hours
87
+ ...overrideOptions
88
+ };
89
+ }
84
90
  function CacheCustom(overrideOptions) {
85
91
  return overrideOptions;
86
92
  }
@@ -102,7 +108,7 @@ function getCacheControlSetting(userCacheOptions, options) {
102
108
  ...options
103
109
  };
104
110
  } else {
105
- return userCacheOptions || CacheShort();
111
+ return userCacheOptions || CacheDefault();
106
112
  }
107
113
  }
108
114
  function generateDefaultCacheControlHeader(userCacheOptions) {
@@ -173,7 +179,7 @@ function getKeyUrl(key) {
173
179
  return `https://shopify.dev/?${key}`;
174
180
  }
175
181
  function getCacheOption(userCacheOptions) {
176
- return userCacheOptions || CacheShort();
182
+ return userCacheOptions || CacheDefault();
177
183
  }
178
184
  async function getItemFromCache(cache, key) {
179
185
  if (!cache)
@@ -357,26 +363,63 @@ var warnOnce = (string) => {
357
363
  };
358
364
 
359
365
  // src/version.ts
360
- var LIB_VERSION = "2023.7.13";
366
+ var LIB_VERSION = "2023.10.1";
367
+
368
+ // src/utils/graphql.ts
369
+ function minifyQuery(string) {
370
+ return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
371
+ }
372
+ var IS_QUERY_RE = /(^|}\s)query[\s({]/im;
373
+ var IS_MUTATION_RE = /(^|}\s)mutation[\s({]/im;
374
+ function assertQuery(query, callerName) {
375
+ if (!IS_QUERY_RE.test(query)) {
376
+ throw new Error(`[h2:error:${callerName}] Can only execute queries`);
377
+ }
378
+ }
379
+ function assertMutation(query, callerName) {
380
+ if (!IS_MUTATION_RE.test(query)) {
381
+ throw new Error(`[h2:error:${callerName}] Can only execute mutations`);
382
+ }
383
+ }
384
+ function throwGraphQLError({
385
+ response,
386
+ errors,
387
+ type,
388
+ query,
389
+ queryVariables,
390
+ ErrorConstructor = Error,
391
+ client = "storefront"
392
+ }) {
393
+ const requestId = response.headers.get("x-request-id");
394
+ const errorMessage = (typeof errors === "string" ? errors : errors?.map?.((error) => error.message).join("\n")) || `API response error: ${response.status}`;
395
+ throw new ErrorConstructor(
396
+ `[h2:error:${client}.${type}] ` + errorMessage + (requestId ? ` - Request ID: ${requestId}` : ""),
397
+ {
398
+ cause: JSON.stringify({
399
+ errors,
400
+ requestId,
401
+ ...{
402
+ graphql: {
403
+ query,
404
+ variables: JSON.stringify(queryVariables)
405
+ }
406
+ }
407
+ })
408
+ }
409
+ );
410
+ }
361
411
 
362
412
  // src/storefront.ts
363
413
  var StorefrontApiError = class extends Error {
364
414
  };
365
415
  var isStorefrontApiError = (error) => error instanceof StorefrontApiError;
366
- var isQueryRE = /(^|}\s)query[\s({]/im;
367
- var isMutationRE = /(^|}\s)mutation[\s({]/im;
368
- function minifyQuery(string) {
369
- return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
370
- }
371
416
  var defaultI18n = { language: "EN", country: "US" };
372
417
  function createStorefrontClient(options) {
373
418
  const {
374
419
  storefrontHeaders,
375
420
  cache,
376
421
  waitUntil,
377
- buyerIp,
378
422
  i18n,
379
- requestGroupId,
380
423
  storefrontId,
381
424
  ...clientOptions
382
425
  } = options;
@@ -395,9 +438,9 @@ function createStorefrontClient(options) {
395
438
  const getHeaders = clientOptions.privateStorefrontToken ? getPrivateTokenHeaders : getPublicTokenHeaders;
396
439
  const defaultHeaders = getHeaders({
397
440
  contentType: "json",
398
- buyerIp: storefrontHeaders?.buyerIp || buyerIp
441
+ buyerIp: storefrontHeaders?.buyerIp || ""
399
442
  });
400
- defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || requestGroupId || generateUUID();
443
+ defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
401
444
  if (storefrontId)
402
445
  defaultHeaders[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
403
446
  defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
@@ -408,11 +451,14 @@ function createStorefrontClient(options) {
408
451
  if (cookies[SHOPIFY_S])
409
452
  defaultHeaders[SHOPIFY_STOREFRONT_S_HEADER] = cookies[SHOPIFY_S];
410
453
  }
411
- if (!storefrontHeaders) {
412
- warnOnce(
413
- H2_PREFIX_WARN + "`requestGroupId` and `buyerIp` will be deprecated in the next calendar release. Please use `getStorefrontHeaders`"
414
- );
415
- }
454
+ const cacheKeyHeader = JSON.stringify({
455
+ "content-type": defaultHeaders["content-type"],
456
+ "user-agent": defaultHeaders["user-agent"],
457
+ [SDK_VARIANT_HEADER]: defaultHeaders[SDK_VARIANT_HEADER],
458
+ [SDK_VARIANT_SOURCE_HEADER]: defaultHeaders[SDK_VARIANT_SOURCE_HEADER],
459
+ [SDK_VERSION_HEADER]: defaultHeaders[SDK_VERSION_HEADER],
460
+ [STOREFRONT_ACCESS_TOKEN_HEADER]: defaultHeaders[STOREFRONT_ACCESS_TOKEN_HEADER]
461
+ });
416
462
  async function fetchStorefrontApi({
417
463
  query,
418
464
  mutation,
@@ -441,22 +487,13 @@ function createStorefrontClient(options) {
441
487
  };
442
488
  const cacheKey = [
443
489
  url,
444
- {
445
- method: requestInit.method,
446
- headers: {
447
- "content-type": defaultHeaders["content-type"],
448
- "user-agent": defaultHeaders["user-agent"],
449
- [SDK_VARIANT_HEADER]: defaultHeaders[SDK_VARIANT_HEADER],
450
- [SDK_VARIANT_SOURCE_HEADER]: defaultHeaders[SDK_VARIANT_SOURCE_HEADER],
451
- [SDK_VERSION_HEADER]: defaultHeaders[SDK_VERSION_HEADER],
452
- [STOREFRONT_ACCESS_TOKEN_HEADER]: defaultHeaders[STOREFRONT_ACCESS_TOKEN_HEADER]
453
- },
454
- body: requestInit.body
455
- }
490
+ requestInit.method,
491
+ cacheKeyHeader,
492
+ requestInit.body
456
493
  ];
457
494
  const [body, response] = await fetchWithServerCache(url, requestInit, {
458
495
  cacheInstance: mutation ? void 0 : cache,
459
- cache: cacheOptions || CacheShort(),
496
+ cache: cacheOptions || CacheDefault(),
460
497
  cacheKey,
461
498
  shouldCacheResponse: checkGraphQLErrors,
462
499
  waitUntil,
@@ -480,11 +517,11 @@ function createStorefrontClient(options) {
480
517
  } catch (_e) {
481
518
  errors2 = [{ message: body }];
482
519
  }
483
- throwError({ ...errorOptions, errors: errors2 });
520
+ throwGraphQLError({ ...errorOptions, errors: errors2 });
484
521
  }
485
522
  const { data, errors } = body;
486
523
  if (errors?.length) {
487
- throwError({
524
+ throwGraphQLError({
488
525
  ...errorOptions,
489
526
  errors,
490
527
  ErrorConstructor: StorefrontApiError
@@ -510,11 +547,7 @@ function createStorefrontClient(options) {
510
547
  */
511
548
  query: (query, payload) => {
512
549
  query = minifyQuery(query);
513
- if (isMutationRE.test(query)) {
514
- throw new Error(
515
- "[h2:error:storefront.query] Cannot execute mutations"
516
- );
517
- }
550
+ assertQuery(query, "storefront.query");
518
551
  const result = fetchStorefrontApi({
519
552
  ...payload,
520
553
  query
@@ -538,11 +571,7 @@ function createStorefrontClient(options) {
538
571
  */
539
572
  mutate: (mutation, payload) => {
540
573
  mutation = minifyQuery(mutation);
541
- if (isQueryRE.test(mutation)) {
542
- throw new Error(
543
- "[h2:error:storefront.mutate] Cannot execute queries"
544
- );
545
- }
574
+ assertMutation(mutation, "storefront.mutate");
546
575
  const result = fetchStorefrontApi({
547
576
  ...payload,
548
577
  mutation
@@ -585,32 +614,6 @@ function createStorefrontClient(options) {
585
614
  }
586
615
  };
587
616
  }
588
- function throwError({
589
- response,
590
- errors,
591
- type,
592
- query,
593
- queryVariables,
594
- ErrorConstructor = Error
595
- }) {
596
- const requestId = response.headers.get("x-request-id");
597
- const errorMessage = (typeof errors === "string" ? errors : errors?.map?.((error) => error.message).join("\n")) || `API response error: ${response.status}`;
598
- throw new ErrorConstructor(
599
- `[h2:error:storefront.${type}] ` + errorMessage + (requestId ? ` - Request ID: ${requestId}` : ""),
600
- {
601
- cause: JSON.stringify({
602
- errors,
603
- requestId,
604
- ...{
605
- graphql: {
606
- query,
607
- variables: JSON.stringify(queryVariables)
608
- }
609
- }
610
- })
611
- }
612
- );
613
- }
614
617
 
615
618
  // src/utils/request.ts
616
619
  function getHeader(request, key) {
@@ -790,76 +793,115 @@ var graphiqlLoader = async function graphiqlLoader2({
790
793
  const url = storefront.getApiUrl();
791
794
  const accessToken = storefront.getPublicTokenHeaders()["X-Shopify-Storefront-Access-Token"];
792
795
  const favicon = `https://avatars.githubusercontent.com/u/12972006?s=48&v=4`;
796
+ const html = String.raw;
793
797
  return new Response(
794
- `
795
- <!DOCTYPE html>
796
- <html lang="en">
797
- <head>
798
- <title>GraphiQL</title>
799
- <link rel="icon" type="image/x-icon" href="${favicon}">
800
- <style>
801
- body {
802
- height: 100%;
803
- margin: 0;
804
- width: 100%;
805
- overflow: hidden;
806
- }
798
+ html`
799
+ <!DOCTYPE html>
800
+ <html lang="en">
801
+ <head>
802
+ <title>GraphiQL</title>
803
+ <link rel="icon" type="image/x-icon" href="${favicon}" />
804
+ <style>
805
+ body {
806
+ height: 100%;
807
+ margin: 0;
808
+ width: 100%;
809
+ overflow: hidden;
810
+ background-color: hsl(219, 29%, 18%);
811
+ }
807
812
 
808
- #graphiql {
809
- height: 100vh;
810
- }
811
- </style>
813
+ #graphiql {
814
+ height: 100vh;
815
+ }
812
816
 
813
- <script
814
- crossorigin
815
- src="https://unpkg.com/react@18/umd/react.development.js"
816
- ></script>
817
- <script
818
- crossorigin
819
- src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
820
- ></script>
821
- <link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
822
- </head>
817
+ #graphiql > .placeholder {
818
+ color: slategray;
819
+ width: fit-content;
820
+ margin: 40px auto;
821
+ font-family: Arial;
822
+ }
823
+ </style>
823
824
 
824
- <body>
825
- <div id="graphiql">Loading...</div>
826
- <script
827
- src="https://unpkg.com/graphiql@3/graphiql.min.js"
828
- type="application/javascript"
829
- ></script>
830
- <script>
831
- const windowUrl = new URL(document.URL);
825
+ <script
826
+ crossorigin
827
+ src="https://unpkg.com/react@18/umd/react.development.js"
828
+ ></script>
829
+ <script
830
+ crossorigin
831
+ src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
832
+ ></script>
833
+ <link
834
+ rel="stylesheet"
835
+ href="https://unpkg.com/graphiql@3/graphiql.min.css"
836
+ />
837
+ <link
838
+ rel="stylesheet"
839
+ href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css"
840
+ />
841
+ </head>
832
842
 
833
- let query = '{\\n shop {\\n name\\n }\\n}';
834
- if (windowUrl.searchParams.has('query')) {
835
- query = decodeURIComponent(windowUrl.searchParams.get('query') ?? '');
836
- // Prettify query
837
- if (query) query = GraphiQL.GraphQL.print(GraphiQL.GraphQL.parse(query));
838
- }
843
+ <body>
844
+ <div id="graphiql">
845
+ <div class="placeholder">Loading GraphiQL...</div>
846
+ </div>
839
847
 
840
- let variables;
841
- if (windowUrl.searchParams.has('variables')) {
842
- variables = decodeURIComponent(windowUrl.searchParams.get('variables') ?? '');
843
- // Prettify variables
844
- if (variables) variables = JSON.stringify(JSON.parse(variables), null, 2);
845
- }
848
+ <script
849
+ src="https://unpkg.com/graphiql@3/graphiql.min.js"
850
+ type="application/javascript"
851
+ crossorigin="anonymous"
852
+ ></script>
853
+ <script
854
+ src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
855
+ type="application/javascript"
856
+ crossorigin="anonymous"
857
+ ></script>
846
858
 
847
- const root = ReactDOM.createRoot(document.getElementById('graphiql'));
848
- root.render(
849
- React.createElement(GraphiQL, {
850
- fetcher: GraphiQL.createFetcher({
851
- url: '${url}',
852
- headers: {'X-Shopify-Storefront-Access-Token': '${accessToken}'}
853
- }),
854
- defaultEditorToolsVisibility: true,
855
- query,
856
- variables
857
- }),
858
- );
859
- </script>
860
- </body>
861
- </html>
862
- `,
859
+ <script>
860
+ const windowUrl = new URL(document.URL);
861
+
862
+ let query = '{ shop { name } }';
863
+ if (windowUrl.searchParams.has('query')) {
864
+ query = decodeURIComponent(
865
+ windowUrl.searchParams.get('query') ?? query,
866
+ );
867
+ }
868
+
869
+ // Prettify query
870
+ query = GraphiQL.GraphQL.print(GraphiQL.GraphQL.parse(query));
871
+
872
+ let variables;
873
+ if (windowUrl.searchParams.has('variables')) {
874
+ variables = decodeURIComponent(
875
+ windowUrl.searchParams.get('variables') ?? '',
876
+ );
877
+ }
878
+
879
+ // Prettify variables
880
+ if (variables) {
881
+ variables = JSON.stringify(JSON.parse(variables), null, 2);
882
+ }
883
+
884
+ const root = ReactDOM.createRoot(
885
+ document.getElementById('graphiql'),
886
+ );
887
+ root.render(
888
+ React.createElement(GraphiQL, {
889
+ fetcher: GraphiQL.createFetcher({
890
+ url: '${url}',
891
+ headers: {
892
+ 'X-Shopify-Storefront-Access-Token': '${accessToken}',
893
+ },
894
+ }),
895
+ defaultEditorToolsVisibility: true,
896
+ query,
897
+ variables,
898
+ plugins: [GraphiQLPluginExplorer.explorerPlugin()],
899
+ }),
900
+ );
901
+ </script>
902
+ </body>
903
+ </html>
904
+ `,
863
905
  { status: 200, headers: { "content-type": "text/html" } }
864
906
  );
865
907
  };
@@ -965,14 +1007,15 @@ function generateSeoTags(seoInput) {
965
1007
  if (!content) {
966
1008
  break;
967
1009
  }
1010
+ const urlWithoutParams = content.split("?")[0];
968
1011
  tagResults.push(
969
1012
  generateTag("link", {
970
1013
  rel: "canonical",
971
- href: content
1014
+ href: urlWithoutParams
972
1015
  }),
973
1016
  generateTag("meta", {
974
1017
  property: "og:url",
975
- content
1018
+ content: urlWithoutParams
976
1019
  })
977
1020
  );
978
1021
  break;
@@ -1203,7 +1246,7 @@ function Seo({ debug }) {
1203
1246
  return [];
1204
1247
  }
1205
1248
  if (handleSeo) {
1206
- return recursivelyInvokeOrReturn(handle.seo, routeData);
1249
+ return recursivelyInvokeOrReturn(handleSeo, routeData);
1207
1250
  } else {
1208
1251
  return [loaderSeo];
1209
1252
  }
@@ -1469,6 +1512,456 @@ function getPaginationVariables(request, options = { pageBy: 20 }) {
1469
1512
  const variables = isPrevious ? prevPage : nextPage;
1470
1513
  return variables;
1471
1514
  }
1515
+
1516
+ // src/customer/BadRequest.ts
1517
+ var BadRequest = class extends Response {
1518
+ constructor(message, helpMessage) {
1519
+ if (helpMessage && true) {
1520
+ console.error("Customer Account API Error: " + helpMessage);
1521
+ }
1522
+ super(`Bad request: ${message}`, { status: 400 });
1523
+ }
1524
+ };
1525
+
1526
+ // src/customer/auth.helpers.ts
1527
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
1528
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
1529
+ function redirect2(path, options = {}) {
1530
+ const headers = options.headers ? new Headers(options.headers) : new Headers({});
1531
+ headers.set("location", path);
1532
+ return new Response(null, { status: options.status || 302, headers });
1533
+ }
1534
+ async function refreshToken({
1535
+ session,
1536
+ customerAccountId,
1537
+ customerAccountUrl,
1538
+ origin
1539
+ }) {
1540
+ const newBody = new URLSearchParams();
1541
+ const refreshToken2 = session.get("refresh_token");
1542
+ if (!refreshToken2)
1543
+ throw new BadRequest(
1544
+ "Unauthorized",
1545
+ "No refresh_token in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1546
+ );
1547
+ newBody.append("grant_type", "refresh_token");
1548
+ newBody.append("refresh_token", refreshToken2);
1549
+ newBody.append("client_id", customerAccountId);
1550
+ const headers = {
1551
+ "content-type": "application/x-www-form-urlencoded",
1552
+ "User-Agent": USER_AGENT,
1553
+ Origin: origin
1554
+ };
1555
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1556
+ method: "POST",
1557
+ headers,
1558
+ body: newBody
1559
+ });
1560
+ if (!response.ok) {
1561
+ const text = await response.text();
1562
+ throw new Response(text, {
1563
+ status: response.status,
1564
+ headers: {
1565
+ "Content-Type": "text/html; charset=utf-8"
1566
+ }
1567
+ });
1568
+ }
1569
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
1570
+ session.set("customer_authorization_code_token", access_token);
1571
+ session.set(
1572
+ "expires_at",
1573
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
1574
+ );
1575
+ session.set("id_token", id_token);
1576
+ session.set("refresh_token", refresh_token);
1577
+ const customerAccessToken = await exchangeAccessToken(
1578
+ session,
1579
+ customerAccountId,
1580
+ customerAccountUrl,
1581
+ origin
1582
+ );
1583
+ session.set("customer_access_token", customerAccessToken);
1584
+ }
1585
+ function clearSession(session) {
1586
+ session.unset("code-verifier");
1587
+ session.unset("customer_authorization_code_token");
1588
+ session.unset("expires_at");
1589
+ session.unset("id_token");
1590
+ session.unset("refresh_token");
1591
+ session.unset("customer_access_token");
1592
+ session.unset("state");
1593
+ session.unset("nonce");
1594
+ }
1595
+ async function checkExpires({
1596
+ locks,
1597
+ expiresAt,
1598
+ session,
1599
+ customerAccountId,
1600
+ customerAccountUrl,
1601
+ origin
1602
+ }) {
1603
+ if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
1604
+ try {
1605
+ if (!locks.refresh)
1606
+ locks.refresh = refreshToken({
1607
+ session,
1608
+ customerAccountId,
1609
+ customerAccountUrl,
1610
+ origin
1611
+ });
1612
+ await locks.refresh;
1613
+ delete locks.refresh;
1614
+ } catch (error) {
1615
+ clearSession(session);
1616
+ if (error && error.status !== 401) {
1617
+ throw error;
1618
+ } else {
1619
+ throw new BadRequest(
1620
+ "Unauthorized",
1621
+ "Login before querying the Customer Account API."
1622
+ );
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ async function generateCodeVerifier() {
1628
+ const rando = generateRandomCode();
1629
+ return base64UrlEncode(rando);
1630
+ }
1631
+ async function generateCodeChallenge(codeVerifier) {
1632
+ const digestOp = await crypto.subtle.digest(
1633
+ { name: "SHA-256" },
1634
+ new TextEncoder().encode(codeVerifier)
1635
+ );
1636
+ const hash = convertBufferToString(digestOp);
1637
+ return base64UrlEncode(hash);
1638
+ }
1639
+ function generateRandomCode() {
1640
+ const array = new Uint8Array(32);
1641
+ crypto.getRandomValues(array);
1642
+ return String.fromCharCode.apply(null, Array.from(array));
1643
+ }
1644
+ function base64UrlEncode(str) {
1645
+ const base64 = btoa(str);
1646
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1647
+ }
1648
+ function convertBufferToString(hash) {
1649
+ const uintArray = new Uint8Array(hash);
1650
+ const numberArray = Array.from(uintArray);
1651
+ return String.fromCharCode(...numberArray);
1652
+ }
1653
+ async function generateState() {
1654
+ const timestamp = Date.now().toString();
1655
+ const randomString = Math.random().toString(36).substring(2);
1656
+ return timestamp + randomString;
1657
+ }
1658
+ async function exchangeAccessToken(session, customerAccountId, customerAccountUrl, origin) {
1659
+ const clientId = customerAccountId;
1660
+ const accessToken = session.get("customer_authorization_code_token");
1661
+ if (!accessToken)
1662
+ throw new BadRequest(
1663
+ "Unauthorized",
1664
+ "No access token found in the session. Make sure your session is configured correctly and passed to `createCustomerClient`."
1665
+ );
1666
+ const body = new URLSearchParams();
1667
+ body.append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
1668
+ body.append("client_id", clientId);
1669
+ body.append("audience", CUSTOMER_API_CLIENT_ID);
1670
+ body.append("subject_token", accessToken);
1671
+ body.append(
1672
+ "subject_token_type",
1673
+ "urn:ietf:params:oauth:token-type:access_token"
1674
+ );
1675
+ body.append("scopes", "https://api.customers.com/auth/customer.graphql");
1676
+ const headers = {
1677
+ "content-type": "application/x-www-form-urlencoded",
1678
+ "User-Agent": USER_AGENT,
1679
+ Origin: origin
1680
+ };
1681
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1682
+ method: "POST",
1683
+ headers,
1684
+ body
1685
+ });
1686
+ const data = await response.json();
1687
+ if (data.error) {
1688
+ throw new BadRequest(data.error_description);
1689
+ }
1690
+ return data.access_token;
1691
+ }
1692
+ function getNonce(token) {
1693
+ return decodeJwt(token).payload.nonce;
1694
+ }
1695
+ function decodeJwt(token) {
1696
+ const [header, payload, signature] = token.split(".");
1697
+ const decodedHeader = JSON.parse(atob(header));
1698
+ const decodedPayload = JSON.parse(atob(payload));
1699
+ return {
1700
+ header: decodedHeader,
1701
+ payload: decodedPayload,
1702
+ signature
1703
+ };
1704
+ }
1705
+
1706
+ // src/csp/nonce.ts
1707
+ function generateNonce() {
1708
+ return toHexString(randomUint8Array());
1709
+ }
1710
+ function randomUint8Array() {
1711
+ try {
1712
+ return crypto.getRandomValues(new Uint8Array(16));
1713
+ } catch (e) {
1714
+ return new Uint8Array(16).map(() => Math.random() * 255 | 0);
1715
+ }
1716
+ }
1717
+ function toHexString(byteArray) {
1718
+ return Array.from(byteArray, function(byte) {
1719
+ return ("0" + (byte & 255).toString(16)).slice(-2);
1720
+ }).join("");
1721
+ }
1722
+
1723
+ // src/customer/customer.ts
1724
+ function createCustomerClient({
1725
+ session,
1726
+ customerAccountId,
1727
+ customerAccountUrl,
1728
+ customerApiVersion = "2023-10",
1729
+ request,
1730
+ waitUntil
1731
+ }) {
1732
+ if (!request?.url) {
1733
+ throw new Error(
1734
+ "[h2:error:createCustomerClient] The request object does not contain a URL."
1735
+ );
1736
+ }
1737
+ const url = new URL(request.url);
1738
+ const origin = url.protocol === "http:" ? url.origin.replace("http", "https") : url.origin;
1739
+ const locks = {};
1740
+ const logSubRequestEvent = (query, startTime) => {
1741
+ globalThis.__H2O_LOG_EVENT?.({
1742
+ eventType: "subrequest",
1743
+ url: `https://shopify.dev/?${hashKey([
1744
+ `Customer Account `,
1745
+ /((query|mutation) [^\s\(]+)/g.exec(query)?.[0] || query.substring(0, 10)
1746
+ ])}`,
1747
+ startTime,
1748
+ waitUntil,
1749
+ ...getDebugHeaders(request)
1750
+ });
1751
+ } ;
1752
+ async function fetchCustomerAPI({
1753
+ query,
1754
+ type,
1755
+ variables = {}
1756
+ }) {
1757
+ const accessToken = session.get("customer_access_token");
1758
+ const expiresAt = session.get("expires_at");
1759
+ if (!accessToken || !expiresAt)
1760
+ throw new BadRequest(
1761
+ "Unauthorized",
1762
+ "Login before querying the Customer Account API."
1763
+ );
1764
+ await checkExpires({
1765
+ locks,
1766
+ expiresAt,
1767
+ session,
1768
+ customerAccountId,
1769
+ customerAccountUrl,
1770
+ origin
1771
+ });
1772
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1773
+ const response = await fetch(
1774
+ `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`,
1775
+ {
1776
+ method: "POST",
1777
+ headers: {
1778
+ "Content-Type": "application/json",
1779
+ "User-Agent": USER_AGENT,
1780
+ Origin: origin,
1781
+ Authorization: accessToken
1782
+ },
1783
+ body: JSON.stringify({
1784
+ operationName: "SomeQuery",
1785
+ query,
1786
+ variables
1787
+ })
1788
+ }
1789
+ );
1790
+ logSubRequestEvent?.(query, startTime);
1791
+ const body = await response.text();
1792
+ const errorOptions = {
1793
+ response,
1794
+ type,
1795
+ query,
1796
+ queryVariables: variables,
1797
+ errors: void 0,
1798
+ client: "customer"
1799
+ };
1800
+ if (!response.ok) {
1801
+ let errors;
1802
+ try {
1803
+ errors = parseJSON(body);
1804
+ } catch (_e) {
1805
+ errors = [{ message: body }];
1806
+ }
1807
+ throwGraphQLError({ ...errorOptions, errors });
1808
+ }
1809
+ try {
1810
+ return parseJSON(body).data;
1811
+ } catch (e) {
1812
+ throwGraphQLError({ ...errorOptions, errors: [{ message: body }] });
1813
+ }
1814
+ }
1815
+ return {
1816
+ login: async () => {
1817
+ const loginUrl = new URL(customerAccountUrl + "/auth/oauth/authorize");
1818
+ const state = await generateState();
1819
+ const nonce = await generateNonce();
1820
+ loginUrl.searchParams.set("client_id", customerAccountId);
1821
+ loginUrl.searchParams.set("scope", "openid email");
1822
+ loginUrl.searchParams.append("response_type", "code");
1823
+ loginUrl.searchParams.append("redirect_uri", origin + "/authorize");
1824
+ loginUrl.searchParams.set(
1825
+ "scope",
1826
+ "openid email https://api.customers.com/auth/customer.graphql"
1827
+ );
1828
+ loginUrl.searchParams.append("state", state);
1829
+ loginUrl.searchParams.append("nonce", nonce);
1830
+ const verifier = await generateCodeVerifier();
1831
+ const challenge = await generateCodeChallenge(verifier);
1832
+ session.set("code-verifier", verifier);
1833
+ session.set("state", state);
1834
+ session.set("nonce", nonce);
1835
+ loginUrl.searchParams.append("code_challenge", challenge);
1836
+ loginUrl.searchParams.append("code_challenge_method", "S256");
1837
+ return redirect2(loginUrl.toString(), {
1838
+ headers: {
1839
+ "Set-Cookie": await session.commit()
1840
+ }
1841
+ });
1842
+ },
1843
+ logout: async () => {
1844
+ const idToken = session.get("id_token");
1845
+ clearSession(session);
1846
+ return redirect2(
1847
+ `${customerAccountUrl}/auth/logout?id_token_hint=${idToken}`,
1848
+ {
1849
+ status: 302,
1850
+ headers: {
1851
+ "Set-Cookie": await session.commit()
1852
+ }
1853
+ }
1854
+ );
1855
+ },
1856
+ isLoggedIn: async () => {
1857
+ const expiresAt = session.get("expires_at");
1858
+ if (!session.get("customer_access_token") || !expiresAt)
1859
+ return false;
1860
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1861
+ try {
1862
+ await checkExpires({
1863
+ locks,
1864
+ expiresAt,
1865
+ session,
1866
+ customerAccountId,
1867
+ customerAccountUrl,
1868
+ origin
1869
+ });
1870
+ logSubRequestEvent?.(" check expires", startTime);
1871
+ } catch {
1872
+ return false;
1873
+ }
1874
+ return true;
1875
+ },
1876
+ mutate(mutation, options) {
1877
+ mutation = minifyQuery(mutation);
1878
+ assertMutation(mutation, "customer.mutate");
1879
+ return fetchCustomerAPI({ query: mutation, type: "mutation", ...options });
1880
+ },
1881
+ query(query, options) {
1882
+ query = minifyQuery(query);
1883
+ assertQuery(query, "customer.query");
1884
+ return fetchCustomerAPI({ query, type: "query", ...options });
1885
+ },
1886
+ authorize: async (redirectPath = "/") => {
1887
+ const code = url.searchParams.get("code");
1888
+ const state = url.searchParams.get("state");
1889
+ if (!code || !state) {
1890
+ clearSession(session);
1891
+ throw new BadRequest(
1892
+ "Unauthorized",
1893
+ "No code or state parameter found in the redirect URL."
1894
+ );
1895
+ }
1896
+ if (session.get("state") !== state) {
1897
+ clearSession(session);
1898
+ throw new BadRequest(
1899
+ "Unauthorized",
1900
+ "The session state does not match the state parameter. Make sure that the session is configured correctly and passed to `createCustomerClient`."
1901
+ );
1902
+ }
1903
+ const clientId = customerAccountId;
1904
+ const body = new URLSearchParams();
1905
+ body.append("grant_type", "authorization_code");
1906
+ body.append("client_id", clientId);
1907
+ body.append("redirect_uri", origin + "/authorize");
1908
+ body.append("code", code);
1909
+ const codeVerifier = session.get("code-verifier");
1910
+ if (!codeVerifier)
1911
+ throw new BadRequest(
1912
+ "Unauthorized",
1913
+ "No code verifier found in the session. Make sure that the session is configured correctly and passed to `createCustomerClient`."
1914
+ );
1915
+ body.append("code_verifier", codeVerifier);
1916
+ const headers = {
1917
+ "content-type": "application/x-www-form-urlencoded",
1918
+ "User-Agent": USER_AGENT,
1919
+ Origin: origin
1920
+ };
1921
+ const response = await fetch(`${customerAccountUrl}/auth/oauth/token`, {
1922
+ method: "POST",
1923
+ headers,
1924
+ body
1925
+ });
1926
+ if (!response.ok) {
1927
+ throw new Response(await response.text(), {
1928
+ status: response.status,
1929
+ headers: {
1930
+ "Content-Type": "text/html; charset=utf-8"
1931
+ }
1932
+ });
1933
+ }
1934
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
1935
+ const sessionNonce = session.get("nonce");
1936
+ const responseNonce = await getNonce(id_token);
1937
+ if (sessionNonce !== responseNonce) {
1938
+ throw new BadRequest(
1939
+ "Unauthorized",
1940
+ `Returned nonce does not match: ${sessionNonce} !== ${responseNonce}`
1941
+ );
1942
+ }
1943
+ session.set("customer_authorization_code_token", access_token);
1944
+ session.set(
1945
+ "expires_at",
1946
+ new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + ""
1947
+ );
1948
+ session.set("id_token", id_token);
1949
+ session.set("refresh_token", refresh_token);
1950
+ const customerAccessToken = await exchangeAccessToken(
1951
+ session,
1952
+ customerAccountId,
1953
+ customerAccountUrl,
1954
+ origin
1955
+ );
1956
+ session.set("customer_access_token", customerAccessToken);
1957
+ return redirect2(redirectPath, {
1958
+ headers: {
1959
+ "Set-Cookie": await session.commit()
1960
+ }
1961
+ });
1962
+ }
1963
+ };
1964
+ }
1472
1965
  var INPUT_NAME = "cartFormInput";
1473
1966
  function CartForm({
1474
1967
  children,
@@ -2162,10 +2655,10 @@ function createCartHandler(options) {
2162
2655
  },
2163
2656
  deleteMetafield: cartMetafieldDeleteDefault(mutateOptions)
2164
2657
  };
2165
- if ("customMethods__unstable" in options) {
2658
+ if ("customMethods" in options) {
2166
2659
  return {
2167
2660
  ...methods,
2168
- ...options.customMethods__unstable ?? {}
2661
+ ...options.customMethods ?? {}
2169
2662
  };
2170
2663
  } else {
2171
2664
  return methods;
@@ -2262,25 +2755,6 @@ function useVariantPath(handle, productPath) {
2262
2755
  };
2263
2756
  }, [pathname, search, handle, productPath]);
2264
2757
  }
2265
-
2266
- // src/csp/nonce.ts
2267
- function generateNonce() {
2268
- return toHexString(randomUint8Array());
2269
- }
2270
- function randomUint8Array() {
2271
- try {
2272
- return crypto.getRandomValues(new Uint8Array(16));
2273
- } catch (e) {
2274
- return new Uint8Array(16).map(() => Math.random() * 255 | 0);
2275
- }
2276
- }
2277
- function toHexString(byteArray) {
2278
- return Array.from(byteArray, function(byte) {
2279
- return ("0" + (byte & 255).toString(16)).slice(-2);
2280
- }).join("");
2281
- }
2282
-
2283
- // src/csp/csp.ts
2284
2758
  var NonceContext = createContext(void 0);
2285
2759
  var NonceProvider = NonceContext.Provider;
2286
2760
  var useNonce = () => useContext(NonceContext);
@@ -2333,9 +2807,8 @@ var Script = forwardRef(
2333
2807
  function useOptimisticData(identifier) {
2334
2808
  const fetchers = useFetchers();
2335
2809
  const data = {};
2336
- for (const fetcher of fetchers) {
2337
- const formData = fetcher.submission?.formData;
2338
- if (formData && formData.get("optimistic-identifier") === identifier) {
2810
+ for (const { formData } of fetchers) {
2811
+ if (formData?.get("optimistic-identifier") === identifier) {
2339
2812
  try {
2340
2813
  if (formData.has("optimistic-data")) {
2341
2814
  const dataInForm = JSON.parse(
@@ -2372,8 +2845,8 @@ function OptimisticInput({ id, data }) {
2372
2845
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
2373
2846
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
2374
2847
  //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
2375
- //! @see https://shopify.dev/docs/api/storefront/2023-07/mutations/cartMetafieldDelete
2848
+ //! @see https://shopify.dev/docs/api/storefront/2023-10/mutations/cartMetafieldDelete
2376
2849
 
2377
- export { CacheCustom, CacheLong, CacheNone, CacheShort, CartForm, InMemoryCache, OptimisticInput, Pagination, Script, Seo, StorefrontApiError, VariantSelector, cartAttributesUpdateDefault, cartBuyerIdentityUpdateDefault, cartCreateDefault, cartDiscountCodesUpdateDefault, cartGetDefault, cartGetIdDefault, cartLinesAddDefault, cartLinesRemoveDefault, cartLinesUpdateDefault, cartMetafieldDeleteDefault, cartMetafieldsSetDefault, cartNoteUpdateDefault, cartSelectedDeliveryOptionsUpdateDefault, cartSetIdDefault, createCartHandler, createContentSecurityPolicy, createStorefrontClient, createWithCache, generateCacheControlHeader, getPaginationVariables, getSelectedProductOptions, graphiqlLoader, isStorefrontApiError, storefrontRedirect, useNonce, useOptimisticData };
2850
+ export { CacheCustom, CacheLong, CacheNone, CacheShort, CartForm, InMemoryCache, OptimisticInput, Pagination, Script, Seo, StorefrontApiError, VariantSelector, cartAttributesUpdateDefault, cartBuyerIdentityUpdateDefault, cartCreateDefault, cartDiscountCodesUpdateDefault, cartGetDefault, cartGetIdDefault, cartLinesAddDefault, cartLinesRemoveDefault, cartLinesUpdateDefault, cartMetafieldDeleteDefault, cartMetafieldsSetDefault, cartNoteUpdateDefault, cartSelectedDeliveryOptionsUpdateDefault, cartSetIdDefault, createCartHandler, createContentSecurityPolicy, createCustomerClient as createCustomerClient__unstable, createStorefrontClient, createWithCache, generateCacheControlHeader, getPaginationVariables, getSelectedProductOptions, graphiqlLoader, isStorefrontApiError, storefrontRedirect, useNonce, useOptimisticData };
2378
2851
  //# sourceMappingURL=out.js.map
2379
2852
  //# sourceMappingURL=index.js.map