@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.
@@ -5,10 +5,12 @@ var react = require('react');
5
5
  var react$1 = require('@remix-run/react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var cspBuilder = require('content-security-policy-builder');
8
+ var invariant = require('tiny-invariant');
8
9
 
9
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
11
 
11
12
  var cspBuilder__default = /*#__PURE__*/_interopDefault(cspBuilder);
13
+ var invariant__default = /*#__PURE__*/_interopDefault(invariant);
12
14
 
13
15
  var __defProp = Object.defineProperty;
14
16
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -482,12 +484,20 @@ async function fetchWithServerCache(url, requestInit, {
482
484
  ).then(fromSerializableResponse);
483
485
  }
484
486
 
487
+ // src/version.ts
488
+ var LIB_VERSION = "2024.4.2";
489
+
485
490
  // src/constants.ts
486
491
  var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
487
492
  var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
488
493
  var SDK_VARIANT_HEADER = "X-SDK-Variant";
489
494
  var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
490
495
  var SDK_VERSION_HEADER = "X-SDK-Version";
496
+ var DEFAULT_CUSTOMER_API_VERSION = "2024-04";
497
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
498
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
499
+ var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
500
+ var BUYER_SESSION_KEY = "buyer";
491
501
 
492
502
  // src/utils/uuid.ts
493
503
  function generateUUID() {
@@ -507,9 +517,6 @@ var warnOnce = (string) => {
507
517
  }
508
518
  };
509
519
 
510
- // src/version.ts
511
- var LIB_VERSION = "2024.4.1";
512
-
513
520
  // src/utils/graphql.ts
514
521
  function minifyQuery(string) {
515
522
  return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
@@ -2295,12 +2302,6 @@ function getPaginationVariables(request, options = { pageBy: 20 }) {
2295
2302
  return variables;
2296
2303
  }
2297
2304
 
2298
- // src/customer/constants.ts
2299
- var DEFAULT_CUSTOMER_API_VERSION = "2024-04";
2300
- var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
2301
- var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
2302
- var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
2303
-
2304
2305
  // src/customer/BadRequest.ts
2305
2306
  var BadRequest = class extends Response {
2306
2307
  constructor(message, helpMessage, headers) {
@@ -2343,7 +2344,8 @@ async function refreshToken({
2343
2344
  customerAccountId,
2344
2345
  customerAccountUrl,
2345
2346
  httpsOrigin,
2346
- debugInfo
2347
+ debugInfo,
2348
+ exchangeForStorefrontCustomerAccessToken
2347
2349
  }) {
2348
2350
  const newBody = new URLSearchParams();
2349
2351
  const customerAccount = session.get(CUSTOMER_ACCOUNT_SESSION_KEY);
@@ -2399,9 +2401,11 @@ async function refreshToken({
2399
2401
  refreshToken: refresh_token,
2400
2402
  idToken: id_token
2401
2403
  });
2404
+ await exchangeForStorefrontCustomerAccessToken();
2402
2405
  }
2403
2406
  function clearSession(session) {
2404
2407
  session.unset(CUSTOMER_ACCOUNT_SESSION_KEY);
2408
+ session.unset(BUYER_SESSION_KEY);
2405
2409
  }
2406
2410
  async function checkExpires({
2407
2411
  locks,
@@ -2410,7 +2414,8 @@ async function checkExpires({
2410
2414
  customerAccountId,
2411
2415
  customerAccountUrl,
2412
2416
  httpsOrigin,
2413
- debugInfo
2417
+ debugInfo,
2418
+ exchangeForStorefrontCustomerAccessToken
2414
2419
  }) {
2415
2420
  if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
2416
2421
  try {
@@ -2420,7 +2425,8 @@ async function checkExpires({
2420
2425
  customerAccountId,
2421
2426
  customerAccountUrl,
2422
2427
  httpsOrigin,
2423
- debugInfo
2428
+ debugInfo,
2429
+ exchangeForStorefrontCustomerAccessToken
2424
2430
  });
2425
2431
  await locks.refresh;
2426
2432
  delete locks.refresh;
@@ -2564,7 +2570,8 @@ function createCustomerAccountClient({
2564
2570
  waitUntil,
2565
2571
  authUrl,
2566
2572
  customAuthStatusHandler,
2567
- logErrors = true
2573
+ logErrors = true,
2574
+ unstableB2b = false
2568
2575
  }) {
2569
2576
  if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
2570
2577
  console.warn(
@@ -2587,7 +2594,7 @@ function createCustomerAccountClient({
2587
2594
  const customerAccountApiUrl = `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`;
2588
2595
  const locks = {};
2589
2596
  async function fetchCustomerAPI({
2590
- query,
2597
+ query: query2,
2591
2598
  type,
2592
2599
  variables = {}
2593
2600
  }) {
@@ -2605,7 +2612,7 @@ function createCustomerAccountClient({
2605
2612
  Origin: httpsOrigin,
2606
2613
  Authorization: accessToken
2607
2614
  },
2608
- body: JSON.stringify({ query, variables })
2615
+ body: JSON.stringify({ query: query2, variables })
2609
2616
  });
2610
2617
  logSubRequestEvent?.({
2611
2618
  url: customerAccountApiUrl,
@@ -2613,7 +2620,7 @@ function createCustomerAccountClient({
2613
2620
  response,
2614
2621
  waitUntil,
2615
2622
  stackInfo,
2616
- query,
2623
+ query: query2,
2617
2624
  variables,
2618
2625
  ...getDebugHeaders(request)
2619
2626
  });
@@ -2622,7 +2629,7 @@ function createCustomerAccountClient({
2622
2629
  url: customerAccountApiUrl,
2623
2630
  response,
2624
2631
  type,
2625
- query,
2632
+ query: query2,
2626
2633
  queryVariables: variables,
2627
2634
  errors: void 0,
2628
2635
  client: "customer"
@@ -2653,7 +2660,7 @@ function createCustomerAccountClient({
2653
2660
  clientOperation: `customerAccount.${errorOptions.type}`,
2654
2661
  requestId: response.headers.get("x-request-id"),
2655
2662
  queryVariables: variables,
2656
- query
2663
+ query: query2
2657
2664
  })
2658
2665
  );
2659
2666
  return { ...APIresponse, ...errors && { errors: gqlErrors } };
@@ -2682,7 +2689,8 @@ function createCustomerAccountClient({
2682
2689
  waitUntil,
2683
2690
  stackInfo,
2684
2691
  ...getDebugHeaders(request)
2685
- }
2692
+ },
2693
+ exchangeForStorefrontCustomerAccessToken
2686
2694
  });
2687
2695
  } catch {
2688
2696
  return false;
@@ -2699,6 +2707,55 @@ function createCustomerAccountClient({
2699
2707
  if (hasAccessToken)
2700
2708
  return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
2701
2709
  }
2710
+ async function mutate(mutation, options) {
2711
+ ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2712
+ mutation = minifyQuery(mutation);
2713
+ assertMutation(mutation, "customer.mutate");
2714
+ return withSyncStack(
2715
+ fetchCustomerAPI({ query: mutation, type: "mutation", ...options }),
2716
+ { logErrors }
2717
+ );
2718
+ }
2719
+ async function query(query2, options) {
2720
+ ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2721
+ query2 = minifyQuery(query2);
2722
+ assertQuery(query2, "customer.query");
2723
+ return withSyncStack(fetchCustomerAPI({ query: query2, type: "query", ...options }), {
2724
+ logErrors
2725
+ });
2726
+ }
2727
+ function setBuyer(buyer) {
2728
+ session.set(BUYER_SESSION_KEY, {
2729
+ ...session.get(BUYER_SESSION_KEY),
2730
+ ...buyer
2731
+ });
2732
+ }
2733
+ async function getBuyer() {
2734
+ const hasAccessToken = await isLoggedIn();
2735
+ if (!hasAccessToken) {
2736
+ return;
2737
+ }
2738
+ return session.get(BUYER_SESSION_KEY);
2739
+ }
2740
+ async function exchangeForStorefrontCustomerAccessToken() {
2741
+ if (!unstableB2b) {
2742
+ return;
2743
+ }
2744
+ const STOREFRONT_CUSTOMER_ACCOUNT_TOKEN_CREATE = `#graphql
2745
+ mutation storefrontCustomerAccessTokenCreate {
2746
+ storefrontCustomerAccessTokenCreate {
2747
+ customerAccessToken
2748
+ }
2749
+ }
2750
+ `;
2751
+ const { data } = await mutate(STOREFRONT_CUSTOMER_ACCOUNT_TOKEN_CREATE);
2752
+ const customerAccessToken = data?.storefrontCustomerAccessTokenCreate?.customerAccessToken;
2753
+ if (customerAccessToken) {
2754
+ setBuyer({
2755
+ customerAccessToken
2756
+ });
2757
+ }
2758
+ }
2702
2759
  return {
2703
2760
  login: async (options) => {
2704
2761
  ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
@@ -2766,24 +2823,8 @@ function createCustomerAccountClient({
2766
2823
  handleAuthStatus,
2767
2824
  getAccessToken,
2768
2825
  getApiUrl: () => customerAccountApiUrl,
2769
- mutate(mutation, options) {
2770
- ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2771
- mutation = minifyQuery(mutation);
2772
- assertMutation(mutation, "customer.mutate");
2773
- return withSyncStack(
2774
- fetchCustomerAPI({ query: mutation, type: "mutation", ...options }),
2775
- { logErrors }
2776
- );
2777
- },
2778
- query(query, options) {
2779
- ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2780
- query = minifyQuery(query);
2781
- assertQuery(query, "customer.query");
2782
- return withSyncStack(
2783
- fetchCustomerAPI({ query, type: "query", ...options }),
2784
- { logErrors }
2785
- );
2786
- },
2826
+ mutate,
2827
+ query,
2787
2828
  authorize: async () => {
2788
2829
  ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId);
2789
2830
  const code = requestUrl.searchParams.get("code");
@@ -2878,19 +2919,19 @@ function createCustomerAccountClient({
2878
2919
  )?.redirectPath;
2879
2920
  session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
2880
2921
  accessToken: customerAccessToken,
2881
- expiresAt: new Date(
2882
- (/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3
2883
- ).getTime() + "",
2922
+ expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + "",
2884
2923
  refreshToken: refresh_token,
2885
- idToken: id_token,
2886
- redirectPath: void 0
2924
+ idToken: id_token
2887
2925
  });
2926
+ await exchangeForStorefrontCustomerAccessToken();
2888
2927
  return redirect(redirectPath || DEFAULT_REDIRECT_PATH, {
2889
2928
  headers: {
2890
2929
  "Set-Cookie": await session.commit()
2891
2930
  }
2892
2931
  });
2893
- }
2932
+ },
2933
+ UNSTABLE_setBuyer: setBuyer,
2934
+ UNSTABLE_getBuyer: getBuyer
2894
2935
  };
2895
2936
  }
2896
2937
  function ifInvalidCredentialThrowError(customerAccountUrl, customerAccountId) {
@@ -2991,10 +3032,18 @@ var MINIMAL_CART_FRAGMENT = `#graphql
2991
3032
  // src/cart/queries/cartCreateDefault.ts
2992
3033
  function cartCreateDefault(options) {
2993
3034
  return async (input, optionalParams) => {
3035
+ const buyer = options.customerAccount ? await options.customerAccount.UNSTABLE_getBuyer() : void 0;
2994
3036
  const { cartId, ...restOfOptionalParams } = optionalParams || {};
3037
+ const { buyerIdentity, ...restOfInput } = input;
2995
3038
  const { cartCreate, errors } = await options.storefront.mutate(CART_CREATE_MUTATION(options.cartFragment), {
2996
3039
  variables: {
2997
- input,
3040
+ input: {
3041
+ ...restOfInput,
3042
+ buyerIdentity: {
3043
+ ...buyer,
3044
+ ...buyerIdentity
3045
+ }
3046
+ },
2998
3047
  ...restOfOptionalParams
2999
3048
  }
3000
3049
  });
@@ -3321,10 +3370,19 @@ var CART_DISCOUNT_CODE_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT)
3321
3370
  // src/cart/queries/cartBuyerIdentityUpdateDefault.ts
3322
3371
  function cartBuyerIdentityUpdateDefault(options) {
3323
3372
  return async (buyerIdentity, optionalParams) => {
3373
+ if (buyerIdentity.companyLocationId && options.customerAccount) {
3374
+ options.customerAccount.UNSTABLE_setBuyer({
3375
+ companyLocationId: buyerIdentity.companyLocationId
3376
+ });
3377
+ }
3378
+ const buyer = options.customerAccount ? await options.customerAccount.UNSTABLE_getBuyer() : void 0;
3324
3379
  const { cartBuyerIdentityUpdate, errors } = await options.storefront.mutate(CART_BUYER_IDENTITY_UPDATE_MUTATION(options.cartFragment), {
3325
3380
  variables: {
3326
3381
  cartId: options.getCartId(),
3327
- buyerIdentity,
3382
+ buyerIdentity: {
3383
+ ...buyer,
3384
+ ...buyerIdentity
3385
+ },
3328
3386
  ...optionalParams
3329
3387
  }
3330
3388
  });
@@ -3592,7 +3650,8 @@ function createCartHandler(options) {
3592
3650
  const mutateOptions = {
3593
3651
  storefront,
3594
3652
  getCartId,
3595
- cartFragment: cartMutateFragment
3653
+ cartFragment: cartMutateFragment,
3654
+ customerAccount
3596
3655
  };
3597
3656
  const _cartCreate = cartCreateDefault(mutateOptions);
3598
3657
  const cartCreate = async function(...args) {
@@ -3819,7 +3878,11 @@ function createCSPHeader(nonce, props) {
3819
3878
  function addCspDirective(currentValue, value) {
3820
3879
  const normalizedValue = typeof value === "string" ? [value] : value;
3821
3880
  const normalizedCurrentValue = Array.isArray(currentValue) ? currentValue : [String(currentValue)];
3822
- const newValue = Array.isArray(normalizedValue) ? [...normalizedCurrentValue, ...normalizedValue] : normalizedValue;
3881
+ const newValue = Array.isArray(normalizedValue) ? (
3882
+ // If the default directive is `none`, don't
3883
+ // merge the override with the default value.
3884
+ normalizedValue.every((a) => a === `'none'`) ? normalizedCurrentValue : [...normalizedCurrentValue, ...normalizedValue]
3885
+ ) : normalizedValue;
3823
3886
  return newValue;
3824
3887
  }
3825
3888
  var Script = react.forwardRef(
@@ -3927,6 +3990,7 @@ function useCustomerPrivacy(props) {
3927
3990
  const {
3928
3991
  withPrivacyBanner = true,
3929
3992
  onVisitorConsentCollected,
3993
+ onReady,
3930
3994
  ...consentConfig
3931
3995
  } = props;
3932
3996
  const loadedEvent = react.useRef(false);
@@ -3983,6 +4047,9 @@ function useCustomerPrivacy(props) {
3983
4047
  );
3984
4048
  };
3985
4049
  }
4050
+ if (onReady && !withPrivacyBanner) {
4051
+ onReady();
4052
+ }
3986
4053
  }, [scriptStatus, withPrivacyBanner, consentConfig]);
3987
4054
  return;
3988
4055
  }
@@ -4003,17 +4070,26 @@ function getCustomerPrivacyRequired() {
4003
4070
  return customerPrivacy;
4004
4071
  }
4005
4072
  function ShopifyAnalytics({
4006
- consent
4073
+ consent,
4074
+ onReady
4007
4075
  }) {
4008
4076
  const { subscribe: subscribe2, register: register2, canTrack } = useAnalytics();
4009
4077
  const { ready: shopifyAnalyticsReady } = register2("Internal_Shopify_Analytics");
4010
4078
  const { ready: customerPrivacyReady } = register2(
4011
4079
  "Internal_Shopify_CustomerPrivacy"
4012
4080
  );
4013
- const { checkoutDomain, storefrontAccessToken } = consent;
4014
- checkoutDomain && storefrontAccessToken && useCustomerPrivacy({
4081
+ const analyticsReady = () => {
4082
+ customerPrivacyReady();
4083
+ onReady();
4084
+ };
4085
+ useCustomerPrivacy({
4015
4086
  ...consent,
4016
- onVisitorConsentCollected: customerPrivacyReady
4087
+ onVisitorConsentCollected: analyticsReady,
4088
+ onReady: () => {
4089
+ if (!consent.withPrivacyBanner) {
4090
+ analyticsReady();
4091
+ }
4092
+ }
4017
4093
  });
4018
4094
  hydrogenReact.useShopifyCookies({ hasUserConsent: canTrack() });
4019
4095
  react.useEffect(() => {
@@ -4033,7 +4109,7 @@ function logMissingConfig2(fieldName) {
4033
4109
  }
4034
4110
  function prepareBasePageViewPayload(payload) {
4035
4111
  const customerPrivacy = getCustomerPrivacyRequired();
4036
- const hasUserConsent = customerPrivacy.userCanBeTracked();
4112
+ const hasUserConsent = customerPrivacy.analyticsProcessingAllowed();
4037
4113
  if (!payload?.shop?.shopId) {
4038
4114
  logMissingConfig2("shopId");
4039
4115
  return;
@@ -4323,8 +4399,10 @@ function CartAnalytics({
4323
4399
  updatedAt: cart.updatedAt
4324
4400
  })
4325
4401
  );
4326
- prevCart?.lines?.nodes?.forEach((prevLine) => {
4327
- const matchedLineId = cart?.lines.nodes.filter(
4402
+ const previousCartLines = prevCart?.lines ? hydrogenReact.flattenConnection(prevCart?.lines) : [];
4403
+ const currentCartLines = cart.lines ? hydrogenReact.flattenConnection(cart.lines) : [];
4404
+ previousCartLines?.forEach((prevLine) => {
4405
+ const matchedLineId = currentCartLines.filter(
4328
4406
  (line) => prevLine.id === line.id
4329
4407
  );
4330
4408
  if (matchedLineId?.length === 1) {
@@ -4349,8 +4427,8 @@ function CartAnalytics({
4349
4427
  });
4350
4428
  }
4351
4429
  });
4352
- cart?.lines?.nodes?.forEach((line) => {
4353
- const matchedLineId = prevCart?.lines.nodes.filter(
4430
+ currentCartLines?.forEach((line) => {
4431
+ const matchedLineId = previousCartLines.filter(
4354
4432
  (previousLine) => line.id === previousLine.id
4355
4433
  );
4356
4434
  if (!matchedLineId || matchedLineId.length === 0) {
@@ -4433,19 +4511,40 @@ function register(key) {
4433
4511
  };
4434
4512
  }
4435
4513
  function shopifyCanTrack() {
4436
- if (typeof window !== "undefined" && typeof window?.Shopify === "object" && typeof window?.Shopify?.customerPrivacy === "object" && typeof window?.Shopify?.customerPrivacy?.userCanBeTracked === "function") {
4437
- return window.Shopify.customerPrivacy.userCanBeTracked();
4514
+ try {
4515
+ return window.Shopify.customerPrivacy.analyticsProcessingAllowed();
4516
+ } catch (e) {
4438
4517
  }
4439
4518
  return false;
4440
4519
  }
4520
+ function messageOnError(field) {
4521
+ return `[h2:error:Analytics.Provider] - ${field} is required`;
4522
+ }
4441
4523
  function AnalyticsProvider({
4442
4524
  canTrack: customCanTrack,
4443
4525
  cart: currentCart,
4444
4526
  children,
4445
4527
  consent,
4446
4528
  customData = {},
4447
- shop: shopProp = null
4529
+ shop: shopProp = null,
4530
+ disableThrowOnError = false
4448
4531
  }) {
4532
+ if (!consent.checkoutDomain) {
4533
+ const errorMsg = messageOnError("consent.checkoutDomain");
4534
+ if (disableThrowOnError) {
4535
+ console.error(errorMsg);
4536
+ } else {
4537
+ invariant__default.default(false, errorMsg);
4538
+ }
4539
+ }
4540
+ if (!consent.storefrontAccessToken) {
4541
+ const errorMsg = messageOnError("consent.storefrontAccessToken");
4542
+ if (disableThrowOnError) {
4543
+ console.error(errorMsg);
4544
+ } else {
4545
+ invariant__default.default(false, errorMsg);
4546
+ }
4547
+ }
4449
4548
  const listenerSet = react.useRef(false);
4450
4549
  const { shop } = useShopAnalytics(shopProp);
4451
4550
  const [consentLoaded, setConsentLoaded] = react.useState(
@@ -4455,17 +4554,6 @@ function AnalyticsProvider({
4455
4554
  const [canTrack, setCanTrack] = react.useState(
4456
4555
  customCanTrack ? () => customCanTrack : () => shopifyCanTrack
4457
4556
  );
4458
- react.useEffect(() => {
4459
- if (customCanTrack)
4460
- return;
4461
- if (listenerSet.current)
4462
- return;
4463
- listenerSet.current = true;
4464
- document.addEventListener("visitorConsentCollected", () => {
4465
- setConsentLoaded(true);
4466
- setCanTrack(() => shopifyCanTrack);
4467
- });
4468
- }, [setConsentLoaded, setCanTrack, customCanTrack]);
4469
4557
  const value = react.useMemo(() => {
4470
4558
  return {
4471
4559
  canTrack,
@@ -4494,9 +4582,19 @@ function AnalyticsProvider({
4494
4582
  ]);
4495
4583
  return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsContext.Provider, { value, children: [
4496
4584
  children,
4497
- shop && /* @__PURE__ */ jsxRuntime.jsx(AnalyticsPageView, {}),
4498
- shop && currentCart && /* @__PURE__ */ jsxRuntime.jsx(CartAnalytics, { cart: currentCart, setCarts }),
4499
- shop && consent && /* @__PURE__ */ jsxRuntime.jsx(ShopifyAnalytics, { consent })
4585
+ !!shop && /* @__PURE__ */ jsxRuntime.jsx(AnalyticsPageView, {}),
4586
+ !!shop && !!currentCart && /* @__PURE__ */ jsxRuntime.jsx(CartAnalytics, { cart: currentCart, setCarts }),
4587
+ !!shop && /* @__PURE__ */ jsxRuntime.jsx(
4588
+ ShopifyAnalytics,
4589
+ {
4590
+ consent,
4591
+ onReady: () => {
4592
+ listenerSet.current = true;
4593
+ setConsentLoaded(true);
4594
+ setCanTrack(() => shopifyCanTrack);
4595
+ }
4596
+ }
4597
+ )
4500
4598
  ] });
4501
4599
  }
4502
4600
  function useAnalytics() {