@shopify/hydrogen 2023.7.2 → 2023.7.4

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.
@@ -5,6 +5,11 @@ var serverRuntime = require('@remix-run/server-runtime');
5
5
  var react = require('react');
6
6
  var react$1 = require('@remix-run/react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var cspBuilder = require('content-security-policy-builder');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var cspBuilder__default = /*#__PURE__*/_interopDefault(cspBuilder);
8
13
 
9
14
  var __defProp = Object.defineProperty;
10
15
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -164,7 +169,9 @@ function CacheLong(overrideOptions) {
164
169
  return {
165
170
  mode: PUBLIC,
166
171
  maxAge: 3600,
172
+ // 1 hour
167
173
  staleWhileRevalidate: 82800,
174
+ // 23 Hours
168
175
  ...overrideOptions
169
176
  };
170
177
  }
@@ -218,7 +225,7 @@ async function setItem(cache, request, response, userCacheOptions) {
218
225
  );
219
226
  response.headers.set("cache-control", paddedCacheControlString);
220
227
  response.headers.set("real-cache-control", cacheControlString);
221
- response.headers.set("cache-put-date", new Date().toUTCString());
228
+ response.headers.set("cache-put-date", (/* @__PURE__ */ new Date()).toUTCString());
222
229
  await cache.put(request, response);
223
230
  }
224
231
  async function deleteItem(cache, request) {
@@ -235,7 +242,7 @@ function calculateAge(response, responseDate) {
235
242
  responseMaxAge = parseFloat(maxAgeMatch[1]);
236
243
  }
237
244
  }
238
- const ageInMs = new Date().valueOf() - new Date(responseDate).valueOf();
245
+ const ageInMs = (/* @__PURE__ */ new Date()).valueOf() - new Date(responseDate).valueOf();
239
246
  return [ageInMs / 1e3, responseMaxAge];
240
247
  }
