{"version":3,"file":"useShopifyCookies.mjs","names":[],"sources":["../../src/useShopifyCookies.tsx"],"sourcesContent":["import {useEffect, useRef, useState} from 'react';\n// @ts-ignore - worktop/cookie types not properly exported\nimport {stringify} from 'worktop/cookie';\nimport {SHOPIFY_Y, SHOPIFY_S} from './cart-constants.js';\nimport {buildUUID} from './cookies-utils.js';\nimport {\n  getTrackingValues,\n  SHOPIFY_UNIQUE_TOKEN_HEADER,\n  SHOPIFY_VISIT_TOKEN_HEADER,\n} from './tracking-utils.js';\n\nconst longTermLength = 60 * 60 * 24 * 360 * 1; // ~1 year expiry\nconst shortTermLength = 60 * 30; // 30 mins\n\ntype UseShopifyCookiesOptions = CoreShopifyCookiesOptions & {\n  /**\n   * If set to `false`, Shopify cookies will be removed.\n   * If set to `true`, Shopify unique user token cookie will have cookie expiry of 1 year.\n   * Defaults to false.\n   **/\n  hasUserConsent?: boolean;\n  /**\n   * The domain scope of the cookie. Defaults to empty string.\n   **/\n  domain?: string;\n  /**\n   * The checkout domain of the shop. Defaults to empty string. If set, the cookie domain will check if it can be set with the checkout domain.\n   */\n  checkoutDomain?: string;\n  /**\n   * If set to `true`, it skips modifying the deprecated shopify_y and shopify_s cookies.\n   */\n  ignoreDeprecatedCookies?: boolean;\n};\n\n/**\n * Sets the `shopify_y` and `shopify_s` cookies in the browser based on user consent\n * for backward compatibility support.\n *\n * If `fetchTrackingValues` is true, it makes a request to Storefront API\n * to fetch or refresh Shopiy analytics and marketing cookies and tracking values.\n * Generally speaking, this should only be needed if you're not using Hydrogen's\n * built-in analytics components and hooks that already handle this automatically.\n * For example, set it to `true` if you are using `hydrogen-react` only with\n * a different framework and still need to make a same-domain request to\n * Storefront API to set cookies.\n *\n * If `ignoreDeprecatedCookies` is true, it skips setting the deprecated cookies entirely.\n * Useful when you only want to use the newer tracking values and not rely on the deprecated ones.\n *\n * @returns `true` when cookies are set and ready.\n * @publicDocs\n */\nexport function useShopifyCookies(options?: UseShopifyCookiesOptions): boolean {\n  const {\n    hasUserConsent,\n    domain = '',\n    checkoutDomain = '',\n    storefrontAccessToken,\n    fetchTrackingValues,\n    ignoreDeprecatedCookies = false,\n  } = options || {};\n\n  const coreCookiesReady = useCoreShopifyCookies({\n    storefrontAccessToken,\n    fetchTrackingValues,\n    checkoutDomain,\n  });\n\n  useEffect(() => {\n    // Skip setting JS cookies until http-only cookies and server-timing\n    // are ready so that we have values synced in JS and http-only cookies.\n    if (ignoreDeprecatedCookies || !coreCookiesReady) return;\n\n    /**\n     * Setting cookie with domain\n     *\n     * If no domain is provided, the cookie will be set for the current host.\n     * For Shopify, we need to ensure this domain is set with a leading dot.\n     */\n\n    // Use override domain or current host\n    let currentDomain = domain || window.location.host;\n\n    if (checkoutDomain) {\n      const checkoutDomainParts = checkoutDomain.split('.').reverse();\n      const currentDomainParts = currentDomain.split('.').reverse();\n      const sameDomainParts: Array<string> = [];\n      checkoutDomainParts.forEach((part, index) => {\n        if (part === currentDomainParts[index]) {\n          sameDomainParts.push(part);\n        }\n      });\n\n      currentDomain = sameDomainParts.reverse().join('.');\n    }\n\n    // Reset domain if localhost\n    if (/^localhost/.test(currentDomain)) currentDomain = '';\n\n    // Shopify checkout only consumes cookies set with leading dot domain\n    const domainWithLeadingDot = currentDomain\n      ? /^\\./.test(currentDomain)\n        ? currentDomain\n        : `.${currentDomain}`\n      : '';\n\n    /**\n     * Set user and session cookies and refresh the expiry time\n     */\n    if (hasUserConsent) {\n      const trackingValues = getTrackingValues();\n      if (\n        (\n          trackingValues.uniqueToken ||\n          trackingValues.visitToken ||\n          ''\n        ).startsWith('00000000-')\n      ) {\n        // Skip writing cookies when tracking values signal we don't have consent yet\n        return;\n      }\n\n      setCookie(\n        SHOPIFY_Y,\n        trackingValues.uniqueToken || buildUUID(),\n        longTermLength,\n        domainWithLeadingDot,\n      );\n      setCookie(\n        SHOPIFY_S,\n        trackingValues.visitToken || buildUUID(),\n        shortTermLength,\n        domainWithLeadingDot,\n      );\n    } else {\n      setCookie(SHOPIFY_Y, '', 0, domainWithLeadingDot);\n      setCookie(SHOPIFY_S, '', 0, domainWithLeadingDot);\n    }\n  }, [\n    coreCookiesReady,\n    hasUserConsent,\n    domain,\n    checkoutDomain,\n    ignoreDeprecatedCookies,\n  ]);\n\n  return coreCookiesReady;\n}\n\nfunction setCookie(\n  name: string,\n  value: string,\n  maxage: number,\n  domain: string,\n): void {\n  document.cookie = stringify(name, value, {\n    maxage,\n    domain,\n    samesite: 'Lax',\n    path: '/',\n  });\n}\n\nasync function fetchTrackingValuesFromBrowser(\n  storefrontAccessToken?: string,\n  storefrontApiDomain = '',\n): Promise<void> {\n  // These values might come from server-timing or old cookies.\n  // If consent cannot be initially assumed, these tokens\n  // will be dropped in SFAPI and it will return a mock token\n  // starting with '00000000-'.\n  // However, if consent can be assumed initially, these tokens\n  // will be used to create proper cookies and continue our flow.\n  const {uniqueToken, visitToken} = getTrackingValues();\n\n  const response = await fetch(\n    // TODO: update this endpoint when it becomes stable\n    `${storefrontApiDomain.replace(/\\/+$/, '')}/api/unstable/graphql.json`,\n    {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        ...(storefrontAccessToken && {\n          'X-Shopify-Storefront-Access-Token': storefrontAccessToken,\n        }),\n        ...(visitToken || uniqueToken\n          ? {\n              [SHOPIFY_VISIT_TOKEN_HEADER]: visitToken,\n              [SHOPIFY_UNIQUE_TOKEN_HEADER]: uniqueToken,\n            }\n          : undefined),\n      },\n      body: JSON.stringify({\n        query:\n          // This query ensures we get _cmp (consent) server-timing header, which is not available in other queries.\n          // This value can be passed later to consent-tracking-api and privacy-banner scripts to avoid extra requests.\n          'query ensureCookies { consentManagement { cookies(visitorConsent:{}) { cookieDomain } } }',\n      }),\n    },\n  );\n\n  if (!response.ok) {\n    throw new Error(\n      `Failed to fetch consent from browser: ${response.status} ${response.statusText}`,\n    );\n  }\n\n  // Consume the body to complete the request and\n  // ensure server-timing is available in performance API\n  await response.json();\n\n  // Ensure we cache the latest tracking values from resources timing\n  getTrackingValues();\n}\n\ntype CoreShopifyCookiesOptions = {\n  storefrontAccessToken?: string;\n  fetchTrackingValues?: boolean;\n  checkoutDomain?: string;\n};\n\n/**\n * Gets http-only cookies from Storefront API via same-origin fetch request.\n * Falls back to checkout domain if provided to at least obtain the tracking\n * values via server-timing headers.\n */\nfunction useCoreShopifyCookies({\n  checkoutDomain,\n  storefrontAccessToken,\n  fetchTrackingValues = false,\n}: CoreShopifyCookiesOptions) {\n  const [cookiesReady, setCookiesReady] = useState(!fetchTrackingValues);\n  const hasFetchedTrackingValues = useRef(false);\n\n  useEffect(() => {\n    if (!fetchTrackingValues) {\n      // Backend did the work, or proxy is disabled.\n      setCookiesReady(true);\n      return;\n    }\n\n    // React runs effects twice in dev mode, avoid double fetching\n    if (hasFetchedTrackingValues.current) return;\n    hasFetchedTrackingValues.current = true;\n\n    // Fetch consent from browser via proxy\n    fetchTrackingValuesFromBrowser(storefrontAccessToken)\n      .catch((error) =>\n        checkoutDomain\n          ? // Retry with checkout domain if available to at least\n            // get the server-timing values for tracking.\n            fetchTrackingValuesFromBrowser(\n              storefrontAccessToken,\n              checkoutDomain,\n            )\n          : Promise.reject(error),\n      )\n      .catch((error) => {\n        console.warn(\n          '[h2:warn:useShopifyCookies] Failed to fetch tracking values from browser: ' +\n            (error instanceof Error ? error.message : String(error)),\n        );\n      })\n      .finally(() => {\n        // Proceed even on errors, degraded tracking is better than no app\n        setCookiesReady(true);\n      });\n  }, [checkoutDomain, fetchTrackingValues, storefrontAccessToken]);\n\n  return cookiesReady;\n}\n"],"mappings":";;;;;;AAWA,IAAM,iBAAiB,OAAU,KAAK,MAAM;AAC5C,IAAM,kBAAkB;;;;;;;;;;;;;;;;;;;AAyCxB,SAAgB,kBAAkB,SAA6C;CAC7E,MAAM,EACJ,gBACA,SAAS,IACT,iBAAiB,IACjB,uBACA,qBACA,0BAA0B,UACxB,WAAW,EAAE;CAEjB,MAAM,mBAAmB,sBAAsB;EAC7C;EACA;EACA;EACD,CAAC;AAEF,iBAAgB;AAGd,MAAI,2BAA2B,CAAC,iBAAkB;;;;;;;EAUlD,IAAI,gBAAgB,UAAU,OAAO,SAAS;AAE9C,MAAI,gBAAgB;GAClB,MAAM,sBAAsB,eAAe,MAAM,IAAI,CAAC,SAAS;GAC/D,MAAM,qBAAqB,cAAc,MAAM,IAAI,CAAC,SAAS;GAC7D,MAAM,kBAAiC,EAAE;AACzC,uBAAoB,SAAS,MAAM,UAAU;AAC3C,QAAI,SAAS,mBAAmB,OAC9B,iBAAgB,KAAK,KAAK;KAE5B;AAEF,mBAAgB,gBAAgB,SAAS,CAAC,KAAK,IAAI;;AAIrD,MAAI,aAAa,KAAK,cAAc,CAAE,iBAAgB;EAGtD,MAAM,uBAAuB,gBACzB,MAAM,KAAK,cAAc,GACvB,gBACA,IAAI,kBACN;;;;AAKJ,MAAI,gBAAgB;GAClB,MAAM,iBAAiB,mBAAmB;AAC1C,QAEI,eAAe,eACf,eAAe,cACf,IACA,WAAW,YAAY,CAGzB;AAGF,aACE,WACA,eAAe,eAAe,WAAW,EACzC,gBACA,qBACD;AACD,aACE,WACA,eAAe,cAAc,WAAW,EACxC,iBACA,qBACD;SACI;AACL,aAAU,WAAW,IAAI,GAAG,qBAAqB;AACjD,aAAU,WAAW,IAAI,GAAG,qBAAqB;;IAElD;EACD;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO;;AAGT,SAAS,UACP,MACA,OACA,QACA,QACM;AACN,UAAS,SAAS,UAAU,MAAM,OAAO;EACvC;EACA;EACA,UAAU;EACV,MAAM;EACP,CAAC;;AAGJ,eAAe,+BACb,uBACA,sBAAsB,IACP;CAOf,MAAM,EAAC,aAAa,eAAc,mBAAmB;CAErD,MAAM,WAAW,MAAM,MAErB,GAAG,oBAAoB,QAAQ,QAAQ,GAAG,CAAC,6BAC3C;EACE,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,GAAI,yBAAyB,EAC3B,qCAAqC,uBACtC;GACD,GAAI,cAAc,cACd;KACG,6BAA6B;KAC7B,8BAA8B;IAChC,GACD,KAAA;GACL;EACD,MAAM,KAAK,UAAU,EACnB,OAGE,6FACH,CAAC;EACH,CACF;AAED,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MACR,yCAAyC,SAAS,OAAO,GAAG,SAAS,aACtE;AAKH,OAAM,SAAS,MAAM;AAGrB,oBAAmB;;;;;;;AAcrB,SAAS,sBAAsB,EAC7B,gBACA,uBACA,sBAAsB,SACM;CAC5B,MAAM,CAAC,cAAc,mBAAmB,SAAS,CAAC,oBAAoB;CACtE,MAAM,2BAA2B,OAAO,MAAM;AAE9C,iBAAgB;AACd,MAAI,CAAC,qBAAqB;AAExB,mBAAgB,KAAK;AACrB;;AAIF,MAAI,yBAAyB,QAAS;AACtC,2BAAyB,UAAU;AAGnC,iCAA+B,sBAAsB,CAClD,OAAO,UACN,iBAGI,+BACE,uBACA,eACD,GACD,QAAQ,OAAO,MAAM,CAC1B,CACA,OAAO,UAAU;AAChB,WAAQ,KACN,gFACG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAC1D;IACD,CACD,cAAc;AAEb,mBAAgB,KAAK;IACrB;IACH;EAAC;EAAgB;EAAqB;EAAsB,CAAC;AAEhE,QAAO"}