@shopify/hydrogen 2024.4.1 → 2024.4.2

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.
@@ -4,6 +4,7 @@ import { lazy, createContext, forwardRef, useMemo, createElement, Suspense, Frag
4
4
  import { useMatches, useLocation, useNavigation, Link, useNavigate, useFetcher, useFetchers } from '@remix-run/react';
5
5
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
6
6
  import cspBuilder from 'content-security-policy-builder';
7
+ import invariant from 'tiny-invariant';
7
8
 
8
9
  // src/storefront.ts
9
10
 
@@ -388,12 +389,20 @@ async function fetchWithServerCache(url, requestInit, {
388
389
  ).then(fromSerializableResponse);
389
390
  }
390
391
 
392
+ // src/version.ts
393
+ var LIB_VERSION = "2024.4.2";
394
+
391
395
  // src/constants.ts
392
396
  var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
393
397
  var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
394
398
  var SDK_VARIANT_HEADER = "X-SDK-Variant";
395
399
  var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
396
400
  var SDK_VERSION_HEADER = "X-SDK-Version";
401
+ var DEFAULT_CUSTOMER_API_VERSION = "2024-04";
402
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
403
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
404
+ var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
405
+ var BUYER_SESSION_KEY = "buyer";
397
406
 
398
407
  // src/utils/uuid.ts
399
408
  function generateUUID() {
@@ -413,9 +422,6 @@ var warnOnce = (string) => {
413
422
  }
414
423
  };
415
424
 
416
- // src/version.ts
417
- var LIB_VERSION = "2024.4.1";
418
-
419
425
  // src/utils/graphql.ts
420
426
  function minifyQuery(string) {
421
427
  return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
@@ -2201,12 +2207,6 @@ function getPaginationVariables(request, options = { pageBy: 20 }) {
2201
2207
  return variables;
2202
2208
  }
2203
2209
 
2204
- // src/customer/constants.ts
2205
- var DEFAULT_CUSTOMER_API_VERSION = "2024-04";
2206
- var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
2207
- var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
2208
- var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
2209
-
2210
2210
  // src/customer/BadRequest.ts
2211
2211
  var BadRequest = class extends Response {
2212
2212
  constructor(message, helpMessage, headers) {
@@ -2249,7 +2249,8 @@ async function refreshToken({
2249
2249
  customerAccountId,
2250
2250
  customerAccountUrl,
2251
2251
  httpsOrigin,
2252
- debugInfo
2252
+ debugInfo,
2253
+ exchangeForStorefrontCustomerAccessToken
2253
2254
  }) {
2254
2255
  const newBody = new URLSearchParams();
2255
2256
  const customerAccount = session.get(CUSTOMER_ACCOUNT_SESSION_KEY);
@@ -2305,9 +2306,11 @@ async function refreshToken({
2305
2306
  refreshToken: refresh_token,
2306
2307
  idToken: id_token
2307
2308
  });
2309
+ await exchangeForStorefrontCustomerAccessToken();
2308
2310
  }
2309
2311
  function clearSession(session) {
2310
2312
  session.unset(CUSTOMER_ACCOUNT_SESSION_KEY);
2313
+ session.unset(BUYER_SESSION_KEY);
2311
2314
  }
2312
2315
  async function checkExpires({
2313
2316
  locks,
@@ -2316,7 +2319,8 @@ async function checkExpires({
2316
2319
  customerAccountId,
2317
2320
  customerAccountUrl,
2318
2321
  httpsOrigin,
2319
- debugInfo
2322
+ debugInfo,
2323
+ exchangeForStorefrontCustomerAccessToken
2320
2324
  }) {
2321
2325
  if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
2322
2326
  try {
@@ -2326,7 +2330,8 @@ async function checkExpires({
2326
2330
  customerAccountId,
2327
2331
  customerAccountUrl,
2328
2332
  httpsOrigin,
2329
- debugInfo
2333
+ debugInfo,
2334
+ exchangeForStorefrontCustomerAccessToken
2330
2335
  });
2331
2336
  await locks.refresh;
2332
2337
  delete locks.refresh;
@@ -2470,7 +2475,8 @@ function createCustomerAccountClient({
2470
2475
  waitUntil,
2471
2476
  authUrl,
2472
2477
  customAuthStatusHandler,
2473
- logErrors = true
2478
+ logErrors = true,
2479
+ unstableB2b = false
2474
2480
  }) {
2475
2481
  if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
2476
2482
  console.warn(
@@ -2493,7 +2499,7 @@ function createCustomerAccountClient({
2493
2499
  const customerAccountApiUrl = `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`;
2494
2500
  const locks = {};
2495
2501
  async function fetchCustomerAPI({
2496
- query,
2502
+ query: query2,
2497
2503
  type,
2498
2504
  variables = {}
2499
2505
  }) {
@@ -2511,7 +2517,7 @@ function createCustomerAccountClient({
2511
2517
  Origin: httpsOrigin,
2512
2518
  Authorization: accessToken
2513
2519
  },
2514
- body: JSON.stringify({ query, variables })
2520
+ body: JSON.stringify({ query: query2, variables })
2515
2521
  });
2516
2522
  logSubRequestEvent?.({
2517
2523
  url: customerAccountApiUrl,
@@ -2519,7 +2525,7 @@ function createCustomerAccountClient({
2519
2525
  response,
2520
2526
  waitUntil,
2521
2527
  stackInfo,
2522
- query,
2528
+ query: query2,
2523
2529
  variables,
2524
2530
  ...getDebugHeaders(request)
2525
2531
  });
@@ -2528,7 +2534,7 @@ function createCustomerAccountClient({
2528
2534
  url: customerAccountApiUrl,
2529
2535
  response,
2530
2536
  type,
2531
- query,
2537
+ query: query2,
2532
2538
  queryVariables: variables,
2533
2539
  errors: void 0,
2534
2540
  client: "customer"
@@ -2559,7 +2565,7 @@ function createCustomerAccountClient({
2559
2565
  clientOperation: `customerAccount.${errorOptions.type}`,
2560
2566
  requestId: response.headers.get("x-request-id"),
2561
2567
  queryVariables: variables,
2562
- query
2568
+ query: query2
2563
2569
  })
2564
2570
  );
2565
2571
  return { ...APIresponse, ...errors && { errors: gqlErrors } };
@@ -2588,7 +2594,8 @@ function createCustomerAccountClient({
2588
2594
  waitUntil,
2589
2595
  stackInfo,
2590
2596
  ...getDebugHeaders(request)
2591
- }
2597
+ },
2598
+ exchangeForStorefrontCustomerAccessToken
2592
2599
  });
2593
2600
  } catch {
2594
2601
  return false;
@@ -2605,6 +2612,55 @@ function createCustomerAccountClient({
2605
2612
  if (hasAccessToken)
2606
2613
  return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
2607
2614
  }
2615
+ async function mutate(mutation, options) {
2616
+ ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2617
+ mutation = minifyQuery(mutation);
2618
+ assertMutation(mutation, "customer.mutate");
2619
+ return withSyncStack(
2620
+ fetchCustomerAPI({ query: mutation, type: "mutation", ...options }),
2621
+ { logErrors }
2622
+ );
2623
+ }
2624
+ async function query(query2, options) {
2625
+ ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2626
+ query2 = minifyQuery(query2);
2627
+ assertQuery(query2, "customer.query");
2628
+ return withSyncStack(fetchCustomerAPI({ query: query2, type: "query", ...options }), {
2629
+ logErrors
2630
+ });
2631
+ }
2632
+ function setBuyer(buyer) {
2633
+ session.set(BUYER_SESSION_KEY, {
2634
+ ...session.get(BUYER_SESSION_KEY),
2635
+ ...buyer
2636
+ });
2637
+ }
2638
+ async function getBuyer() {
2639
+ const hasAccessToken = await isLoggedIn();
2640
+ if (!hasAccessToken) {
2641
+ return;
2642
+ }
2643
+ return session.get(BUYER_SESSION_KEY);
2644
+ }
2645
+ async function exchangeForStorefrontCustomerAccessToken() {
2646
+ if (!unstableB2b) {
2647
+ return;
2648
+ }
2649
+ const STOREFRONT_CUSTOMER_ACCOUNT_TOKEN_CREATE = `#graphql
2650
+ mutation storefrontCustomerAccessTokenCreate {
2651
+ storefrontCustomerAccessTokenCreate {
2652
+ customerAccessToken
2653
+ }
2654
+ }
2655
+ `;
2656
+ const { data } = await mutate(STOREFRONT_CUSTOMER_ACCOUNT_TOKEN_CREATE);
2657
+ const customerAccessToken = data?.storefrontCustomerAccessTokenCreate?.customerAccessToken;
2658
+ if (customerAccessToken) {
2659
+ setBuyer({
2660
+ customerAccessToken
2661
+ });
2662
+ }
2663
+ }
2608
2664
  return {
2609
2665
  login: async (options) => {
2610
2666
  ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
@@ -2672,24 +2728,8 @@ function createCustomerAccountClient({
2672
2728
  handleAuthStatus,
2673
2729
  getAccessToken,
2674
2730
  getApiUrl: () => customerAccountApiUrl,
2675
- mutate(mutation, options) {
2676
- ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2677
- mutation = minifyQuery(mutation);
2678
- assertMutation(mutation, "customer.mutate");
2679
- return withSyncStack(
2680
- fetchCustomerAPI({ query: mutation, type: "mutation", ...options }),
2681
- { logErrors }
2682
- );
2683
- },
2684
- query(query, options) {
2685
- ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2686
- query = minifyQuery(query);
2687
- assertQuery(query, "customer.query");
2688
- return withSyncStack(
2689
- fetchCustomerAPI({ query, type: "query", ...options }),
2690
- { logErrors }
2691
- );
2692
- },
2731
+ mutate,
2732
+ query,
2693
2733
  authorize: async () => {
2694
2734
  ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2695
2735
  const code = requestUrl.searchParams.get("code");
@@ -2784,19 +2824,19 @@ function createCustomerAccountClient({
2784
2824
  )?.redirectPath;
2785
2825
  session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
2786
2826
  accessToken: customerAccessToken,
2787
- expiresAt: new Date(
2788
- (/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3
2789
- ).getTime() + "",
2827
+ expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + "",
2790
2828
  refreshToken: refresh_token,
2791
- idToken: id_token,
2792
- redirectPath: void 0
2829
+ idToken: id_token
2793
2830
  });
2831
+ await exchangeForStorefrontCustomerAccessToken();
2794
2832
  return redirect(redirectPath || DEFAULT_REDIRECT_PATH, {
2795
2833
  headers: {
2796
2834
  "Set-Cookie": await session.commit()
2797
2835
  }
2798
2836
  });
2799
- }
2837
+ },
2838
+ UNSTABLE_setBuyer: setBuyer,
2839
+ UNSTABLE_getBuyer: getBuyer
2800
2840
  };
2801
2841
  }
2802
2842
  function ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId) {
@@ -2897,10 +2937,18 @@ var MINIMAL_CART_FRAGMENT = `#graphql
2897
2937
  // src/cart/queries/cartCreateDefault.ts
2898
2938
  function cartCreateDefault(options) {
2899
2939
  return async (input, optionalParams) => {
2940
+ const buyer = options.customerAccount ? await options.customerAccount.UNSTABLE_getBuyer() : void 0;
2900
2941
  const { cartId, ...restOfOptionalParams } = optionalParams || {};
2942
+ const { buyerIdentity, ...restOfInput } = input;
2901
2943
  const { cartCreate, errors } = await options.storefront.mutate(CART_CREATE_MUTATION(options.cartFragment), {
2902
2944
  variables: {
2903
- input,
2945
+ input: {
2946
+ ...restOfInput,
2947
+ buyerIdentity: {
2948
+ ...buyer,
2949
+ ...buyerIdentity
2950
+ }
2951
+ },
2904
2952
  ...restOfOptionalParams
2905
2953
  }
2906
2954
  });
@@ -3227,10 +3275,19 @@ var CART_DISCOUNT_CODE_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT)
3227
3275
  // src/cart/queries/cartBuyerIdentityUpdateDefault.ts
3228
3276
  function cartBuyerIdentityUpdateDefault(options) {
3229
3277
  return async (buyerIdentity, optionalParams) => {
3278
+ if (buyerIdentity.companyLocationId && options.customerAccount) {
3279
+ options.customerAccount.UNSTABLE_setBuyer({
3280
+ companyLocationId: buyerIdentity.companyLocationId
3281
+ });
3282
+ }
3283
+ const buyer = options.customerAccount ? await options.customerAccount.UNSTABLE_getBuyer() : void 0;
3230
3284
  const { cartBuyerIdentityUpdate, errors } = await options.storefront.mutate(CART_BUYER_IDENTITY_UPDATE_MUTATION(options.cartFragment), {
3231
3285
  variables: {
3232
3286
  cartId: options.getCartId(),
3233
- buyerIdentity,
3287
+ buyerIdentity: {
3288
+ ...buyer,
3289
+ ...buyerIdentity
3290
+ },
3234
3291
  ...optionalParams
3235
3292
  }
3236
3293
  });
@@ -3498,7 +3555,8 @@ function createCartHandler(options) {
3498
3555
  const mutateOptions = {
3499
3556
  storefront,
3500
3557
  getCartId,
3501
- cartFragment: cartMutateFragment
3558
+ cartFragment: cartMutateFragment,
3559
+ customerAccount
3502
3560
  };
3503
3561
  const _cartCreate = cartCreateDefault(mutateOptions);
3504
3562
  const cartCreate = async function(...args) {
@@ -3725,7 +3783,11 @@ function createCSPHeader(nonce, props) {
3725
3783
  function addCspDirective(currentValue, value) {
3726
3784
  const normalizedValue = typeof value === "string" ? [value] : value;
3727
3785
  const normalizedCurrentValue = Array.isArray(currentValue) ? currentValue : [String(currentValue)];
3728
- const newValue = Array.isArray(normalizedValue) ? [...normalizedCurrentValue, ...normalizedValue] : normalizedValue;
3786
+ const newValue = Array.isArray(normalizedValue) ? (
3787
+ // If the default directive is `none`, don't
3788
+ // merge the override with the default value.
3789
+ normalizedValue.every((a) => a === `'none'`) ? normalizedCurrentValue : [...normalizedCurrentValue, ...normalizedValue]
3790
+ ) : normalizedValue;
3729
3791
  return newValue;
3730
3792
  }
3731
3793
  var Script = forwardRef(
@@ -3833,6 +3895,7 @@ function useCustomerPrivacy(props) {
3833
3895
  const {
3834
3896
  withPrivacyBanner = true,
3835
3897
  onVisitorConsentCollected,
3898
+ onReady,
3836
3899
  ...consentConfig
3837
3900
  } = props;
3838
3901
  const loadedEvent = useRef(false);
@@ -3889,6 +3952,9 @@ function useCustomerPrivacy(props) {
3889
3952
  );
3890
3953
  };
3891
3954
  }
3955
+ if (onReady && !withPrivacyBanner) {
3956
+ onReady();
3957
+ }
3892
3958
  }, [scriptStatus, withPrivacyBanner, consentConfig]);
3893
3959
  return;
3894
3960
  }
@@ -3909,17 +3975,26 @@ function getCustomerPrivacyRequired() {
3909
3975
  return customerPrivacy;
3910
3976
  }
3911
3977
  function ShopifyAnalytics({
3912
- consent
3978
+ consent,
3979
+ onReady
3913
3980
  }) {
3914
3981
  const { subscribe: subscribe2, register: register2, canTrack } = useAnalytics();
3915
3982
  const { ready: shopifyAnalyticsReady } = register2("Internal_Shopify_Analytics");
3916
3983
  const { ready: customerPrivacyReady } = register2(
3917
3984
  "Internal_Shopify_CustomerPrivacy"
3918
3985
  );
3919
- const { checkoutDomain, storefrontAccessToken } = consent;
3920
- checkoutDomain && storefrontAccessToken && useCustomerPrivacy({
3986
+ const analyticsReady = () => {
3987
+ customerPrivacyReady();
3988
+ onReady();
3989
+ };
3990
+ useCustomerPrivacy({
3921
3991
  ...consent,
3922
- onVisitorConsentCollected: customerPrivacyReady
3992
+ onVisitorConsentCollected: analyticsReady,
3993
+ onReady: () => {
3994
+ if (!consent.withPrivacyBanner) {
3995
+ analyticsReady();
3996
+ }
3997
+ }
3923
3998
  });
3924
3999
  useShopifyCookies({ hasUserConsent: canTrack() });
3925
4000
  useEffect(() => {
@@ -3939,7 +4014,7 @@ function logMissingConfig2(fieldName) {
3939
4014
  }
3940
4015
  function prepareBasePageViewPayload(payload) {
3941
4016
  const customerPrivacy = getCustomerPrivacyRequired();
3942
- const hasUserConsent = customerPrivacy.userCanBeTracked();
4017
+ const hasUserConsent = customerPrivacy.analyticsProcessingAllowed();
3943
4018
  if (!payload?.shop?.shopId) {
3944
4019
  logMissingConfig2("shopId");
3945
4020
  return;
@@ -4229,8 +4304,10 @@ function CartAnalytics({
4229
4304
  updatedAt: cart.updatedAt
4230
4305
  })
4231
4306
  );
4232
- prevCart?.lines?.nodes?.forEach((prevLine) => {
4233
- const matchedLineId = cart?.lines.nodes.filter(
4307
+ const previousCartLines = prevCart?.lines ? flattenConnection(prevCart?.lines) : [];
4308
+ const currentCartLines = cart.lines ? flattenConnection(cart.lines) : [];
4309
+ previousCartLines?.forEach((prevLine) => {
4310
+ const matchedLineId = currentCartLines.filter(
4234
4311
  (line) => prevLine.id === line.id
4235
4312
  );
4236
4313
  if (matchedLineId?.length === 1) {
@@ -4255,8 +4332,8 @@ function CartAnalytics({
4255
4332
  });
4256
4333
  }
4257
4334
  });
4258
- cart?.lines?.nodes?.forEach((line) => {
4259
- const matchedLineId = prevCart?.lines.nodes.filter(
4335
+ currentCartLines?.forEach((line) => {
4336
+ const matchedLineId = previousCartLines.filter(
4260
4337
  (previousLine) => line.id === previousLine.id
4261
4338
  );
4262
4339
  if (!matchedLineId || matchedLineId.length === 0) {
@@ -4339,19 +4416,40 @@ function register(key) {
4339
4416
  };
4340
4417
  }
4341
4418
  function shopifyCanTrack() {
4342
- if (typeof window !== "undefined" && typeof window?.Shopify === "object" && typeof window?.Shopify?.customerPrivacy === "object" && typeof window?.Shopify?.customerPrivacy?.userCanBeTracked === "function") {
4343
- return window.Shopify.customerPrivacy.userCanBeTracked();
4419
+ try {
4420
+ return window.Shopify.customerPrivacy.analyticsProcessingAllowed();
4421
+ } catch (e) {
4344
4422
  }
4345
4423
  return false;
4346
4424
  }
4425
+ function messageOnError(field) {
4426
+ return `[h2:error:Analytics.Provider] - ${field} is required`;
4427
+ }
4347
4428
  function AnalyticsProvider({
4348
4429
  canTrack: customCanTrack,
4349
4430
  cart: currentCart,
4350
4431
  children,
4351
4432
  consent,
4352
4433
  customData = {},
4353
- shop: shopProp = null
4434
+ shop: shopProp = null,
4435
+ disableThrowOnError = false
4354
4436
  }) {
4437
+ if (!consent.checkoutDomain) {
4438
+ const errorMsg = messageOnError("consent.checkoutDomain");
4439
+ if (disableThrowOnError) {
4440
+ console.error(errorMsg);
4441
+ } else {
4442
+ invariant(false, errorMsg);
4443
+ }
4444
+ }
4445
+ if (!consent.storefrontAccessToken) {
4446
+ const errorMsg = messageOnError("consent.storefrontAccessToken");
4447
+ if (disableThrowOnError) {
4448
+ console.error(errorMsg);
4449
+ } else {
4450
+ invariant(false, errorMsg);
4451
+ }
4452
+ }
4355
4453
  const listenerSet = useRef(false);
4356
4454
  const { shop } = useShopAnalytics(shopProp);
4357
4455
  const [consentLoaded, setConsentLoaded] = useState(
@@ -4361,17 +4459,6 @@ function AnalyticsProvider({
4361
4459
  const [canTrack, setCanTrack] = useState(
4362
4460
  customCanTrack ? () => customCanTrack : () => shopifyCanTrack
4363
4461
  );
4364
- useEffect(() => {
4365
- if (customCanTrack)
4366
- return;
4367
- if (listenerSet.current)
4368
- return;
4369
- listenerSet.current = true;
4370
- document.addEventListener("visitorConsentCollected", () => {
4371
- setConsentLoaded(true);
4372
- setCanTrack(() => shopifyCanTrack);
4373
- });
4374
- }, [setConsentLoaded, setCanTrack, customCanTrack]);
4375
4462
  const value = useMemo(() => {
4376
4463
  return {
4377
4464
  canTrack,
@@ -4400,9 +4487,19 @@ function AnalyticsProvider({
4400
4487
  ]);
4401
4488
  return /* @__PURE__ */ jsxs(AnalyticsContext.Provider, { value, children: [
4402
4489
  children,
4403
- shop && /* @__PURE__ */ jsx(AnalyticsPageView, {}),
4404
- shop && currentCart && /* @__PURE__ */ jsx(CartAnalytics, { cart: currentCart, setCarts }),
4405
- shop && consent && /* @__PURE__ */ jsx(ShopifyAnalytics, { consent })
4490
+ !!shop && /* @__PURE__ */ jsx(AnalyticsPageView, {}),
4491
+ !!shop && !!currentCart && /* @__PURE__ */ jsx(CartAnalytics, { cart: currentCart, setCarts }),
4492
+ !!shop && /* @__PURE__ */ jsx(
4493
+ ShopifyAnalytics,
4494
+ {
4495
+ consent,
4496
+ onReady: () => {
4497
+ listenerSet.current = true;
4498
+ setConsentLoaded(true);
4499
+ setCanTrack(() => shopifyCanTrack);
4500
+ }
4501
+ }
4502
+ )
4406
4503
  ] });
4407
4504
  }
4408
4505
  function useAnalytics() {