241
248
  function isStale(request, response) {
@@ -321,6 +328,7 @@ async function runWithCache(cacheKey, actionFn, {
321
328
  return actionFn();
322
329
  }
323
330
  const key = hashKey([
331
+ // '__HYDROGEN_CACHE_ID__', // TODO purgeQueryCacheOnBuild
324
332
  ...typeof cacheKey === "string" ? [cacheKey] : cacheKey
325
333
  ]);
326
334
  const cachedItem = await getItemFromCache(cacheInstance, key);
@@ -419,7 +427,7 @@ var warnOnce = (string) => {
419
427
  };
420
428
 
421
429
  // src/version.ts
422
- var LIB_VERSION = "2023.7.2";
430
+ var LIB_VERSION = "2023.7.4";
423
431
 
424
432
  // src/storefront.ts
425
433
  var StorefrontApiError = class extends Error {
@@ -537,6 +545,20 @@ function createStorefrontClient(options) {
537
545
  }
538
546
  return {
539
547
  storefront: {
548
+ /**
549
+ * Sends a GraphQL query to the Storefront API.
550
+ *
551
+ * Example:
552
+ *
553
+ * ```js
554
+ * async function loader ({context: {storefront}}) {
555
+ * const data = await storefront.query('query { ... }', {
556
+ * variables: {},
557
+ * cache: storefront.CacheLong()
558
+ * });
559
+ * }
560
+ * ```
561
+ */
540
562
  query: (query, payload) => {
541
563
  query = minifyQuery(query);
542
564
  if (isMutationRE.test(query)) {
@@ -546,6 +568,19 @@ function createStorefrontClient(options) {
546
568
  }
547
569
  return fetchStorefrontApi({ ...payload, query });
548
570
  },
571
+ /**
572
+ * Sends a GraphQL mutation to the Storefront API.
573
+ *
574
+ * Example:
575
+ *
576
+ * ```js
577
+ * async function loader ({context: {storefront}}) {
578
+ * await storefront.mutate('mutation { ... }', {
579
+ * variables: {},
580
+ * });
581
+ * }
582
+ * ```
583
+ */
549
584
  mutate: (mutation, payload) => {
550
585
  mutation = minifyQuery(mutation);
551
586
  if (isQueryRE.test(mutation)) {
@@ -565,6 +600,25 @@ function createStorefrontClient(options) {
565
600
  getPrivateTokenHeaders,
566
601
  getShopifyDomain,
567
602
  getApiUrl: getStorefrontApiUrl,
603
+ /**
604
+ * Wether it's a GraphQL error returned in the Storefront API response.
605
+ *
606
+ * Example:
607
+ *
608
+ * ```js
609
+ * async function loader ({context: {storefront}}) {
610
+ * try {
611
+ * await storefront.query(...);
612
+ * } catch(error) {
613
+ * if (storefront.isApiError(error)) {
614
+ * // ...
615
+ * }
616
+ *
617
+ * throw error;
618
+ * }
619
+ * }
620
+ * ```
621
+ */
568
622
  isApiError: isStorefrontApiError,
569
623
  i18n: i18n ?? defaultI18n
570
624
  }
@@ -787,11 +841,11 @@ var graphiqlLoader = async function graphiqlLoader2({
787
841
  <script
788
842
  crossorigin
789
843
  src="https://unpkg.com/react@18/umd/react.development.js"
790
- ><\/script>
844
+ ></script>
791
845
  <script
792
846
  crossorigin
793
847
  src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
794
- ><\/script>
848
+ ></script>
795
849
  <link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
796
850
  </head>
797
851
 
@@ -800,7 +854,7 @@ var graphiqlLoader = async function graphiqlLoader2({
800
854
  <script
801
855
  src="https://unpkg.com/graphiql@3/graphiql.min.js"
802
856
  type="application/javascript"
803
- ><\/script>
857
+ ></script>
804
858
  <script>
805
859
  const windowUrl = new URL(document.URL);
806
860
 
@@ -830,7 +884,7 @@ var graphiqlLoader = async function graphiqlLoader2({
830
884
  variables
831
885
  }),
832
886
  );
833
- <\/script>
887
+ </script>
834
888
  </body>
835
889
  </html>
836
890
  `,
@@ -1013,6 +1067,7 @@ function generateSeoTags(seoInput) {
1013
1067
  type: "application/ld+json",
1014
1068
  children: JSON.stringify(block)
1015
1069
  },
1070
+ // @ts-expect-error
1016
1071
  `json-ld-${block?.["@type"] || block?.name || index++}`
1017
1072
  );
1018
1073
  tagResults.push(tag);
@@ -1287,7 +1342,7 @@ function Pagination({
1287
1342
  replace: true
1288
1343
  }) : null;
1289
1344
  },
1290
- [hasNextPage, nextPageUrl]
1345
+ [hasNextPage, nextPageUrl, state]
1291
1346
  );
1292
1347
  const PreviousLink = react.useMemo(
1293
1348
  () => function PrevLink(props) {
@@ -1299,7 +1354,7 @@ function Pagination({
1299
1354
  replace: true
1300
1355
  }) : null;
1301
1356
  },
1302
- [hasPreviousPage, previousPageUrl]
1357
+ [hasPreviousPage, previousPageUrl, state]
1303
1358
  );
1304
1359
  return children({
1305
1360
  state,
@@ -1318,42 +1373,41 @@ function usePagination(connection) {
1318
1373
  const params = new URLSearchParams(search);
1319
1374
  const direction = params.get("direction");
1320
1375
  const isPrevious = direction === "previous";
1321
- const nodes = react.useMemo(() => {
1322
- if (!state || !state?.nodes) {
1323
- return hydrogenReact.flattenConnection(connection);
1324
- }
1325
- if (isPrevious) {
1326
- return [...hydrogenReact.flattenConnection(connection), ...state.nodes];
1327
- } else {
1328
- return [...state.nodes, ...hydrogenReact.flattenConnection(connection)];
1329
- }
1330
- }, [state, connection]);
1331
- const currentPageInfo = react.useMemo(() => {
1332
- let pageStartCursor = state?.pageInfo?.startCursor === void 0 ? connection.pageInfo.startCursor : state.pageInfo.startCursor;
1333
- let pageEndCursor = state?.pageInfo?.endCursor === void 0 ? connection.pageInfo.endCursor : state.pageInfo.endCursor;
1376
+ const [nodes, setNodes] = react.useState(hydrogenReact.flattenConnection(connection));
1377
+ const [currentPageInfo, setCurrentPageInfo] = react.useState({
1378
+ startCursor: connection.pageInfo.startCursor,
1379
+ endCursor: connection.pageInfo.endCursor,
1380
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
1381
+ hasNextPage: connection.pageInfo.hasNextPage
1382
+ });
1383
+ react.useEffect(() => {
1334
1384
  if (state?.nodes) {
1335
- if (isPrevious) {
1336
- pageStartCursor = connection.pageInfo.startCursor;
1337
- } else {
1338
- pageEndCursor = connection.pageInfo.endCursor;
1385
+ setNodes(
1386
+ isPrevious ? [...hydrogenReact.flattenConnection(connection), ...state.nodes] : [...state.nodes, ...hydrogenReact.flattenConnection(connection)]
1387
+ );
1388
+ }
1389
+ if (state?.pageInfo) {
1390
+ let pageStartCursor = state?.pageInfo?.startCursor === void 0 ? connection.pageInfo.startCursor : state.pageInfo.startCursor;
1391
+ let pageEndCursor = state?.pageInfo?.endCursor === void 0 ? connection.pageInfo.endCursor : state.pageInfo.endCursor;
1392
+ let previousPageExists = state?.pageInfo?.hasPreviousPage === void 0 ? connection.pageInfo.hasPreviousPage : state.pageInfo.hasPreviousPage;
1393
+ let nextPageExists = state?.pageInfo?.hasNextPage === void 0 ? connection.pageInfo.hasNextPage : state.pageInfo.hasNextPage;
1394
+ if (state?.nodes) {
1395
+ if (isPrevious) {
1396
+ pageStartCursor = connection.pageInfo.startCursor;
1397
+ previousPageExists = connection.pageInfo.hasPreviousPage;
1398
+ } else {
1399
+ pageEndCursor = connection.pageInfo.endCursor;
1400
+ nextPageExists = connection.pageInfo.hasNextPage;
1401
+ }
1339
1402
  }
1403
+ setCurrentPageInfo({
1404
+ startCursor: pageStartCursor,
1405
+ endCursor: pageEndCursor,
1406
+ hasPreviousPage: previousPageExists,
1407
+ hasNextPage: nextPageExists
1408
+ });
1340
1409
  }
1341
- const previousPageExists = state?.pageInfo?.hasPreviousPage === void 0 ? connection.pageInfo.hasPreviousPage : state.pageInfo.hasPreviousPage;
1342
- const nextPageExists = state?.pageInfo?.hasNextPage === void 0 ? connection.pageInfo.hasNextPage : state.pageInfo.hasNextPage;
1343
- return {
1344
- startCursor: pageStartCursor,
1345
- endCursor: pageEndCursor,
1346
- hasPreviousPage: previousPageExists,
1347
- hasNextPage: nextPageExists
1348
- };
1349
- }, [
1350
- isPrevious,
1351
- state,
1352
- connection.pageInfo.hasNextPage,
1353
- connection.pageInfo.hasPreviousPage,
1354
- connection.pageInfo.startCursor,
1355
- connection.pageInfo.endCursor
1356
- ]);
1410
+ }, [state, connection]);
1357
1411
  const previousPageUrl = react.useMemo(() => {
1358
1412
  const params2 = new URLSearchParams(search);
1359
1413
  params2.set("direction", "previous");
@@ -1398,18 +1452,17 @@ function CartForm({
1398
1452
  route
1399
1453
  }) {
1400
1454
  const fetcher = react$1.useFetcher();
1401
- return /* @__PURE__ */ jsxRuntime.jsxs(fetcher.Form, {
1402
- action: route || "",
1403
- method: "post",
1404
- children: [
1405
- (action || inputs) && /* @__PURE__ */ jsxRuntime.jsx("input", {
1455
+ return /* @__PURE__ */ jsxRuntime.jsxs(fetcher.Form, { action: route || "", method: "post", children: [
1456
+ (action || inputs) && /* @__PURE__ */ jsxRuntime.jsx(
1457
+ "input",
1458
+ {
1406
1459
  type: "hidden",
1407
1460
  name: INPUT_NAME,
1408
1461
  value: JSON.stringify({ action, inputs })
1409
- }),
1410
- typeof children === "function" ? children(fetcher) : children
1411
- ]
1412
- });
1462
+ }
1463
+ ),
1464
+ typeof children === "function" ? children(fetcher) : children
1465
+ ] });
1413
1466
  }
1414
1467
  CartForm.INPUT_NAME = INPUT_NAME;
1415
1468
  CartForm.ACTIONS = {
@@ -2097,10 +2150,14 @@ function VariantSelector({
2097
2150
  handle,
2098
2151
  options = [],
2099
2152
  variants: _variants = [],
2153
+ productPath = "products",
2100
2154
  children
2101
2155
  }) {
2102
2156
  const variants = _variants instanceof Array ? _variants : hydrogenReact.flattenConnection(_variants);
2103
- const { searchParams, path, alreadyOnProductPage } = useVariantPath(handle);
2157
+ const { searchParams, path, alreadyOnProductPage } = useVariantPath(
2158
+ handle,
2159
+ productPath
2160
+ );
2104
2161
  const optionsWithOnlyOneValue = options.filter(
2105
2162
  (option) => option?.values?.length === 1
2106
2163
  );
@@ -2125,7 +2182,10 @@ function VariantSelector({
2125
2182
  )
2126
2183
  );
2127
2184
  const currentParam = searchParams.get(option.name);
2128
- const calculatedActiveValue = currentParam ? currentParam === value : false;
2185
+ const calculatedActiveValue = currentParam ? (
2186
+ // If a URL parameter exists for the current option, check if it equals the current value
2187
+ currentParam === value
2188
+ ) : false;
2129
2189
  if (calculatedActiveValue) {
2130
2190
  activeValue = value;
2131
2191
  }
@@ -2159,31 +2219,103 @@ var getSelectedProductOptions = (request) => {
2159
2219
  });
2160
2220
  return selectedOptions;
2161
2221
  };
2162
- function useVariantPath(handle) {
2222
+ function useVariantPath(handle, productPath) {
2163
2223
  const { pathname, search } = react$1.useLocation();
2164
2224
  return react.useMemo(() => {
2165
2225
  const match = /(\/[a-zA-Z]{2}-[a-zA-Z]{2}\/)/g.exec(pathname);
2166
2226
  const isLocalePathname = match && match.length > 0;
2167
- const path = isLocalePathname ? `${match[0]}products/${handle}` : `/products/${handle}`;
2227
+ productPath = productPath.startsWith("/") ? productPath.substring(1) : productPath;
2228
+ const path = isLocalePathname ? `${match[0]}${productPath}/${handle}` : `/${productPath}/${handle}`;
2168
2229
  const searchParams = new URLSearchParams(search);
2169
2230
  return {
2170
2231
  searchParams,
2232
+ // If the current pathname matches the product page, we need to make sure
2233
+ // that we append to the current search params. Otherwise all the search
2234
+ // params can be generated new.
2171
2235
  alreadyOnProductPage: path === pathname,
2172
2236
  path
2173
2237
  };
2174
- }, [pathname, search, handle]);
2238
+ }, [pathname, search, handle, productPath]);
2175
2239
  }
2176
- //! @see https://shopify.dev/docs/api/storefront/2023-07/mutations/cartMetafieldDelete
2177
- //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartBuyerIdentityUpdate
2178
- //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartDiscountCodesUpdate
2179
- //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
2180
- //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
2181
- //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
2182
- //! @see https://shopify.dev/docs/api/storefront/latest/queries/cart
2240
+
2241
+ // src/csp/nonce.ts
2242
+ function generateNonce() {
2243
+ return toHexString(randomUint8Array());
2244
+ }
2245
+ function randomUint8Array() {
2246
+ try {
2247
+ return crypto.getRandomValues(new Uint8Array(16));
2248
+ } catch (e) {
2249
+ return new Uint8Array(16).map(() => Math.random() * 255 | 0);
2250
+ }
2251
+ }
2252
+ function toHexString(byteArray) {
2253
+ return Array.from(byteArray, function(byte) {
2254
+ return ("0" + (byte & 255).toString(16)).slice(-2);
2255
+ }).join("");
2256
+ }
2257
+
2258
+ // src/csp/csp.ts
2259
+ var NonceContext = react.createContext(void 0);
2260
+ var NonceProvider = NonceContext.Provider;
2261
+ var useNonce = () => react.useContext(NonceContext);
2262
+ function createContentSecurityPolicy(directives = {}) {
2263
+ const nonce = generateNonce();
2264
+ const header = createCSPHeader(nonce, directives);
2265
+ const Provider = ({ children }) => {
2266
+ return react.createElement(NonceProvider, { value: nonce }, children);
2267
+ };
2268
+ return {
2269
+ nonce,
2270
+ header,
2271
+ NonceProvider: Provider
2272
+ };
2273
+ }
2274
+ function createCSPHeader(nonce, directives = {}) {
2275
+ const nonceString = `'nonce-${nonce}'`;
2276
+ const defaultDirectives = {
2277
+ baseUri: ["'self'"],
2278
+ defaultSrc: [
2279
+ "'self'",
2280
+ nonceString,
2281
+ "https://cdn.shopify.com",
2282
+ // Used for the Customer Account API
2283
+ "https://shopify.com"
2284
+ ],
2285
+ frameAncestors: ["none"],
2286
+ styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.shopify.com"],
2287
+ connectSrc: ["'self'", "https://monorail-edge.shopifysvc.com"]
2288
+ };
2289
+ {
2290
+ defaultDirectives.connectSrc = ["*"];
2291
+ }
2292
+ const combinedDirectives = Object.assign({}, defaultDirectives, directives);
2293
+ if (combinedDirectives.scriptSrc instanceof Array && !combinedDirectives.scriptSrc.includes(nonceString)) {
2294
+ combinedDirectives.scriptSrc.push(nonceString);
2295
+ } else if (combinedDirectives.defaultSrc instanceof Array && !combinedDirectives.defaultSrc.includes(nonceString)) {
2296
+ combinedDirectives.defaultSrc.push(nonceString);
2297
+ }
2298
+ return cspBuilder__default.default({
2299
+ directives: combinedDirectives
2300
+ });
2301
+ }
2302
+ var Script = react.forwardRef(
2303
+ (props, ref) => {
2304
+ const nonce = useNonce();
2305
+ return /* @__PURE__ */ jsxRuntime.jsx("script", { ...props, nonce, ref });
2306
+ }
2307
+ );
2183
2308
  //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartCreate
2309
+ //! @see https://shopify.dev/docs/api/storefront/latest/queries/cart
2184
2310
  //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesAdd
2185
- //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesRemove
2186
2311
  //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesUpdate
2312
+ //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesRemove
2313
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartDiscountCodesUpdate
2314
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartBuyerIdentityUpdate
2315
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
2316
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
2317
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
2318
+ //! @see https://shopify.dev/docs/api/storefront/2023-07/mutations/cartMetafieldDelete
2187
2319
 
2188
2320
  Object.defineProperty(exports, 'AnalyticsEventName', {
2189
2321
  enumerable: true,
@@ -2276,6 +2408,7 @@ exports.CacheShort = CacheShort;
2276
2408
  exports.CartForm = CartForm;
2277
2409
  exports.InMemoryCache = InMemoryCache;
2278
2410
  exports.Pagination = Pagination;
2411
+ exports.Script = Script;
2279
2412
  exports.Seo = Seo;
2280
2413
  exports.VariantSelector = VariantSelector;
2281
2414
  exports.cartAttributesUpdateDefault = cartAttributesUpdateDefault;
@@ -2293,6 +2426,7 @@ exports.cartNoteUpdateDefault = cartNoteUpdateDefault;
2293
2426
  exports.cartSelectedDeliveryOptionsUpdateDefault = cartSelectedDeliveryOptionsUpdateDefault;
2294
2427
  exports.cartSetIdDefault = cartSetIdDefault;
2295
2428
  exports.createCartHandler = createCartHandler;
2429
+ exports.createContentSecurityPolicy = createContentSecurityPolicy;
2296
2430
  exports.createStorefrontClient = createStorefrontClient;
2297
2431
  exports.createWithCache = createWithCache;
2298
2432
  exports.generateCacheControlHeader = generateCacheControlHeader;
@@ -2301,5 +2435,6 @@ exports.getSelectedProductOptions = getSelectedProductOptions;
2301
2435
  exports.graphiqlLoader = graphiqlLoader;
2302
2436
  exports.isStorefrontApiError = isStorefrontApiError;
2303
2437
  exports.storefrontRedirect = storefrontRedirect;
2438
+ exports.useNonce = useNonce;
2304
2439
  //# sourceMappingURL=out.js.map
2305
2440
  //# sourceMappingURL=index.cjs.map