{"version":3,"file":"cart-lifecycle-snippet.min.js","mappings":"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","sources":["webpack://lange/../node_modules/@integrabeauty/shopify-ajax-api/src/cart.ts","webpack://lange/../node_modules/@integrabeauty/assert/src/index.ts","webpack://lange/../node_modules/uuid/dist/rng.js","webpack://lange/../node_modules/uuid/dist/stringify.js","webpack://lange/../node_modules/uuid/dist/v4.js","webpack://lange/./lib/object.ts","webpack://lange/./lib/untrusted-value.ts","webpack://lange/./lib/cart/discount/discount.ts","webpack://lange/./lib/cart/state.ts","webpack://lange/./lib/perf.ts","webpack://lange/./lib/cart/transaction.ts","webpack://lange/./snippets/cart-lifecycle/cross-tab.ts","webpack://lange/./snippets/cart-lifecycle/page-load.ts","webpack://lange/./lib/decorate-error.ts","webpack://lange/./lib/cart/error.ts","webpack://lange/./snippets/cart-lifecycle/cart-diff.ts","webpack://lange/./snippets/cart-lifecycle/identity.ts","webpack://lange/./snippets/cart-lifecycle/transforms/attentive-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/awin-transforms.ts","webpack://lange/../node_modules/@integrabeauty/cookie/src/index.ts","webpack://lange/./snippets/cart-lifecycle/transforms/google-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/klaviyo-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/meta-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/microsoft-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/session-transforms.ts","webpack://lange/../node_modules/@integrabeauty/shopify-consent/src/index.ts","webpack://lange/./snippets/cart-lifecycle/transforms/shopify-transforms.ts","webpack://lange/./lib/tune.ts","webpack://lange/./snippets/cart-lifecycle/transforms/shopmy-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/survey-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/tiktok-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/tune-transforms.ts","webpack://lange/./snippets/cart-lifecycle/transforms/unity-transforms.ts","webpack://lange/./lib/cart/discount/helper.ts","webpack://lange/./snippets/cart-lifecycle/url-discounts.ts","webpack://lange/./snippets/cart-lifecycle/transforms/cart-transforms.ts","webpack://lange/./lib/cart/equality.ts","webpack://lange/./snippets/cart-lifecycle/page-show.ts"],"sourcesContent":["import type {} from '@integrabeauty/shopify-browser-types';\n\n/**\n * Represents the value of a cart attribute. Ordinarily values are strings. However, Shopify also\n * supports storing nested objects as values.\n */\nexport type AttributeValue = Record<string, string> | string;\n\n/**\n * The type of the value of `Cart['attributes']`\n */\nexport type Attributes = Record<string, AttributeValue>;\n\n/**\n * This is the stored cart, which is what a get cart answers with. A mutation answers with a\n * superset, so see the response body types alongside each mutation for the extra properties those\n * carry.\n *\n * @todo selling plan properties\n * @todo endpoint request body response body types\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart\n */\nexport interface Cart {\n  /**\n   * An object where the key is the attribute name and the value is the attribute value.\n   */\n  attributes: Attributes;\n\n  /**\n   * A discount application represents a type of discount.\n   *\n   * When a discount is applied to one or more line items, it is said to be _allocated_ to the line\n   * items. That is why line level discount allocation objects have a nested application object that\n   * will correlate with one of the applications in this array.\n   *\n   * When a discount is applied to the whole cart, it will only be present in this array because it\n   * is not allocated to particular lines.\n   */\n  cart_level_discount_applications: DiscountApplication[];\n\n  /**\n   * The ISO currency code for the cart.\n   *\n   * @example \"USD\"\n   */\n  currency: string;\n\n  /**\n   * Discount codes that have been applied to the cart.\n   *\n   * Not all codes are applicable. It is possible for a code to be applied but inapplicable.\n   *\n   * This does not provide information about the amount of each discount. Refer to the cart level\n   * discount applications or line level discount allocations for that information.\n   *\n   * This array will include shipping discounts. A shipping discount will be present in the array if\n   * applied but in the inapplicable state while in the cart. The discount will appear as applicable\n   * at checkout automatically.\n   */\n  discount_codes: DiscountCode[];\n\n  /**\n   * Total quantity of items in a cart\n   */\n  item_count: number;\n\n  /**\n   * The cart's line items.\n   *\n   * A cart will always have a defined items array, even if the cart is empty.\n   */\n  items: Item[];\n\n  items_subtotal_price: number;\n\n  /**\n   * An optional textual note that may be applied to the cart. If a cart becomes a checkout, and\n   * then the checkout becomes an order, the cart note will carry over to the order note.\n   *\n   * An order's note, and an order's note attributes, are two completely separate concepts.\n   */\n  note: string;\n\n  original_total_price: number;\n\n  /**\n   * This is true when at least one item in the cart requires shipping.\n   *\n   * This is false when no items in the cart require shipping.\n   *\n   * Shopify supports digital-only carts. These are carts that when purchased do not require the\n   * creation of a fulfillment. For example, a cart that consists only of a digital gift card.\n   */\n  requires_shipping: boolean;\n\n  /**\n   * This property is defined when there are cart mutations that involve the use of liquid-rendered\n   * sections.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#request-sections\n   */\n  sections: Record<string, string>;\n\n  /**\n   * This is the publicly visible cart id.\n   *\n   * If a checkout is created from this cart, the checkout's cart token will be equal to this value.\n   *\n   * Never write logic that parses this value. The format can change at any time, and has changed\n   * several times in the past.\n   *\n   * The value may contain url-like values, e.g. \"abc?key=123\"\n   */\n  token: string;\n\n  /**\n   * The total reduction in price in currency subunits (e.g. in cents for USD).\n   *\n   * @example 5010\n   */\n  total_discount: number;\n\n  /**\n   * Total price in currency subunits. For example, if the currency is USD, this is in cents.\n   *\n   * @example 1050\n   */\n  total_price: number;\n\n  /**\n   * The total weight of the cart (in grams?). This is a simple sum of the weight of the items of\n   * the cart (unit weight * quantity).\n   *\n   * @example 123.456\n   */\n  total_weight: number;\n}\n\n/**\n * Represents a discount that has been applied to an item.\n */\nexport interface Discount {\n  amount: number;\n  title: string;\n}\n\n/**\n * Represents a discount application that has been allocated to a line item.\n */\nexport interface DiscountAllocation {\n  /**\n   * The amount of the application that was allocated to a particular line item.\n   *\n   * When a discount application is allocated to several line items, this is a partial value of the\n   * full discount application amount that is allocated to this particular line. When the discount\n   * application is only allocated to a single line, this value will be equal to the total value of\n   * the discount application.\n   *\n   * For example, let's say we have cart with 2 line items and 10 dollar off discount application.\n   * The total value of the discount application is 10 dollars. Let's say the discount is allocated\n   * to both lines. The value of each allocation is 5 dollars. For each line this amount will\n   * contain the value 500. Within the nested application object, the application's total amount\n   * will be 1000. This is because each nested application object is a copy of the one application\n   * that happens to have multiple allocations (the discount is applied to multiple lines).\n   *\n   * The amount is in currency subunits. For example, for USD, this is the value of cents. For\n   * example, a discount of $2.50 is 250.\n   *\n   * @example 1050\n   */\n  amount: number;\n\n  /**\n   * The corresponding discount application that was allocated to a particular line item.\n   */\n  discount_application: DiscountApplication;\n}\n\n/**\n * A discount application represents a discount of some kind. There are automatically applied\n * discount applications, discount code applications, and script applied discount applications.\n */\nexport interface DiscountApplication {\n  allocation_method: string;\n  created_at: string;\n  description: string;\n  key: string;\n  target_selection: string;\n  target_type: string;\n  title: string;\n  total_allocated_amount: number;\n  type: string;\n  value: string;\n  value_type: string;\n}\n\n/**\n * The details of an image resource, such as the hero image for a product.\n */\nexport interface Image {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\n/**\n * Represents a line item in a cart.\n */\nexport interface Item {\n  /**\n   * The price of the line item after discounts\n   */\n  discounted_price: number;\n\n  /**\n   * Discount codes applied to the line item.\n   *\n   * @deprecated\n   */\n  discounts: Discount[];\n\n  /**\n   * The main image for the product\n   */\n  featured_image: Image;\n\n  /**\n   * The combined price, in the currency's subunit, of all of the items in the line item. This\n   * includes any line-level discounts.\n   *\n   * The value is equal to line_item.final_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit. This includes any line-level discounts.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_price: number;\n\n  /**\n   * Returns true if the product associated with the line item is a gift card. Returns false if not.\n   */\n  gift_card: boolean;\n\n  /**\n   * The weight of the line item in the store's default weight unit.\n   */\n  grams: number;\n\n  handle: string;\n\n  has_components: boolean;\n\n  /**\n   * The ID of the line item. The ID differs depending on the context. The following table outlines\n   * the possible contexts and their associated values:\n   *\n   * Context Value\n   *\n   * * cart.items The ID of the line item's variant. This ID isn't unique, and can be shared by\n   *   multiple items with the same variant.\n   * * checkout.line_items A temporary unique hash generated for the checkout.\n   * * order.line_items A unique integer ID.\n   */\n  id: number;\n\n  image: string;\n\n  /**\n   * Added in conjunction with parent_relationship to support determining whether a line item can be\n   * removed or have its quantity updated when the line item is a child of another line item, e.g.\n   * part of a bundle or value set or promotional gift.\n   */\n  instructions: {\n    can_remove: boolean;\n    can_update_quantity: boolean;\n  };\n\n  key: string;\n\n  /**\n   * The discount allocations that apply to the line item.\n   */\n  line_level_discount_allocations: DiscountAllocation[];\n\n  /**\n   * The total amount of any discounts applied to the line item in the currency's subunit.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  line_level_total_discount: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  line_price: number;\n\n  /**\n   * The name and value pairs for each option of the variant associated with the line item.\n   *\n   * The array is never empty because variants with no options still have a default option. Because\n   * of this, you should use line_item.product.has_only_default_variant to check whether there's any\n   * information to output.\n   */\n  options_with_values: Option[];\n\n  /**\n   * The combined price of all of the items in a line item in the currency's subunit, before any\n   * discounts have been applied.\n   *\n   * The value is equal to line_item.original_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit, before discounts have been applied.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_price: number;\n\n  /**\n   * The parent relationship of the line item. This is only present if the line item is a child of\n   * another line item. For example, when the line item is part of a bundle or value set or\n   * promotional gift.\n   *\n   * The parent relationship must be established by adding the \"parent_id\" property to the add to\n   * bag request.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_relationship: {\n    parent_key: string;\n  };\n\n  presentment_price: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  price: number;\n\n  product_description: string;\n\n  product_has_only_default_variant: boolean;\n\n  /**\n   * The ID of the line item's product.\n   */\n  product_id: number;\n\n  product_title: string;\n\n  product_type: string;\n\n  properties: Record<string, string>;\n  quantity: number;\n\n  quantity_rule: QuantityRule;\n\n  requires_shipping: boolean;\n\n  /**\n   * The selling plan allocation of the line item.\n   */\n  selling_plan_allocation: SellingPlanAllocation;\n\n  /**\n   * The SKU of the variant in the line item.\n   *\n   * Stock Keeping Unit.\n   */\n  sku: string;\n\n  successfully_fulfilled_quantity: number;\n\n  taxable: boolean;\n\n  /**\n   * The title of the line item. The title is a combination of line_item.product.title and\n   * line_item.variant.title, separated by a hyphen.\n   *\n   * In most contexts, the line item title appears in the customer's preferred language. However, in\n   * the context of an order, the line item title appears in the language that the customer checked\n   * out in. The title can receive an override value from the Cart Transform API. Overrides take\n   * precedence over translations.\n   *\n   * #### Line item title history\n   *\n   * When referencing line item, product, and variant titles in the context of an order, the\n   * following changes might result in a different output than you expect:\n   * * A product or variant being deleted\n   * * A product or variant title being edited\n   *\n   * When line_item.title is referenced for an order, the line item title at the time of the order\n   * is returned. However, when line_item.product.title and line_item.variant.title are referenced,\n   * the current value for each title is returned.\n   */\n  title: string;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  total_discount: number;\n\n  unit_price_measurement: {\n    measured_type: string;\n    quantity_unit: string;\n    quantity_value: string;\n    reference_unit: string;\n    reference_value: number;\n  };\n\n  untranslated_product_title: string;\n\n  untranslated_variant_title: string;\n\n  /**\n   * The relative URL of the variant associated with the line item.\n   */\n  url: string;\n\n  /**\n   * The ID of the line item's variant.\n   */\n  variant_id: number;\n\n  variant_options: string[];\n\n  variant_title: string;\n\n  /**\n   * The vendor of the variant associated with the line item.\n   */\n  vendor: string;\n}\n\nexport interface SellingPlanAllocation {\n  compare_at_price: number;\n\n  per_delivery_price: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n\n  price_adjustments: SellingPlanAllocationPriceAdjustment[];\n  selling_plan: SellingPlan;\n}\n\nexport interface SellingPlanAllocationPriceAdjustment {\n  /**\n   * @example 1\n   */\n  position: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n}\n\nexport interface SellingPlan {\n  description: unknown;\n\n  fixed_selling_plan: boolean;\n\n  id: number;\n  name: string;\n\n  options: SellingPlanOption[];\n\n  price_adjustments: SellingPlanPriceAdjustment[];\n\n  recurring_deliveries: boolean;\n}\n\nexport interface SellingPlanOption {\n  name: string;\n  position: number;\n  value: string;\n}\n\nexport interface SellingPlanPriceAdjustment {\n  order_count: unknown;\n  position: number;\n  value: number;\n  value_type: string;\n}\n\nexport interface Option {\n  name: string;\n  value: string;\n}\n\nexport interface QuantityRule {\n  increment: number;\n  max: number;\n  min: number;\n}\n\nexport interface DiscountCode {\n  /**\n   * Whether the discount is currently applicable. It is possible to apply a discount code to a cart\n   * but have it not yet be applicable. When not applicable, there will not be any associated\n   * discount applications or allocations. The moment someone performs an action that makes the\n   * discount code applicable, Shopify will update the property to true.\n   */\n  applicable: boolean;\n\n  /**\n   * The discount code string.\n   *\n   * This is NOT guaranteed to be uppercase. Discount codes entered at checkout are captured in\n   * whatever case was used during input.\n   *\n   * @example \"MONEY1234\"\n   * @example \"Abc123def\"\n   */\n  code: string;\n}\n\n/**\n * Returns the request id header from the ajax cart api response. Capturing this value is important\n * when helping Shopify support investigate issues.\n *\n * This does not error. If there is a problem, this returns null or undefined.\n */\nfunction getRequestId(response: Response) {\n  try {\n    return response.headers.get('X-Request-Id');\n  } catch (error) {\n    // ignore\n  }\n}\n\n/**\n * Returns whether the given value is shaped like a fetch Response.\n *\n * The return type of fetch is a contract, not a verified fact. A third party that overrides the\n * global fetch can resolve it to null, undefined, or some unrelated value, in which case the very\n * next property access throws an opaque error with no indication that a bad override was the cause.\n * This checks structurally for the members this module reads rather than with an instanceof, so a\n * conforming fetch polyfill that returns its own Response class still passes while a missing or\n * malformed value is trapped at the point it is received.\n *\n * This type check does not technically prove that the value is a genuine Response object, only that\n * it has the expected shape. We are intentionally lying with types here, so that we can leverage\n * the type inference to support the subset of use cases this module actually relies on. More over,\n * this lying is ok, as the call to `response = await fetch` is also lying because it overly trusts\n * that fetch returns value of type Response. We are going to keep trying to \"make fetch happen\"\n * regardless.\n *\n * ## War Story\n *\n * As much as it seems like this is an insane and irrationally paranoid check, you would be\n * surprised. For example, in the wild, we observed a third party vendor that hooked global fetch,\n * had an error in their hook, and simultaneously, another vendor's third party polyfill that only\n * defined fetch without also defining Response, leading to even more obscure cases when older\n * browsers were involved. Then, to make matters worse, a fix was implemented leveraging the clean\n * global fetch using an iframe content window hack to work around the inability to cleanly capture\n * the native fetch before it was erroneously hooked, and this broke certain expressions that use\n * instanceof because instanceof has unintuitive behavior when checking instanceof of a value from a\n * different global context, e.g. from an iframe. A web of lies!\n */\nfunction isResponse(value: any): value is Response {\n  if (value === null || typeof value !== 'object') {\n    return false;\n  }\n\n  if (typeof value.ok !== 'boolean') {\n    return false;\n  }\n\n  if (typeof value.status !== 'number') {\n    return false;\n  }\n\n  if (typeof value.text !== 'function') {\n    return false;\n  }\n\n  if (typeof value.json !== 'function') {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Attaches a single piece of context onto an error before it is thrown.\n *\n * The target may be a value caught upstream rather than one constructed here, and such a value can\n * carry a non-writable property, such as a DOMException whose code is a read-only accessor, or be\n * frozen outright, in which case assigning to it throws. Decorating an error must never itself\n * fail, so a rejected assignment is swallowed. This context is best effort metadata, so the\n * property is skipped when it cannot be set, leaving the error otherwise intact to be thrown and\n * handled by the caller.\n */\nfunction decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (error) {\n    // The target rejects this property. Skip it rather than letting decoration fail.\n  }\n}\n\n/**\n * Returns an absolute url for the given path.\n *\n * It would be nice to just use a relative url in fetch. However, we see real world errors about\n * requests including credentials. Visitors might have buggy extensions inserting document.baseURI\n * or they are browsing the site in some weird way. To work around this, we build up a full url that\n * omits the credentials.\n *\n * Unfortunately, specifying the base url means we have to also solve the problem of determining it.\n * There is currently a desire to avoid location.origin due to browser support. we do not want to\n * touch the document base uri. Use host over hostname since that includes port.\n */\nfunction resolveUrl(path: string) {\n  return new URL(path, `${location.protocol}//${location.host}`);\n}\n\n/**\n * An item to add to the cart\n */\nexport interface AddItem {\n  /**\n   * Variant id\n   *\n   * Shopify says that it supports variant ids as numbers, but we have witnessed strange errors when\n   * using numbers so we impose an artificial limitation on the type here and require strings.\n   */\n  id: string;\n\n  /**\n   * The id of the line item to add the item as a child of. This is used for adding items as\n   * children of value sets and bundles. If not specified, the item is added as a top level line\n   * item.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_id?: number | string;\n\n  /**\n   * Line item properties to include.\n   *\n   * If the variant does not already exist in the cart, then the logic is straightforward. This will\n   * result in a new line item with these properties.\n   *\n   * If the variant exists in the cart, then the logic is convoluted. If the specified properties\n   * are identical to the existing properties, then the quantity of the existing line item\n   * increments. If the specified properties are different, then a new line item is created with\n   * these properties, resulting in the same variant being present in multiple line items.\n   */\n  properties?: Attributes;\n\n  /**\n   * The amount of the variant to add.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n   */\n  quantity: number;\n\n  selling_plan?: number | string;\n}\n\n/**\n * The response body of a cart add.\n *\n * This one is not a cart at all, unlike the response body of a change or an update. It carries only\n * the items the request asked for, so there is no token, no totals, and no view of the rest of the\n * cart. See the notes on the add function for why the body is not worth reading.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartAddResponseBody {\n  /**\n   * The line items the add produced, one per item in the request.\n   *\n   * These look like ordinary cart line items and are typed as such, though a few of the rarer line\n   * item properties have not been seen on one, so treat the shape as the usual line item rather\n   * than a guaranteed match.\n   */\n  items: Item[];\n}\n\n/**\n * Adds items to the cart. At least one item should be specified.\n *\n * This does not fetch the response body. The raw response body does not contain the full cart\n * state. The raw response body may not be the latest state of cart items. The raw response body may\n * contain zero-quantity items still present after prior removal.\n *\n * This validates the response status. If the status is not ok this throws an error.\n *\n * This specifies fetch mode no-store so that the call is not unintentionally cached.\n *\n * Adding an item the cart does not necessarily mean a new line item is created. For each item in\n * the input, Shopify makes a server side determination of whether to update the quantity of an\n * existing line item or add a new line item. The factors in that decision are related to how a line\n * item key is determined, which is poorly documented. Basically, if an item has some different\n * properties, e.g. line item properties, or pricing, then the item will always be added as a new\n * line item, even though the variant id is the same as an existing line item.\n *\n * The line item scripts run on every mutation. The scripts will remove any orphaned value set line\n * items implicitly. Keep this in mind when debugging.\n *\n * The content type header must be specified in the request or Shopify interprets the request as a\n * form submission, which we are not doing here, which leads to an error.\n *\n * Shopify's injected front end code (e.g. Trekkie, Monorail) triggers a console warning when it\n * detects that the number of items requested to be added is less than the number that was added.\n * This is not necessarily an error. It can indicate that the line item scripts removed one or more\n * line items during the add mutation.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n * @see https://help.shopify.com/en/manual/checkout-settings/script-editor/shopify-scripts\n */\nexport async function cartAdd(items: AddItem[]) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/add.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify({ items }),\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    cache: 'no-store',\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'items', items);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response adding to cart');\n    decorateError(error, 'items', items);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} adding to cart`);\n    decorateError(error, 'items', items);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const responseBody = await response.json();\n      decorateError(error, 'response_body', responseBody);\n    } catch (responseConsumeAndParseBodyError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. For example, adding a bundle whose component is unavailable\n    // yields a body like the following, where description is meaningful and message is generic:\n    //\n    // { \"description\": \"The bundle product 'Product Title' cannot be added to the cart.\",\n    // \"message\": \"Cart Error\", \"status\": 422 }\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Either id or line must be specified.\n */\nexport interface CartChangeInput {\n  /**\n   * Can be either the variant id or the line item key.\n   */\n  id?: number | string;\n\n  /**\n   * A cart can have multiple line items that share the same variant_id. For example, when variants\n   * have different line item properties, or automatic discounts create variants at different\n   * prices. To account for this, you can use the line property when updating the cart.\n   *\n   * The value of line is the 1-based index position of the item in the cart.\n   */\n  line?: number;\n\n  /**\n   * The properties property sets the line item properties. Its value must be an object of key-value\n   * pairs.\n   *\n   * Whenever a POST request includes properties, it overwrites the entire properties object. Any\n   * key-value pairs that were already in the properties object are erased.\n   *\n   * It's not possible to set a line item's properties property to an empty object once a value is\n   * set. If you need to remove all line item properties, then you need to remove the entire line\n   * item.\n   *\n   * You can visually hide line item properties at checkout by creating private properties. This\n   * technique might be an alternative to removing a line item when managing properties.\n   */\n  properties?: Record<string, any>;\n\n  /**\n   * The value of the quantity property represents the new quantity you want for the line item. It\n   * must be an integer.\n   *\n   * You can remove a line item by setting the quantity to 0.\n   *\n   * It is not clear from the documentation, but based on the description of the selling plan\n   * property, it looks like quantity is actually optional and defaults to 1 when not specified.\n   */\n  quantity?: number;\n\n  /**\n   * The selling_plan property sets the line item selling plan. Its value must be one of the\n   * following:\n   *\n   * * The selling plan ID: To set a specific selling plan for the line item.\n   * * null or an empty string: To remove any selling plan from the line item.\n   *\n   * When specifying the selling_plan property, consider the following:\n   * * You can use only the line property for identifying the line item.\n   * * You should always specify the quantity property. If the quantity isn't specified, then it's\n   *   assumed to be 1.\n   */\n  selling_plan?: number;\n}\n\n/**\n * The response body of a cart change.\n *\n * The two extra arrays are not part of the stored cart. Neither one is present in a get cart\n * response, and a cart update answers with a different extra property instead, so these describe\n * what the change did rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartChangeResponseBody extends Cart {\n  /**\n   * The line items the change brought into the cart.\n   *\n   * Nothing has been observed to put an entry in here, including a change that raised a quantity,\n   * so whether an entry looks like a removed one is unconfirmed.\n   */\n  items_added: CartChangeItem[];\n\n  /**\n   * The line items the change took out of the cart.\n   *\n   * A removal driven from a cart drawer, which identifies the line by its position rather than by\n   * variant, does report the removed line here. A removal that identifies the line by variant id\n   * has come back empty, so what qualifies is not settled.\n   */\n  items_removed: CartChangeItem[];\n}\n\n/**\n * An entry in one of the two arrays a cart change response carries.\n *\n * This is undocumented and the shape here is what has been observed on a removal. It is close\n * enough to a line item to be mistaken for one and it is not one, so a few differences are worth\n * knowing. The id is not the variant id, the line key arrives under a different property name, and\n * the price is a decimal string rather than an integer in subunits. It also carries no line item\n * properties, no weight, no discount details, and no parent relationship.\n */\nexport interface CartChangeItem {\n  /**\n   * An opaque identifier for the entry, formatted as a uuid.\n   *\n   * This is not the variant id, unlike the id of a cart line item.\n   */\n  id: string;\n\n  image: string;\n\n  presentment_price: number;\n\n  /**\n   * The price as a decimal string, in the presentment currency.\n   *\n   * Note that this is not an integer in currency subunits, which is how a price is expressed\n   * everywhere else in the cart.\n   *\n   * @example \"289.00\"\n   */\n  price: string;\n\n  product_id: number;\n\n  /**\n   * The title of the product, which here includes the variant.\n   *\n   * Note that a cart line item excludes the variant from this property and only combines the two in\n   * its title.\n   *\n   * @example \"TurboStyle - Blush\"\n   */\n  product_title: string;\n\n  product_type: string;\n  quantity: number;\n  sku: string;\n  title: string;\n  untranslated_product_title: string;\n  untranslated_variant_title: string;\n  url: string;\n  variant_id: number;\n  variant_title: string;\n  vendor: string;\n\n  /**\n   * The line item key, which a cart line item exposes as its key instead.\n   *\n   * @example \"47267055599716:fa232aa1a0b5b250164c9843d7a2ee6c\"\n   */\n  view_key: string;\n}\n\n/**\n * Use the /{locale}/cart/change.js endpoint to change the quantity, properties, and selling_plan\n * properties of a cart line item. Only items already in your cart can be changed, and you can\n * change only one line item at a time.\n *\n * The POST data requires either an id or line property to identify the line item to be changed.\n *\n * The value of the id property can be one of the following:\n *\n * * The line item's variant_id\n * * The line item's key\n *\n * A cart can have multiple line items that share the same variant_id. For example, when variants\n * have different line item properties, or automatic discounts create variants at different prices.\n * Because of this, it's recommended to use the line item key to ensure that you're only changing\n * the intended line item.\n *\n * If the item that you're attempting to change isn't already in the cart, then\n * /{locale}/cart/change.js doesn't add it. Instead, it returns a 404 error.\n *\n * The documentation states that the response body is the JSON of the cart. This exposes the\n * response object but does not fetch the response body.\n *\n * ### Removing out of stock items\n * It is unclear but based on a conversation with Shopify support, prefer to use cart/change instead\n * of cart/update to avoid issues with removing an out of stock item from the cart. Using update\n * results in a 422 error but supposedly cart/change does not.\n */\nexport async function cartChange(input: CartChangeInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/change.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response changing cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} changing cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    decorateError(error, 'response_status', response.status);\n    decorateError(error, 'response_url', response.url);\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Use the GET /{locale}/cart.js endpoint to get the cart as JSON.\n *\n * All monetary properties are returned in the customer's presentment currency. To check the\n * customer's presentment currency, you can use the currency field in the response.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#get-locale-cart-js\n */\nexport async function cartFetch() {\n  const url = resolveUrl(Shopify.routes.root + 'cart.js');\n\n  let response;\n  try {\n    response = await fetch(url, { cache: 'no-store' });\n  } catch (error) {\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetching cart did not return a valid response');\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} fetching cart`);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    throw error;\n  }\n\n  // Using response.json() obscures errors and prevents us from being able to see what the raw\n  // response body looks like. The usual cases of errant response bodies include:\n  //\n  // - default response bodies for errors, which are using plain text or html\n  // - firewall interceptions that produce html as a response, usually html\n  // - wifi connect interstitial page load, usually html\n  //\n  // So we are intentionally using response.text() and JSON.parse.\n\n  let responseBodyText;\n  try {\n    responseBodyText = await response.text();\n  } catch (error) {\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  let responseBodyObject;\n  try {\n    responseBodyObject = JSON.parse(responseBodyText);\n  } catch (error) {\n    decorateError(error, 'response_body', responseBodyText);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  if (!isCart(responseBodyObject)) {\n    const error: any = new Error('Unexpected get cart response body');\n    decorateError(error, 'response_body', responseBodyObject);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  return responseBodyObject;\n}\n\nexport interface CartUpdateInput {\n  /**\n   * New cart attributes. Existing cart attributes are overwritten.\n   */\n  attributes?: Attributes;\n\n  /**\n   * Beta shopify feature. Essentially works like Storefront discount apply mutation.\n   *\n   * Pass an empty string to remove discount(s).\n   */\n  discount?: string;\n\n  /**\n   * Cart note.\n   */\n  note?: string;\n\n  /**\n   * Even though Shopify supports specifying variant ids as numbers, we impose the restriction that\n   * the property names of the updates object must be strings due to some strange looking errors\n   * when specifying numbers.\n   */\n  updates?: Record<string, any>;\n}\n\n/**\n * The response body of a cart update.\n *\n * The changelog is not part of the stored cart. It is absent from a get cart response, and a cart\n * change answers with two differently named arrays instead, so it describes what the update did\n * rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartUpdateResponseBody extends Cart {\n  /**\n   * A summary of what the update brought into the cart.\n   *\n   * This is present on every successful update, including one that only writes attributes or a\n   * note, in which case the arrays inside it are empty.\n   */\n  items_changelog: ItemsChangelog;\n}\n\n/**\n * The summary of what an update brought into the cart.\n *\n * This is undocumented, so the shape here is only what has been observed. The added array is the\n * only property seen so far. A removed counterpart would be the obvious sibling but no update\n * response has produced one, not even one that set a quantity to zero, so it is left out rather\n * than guessed at.\n */\nexport interface ItemsChangelog {\n  /**\n   * The variants the update brought into the cart, and the quantity each one landed at.\n   *\n   * Only a variant that was not in the cart beforehand shows up here. Raising or lowering the\n   * quantity of a line that already exists leaves this empty, as does removing one, so this reports\n   * newly created lines rather than every quantity change.\n   */\n  added: ItemsChangelogAddition[];\n}\n\n/**\n * An entry in the added array of an update's changelog.\n *\n * This is not a line item. It carries only the variant and the quantity, so it cannot stand in for\n * the corresponding entry in the cart's items array.\n */\nexport interface ItemsChangelogAddition {\n  /**\n   * The quantity the line item landed at, not the amount by which it changed.\n   */\n  quantity: number;\n\n  /**\n   * Note that this is a number here, unlike elsewhere in this module where a variant id is\n   * restricted to a string on the request side.\n   */\n  variant_id: number;\n}\n\n/**\n * Performs a cart update.\n *\n * This does not return the cart updated state. You will need to do a followup call to fetch the\n * updated state of the cart. One reason this does also return the updated state is that we must\n * perform a second call to fetch the updated state but not all callers are interested in doing so.\n *\n * This does not return the response body of the call. There is no need to fetch the body because\n * the body does not contain useful data. The response body can contain 0 quantity items and other\n * kinds of strange things.\n *\n * This must specify the content type header. Shopify supports both a JSON request body and a form\n * data request body. Shopify defaults to interpreting requests as form submissions. This uses the\n * JSON format so the header is required.\n *\n * Backend scripts may affect the output.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-update-js\n * @see https://shopify.dev/docs/api/ajax/reference/cart#update-line-item-quantities\n * @see https://shopify.dev/docs/api/ajax/reference/cart#private-cart-attributes\n */\nexport async function cartUpdate(input: CartUpdateInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/update.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response updating cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} updating cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const parsedErrorResponseBody = await response.json();\n      decorateError(error, 'response_body', parsedErrorResponseBody);\n    } catch (consumeError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Returns whether the error indicates the add failed because one of the products is out of stock\n * (not available for sale). In this case Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"All 0 <product title> are in your cart\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * ```json\n * {\n *   \"description\": \"The product '<title>' is already sold out.\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This error can happen both when adding an item or updating the quantity of an item.\n *\n * This assumes error messages are in English.\n */\nexport function isUnavailableError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      // this one is only present in the add error response\n      if (description.startsWith('All 0') && description.includes('in your cart')) {\n        return true;\n      }\n\n      // this string is sometimes present in the update error response, not just the add response\n      if (description.includes('already sold out')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the shopper already holds the item's\n * per-order purchase limit in their cart. Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"The maximum quantity of this item is already in your cart.\",\n *   \"message\": \"The maximum quantity of this item is already in your cart.\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isMaxQuantityError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('maximum quantity of this item is already in your cart')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the request mixed a bundle product and\n * an add-on in the same `cart/add.js` call, which Shopify does not allow. Shopify provides an error\n * response body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Bundle products and add-ons cannot be mixed\",\n *   \"message\": \"Bundle products and add-ons cannot be mixed\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isBundleAddonMixError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('bundle products and add-ons cannot be mixed')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the selling plan sent with the item is\n * not one Shopify will accept for that variant. This happens when a rendered page carries a selling\n * plan ID (for example a pre-order deferred-payment plan) that has since become stale: the variant\n * came back in stock, or the plan was edited or removed, after the page was rendered but before the\n * shopper clicked add. Shopify responds with a body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Cannot apply selling plan to variant\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isInvalidSellingPlanError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      if (description.includes('Cannot apply selling plan to variant')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the given value looks like a cart object.\n *\n * This is not a complete validation. This is a shallow check that examines whether the value is an\n * object containing an items property that is an array. Any value that meets that criteria\n * satisfies the meaning of a cart.\n */\nexport function isCart(value: any): value is Cart {\n  return isObject(value) && Array.isArray(value.items);\n}\n\nfunction isObject(value: any): value is Record<string, any> {\n  return typeof value === 'object' && value !== null;\n}\n","/**\n * Throws an assertion error if the given condition is falsy.\n *\n * @param expression any expression, although one that evaluates to true or false is preferred by\n * convention\n * @param message optional error text\n */\nexport function assert(expression: unknown, message?: string) {\n  if (!expression) {\n    throw new AssertionError(message);\n  }\n}\n\nexport class AssertionError extends Error {\n  constructor(message = 'Assertion error') {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n","const rnds8 = new Uint8Array(16);\nexport default function rng() {\n    return crypto.getRandomValues(rnds8);\n}\n","import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n    byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n    return (byteToHex[arr[offset + 0]] +\n        byteToHex[arr[offset + 1]] +\n        byteToHex[arr[offset + 2]] +\n        byteToHex[arr[offset + 3]] +\n        '-' +\n        byteToHex[arr[offset + 4]] +\n        byteToHex[arr[offset + 5]] +\n        '-' +\n        byteToHex[arr[offset + 6]] +\n        byteToHex[arr[offset + 7]] +\n        '-' +\n        byteToHex[arr[offset + 8]] +\n        byteToHex[arr[offset + 9]] +\n        '-' +\n        byteToHex[arr[offset + 10]] +\n        byteToHex[arr[offset + 11]] +\n        byteToHex[arr[offset + 12]] +\n        byteToHex[arr[offset + 13]] +\n        byteToHex[arr[offset + 14]] +\n        byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n    const uuid = unsafeStringify(arr, offset);\n    if (!validate(uuid)) {\n        throw TypeError('Stringified UUID is invalid');\n    }\n    return uuid;\n}\nexport default stringify;\n","import rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n    if (!buf && !options && crypto.randomUUID) {\n        return crypto.randomUUID();\n    }\n    return _v4(options, buf, offset);\n}\nfunction _v4(options, buf, offset) {\n    options = options || {};\n    const rnds = options.random ?? options.rng?.() ?? rng();\n    if (rnds.length < 16) {\n        throw new Error('Random bytes length must be >= 16');\n    }\n    rnds[6] = (rnds[6] & 0x0f) | 0x40;\n    rnds[8] = (rnds[8] & 0x3f) | 0x80;\n    if (buf) {\n        offset = offset || 0;\n        if (offset < 0 || offset + 16 > buf.length) {\n            throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);\n        }\n        for (let i = 0; i < 16; ++i) {\n            buf[offset + i] = rnds[i];\n        }\n        return buf;\n    }\n    return unsafeStringify(rnds);\n}\nexport default v4;\n","/**\n * Returns a deep clone of the given value.\n *\n * Prefers structuredClone and falls back to a JSON round trip. The fallback covers both an\n * environment without structuredClone and one whose structuredClone cannot be trusted. On the\n * storefront a compromised browser can leave a monkey-patched structuredClone that returns\n * undefined for a real object rather than cloning it, which otherwise surfaces as confusing\n * property-access failures far from the cause. The values cloned through here are plain data that\n * round trips faithfully through JSON, so the fallback is equivalent rather than a degradation.\n */\nexport function copy<T>(value: T) {\n  if (typeof structuredClone === 'function') {\n    const cloned = structuredClone(value);\n\n    // A healthy structuredClone returns a nullish result only for a nullish input, so a nullish\n    // clone of a non-nullish value means the primitive cannot be trusted. Fall through to the\n    // serialization clone in that case.\n\n    const inputPresent = value !== null && value !== undefined;\n    const cloneMissing = cloned === null || cloned === undefined;\n\n    let trustworthy = true;\n    if (inputPresent && cloneMissing) {\n      trustworthy = false;\n    }\n\n    if (trustworthy) {\n      return cloned;\n    }\n  }\n\n  return <T>JSON.parse(JSON.stringify(value));\n}\n","/**\n * Returns whether a string contains the word \"javascript\" anywhere. This is a lightweight heuristic\n * for rejecting obviously-junk externally-sourced values before they enter state, not real XSS\n * defense, which happens more strictly at the actual sinks. The broad match suits values from a\n * constrained alphabet, such as discount codes, where the word never legitimately appears.\n */\nexport function containsJavascriptWord(value: any) {\n  return typeof value === 'string' && /javascript/i.test(value);\n}\n\n/**\n * Returns whether a string contains the \"javascript:\" URI scheme. This is a lightweight heuristic\n * for rejecting the classic scheme-injection vector before a value enters state, not real XSS\n * defense, which happens more strictly at the actual sinks. Unlike {@link containsJavascriptWord}\n * this tolerates the bare word, so a value that is itself an arbitrary external URL, such as a\n * referrer, is not falsely rejected merely for mentioning javascript in a path.\n */\nexport function containsJavascriptScheme(value: any) {\n  return typeof value === 'string' && /javascript:/i.test(value);\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptWord } from '../../untrusted-value.js';\n\n/**\n * Returns whether a discount code string is well-formed. This does not guarantee the discount code\n * is valid and in the applicable state. That is why this function is not named isValid.\n *\n * This tolerates any kind of input so as to provide a convenient API. The caller is not obligated\n * to check that the input is a string beforehand. This function exists to avoid that boilerplate.\n * However, callers should be providing values that are strings.\n */\nexport function isWellFormed(value: any) {\n  if (typeof value !== 'string') {\n    return false;\n  }\n\n  if (value.length === 0) {\n    return false;\n  }\n\n  // This catches abuse of path parameters\n  if (value.includes('/') || value.includes('\\\\')) {\n    return false;\n  }\n\n  if (value.includes(' ')) {\n    return false;\n  }\n\n  const upper = value.toUpperCase();\n\n  // This catches common js bugs in the calling code\n  if (upper === 'UNDEFINED' || upper === 'NULL') {\n    return false;\n  }\n\n  if (containsJavascriptWord(upper)) {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Returns normalized array of discount code strings currently applied to the cart.\n *\n * This filters out codes from the cart's discount_codes array property where the applicable value\n * is not true. That is what distinguishes this function from {@link getDiscountCodes}.\n *\n * This normalizes the codes. Shopify does not guarantee the code strings are uppercase. This will\n * uppercase.\n *\n * Tolerates null/undefined cart. Returns an empty array. This is safe to use without checking\n * whether cart is defined.\n *\n * Tolerates invalid discount_codes property. This supports a rare case where the cart was SSRed\n * correctly, but then not fetched. The SSR state is missing the property. In this case an empty\n * array is returned.\n */\nexport function getApplicableDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes\n    .filter(code => code.applicable)\n    .map(code => code.code)\n    .map(code => code.toUpperCase());\n}\n\n/**\n * Returns all codes currently in the cart's discount codes array.\n *\n * This includes applicable as well as inapplicable codes. This inclusion of inapplicable codes is\n * what distinguishes this from the function {@link getApplicableDiscountCodes}.\n *\n * The returned codes are always uppercase. This must uppercase the codes returned because Shopify\n * does not guarantee the codes are upper case. Codes applied at checkout are saved in whatever\n * input case was used. The logic that applies codes while shopping can guarantee the input codes\n * are in uppercase but the logic at checkout cannot because the checkout logic is managed by\n * Shopify.\n *\n * It is possible that the discount_codes array property in the input cart is undefined. Shopify\n * does not support rendering the discount_codes array in liquid. There is a situation where the\n * cart state was server side rendered without discount_codes and then we failed to update the state\n * and therefore failed to set the property dynamically during init. This tolerates that so as to\n * guarantee that an array is always returned.\n */\nexport function getDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes.map(code => code.code).map(code => code.toUpperCase());\n}\n\n/**\n * Totals up the discount amounts the cart has allocated and returns them as a map. Values are in\n * cents.\n *\n * The keys are discount application titles in uppercase rather than discount codes. For an ordinary\n * code those are the same string, which is what lets a caller match this map against the cart's\n * discount codes. They are not guaranteed to agree though, because Shopify treats the title as the\n * customer facing name of the discount rather than as the code. So a caller can end up holding a\n * title here that matches none of the codes on the cart.\n *\n * A code missing from this map has not necessarily done nothing. It means the cart allocated no\n * amount to it. A free shipping code behaves that way. It sits among the cart's discount codes as\n * applicable while appearing in neither array read here, because shipping is not priced until\n * checkout.\n *\n * Tolerates a null or undefined cart, and a cart missing any of the arrays this reads. Returns an\n * empty map. This is safe to use without checking whether the cart is defined.\n */\nexport function getDiscountAmounts(cart: Cart) {\n  // Shopify keeps discounts in two separate places, split by scope, so we read both to find all of\n  // them. A discount covering the cart as a whole lands in cart_level_discount_applications. A\n  // discount covering particular products lands on each line item holding one of those products, in\n  // that item's line_level_discount_allocations.\n  //\n  // The two do not overlap, which is what makes it safe to add both into one map without counting a\n  // discount twice. We checked this in real carts from both directions. A cart wide code showed up\n  // only in the cart level array with the line allocations empty, and a product code showed up only\n  // on the lines with the cart level array empty.\n  //\n  // A product discount is allocated separately to every line it touches, so its total is the sum of\n  // those per line amounts. That is what the running total is for, and it is also why the same code\n  // can appear many times while we walk the lines.\n  //\n  // Note which field each loop reads, because the two arrays do not name their amounts the same\n  // way. The line loop reads the allocation's amount, which is the share allocated to that one\n  // line. The application sitting inside that allocation carries a total_allocated_amount too, and\n  // in the carts we looked at it happened to equal the same share, but it describes the application\n  // rather than the line, so we do not read it there.\n  //\n  // We skip anything that is not a discount code. Automatically applied discounts and script\n  // applied discounts land in these same arrays, but they carry no code for a caller to match\n  // against, so they do not belong in a map keyed this way.\n\n  const discountAmounts = new Map<string, number>();\n\n  if (!cart) {\n    return discountAmounts;\n  }\n\n  // The isArray checks in the following code are considered acceptable paranoia. We want this to\n  // work in bad environments with partial carts, which we have surprisingly encountered.\n\n  // Discounts covering the cart as a whole\n\n  if (Array.isArray(cart.cart_level_discount_applications)) {\n    for (const application of cart.cart_level_discount_applications) {\n      if (application.type === 'discount_code') {\n        const code = application.title.toUpperCase();\n        const discount = application.total_allocated_amount;\n        const currentTotal = discountAmounts.get(code) || 0;\n        discountAmounts.set(code, currentTotal + discount);\n      }\n    }\n  }\n\n  // Discounts covering particular products, spread over the lines holding those products\n\n  if (Array.isArray(cart.items)) {\n    for (const lineItem of cart.items) {\n      if (!Array.isArray(lineItem.line_level_discount_allocations)) {\n        continue;\n      }\n\n      for (const allocation of lineItem.line_level_discount_allocations) {\n        if (allocation.discount_application.type === 'discount_code') {\n          const code = allocation.discount_application.title.toUpperCase();\n          const discount = allocation.amount;\n          const currentTotal = discountAmounts.get(code) || 0;\n          discountAmounts.set(code, currentTotal + discount);\n        }\n      }\n    }\n  }\n\n  return discountAmounts;\n}\n","import { assert } from '@integrabeauty/assert';\nimport { type Cart, cartFetch as cartFetchOnce, isCart } from '@integrabeauty/shopify-ajax-api';\nimport { v4 as uuidv4 } from 'uuid';\nimport { copy } from '../object.js';\nimport { getApplicableDiscountCodes } from './discount/discount.js';\nimport type { Transaction } from './transaction.js';\n\n/**\n * This event is generated when the state of the cart changes. The event includes the old state and\n * the new state and some deltas.\n *\n * Various cart api calls can result in unexpected behavior. For example, adding an item to cart\n * could cause the line key of unrelated cart lines to change. Therefore, this does not generate\n * deltas based on line key. In addition, each time cart state is obtained, there is no guarantee\n * that the state only changed because of the specified change. For example, a visitor interacting\n * with the same cart in multiple tabs may update the state in one tab, but because the deltas are\n * based on the prior state per tab, the next update in one of the tabs would diff against older\n * state. Therefore, listeners should refrain from operating on deltas and always treat each cart\n * update holistically.\n */\ntype CartUpdatedEvent = CustomEvent<{\n  /**\n   * The new cart state.\n   */\n  cart: Cart;\n\n  /**\n   * Discount codes present in the new cart state that are not present in the old cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_added: string[];\n\n  /**\n   * Discount codes present in the old cart state that are not present in the new cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_removed: string[];\n\n  /**\n   * A unique event id. This is useful if tracking this event client side and server side and there\n   * is a need to correlate front end and backend events.\n   */\n  event_id: string;\n\n  /**\n   * Whether this event represents a cart initialization.\n   *\n   * The diffs for an initial update are not useful. In this case the transition is from the empty\n   * state. Do not treat an initial update as a non-initial update.\n   */\n  is_initial?: boolean;\n\n  /**\n   * The previous state of the cart.\n   *\n   * This may be null. For example, when initializing the cart.\n   */\n  previous: Cart;\n\n  /**\n   * Certain transactions provide a description of the context in which the transaction occurred.\n   * For example, two add to cart elements might specify two scenarios when adding to cart, which\n   * then generates an update event with different scenarios, which can then be relayed downstream\n   * to analytics to differentiate in reporting.\n   *\n   * This is primarily an analytics label, but be aware that some consumers also branch on specific\n   * values as behavior, for example suppressing UI reactions to updates the shopper did not\n   * initiate in this tab. Certain values are therefore effectively part of behavior, not only\n   * reporting, so renaming one can change how the UI reacts, not just how a change is reported.\n   *\n   * This is intentionally a broad type. This is not meant to be a type safe enumeration. There are\n   * no heavy safeguards here.\n   */\n  scenario?: string;\n\n  /**\n   * Details of the cart service transaction that triggered this update event.\n   */\n  transaction: {\n    /**\n     * Transaction id\n     */\n    id: number;\n\n    /**\n     * Transaction name\n     */\n    name: string;\n  };\n\n  /**\n   * An array of variant units added to cart. This includes both variants added as new line items\n   * and quantity increases of existing line items.\n   *\n   * A product variant in this array does not contain all the properties of a cart item because we\n   * cannot trivially discern which lines changed when a cart updates.\n   */\n  variants_added: ProductVariant[];\n\n  /**\n   * Variants removed from cart. This can be removal of lines or quantity decreases.\n   *\n   * A product variant does not contain all the properties of a cart item because we cannot\n   * trivially discern which lines changed as a result of a state change.\n   */\n  variants_removed: ProductVariant[];\n}>;\n\n/**\n * Represents a product variant. A Shopify product has one or more variants. A Shopify product with\n * no explicitly configured variant still has a default variant.\n *\n * This should not be confused with a canonical product variant that exists in a single line item in\n * a cart object. This is an amalgamation, a composition of the variant as it exists in one or more\n * line items in a cart. This assumes that price and other properties are the same, apart from the\n * special merged_properties property, which combines the line item properties of the lines\n * containing the variant together.\n */\ninterface ProductVariant {\n  /**\n   * Path to variant image\n   *\n   * @example \"//langehair.com/cdn/shop/products/filename.jpg?v=1680740790\"\n   */\n  image_url: string;\n\n  /**\n   * The combined line item properties of the line items containing this variant\n   *\n   * The key of this object is the property name.\n   *\n   * The value for each key in this object are the distinct values that were merged together.\n   */\n  merged_properties: Record<string, string[]>;\n\n  price: number;\n  product_id: number;\n  product_title: string;\n  product_type: string;\n  quantity: number;\n  sku: string;\n\n  /**\n   * Product variant url (relative)\n   *\n   * @example \"/products/salt-sea-texturizing-spray?variant=41345560477796\"\n   */\n  url: string;\n\n  variant_id: number;\n  variant_title: string;\n}\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-updated': CartUpdatedEvent;\n  }\n}\n\n/**\n * Sets the current state of the cart. This is the in memory state that is stored as the value of a\n * global variable, not the server side state.\n *\n * In memory state is PER TAB PER WINDOW/APP. If someone is browsing in multiple tabs concurrently\n * the state may conflict. The cart token corresponds to a server side cart id and is stored in a\n * cookie that is shared among all tabs, so it is possible for multiple tabs to refer to the same\n * cart object.\n *\n * Always use this method to update the state of the global cart variable. Do not update the state\n * of the global cart variable directly.\n *\n * Throws an error if the given cart is not a cart. This also indicates a more insidious problem\n * where there is no longer a guarantee that client side state reflects server side state, so\n * consider some error handling.\n *\n * This saves a copy of the cart so that future updates by reference to the cart object do not\n * modify the global cart state.\n *\n * Emit a cart updated event that contains some of the differences. This emits the updated event\n * even when there are no changes detected.\n *\n * Any update can result in unrelated cart changes. Changing one line's properties could cause line\n * merging. Also, prior state comes from page state, which is per tab, so interactions in multiple\n * tabs can produce strange behavior.\n *\n * The dispatcher is the target the updated event is dispatched from. It is required but may be\n * null, and it may be any event target, not necessarily an element or window. The target is used as\n * given, with two exceptions that exist only to keep the event reaching window level listeners: a\n * null target dispatches from window, and so does a detached element. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners. Callers should prefer the element most directly responsible for\n * the change, so a connected element carries truthful provenance and bubbles, crossing shadow\n * boundaries, up to the global listeners. Genuinely ambient updates with no originating element,\n * such as init or a cross tab sync, pass window (or null).\n */\nexport function write(dispatcher: EventTarget | null, cart: Cart, transaction: Transaction,\n  scenario?: string, initial = false) {\n  const previous = initial ? null : read();\n\n  // Update global state.\n  assert(isCart(cart), 'Cannot write invalid cart');\n  cart_lifecycle.cart = copy(cart);\n\n  // Prepare for diffing. We group by variant because line keys change in unexpected ways.\n  const oldVariants = groupByVariant(previous);\n  const newVariants = groupByVariant(cart);\n\n  // For discount code deltas, we only examine deltas in codes that both exist and are in the\n  // applicable state in the cart's discount_codes array.\n\n  const prevCodes = getApplicableDiscountCodes(previous);\n  const currCodes = getApplicableDiscountCodes(cart);\n\n  // Emit an event with diffs\n\n  type Detail = CartUpdatedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-updated', {\n    detail: {\n      cart,\n      discount_codes_added: complement(currCodes, prevCodes),\n      discount_codes_removed: complement(prevCodes, currCodes),\n      event_id: uuidv4(),\n      is_initial: initial,\n      previous,\n      scenario,\n      variants_added: findVariantsAdded(oldVariants, newVariants),\n      variants_removed: findVariantsRemoved(oldVariants, newVariants),\n      transaction: {\n        id: transaction.id,\n        name: transaction.name\n      }\n    },\n    bubbles: true,\n    composed: true\n  });\n\n  // Dispatch the update from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given, so it carries truthful provenance and\n  // bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Returns a copy of the global cart state.\n *\n * Throws an error if the global cart object is not a cart object.\n */\nexport function read() {\n  assert(isCart(cart_lifecycle.cart), 'Global cart state is invalid on read');\n  return copy(cart_lifecycle.cart);\n}\n\ninterface FetchOptions {\n  /**\n   * The total number of requests to make, counting the first one.\n   */\n  attempts?: number;\n\n  /**\n   * How long to wait in milliseconds before each retry, one entry per retry.\n   */\n  delays?: number[];\n\n  /**\n   * How long in milliseconds to wait on a single attempt before counting it as failed. Leaving it\n   * out means each attempt waits as long as the request takes.\n   */\n  timeout?: number;\n}\n\n/**\n * Reads the server side cart state.\n *\n * This wraps the ajax api call of the same name and is what cart code here should reach for\n * instead. The only things it adds are a few attempts and an optional time limit on each one, so it\n * behaves the same otherwise.\n *\n * A cart read is idempotent and it sits in the middle of things the shopper is waiting on, like\n * adding to cart, so a single failed request is worth another go rather than turning into an error\n * the shopper sees. We make up to a few attempts, waiting a little longer before each retry. If\n * every attempt fails then the error from the last one is thrown and the caller decides what that\n * means. Some callers treat it as fatal, some stay quiet about it, so this forms no opinion.\n *\n * Do not use this to react to a mutation you just made unless you are fine with a repeat request,\n * because each attempt sends a fresh one.\n *\n * Callers can pass options to change how many attempts are made, how long to wait between them, and\n * how long a single attempt gets before it counts as failed. Leaving them out gets the defaults\n * below, and no time limit.\n *\n * There is no limit on the total time across all attempts. The caller is responsible for picking\n * options that keep the worst case acceptable, which is every attempt using its full time limit\n * plus every wait between them.\n */\nexport async function cartFetch(options: FetchOptions = {}) {\n  // How many times we are willing to ask the server before giving up. The first attempt counts, so\n  // this is the total number of requests and not the number of retries on top of one.\n  const limit = options.attempts || 3;\n\n  // How long we wait before each retry. Shopify throttles cart reads, so going right back at it\n  // just piles more requests onto something that already told us to slow down. These stay short as\n  // a balance between waiting long enough to avoid another 429 and waiting so long the user\n  // perceives the UI as broken.\n  const delays = options.delays || [100, 250];\n\n  let lastError: Error;\n\n  for (let attempts = 0; attempts < limit; attempts++) {\n    if (attempts > 0) {\n      const delay = delays[attempts - 1];\n      await resolveAfterTimeout(delay);\n    }\n\n    try {\n      const cart = await cartFetchOnceWithTimeout(options.timeout);\n      return cart;\n    } catch (error) {\n      lastError = error;\n    }\n  }\n\n  throw lastError;\n}\n\n/**\n * Reads the cart once, giving up after the given number of milliseconds. Leaving out the timeout\n * means there is no limit.\n *\n * Giving up does not stop the request. It keeps going in the background and whatever it eventually\n * returns is ignored. That is fine because reading the cart changes nothing, but it means this must\n * never be used for a cart write, since a write that times out here can still land later.\n */\nasync function cartFetchOnceWithTimeout(timeout: number) {\n  if (!timeout) {\n    return cartFetchOnce();\n  }\n\n  let timer: ReturnType<typeof setTimeout>;\n  const timeoutPromise = new Promise<never>((_resolve, reject) => {\n    const error = new Error('Timed out fetching cart');\n    timer = setTimeout(reject, timeout, error);\n  });\n\n  try {\n    const cart = await Promise.race([cartFetchOnce(), timeoutPromise]);\n    return cart;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\n/**\n * Returns a promise which resolves after the given timeout\n */\nfunction resolveAfterTimeout(milliseconds: number) {\n  return new Promise(resolve => {\n    setTimeout(resolve, milliseconds);\n  });\n}\n\ntype ProductVariantMap = Record<string, ProductVariant>;\n\n/**\n * Aggregates cart line items by variant. Returns the aggregated variants as an object where each\n * key of the object is a variant id and each value is the corresponding variant object.\n *\n * This does some complex stuff. One key thing to constantly point out is that a variant can appear\n * in more than one line item in a cart. There are several reasons why. Shopify generally refers to\n * this as a variant split. Variant splits happen for reasons such as having different line item\n * properties, or different prices as a result of allocated discounted applications, or because of\n * more rare reasons like product subscriptions. Because we are grouping by variant id, we have to\n * invent some novel ways of combining the different values of the line items containing the same\n * variant.\n *\n * To combine the line item properties, we invent a new property, merged_properties, that contains\n * the properties of each line item. In each merged properties value, there is an array of values.\n * When appending to the values array, we only append unique values (using exact value comparison).\n *\n * There are many downstream concerns. Here is one example. When relaying an add to cart event to a\n * third party analytics system, we sometimes want the product's image url. This function's output\n * is the input to the variants_added array of the cart updated event emitted as a result of a cart\n * write. So we have to embed the image url here in the variant object so that it can be accessed\n * without having to do some kind of later lookup.\n *\n * @param cart the input cart to aggregate\n */\nfunction groupByVariant(cart: Cart) {\n  const output: ProductVariantMap = {};\n\n  // It is important to return null here, and not the empty object, so that later functions that use\n  // this output can trivially distinguish between transitions from null state and transitions from\n  // empty cart state. The transition from null state happens on init.\n\n  if (!cart) {\n    return null;\n  }\n\n  for (const item of cart.items) {\n    const variant = output[item.variant_id.toString()];\n    if (variant) {\n      // Merge the item's properties into the variant's properties.\n      for (const property in item.properties || {}) {\n        variant.merged_properties[property] = variant.merged_properties[property] || [];\n        const value = item.properties[property];\n        if (!variant.merged_properties[property].includes(value)) {\n          variant.merged_properties[property].push(value);\n        }\n      }\n\n      variant.quantity += item.quantity;\n    } else {\n      // Merge in the initial properties, where each value is a one value array.\n      const properties: Record<string, string[]> = {};\n      for (const key in item.properties || {}) {\n        properties[key] = [item.properties[key]];\n      }\n\n      output[item.variant_id.toString()] = {\n        image_url: item.image,\n        merged_properties: properties,\n        // eslint-disable-next-line @typescript-eslint/no-deprecated\n        price: item.price,\n        product_id: item.product_id,\n        product_title: item.product_title,\n        product_type: item.product_type,\n        quantity: item.quantity,\n        sku: item.sku,\n        url: item.url,\n        variant_id: item.variant_id,\n        variant_title: item.variant_title\n      };\n    }\n  }\n\n  return output;\n}\n\n/**\n * Returns an array of variant data objects that were added as a result of the write mutation.\n *\n * This tolerates nulls\n *\n * @param c0map variants in old cart state\n * @param c1map variants in new cart state\n * @returns variants added array\n */\nfunction findVariantsAdded(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  // When the cart transitions from the null state, there are no previous variants. This means that\n  // all variants in the new state have been added. Transitioning from null can mean multiple\n  // things. It can mean either that cart state was initialized on page load, or that cart state was\n  // recovered from error. Because of the ambiguity we are not emitting adds for transitions from\n  // the null state.\n\n  if (!c0map) {\n    return [];\n  }\n\n  // Figure out which variants had quantity increases in any of the line items containing those\n  // variants. Any variant that had a quantity increase is referred to here as an increment. In\n  // addition, look for any variants that are present in the new state but not in the old state, in\n  // which case their full quantity is the quantity added.\n\n  const increments: ProductVariantMap = {};\n  for (const key in c1map) {\n    if (c0map[key]) {\n      // In this branch, the variant was present in the old map and is present also in the new map.\n\n      if (c1map[key].quantity > c0map[key].quantity) {\n        increments[key] = copy(c1map[key]);\n        increments[key].quantity = c1map[key].quantity - c0map[key].quantity;\n      } else {\n        // ignore 0 quantity change and negative quantity change (decrements).\n      }\n    } else {\n      // In this branch, the variant is present in the new map but not in the old map.\n\n      increments[key] = copy(c1map[key]);\n    }\n  }\n\n  return Object.keys(increments).map(key => increments[key]);\n}\n\nfunction findVariantsRemoved(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  if (!c0map) {\n    return [];\n  }\n\n  const decrements: ProductVariantMap = {};\n  for (const key in c0map) {\n    if (c1map[key]) {\n      if (c1map[key].quantity < c0map[key].quantity) {\n        decrements[key] = copy(c1map[key]);\n        decrements[key].quantity = c0map[key].quantity - c1map[key].quantity;\n      } else {\n        // ignore 0 quantity change\n      }\n    } else {\n      // if the old variant is not found in the updated cart, all quantity was removed\n      decrements[key] = copy(c0map[key]);\n    }\n  }\n\n  return Object.keys(decrements).map(key => decrements[key]);\n}\n\n/**\n * Return all values present in array a that are not present in array b.\n *\n * The complement of a set is the set that includes all the elements of the universal set that are\n * not present in the given set. Here, a is the universal set and b is the given set.\n */\nfunction complement(a: string[], b: string[]) {\n  assert(Array.isArray(a), 'complement parameter \"a\" is not an array');\n  assert(Array.isArray(b), 'complement parameter \"b\" is not an array');\n  return a.filter(value => !b.includes(value));\n}\n","/**\n * Returns a timestamp in milliseconds for measuring durations.\n *\n * This decorates performance.now().\n *\n * This tries to use the native high-resolution epoch, falling back to Date.now().\n *\n * If native throws, this still returns a value, but it logs a message as a side effect.\n *\n * This happens in a try/catch, so it is safe.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now\n */\nexport function now() {\n  try {\n    if (typeof performance?.now === 'function') {\n      return performance.now();\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n\n  return Date.now();\n}\n\n/**\n * Records a named point on the User Timing timeline in devtools.\n *\n * This decorates performance.mark within a try/catch that suppresses errors.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark\n */\nexport function mark(name: string) {\n  try {\n    if (typeof performance?.mark === 'function') {\n      performance.mark(name);\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\n/**\n * Records a named span between two timestamps on the User Timing timeline in devtools.\n *\n * This decorates performance.measure within a try/catch that suppresses errors.\n *\n * The bounds are explicit timestamps rather than named marks, which avoids the error the marked\n * name form throws when a start mark was never recorded.\n *\n * The detail param is optional and must be structured-cloneable. It should be a plain object. If\n * the value cannot be cloned, then performance.measure throws, which is caught and logged by this\n * wrapper. If specified, the detail shows up in the tooltip in the devtools panel.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure\n */\nexport function measure(name: string, start: number, end: number, detail?: unknown) {\n  try {\n    if (typeof performance?.measure === 'function') {\n      performance.measure(name, { start, end, detail });\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n","import { measure, now } from '../perf.js';\n\ntype CartTransactionStartedEvent = CustomEvent<Transaction>;\ntype CartTransactionCompletedEvent = CustomEvent<Transaction>;\n\n/**\n * The bookkeeping shared by every copy of this module. Each entry bundles its own copy, so the\n * numbers and the timeline have to sit on the window for the copies to agree on them.\n */\ninterface State {\n  /** The number of currently active cart service transactions. */\n  activeCount: number;\n\n  /** Number of cart operations started so far, and the source of transaction ids. */\n  count: number;\n\n  /** A rolling log of recent transaction start and end entries. */\n  timeline: Record<string, any>[];\n}\n\ndeclare global {\n  var cart_transaction: State;\n\n  interface WindowEventMap {\n    'cart-transaction-completed': CartTransactionCompletedEvent;\n    'cart-transaction-started': CartTransactionStartedEvent;\n  }\n}\n\n/**\n * Obtain the shared state, initializing it as needed.\n *\n * We do not assume the global state is reliably present and instead access state via this function\n * every time because we have seen very surprising errors in the wild to which we wish to remain\n * resilient, even at the cost of generating artificial and inaccurate state.\n */\nfunction getSharedState() {\n  window.cart_transaction = window.cart_transaction || { activeCount: 0, count: 0, timeline: [] };\n\n  const shared = window.cart_transaction;\n\n  if (!Number.isInteger(shared.activeCount) || shared.activeCount < 0) {\n    shared.activeCount = 0;\n  }\n\n  if (!Number.isInteger(shared.count)) {\n    shared.count = 0;\n  }\n\n  if (!Array.isArray(shared.timeline)) {\n    shared.timeline = [];\n  }\n\n  return shared;\n}\n\n/**\n * A transaction represents an action carried out by the cart service. For example, an add to cart\n * transaction or a remove from cart transaction.\n *\n * A transaction is a plain record minted by the start function and completed through the handle it\n * carries. The bookkeeping fields live on the record because completion is a module function that\n * operates on the record rather than a method that closes over it.\n */\nexport interface Transaction {\n  /** The arguments to the start of the transaction */\n  args: any[];\n\n  /** Complete this transaction. Idempotent, and reachable only through the handle. */\n  complete?(didTimeOut?: boolean): void;\n\n  /** Whether completion has already run, so a second call is a no-op */\n  completed: boolean;\n\n  /** The transaction start time in milliseconds */\n  epoch: number;\n\n  /** Transaction unique global id */\n  id: number;\n\n  /** Transaction name, e.g. function name */\n  name: string;\n\n  /**\n   * The transaction start time on the monotonic clock, used only to compute duration.\n   *\n   * This is separate from epoch, which stays wall-clock so it still answers when the transaction\n   * happened. The two clocks have different origins, so never mix them in one subtraction.\n   */\n  startMillis: number;\n\n  /** Timeout timer id */\n  timer?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Start a cart service transaction and return its handle. Use the handle's complete method to end\n * the transaction.\n */\nexport function start(name: string, ...args: any[]) {\n  const shared = getSharedState();\n\n  // Increment the shared transaction id. A simple monotonic counter ensures that no two\n  // transactions have the same id (but see the caveats mentioned in getting shared state).\n\n  shared.count++;\n\n  const transaction: Transaction = {\n    args,\n    epoch: Date.now(),\n    id: shared.count,\n    name,\n    completed: false,\n    startMillis: now()\n  };\n\n  // Expose complete as a \"method\" of the returned object. Callers are expected to complete the\n  // transaction via its own method. This is better than exporting the top level complete function\n  // as a free function where callers could mistakenly complete the wrong transaction.\n\n  transaction.complete = (didTimeOut = false) => complete(transaction, didTimeOut);\n\n  // Increment the number of active transactions. This should be a transition from 0 to 1.\n\n  shared.activeCount++;\n\n  // If as a result of the increment there is now more than one active transaction, report the\n  // problem but continue. The goal is not to error. Many transactions are able to reconcile from\n  // overlapping flights.\n\n  if (shared.activeCount > 1) {\n    reportConcurrency(transaction);\n  }\n\n  // Record the transaction in the active transactions timeline. This is a fresh object instead of\n  // the original transaction so that it snapshots.\n\n  append({ epoch: transaction.epoch, id: transaction.id, name: transaction.name, type: 'start' });\n\n  // Start a timer that ensures the transaction settles eventually. Ensuring this is important\n  // because the upper layers typically lock down the UI during a transaction, and we want to\n  // prevent locking the UI indefinitely.\n\n  transaction.timer = setTimeout(complete, 30 * 1000, transaction, true);\n\n  console.log('Cart transaction started', transaction.name);\n\n  type Detail = CartTransactionStartedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-started', { detail: transaction });\n  dispatchEvent(event);\n\n  return transaction;\n}\n\n/**\n * Complete a transaction.\n *\n * Appends the transaction to the transaction history log.\n *\n * Emits a transaction completed event. Various transactions should complete when done to signal the\n * completion to listeners.\n *\n * This runs either manually whenever the transaction completes, or automatically, if the\n * transaction did not complete quickly enough (e.g. because of an error, omission, or a genuinely\n * long operation that timed out).\n *\n * Transactions should be short-lived. Transactions are automatically considered complete after a\n * certain amount of time so as to guarantee completion. Higher layer interfaces rely on transaction\n * state to lock interfaces for periods of time. Locking must be definite.\n *\n * Completion is idempotent. The timeout safety net and the caller's own completion can both fire\n * for one transaction when a tab is suspended long enough for the operation to outlive the timeout,\n * as happens on a backgrounded mobile tab that resumes. Whichever fires first records the outcome\n * and any later call is a no-op, so the active transaction count decrements exactly once per start\n * and cannot drift negative and defeat the interface locks that read it.\n */\nfunction complete(transaction: Transaction, didTimeOut = false) {\n  // A transaction that already timed out lands here when its operation finally finishes, so we log\n  // how long it really took. That tells a stalled request that eventually came back apart from one\n  // that never did.\n\n  if (transaction.completed) {\n    const duration = Math.round(now() - transaction.startMillis);\n    console.log('Cart transaction already completed', transaction.name, didTimeOut, duration);\n    return;\n  }\n\n  transaction.completed = true;\n\n  clearTimeout(transaction.timer);\n  transaction.timer = undefined;\n\n  append({\n    epoch: Date.now(),\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut,\n    type: 'end'\n  });\n\n  const shared = getSharedState();\n\n  if (shared.activeCount > 0) {\n    shared.activeCount--;\n  }\n\n  if (didTimeOut) {\n    console.log('Cart transaction timed out', transaction.name);\n  }\n\n  console.log('Cart transaction completed', transaction.name);\n\n  // Capture performance data representing each transaction's duration.\n\n  measure(`cart-${transaction.name}-${transaction.id}`, transaction.startMillis, now(), {\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut\n  });\n\n  type Detail = CartTransactionCompletedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-completed', { detail: transaction });\n  dispatchEvent(event);\n}\n\n/**\n * Capture a concurrent transaction in way that observers can see it\n */\nfunction reportConcurrency(transaction: Transaction) {\n  const state = getSharedState();\n\n  // Snapshot the state. The reason for this is because the state that is rendered in the devtools\n  // console is the state of the live reference when the value is viewed in the log, which is after\n  // the time it was recorded. Snapshotting saves the value at the time of recording it.\n  //\n  // This is a shallow snapshot. Since the timeline is append only, we are basically just recording\n  // the timeline up to the time of the snapshot, e.g. basically this is just a partial view of an\n  // array that may have more entries by the time it is expanded but those entries are hidden.\n\n  const snapshot = [...state.timeline];\n\n  console.warn('Concurrent transaction started', {\n    id: transaction.id,\n    name: transaction.name,\n    timeline: snapshot\n  });\n}\n\n/**\n * Add a timeline entry.\n *\n * The type of entry here is intentionally general because this has a relaxed opinion on what gets\n * stored.\n */\nfunction append(entry: Record<string, any>) {\n  const state = getSharedState();\n  const timeline = state.timeline;\n\n  timeline.push(entry);\n\n  const capacity = 50;\n\n  // Keep only the most recent entries so the timeline does not grow without bound over a long\n  // session. Removing from the front drops the oldest; the clamp guards against a negative removal\n  // count while the timeline is still under the cap.\n  // TODO: the clamp may be unnecessary, since splice already treats a negative delete count as\n  // zero, so it is likely defensive rather than required. Verify and drop it if so.\n  timeline.splice(0, Math.max(timeline.length - capacity, 0));\n}\n","import { type Cart, isCart } from '@integrabeauty/shopify-ajax-api';\nimport { write } from '../../lib/cart/state.js';\nimport { start, type Transaction } from '../../lib/cart/transaction.js';\n\n// Cross-tab cart coordination. A shopper can interact with the same server side cart from multiple\n// tabs at once, because the cart token lives in a shared cookie. Without coordination, a change\n// made in one tab leaves every other tab showing stale state until its next reload. This module\n// keeps them roughly in sync by broadcasting local cart changes to the other tabs and applying\n// changes that arrive from them.\n//\n// This file is written with the expectation that it is loaded once per page.\n//\n// The listeners are bound once at module load and this module then stays armed but inert until it\n// observes a successful init. It does not wait to be told when init finishes, nor does it care\n// whether init succeeded, because its own gates make it safe to be listening throughout. It refuses\n// to apply anything from another tab until it has seen the initial cart-updated, and it refuses\n// while a transaction is active in this tab, so it cannot start a competing transaction during init\n// even though only a single active transaction is supported. An arriving message before init simply\n// short circuits, so binding early costs nothing.\n//\n// There is no event replay queue. The listeners are bound synchronously at load, and the async init\n// path only dispatches the initial cart-updated after its first await, so this is always listening\n// before that event fires and learns of the successful init live.\n\n/**\n * A lazily-initialized channel used to send and receive cart state across tabs.\n *\n * A BroadcastChannel never notifies its own instance, only other instances of the same channel\n * name. We rely on that to avoid a tab reacting to its own broadcast. Because this module loads\n * once per page there is only ever one instance here, so the \"same instance does not self-notify\"\n * guarantee holds and there is no risk of a local echo.\n */\nlet channel: BroadcastChannel;\n\n/**\n * Tracks whether the cart initialized successfully. We refuse to apply changes from other tabs\n * until it has, because before a successful init there is no trustworthy local state to reconcile\n * against, and a failed init has already shown the shopper an error and left this tab in a state\n * where further mutations are not safe.\n */\nlet cartInitialized = false;\n\n/**\n * Tracks whether there are any active transactions, so we can ignore incoming changes from other\n * tabs while this tab is in the middle of its own mutation.\n *\n * This value is not guaranteed to be accurate at all times due to the complexities of race\n * conditions and errors. This should be treated like a hint.\n */\nlet activeTransactionCount = 0;\n\n/**\n * The transaction wrapping an inbound cross-tab apply while it is in progress, or null when none\n * is. It is set for the synchronous span during which we apply a cart state received from another\n * tab. Its mere presence marks that span, so the broadcast listener knows the cart-updated it is\n * seeing came from an inbound apply and skips it.\n *\n * This exists to break the broadcast loop. Applying an incoming state calls the normal write, which\n * emits a cart-updated event, which our own broadcast listener would otherwise send straight back\n * out, bouncing the same state between tabs forever. The write and its event dispatch are\n * synchronous, so this is set for exactly that window and the listener reads it while it is still\n * set. This is why the broadcast decision is made synchronously and only the send is deferred.\n *\n * This is a dedicated marker rather than a reuse of the active-transaction count, because the\n * broadcast listener also fires inside a transaction for ordinary local mutations, and those must\n * still broadcast. Only a marker set exclusively during an inbound apply tells the two apart. Its\n * presence alone is the signal, deliberately, so the loop-breaker stays a local invariant and does\n * not depend on the emitted event carrying any particular transaction identity.\n */\nlet inboundTransaction: Transaction = null;\n\n/**\n * Tries to open the channel for sending and receiving cart state messages.\n *\n * This is infallible. There is no need to wrap calls in try/catch. On error the channel is left\n * undefined and a warning is logged. It is safe to call repeatedly; it only opens the channel once.\n */\nfunction openChannel() {\n  if (!channel && typeof BroadcastChannel === 'function') {\n    try {\n      channel = new BroadcastChannel('cart-state');\n      channel.addEventListener('message', onMessage);\n    } catch (error) {\n      console.warn(error);\n    }\n  }\n}\n\n/**\n * Reacts to local cart state changes by broadcasting them to the other tabs.\n *\n * An initial update is never broadcast. It is not a mutation made in this tab, and any other tab\n * acquires the latest state on its own init, so replaying it across tabs is both pointless and a\n * source of spurious cross-tab reactions.\n *\n * A change we are applying because it arrived from another tab is never broadcast either, otherwise\n * the same state ping-pongs between tabs endlessly.\n *\n * The decision to broadcast is made here, synchronously, while the inbound transaction that marks\n * an incoming apply is still set. Only the send itself is deferred to a later task. The deferral\n * matters for ordering: the broadcast must go out strictly after every local reaction to this same\n * event has finished, so that same-tab handling always wins over anything sourced from another tab.\n * Deferring the send guarantees it runs after all synchronous listeners for this event, regardless\n * of the order in which those listeners were registered. Broadcasting later than the local handling\n * is always safe; broadcasting before it is not.\n *\n * The deferral does open one narrow window. Because the send now fires a task after this tab's own\n * transaction has finished rather than during it, an update arriving from another tab in that gap\n * can be applied here first (nothing suppresses an incoming apply once the local transaction has\n * completed), and this tab then still sends the state it had captured, which is now superseded. The\n * other tabs are left briefly behind until the next write resyncs everyone. This is an accepted\n * tradeoff rather than a defect. Cross-tab sync is best effort, the window is a single task, it\n * requires the same shopper mutating two tabs almost simultaneously, and the next mutation in any\n * tab corrects it.\n */\nfunction onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n  if (event.detail.is_initial) {\n    cartInitialized = true;\n    return;\n  }\n\n  if (inboundTransaction) {\n    return;\n  }\n\n  setTimeout(broadcastCartState, 0, event.detail.cart);\n}\n\n/**\n * Applies a cart state received from another tab.\n *\n * The validity check comes first on purpose. This channel is shared across the whole origin, so\n * anything running on the page, including browser extensions, can post to it, and most of that\n * traffic is not cart state at all. Rejecting anything that is not a cart up front discards that\n * noise in a single check, before we bother consulting this tab's own state. It is also why the\n * payload is treated as untrusted rather than cast.\n *\n * If a message arrives before init completed, or init failed, we do not react. The shopper has been\n * shown a visual error, and we do not want to make further state changes in this tab because\n * nothing is guaranteed to work anymore.\n *\n * We also ignore messages while a transaction is active in this tab. This is not foolproof. If a\n * message arrives while a transaction is active in another tab, but the transaction started event\n * was not observed because this code did not load in time, then the active transaction count is 0.\n * This is probably rare, requiring an impatient shopper who starts a transaction in one tab, then\n * opens a new tab and starts a transaction there while the first is still ongoing.\n */\nfunction onMessage(event: MessageEvent) {\n  const cart = event.data;\n\n  if (!isCart(cart)) {\n    return;\n  }\n\n  if (!cartInitialized) {\n    return;\n  }\n\n  if (activeTransactionCount > 0) {\n    return;\n  }\n\n  // Start a transaction so as to lock down the ui, then update the cart state. Recording the\n  // transaction lets the broadcast listener recognize the resulting event as this inbound apply and\n  // skip it, so the state does not bounce back to the tab that sent it.\n\n  // This transaction is not linked to the DOM, so we dispatch from window.\n\n  inboundTransaction = start('crossTabSync');\n  try {\n    write(window, cart, inboundTransaction, 'cross-tab-sync');\n  } catch (error) {\n    // Warn for diagnostics, but deliberately do not escalate to a user-facing cart error. Reporting\n    // this failure would emit the cart update error event, surfacing an error message and, for the\n    // init and cookie cases, opening the cart drawer, all for a background sync the shopper did not\n    // initiate in this tab. That would be wrong here. If this path is ever made to report errors,\n    // weigh that consequence first.\n    console.warn(error);\n  } finally {\n    inboundTransaction.complete();\n    inboundTransaction = null;\n  }\n}\n\n/**\n * Tries to send a message containing cart state to all other tabs.\n *\n * The reason this uses optional chaining is because channel is not guaranteed to be initialized.\n * The earlier call to open the channel does not guarantee it opens a channel, because that call\n * only assigns a value to channel when the browser has support for BroadcastChannel.\n *\n * For now the content of the message is the cart state itself. Cart state is simple and trivially\n * serializable (structured-cloneable) so there is no need for a more complex message structure.\n *\n * This runs from a deferred task and swallows its own errors. Not all user agents properly\n * implement postMessage, or allow it in every context (e.g. within some iframes), so the send can\n * throw.\n */\nfunction broadcastCartState(cart: Cart) {\n  try {\n    channel?.postMessage(cart);\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\nfunction onTransactionStarted(_event: WindowEventMap['cart-transaction-started']) {\n  activeTransactionCount++;\n}\n\nfunction onTransactionCompleted(_event: WindowEventMap['cart-transaction-completed']) {\n  if (activeTransactionCount > 0) {\n    activeTransactionCount--;\n  } else {\n    // Due to out of order loads with arbitrary delays, this module may start observing in the midst\n    // of an active transaction in another tab. Eventually the transaction will complete, but the\n    // accompanying increment that should have preceded the decrement was not observed.\n  }\n}\n\nopenChannel();\n\naddEventListener('cart-updated', onCartUpdated);\naddEventListener('cart-transaction-started', onTransactionStarted);\naddEventListener('cart-transaction-completed', onTransactionCompleted);\n","import { cartUpdate } from '@integrabeauty/shopify-ajax-api';\nimport { report } from '../../lib/cart/error.js';\nimport { cartFetch, write } from '../../lib/cart/state.js';\nimport { start, type Transaction } from '../../lib/cart/transaction.js';\nimport { getAttributeChanges } from './cart-diff.js';\nimport { applyTransforms, createTransforms } from './transforms/cart-transforms.js';\nimport { applyUrlDiscounts } from './url-discounts.js';\n\n/**\n * Where init stands on this page. Stays undefined when init never starts, such as in the theme\n * editor.\n */\nlet initStatus: 'failed' | 'running' | 'succeeded';\n\ntype FetchOptions = Parameters<typeof cartFetch>[0];\n\n/**\n * Initialize, or re-initialize, cart state.\n *\n * Init fails on origins that are not langehair.com because it violates the CSP. Typically this\n * indicates the site is loaded somewhere where we do not actually want cart functionality, such as\n * the theme editor. We check for this by examining the url and simply do not initialize in those\n * other situations.\n *\n * On success, emits a cart updated event. The event has an is_initial property set to true to\n * distinguish initial cart updated events from other kinds of cart updated events. This is helpful\n * because the diffs for initial cart updated events are not populated.\n *\n * On error, a cart update erred event is emitted instead of a cart updated event.\n *\n * The initial cart updated event may occur multiple times on a page because a cart can be\n * initialized multiple times. Listeners should not assume a cart is only initialized once per page.\n *\n * Any cart updated event can reference a new cart with a new id. Users should not assume the id is\n * constant.\n *\n * This does not trace errors. Callers are responsible for error tracing.\n *\n * This can throw, but generally does not. Instead, when there is a problem, an event is emitted. As\n * such, callers need to handle errors both by checking for whether the function threw and also by\n * observing error events. The promise rejection must be handled because we have a rule that all\n * calls to promise returning functions (which means all async qualified functions) must catch\n * promise rejections because of the frequent mistake of accidentally ignoring promise rejections,\n * in other words, ignoring errors. However, this does not throw unless something is really wrong\n * such as lack of support for JavaScript class syntax.\n *\n * This does not check whether there is existing cart state that is stale. This will always fetch\n * the latest state.\n */\nasync function init(fetchOptions?: FetchOptions) {\n  if (location.protocol !== 'https:') {\n    return;\n  }\n\n  const allowedHostnames = [\n    'langehair.com',\n    'integrabeauty.myshopify.com'\n  ];\n\n  if (!allowedHostnames.includes(location.hostname)) {\n    return;\n  }\n\n  // This module expects that the Shopify global is correctly declared and initialized. We\n  // intentionally are not being defensive about its presence here.\n\n  initStatus = 'running';\n\n  // We record whether the shopper was online and looking at the page, both when init starts and\n  // when it fails, along with how many times the page was hidden in between. These only go on the\n  // error report and never change what init does.\n  const onlineAtStart = navigator.onLine;\n  const visibilityAtStart = document.visibilityState;\n  let timesHidden = 0;\n  const onVisibilityChange = () => {\n    if (document.visibilityState === 'hidden') {\n      timesHidden++;\n    }\n  };\n\n  document.addEventListener('visibilitychange', onVisibilityChange);\n\n  const transaction = start('init');\n  try {\n    await initUnsafe(transaction, fetchOptions);\n    initStatus = 'succeeded';\n  } catch (error) {\n    initStatus = 'failed';\n\n    // We log the error so it rides along as context on whatever gets reported later on this page.\n    console.log('Cart init failed', error);\n\n    report(window, {\n      code: 'INIT',\n      error,\n      inputs: {\n        country: Shopify.country,\n        elapsed_ms: Date.now() - transaction.epoch,\n        online_at_error: navigator.onLine,\n        online_at_start: onlineAtStart,\n        visibility_at_error: document.visibilityState,\n        visibility_at_start: visibilityAtStart,\n        times_hidden_during_init: timesHidden\n      },\n      scenario: 'cart-lifecycle',\n      transaction\n    });\n  } finally {\n    document.removeEventListener('visibilitychange', onVisibilityChange);\n    transaction.complete();\n  }\n}\n\nasync function initUnsafe(transaction: Transaction, fetchOptions?: FetchOptions) {\n  // We have to fetch cart state instead of relying on the server side rendered state because there\n  // is no way to render cart.discount_codes via liquid. If this fetch fails, then init fails.\n  //\n  // Init is more patient than the default because failing here shows the shopper an error. The\n  // longer waits give a short connection drop or a 429 time to clear. The time limit keeps one hung\n  // request from using up all the attempts. It is 8s because on a throttled Slow 3G connection a\n  // cart read during page load took about 3s, and an attempt that times out throws away its request\n  // even when the answer was about to arrive.\n  //\n  // The worst case is every attempt timing out, which is 3 attempts at 8s each plus 3s of waits, so\n  // about 27s. Keep that under the 30s cart transaction timeout, because past it the transaction\n  // counts as complete while init is still running.\n  //\n  // A caller can pass its own options instead, such as a retry the shopper asked for.\n\n  const options = fetchOptions || { attempts: 3, delays: [1000, 2000], timeout: 8000 };\n  const fetched = await cartFetch(options);\n\n  // Apply various transforms to the cart state and get an updated cart\n\n  const transforms = createTransforms();\n  const transformed = applyTransforms(fetched, transforms);\n\n  // Determine whether any attributes changed\n\n  const changes = getAttributeChanges(fetched.attributes, transformed.attributes);\n\n  let cart = fetched;\n\n  // If at least one attribute changed, perform an update. Update errors here are not fatal to init\n  // because we have at least fetched the cart.\n  //\n  // This call could apply the discount changes at the same time as the attribute changes in the\n  // future. We are waiting to make that change because it is hairy.\n\n  if (Object.keys(changes).length > 0) {\n    try {\n      await cartUpdate({ attributes: changes });\n      cart = await cartFetch();\n\n      for (const key in changes) {\n        console.debug('Changed cart attribute %s from \"%s\" to \"%s\"', key, fetched.attributes[key],\n          cart.attributes[key]);\n      }\n    } catch (error) {\n      console.warn(error);\n    }\n  }\n\n  // Apply discount codes from the url parameter. This is also non-fatal to init.\n  //\n  // This is deliberately best-effort rather than being folded into the attribute update. Discount\n  // application is a heavily battle tested unit that owns the nuance of merging with codes from an\n  // earlier ad click, applying overrides, and trimming an overcrowded code list, so it is kept\n  // whole and isolated for now. Combining it into a single write is a future improvement.\n\n  try {\n    cart = await applyUrlDiscounts(cart);\n  } catch (error) {\n    console.warn(error);\n  }\n\n  // We now have an updated cart state in memory. Persist cart state to global page state, and\n  // notify observers. This write is not linked to the dom, so we dispatch from window.\n\n  write(window, cart, transaction, 'init', true);\n}\n\n/**\n * Runs init again when asked, which is how the shopper retries after init failed.\n *\n * The code asking for the retry lives in a different bundle than this one. We listen for an event\n * here so that code can ask without the two bundles sharing a library to call init through.\n *\n * Other cart code assumes there is only one successful init per page, so we only honor a request\n * after our own attempt has finished and failed. A request that arrives while an attempt is still\n * running, or after init succeeded, is ignored.\n */\nfunction onCartInitRequested(_event: WindowEventMap['cart-init-requested']) {\n  if (initStatus === 'failed') {\n    // The shopper is watching this one, so we make a single attempt and let them tap again if it\n    // fails. A failing retry takes 8s at worst instead of about 27s. The time limit stays the same\n    // as page load so a slow phone still gets its answer.\n    init({ attempts: 1, delays: [], timeout: 8000 }).catch(console.error);\n  } else {\n    console.debug('Ignoring cart init request', initStatus);\n  }\n}\n\nfunction main() {\n  addEventListener('cart-init-requested', onCartInitRequested);\n  init().catch(console.error);\n}\n\nmain();\n","/**\n * Copies a single piece of supplementary context onto an error before it is thrown or reported.\n *\n * Use this when decorating a value the calling code did not construct, such as a caught value or an\n * error handed in by another module. An error the caller just built with new is always writable, so\n * plain assignment is fine there. A value from elsewhere may be a foreign object this code does not\n * own. Such a value can carry a non-writable property, such as a DOMException whose code is a read-\n * only accessor, or be frozen outright, and assigning to it then throws. Attaching context must\n * never itself fail, so a rejected assignment is swallowed and the remaining context is still\n * attempted. Guarding each property in isolation means a single read-only slot, the common case,\n * costs only that one property rather than all of them.\n *\n * In practice the value that triggers this is almost always a DOMException that reached the caller\n * from a fetch the browser cut short, such as an abort when an in-flight request is interrupted.\n * The platform defines a DOMException's code, name, and message as read-only attributes, so\n * assigning a code onto one throws under the strict mode that all bundled module code runs in. It\n * surfaces from Safari far more than elsewhere for two reasons. The reported wording, attempted to\n * assign to readonly property, is JavaScriptCore's phrasing for this failure, so the same bug in\n * another engine is worded differently and groups as a separate report, which makes this one look\n * Safari specific even though the read-only rule is not. And iOS Safari abandons in-flight fetches\n * aggressively when a tab is backgrounded or a connection drops, which is when such a DOMException\n * tends to be produced and handed to code that assumed an ordinary writable error.\n *\n * A swallowed failure is logged at a level low enough that it is recorded as a breadcrumb for\n * monitoring rather than escalated into its own captured event. That keeps the lost context visible\n * alongside the eventual report without adding fresh noise of its own. The breadcrumb names the\n * property that could not be attached, the kind of value that rejected it, and why, which is enough\n * to recognize a read-only or frozen caught value after the fact.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Read-only\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException\n * @see https://webidl.spec.whatwg.org/#dfn-read-only\n */\nexport function decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (assignmentError) {\n    let reason: string;\n    if (assignmentError instanceof Error) {\n      reason = assignmentError.message;\n    } else {\n      reason = `${assignmentError}`;\n    }\n\n    console.log('Failed to assign error property', {\n      key,\n      name: error?.name,\n      tag: Object.prototype.toString.call(error),\n      reason\n    });\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { decorateError } from '../decorate-error.js';\nimport type { Transaction } from './transaction.js';\n\ntype CartUpdateErredEvent = CustomEvent<{\n  /**\n   * Cart state.\n   *\n   * This may be undefined or null or invalid because almost nothing is guaranteed.\n   */\n  cart?: Cart;\n\n  /**\n   * An error code that describes the context of the error or the type of the error.\n   */\n  code: string;\n\n  /**\n   * The error that occurred. This will almost always be of type `Error`. However, this is set to\n   * the value of whatever error was caught. Values not of type `Error` can be thrown. We are not\n   * checking that the value is an instance of an `Error` first. It is not safe to assume this is of\n   * type `Error` particularly because of code minimization and third party modifications.\n   */\n  error: any;\n\n  /**\n   * A mix of misc properties that various cart transactions will include that help provide more\n   * information about the inputs to the code that led to the error.\n   */\n  inputs?: Record<string, any>;\n\n  /**\n   * If true, this means the event was emitted by a multi-fetch operation where some of the fetches\n   * succeeded. For example, consider the addItems call, which has to do two fetches in its virtual\n   * transaction. This will be true when the fetch to add the item succeeded but the fetch to get\n   * the new cart state after the add failed. This flag exists so that callers can choose to react\n   * to this situation differently, e.g. by retrying just the cart state fetch, or changing what\n   * error message is displayed (a failure to add versus a partially successful add).\n   */\n  partial?: boolean;\n\n  /**\n   * An optional programmatic code or human-readable description of the context in which some cart\n   * update was taken. This is primarily for analytics, but can also be used for ui logic.\n   *\n   * For example:\n   * * distinctly identify two different add to cart buttons\n   * * distinctly identify a particular ui element used to apply a discount\n   */\n  scenario?: string;\n\n  /**\n   * The transaction in which the error occurred.\n   */\n  transaction: Transaction;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-update-erred': CartUpdateErredEvent;\n  }\n}\n\n/**\n * Broadcasts a cart failure and, when it is a genuine programmer error, surfaces it to the global\n * error handler so that monitoring records it.\n *\n * The cart helpers deliberately catch their own failures so that the code awaiting them is not\n * forced to handle a promise rejection. Every failure is broadcast as an event for the interface to\n * react to. That catch keeps the promise from rejecting, but for an unexpected failure it does not\n * mean the problem was actually dealt with. It is still a real error that would have reached the\n * top level had it never been caught. The escalation step re-throws those so that they do, leaving\n * error monitoring to record whatever uncaught errors arrive with no knowledge of the cart.\n */\nexport function report(dispatcher: EventTarget | null, detail: CartUpdateErredEvent['detail']) {\n  dispatchCartUpdateErred(dispatcher, detail);\n  escalateCartError(detail);\n}\n\n/**\n * Dispatches the failure as an event for the interface to react to.\n *\n * The dispatcher is the target the event is dispatched from. It is required but may be null, and it\n * may be any event target, not necessarily an element or window. The target is used as given, with\n * two exceptions that exist only to keep the event reaching window level listeners: a null target\n * dispatches from window, and so does a detached element.\n *\n * A detached element is swapped for window because of how bubbling works. We want to guarantee the\n * event bubbles up to window so that window level listeners are notified. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners.\n */\nfunction dispatchCartUpdateErred(dispatcher: EventTarget | null,\n  detail: CartUpdateErredEvent['detail']) {\n  // Dispatch the failure from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given. Callers should still prefer the\n  // element most directly responsible for the failure, so a connected element carries truthful\n  // provenance and bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  const event = new CustomEvent<typeof detail>('cart-update-erred', {\n    detail,\n    bubbles: true,\n    composed: true\n  });\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Surfaces a genuine programmer error to the global error handler so that monitoring records it.\n *\n * Some failures are expected outcomes with their own user facing messaging rather than programmer\n * errors: a shopper adding an out of stock item, or mistyping a discount code. Those are never\n * escalated to monitoring. Each such failure is identified by its code, the same classification the\n * cart helpers attach for the interface, so the list here must keep pace with the codes those\n * helpers produce.\n *\n * The throw is deferred to a later task so that it escapes the surrounding promise chain instead of\n * rejecting it, preserving the rejection-free contract of the cart helpers. The error keeps the\n * stack captured when it was first created, so the report still points at the origin of the failure\n * rather than at this deferred throw. However, beware that this does mean that the error event\n * properties will reflect this throw context, not the original error context, which means that the\n * instrumenting code must prioritize the original error properties over the error event properties.\n *\n * When available, the throw is routed through the reportError api, which surfaces the value to the\n * global error handler without the extra task hop. Older engines that lack it fall back to a\n * deferred throw, which reaches the same handler.\n *\n * The context describing the failure is attached to the error so that monitoring which serializes\n * the non-standard properties of an error can include it. A value that is not an error is wrapped\n * so that the context still has somewhere to live.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/reportError\n */\nfunction escalateCartError(detail: CartUpdateErredEvent['detail']) {\n  // These are expected outcomes with their own user facing messaging rather than programmer errors.\n  //\n  // This list of codes to ignore deliberately excludes invalid selling plan codes such as\n  // ADD_ITEMS_INVALID_SELLING_PLAN, ADD_ITEMS_INVALID_BACKORDER_SELLING_PLAN, and\n  // ADD_ITEMS_INVALID_TBYB_SELLING_PLAN. Unlike the ephemeral shopper conditions, a rejected\n  // selling plan is a plan we attached to the item, so it signals that something is misconfigured\n  // rather than a transient outcome. We want these to continue reaching error diagnostics so that\n  // misconfiguration is visible.\n\n  const ignoredCodes = [\n    'APPLY_DISCOUNTS',\n    'ADD_ITEMS_OOS',\n    'ADD_ITEMS_BUNDLE_ADDON_MIX',\n    'ADD_ITEMS_MAX_QUANTITY',\n    'CHANGE_CART_OOS',\n    'UPDATE_CART_OOS'\n  ];\n\n  if (detail && !ignoredCodes.includes(detail.code)) {\n    let error: any;\n\n    if (detail.error instanceof Error) {\n      error = detail.error;\n    } else {\n      error = new Error(`${detail.error}`);\n    }\n\n    decorateError(error, 'code', detail.code);\n    decorateError(error, 'inputs', detail.inputs);\n    decorateError(error, 'scenario', detail.scenario);\n    decorateError(error, 'cart', detail.cart);\n    decorateError(error, 'transaction_id', detail.transaction?.id);\n    decorateError(error, 'transaction_name', detail.transaction?.name);\n\n    if (typeof reportError === 'function') {\n      reportError(error);\n    } else {\n      setTimeout(raise, 0, error);\n    }\n  }\n}\n\nfunction raise(value: unknown) {\n  throw value;\n}\n","import type { AttributeValue, Attributes } from '@integrabeauty/shopify-ajax-api';\n\n/**\n * Compares the transformed attributes against the original attributes and returns only the entries\n * that changed, which are exactly the entries to send in the update. Entries that no transform\n * touched are identical in both and so are excluded, so attributes we do not own, such as the ones\n * Global-E and Forter inject, never enter the update. An empty result means nothing changed and no\n * update is needed.\n */\nexport function getAttributeChanges(from: Attributes, to: Attributes) {\n  const source = from || {};\n  const changes: Attributes = {};\n  for (const key in to) {\n    if (compareAttributeValues(source[key], to[key])) {\n      // unchanged, skip\n    } else {\n      changes[key] = to[key];\n    }\n  }\n\n  return changes;\n}\n\n/**\n * Returns whether two attribute values are equivalent. This is not strict equivalence. For values\n * that are objects, we only check the number of properties.\n *\n * Currently this does not treat two nulls as equal.\n */\nfunction compareAttributeValues(value0: AttributeValue, value1: AttributeValue) {\n  if (typeof value0 === 'string' && typeof value1 === 'string') {\n    return value0 === value1;\n  }\n\n  if (typeof value0 === 'object' && value0 !== null && typeof value1 === 'object' &&\n    value1 !== null) {\n    // for nested objects we just do a shallow check\n    const keys0 = Object.keys(value0);\n    const keys1 = Object.keys(value1);\n    return keys0.length === keys1.length;\n  }\n\n  return false;\n}\n","import '@integrabeauty/custom-events';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../lib/untrusted-value.js';\n\n/**\n * Reads a value from local storage, trying each key in order from first to last and returning the\n * first stored value found. Suppresses the error and skips to the next key when the store cannot be\n * reached. Some in-app browsers, such as the WebViews used by TikTok and Instagram, disable DOM\n * storage, leaving the local storage object null so a bare read throws. A missing key and an\n * unreachable store both mean \"no stored value\" to the callers here, so both collapse to null. The\n * failure is logged at debug so it survives as a breadcrumb rather than being promoted to a\n * captured event.\n */\nfunction readLocalStorageItem(...keys: string[]) {\n  for (const key of keys) {\n    try {\n      const value = localStorage.getItem(key);\n      if (value) {\n        return value;\n      }\n    } catch (error) {\n      console.debug(error);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Writes a value to local storage, suppressing the error when the store cannot be reached. Some in-\n * app browsers, such as the WebViews used by TikTok and Instagram, disable DOM storage, leaving the\n * local storage object null so a bare write throws. The failure is logged at debug so it survives\n * as a breadcrumb rather than being promoted to a captured event.\n */\nfunction writeLocalStorageItem(key: string, value: string) {\n  try {\n    localStorage.setItem(key, value);\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\n/**\n * Removes a value from local storage, suppressing the error when the store cannot be reached. Some\n * in-app browsers, such as the WebViews used by TikTok and Instagram, disable DOM storage, leaving\n * the local storage object null so a bare removal throws. A failed removal is not worth knowing\n * about, so unlike the read and write helpers this does not even log it.\n */\nfunction removeLocalStorageItem(key: string) {\n  try {\n    localStorage.removeItem(key);\n  } catch (error) {\n    // ignore\n  }\n}\n\nexport function applyIdentityAttributes(cart: Cart) {\n  if (!cart.attributes) {\n    cart.attributes = {};\n  }\n\n  // Gather first, including the migration writes, which have their own error handling. The two\n  // assignments at the end cannot throw.\n\n  // The storage keys migrated from a cart_init_ prefix to a cart_lifecycle_ prefix when the section\n  // was renamed. The old keys are persisted in shoppers' browsers, so we cannot simply drop them.\n  // Instead each read tries the new key first and falls back to the legacy key, while every write\n  // targets the new key and then deletes the legacy one. A value found only under a legacy key is\n  // therefore promoted to the new prefix on the next write, so the old keys drain away over time as\n  // shoppers return.\n\n  let phone = readLocalStorageItem('cart_lifecycle_identity_phone', 'cart_init_identity_phone');\n\n  // We deprecated the vendor specific storage keys in favor of more generic keys for storing\n  // captured email and phones values, allowing us to use any identity-captured signal to store\n  // these values. However, to avoid losing data we still check for the old vendor specific keys\n  // that were used in the past, and if we find them we copy them over to the new generic keys. We\n  // only do this copying if the new generic keys are not already set, to avoid overwriting any more\n  // recently captured values that might have been captured from a different signal. This is a bit\n  // of a hack, but it allows us to preserve data while still migrating to the new keys.\n  if (!phone) {\n    phone = readLocalStorageItem('cart_lifecycle_attentive_phone', 'cart_init_attentive_phone');\n    if (phone) {\n      writeLocalStorageItem('cart_lifecycle_identity_phone', phone);\n      removeLocalStorageItem('cart_init_identity_phone');\n    }\n  }\n\n  let email = readLocalStorageItem('cart_lifecycle_identity_email', 'cart_init_identity_email');\n\n  // See the above comment for phone as we are doing the same thing for email. We are checking for\n  // the old attentive specific key and copying it over to the new generic key if the new generic\n  // key is not already set.\n  if (!email) {\n    email = readLocalStorageItem('cart_lifecycle_attentive_email', 'cart_init_attentive_email');\n    if (email) {\n      writeLocalStorageItem('cart_lifecycle_identity_email', email);\n      removeLocalStorageItem('cart_init_identity_email');\n    }\n  }\n\n  if (phone && !containsJavascriptScheme(phone)) {\n    cart.attributes.identity_phone = phone;\n  }\n\n  if (email && !containsJavascriptScheme(email)) {\n    cart.attributes.identity_email = email;\n  }\n}\n\nfunction onIdentityCaptured(event: WindowEventMap['identity-captured']) {\n  // A little extra defensiveness\n  const detail = event.detail || {};\n\n  const email = detail.email;\n  if (email) {\n    writeLocalStorageItem('cart_lifecycle_identity_email', email);\n    removeLocalStorageItem('cart_init_identity_email');\n  }\n\n  const phone = detail.phone;\n  if (phone) {\n    writeLocalStorageItem('cart_lifecycle_identity_phone', phone);\n    removeLocalStorageItem('cart_init_identity_phone');\n  }\n}\n\n// This can start listening whenever because it is ok if capture is delayed to a later page load.\naddEventListener('identity-captured', onIdentityCaptured);\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\n\nexport function applyAttentiveLastClickEpoch(cart: Cart) {\n  const url = new URL(location.href);\n  const utmSource = url.searchParams.get('utm_source')?.trim();\n  const utmMedium = url.searchParams.get('utm_medium')?.trim();\n  if (utmSource?.toLowerCase() === 'attentive' && utmMedium?.toLowerCase() === 'sms') {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.attentive_last_click_epoch = Date.now().toString();\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\n/**\n * The Share-A-Sale affiliate platform is now owned by AWIN and utilizes their official Shopify App\n * for tracking. A result of this transition is that Share-A-Sale click IDs are now passed via the\n * \"awc\" URL parameter as opposed to the previous \"sscid\" parameter.\n */\nexport function applyShareASaleAttributes(cart: Cart) {\n  const url = new URL(location.href);\n  const clickId = url.searchParams.get('awc');\n  if (!clickId) {\n    return;\n  }\n\n  cart.attributes = cart.attributes || {};\n\n  const href = location.href;\n  const changed = !containsJavascriptScheme(href) &&\n    cart.attributes.shareasale_click_url !== href;\n  if (changed) {\n    cart.attributes.shareasale_click_url = href;\n  }\n\n  cart.attributes.shareasale_click_epoch = Date.now().toString();\n}\n","/**\n * Represents the properties of a cookie.\n */\nexport interface Cookie {\n  /**\n   * Include a leading \".\" to include subdomains.\n   *\n   * @example \".langehair.com\"\n   */\n  domain: string;\n\n  /**\n   * Indicates the maximum lifetime of the cookie as an HTTP-date timestamp. See Date for the\n   * required formatting.\n   *\n   * If unspecified, the cookie becomes a session cookie. A session finishes when the client shuts\n   * down, after which the session cookie is removed.\n   *\n   * Warning: Many web browsers have a session restore feature that will save all tabs and restore\n   * them the next time the browser is used. Session cookies will also be restored, as if the\n   * browser was never closed.\n   *\n   * When an Expires date is set, the deadline is relative to the client the cookie is being set on,\n   * not the server.\n   */\n  expires: Date;\n\n  /**\n   * A <cookie-name> can contain any US-ASCII characters except for: control characters (ASCII\n   * characters `0` up to `31` and ASCII character `127`) or separator characters (space, tab and\n   * the characters: `( ) < > @ , ; : \\ \" / [ ] ? = { })`.\n   */\n  name: string;\n\n  /**\n   * Path is generally \"/\" which is for all pages for a domain. Otherwise the cookie is specific to\n   * the given path (e.g. particular page).\n   */\n  path: string;\n\n  /**\n   * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n   * against cross-site request forgery attacks (CSRF).\n   *\n   * * Strict Means that the browser sends the cookie only for same-site requests, that is, requests\n   *   originating from the same site that set the cookie. If a request originates from a different\n   *   domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute\n   *   are sent.\n   * * Lax Means that the cookie is not sent on cross-site requests, such as on requests to load\n   *   images or frames, but is sent when a user is navigating to the origin site from an external\n   *   site (for example, when following a link). This is the default behavior if the SameSite\n   *   attribute is not specified.\n   * * None means that the browser sends the cookie with both cross-site and same-site requests. The\n   *   Secure attribute must also be set when setting this value, like so SameSite=None; Secure. If\n   *   Secure is missing an error will be logged\n   *\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n   */\n  same_site: 'Lax' | 'None' | 'Strict';\n\n  /**\n   * Indicates that the cookie is sent to the server only when a request is made with the https:\n   * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n   */\n  secure: boolean;\n\n  /**\n   * A <cookie-value> can optionally be wrapped in double quotes and include any US-ASCII character\n   * excluding control characters (ASCII characters 0 up to 31 and ASCII character 127), Whitespace,\n   * double quotes, commas, semicolons, and backslashes.\n   */\n  value: string;\n}\n\n/**\n * Decodes a percent-encoded cookie name or value, falling back to the raw input when decoding is\n * not possible.\n *\n * Cookie storage is shared by every script on a domain, including third party libraries that write\n * raw, improperly-encoded values. A single cookie whose value contains a malformed percent-\n * sequence, such as a literal percent sign that is not followed by two hexadecimal digits, makes\n * decodeURIComponent throw a URIError. Because the whole cookie string is parsed at once, one such\n * cookie would otherwise poison every read on the page rather than just its own entry. Returning\n * the undecoded text keeps the remaining cookies readable, and a value that was never percent-\n * encoded is returned unchanged anyway.\n */\nfunction safeDecode(value: string) {\n  try {\n    return decodeURIComponent(value);\n  } catch (error) {\n    return value;\n  }\n}\n\n/**\n * Parses the value of document.cookie into an an array of cookie objects.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookies can be set with separate flags. When there are two cookies with the same name but with\n * different flags, the name will appear twice in document.cookie. Therefore, this returns an array\n * of Cookie objects instead of a single object where each name is a property.\n *\n * @throws {Error} if document is undefined, if document.cookie cannot be accessed for some reason\n * such as security restrictions on iframes or browser restrictions, or if document.cookie does not\n * produce a string\n */\nexport function parse() {\n  return document.cookie.split(';').map<Pick<Cookie, 'name' | 'value'>>(parts => {\n    const index = parts.indexOf('=');\n    // decodeURIComponent(undefined) produces strange results in some browsers, such as the string\n    // undefined, so we have to avoid that\n    const encodedValue = parts.slice(index + 1)?.trim();\n    return {\n      name: safeDecode(parts.slice(0, index).trim()),\n      value: typeof encodedValue === 'string' ? safeDecode(encodedValue) : undefined\n    };\n  });\n}\n\n/**\n * Retrieves the value of a particular cookie by name.\n *\n * If a cookie with the given name is not found, then undefined is returned.\n *\n * Internally this calls a private function to parse the cookie and that function accesses\n * document.cookie and for that reason this function can throw. This will almost never happen except\n * when attempting to access cookies cross-context, e.g. accessing parent window cookies from within\n * an iframe where the iframe has certain attributes. Some third party libraries do this naively and\n * without concern for the security constraints imposed by browsers. As a result, you should always\n * consider handling errors when calling this function.\n *\n * Because this can throw, **please implement proper error handling** when calling this function.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookie values are always strings. If you happen to be storing something like a JSON-like object\n * in the cookie value, then the raw JSON string is returned and is not parsed on your behalf. If\n * you are enclosing your value in double quotes, the leading and trailing double quote characters\n * are included in the returned value.\n *\n * **WARNING**: This is a naive read. When examining the list of cookies, this returns the first one\n * in the list. More than one cookie can have the same name when two or more cookies have the same\n * name but different flags. This is entirely dependent on the order of the names in\n * document.cookie, which is completely unknown and can change at any time and probably varies by\n * browser. Therefore, when there is more than one cookie with the same name, it is possible this\n * returns an unexpected cookie value.\n *\n * @throws {SecurityError}\n * @throws {Error}\n */\nexport function read(name: string) {\n  return readAll(name).shift();\n}\n\nexport function readAll(name: string) {\n  return parse().filter(cookie => cookie.name === name).map(cookie => cookie.value);\n}\n\n/**\n * Given a cookie, generate the string that can be supplied to document.cookie\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function serialize(cookie: Partial<Cookie>) {\n  if (!cookie.name) {\n    throw new Error('Cookie name is required');\n  }\n\n  const parts: string[] = [];\n\n  parts.push(`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value || '')}`);\n\n  if (typeof cookie.path === 'string') {\n    parts.push(`Path=${cookie.path}`);\n  }\n\n  if (typeof cookie.domain === 'string') {\n    parts.push(`Domain=${cookie.domain}`);\n  }\n\n  if (cookie.expires && typeof cookie.expires.getTime === 'function') {\n    parts.push(`Expires=${cookie.expires.toUTCString()}`);\n  }\n\n  if (typeof cookie.same_site === 'string') {\n    parts.push(`SameSite=${cookie.same_site}`);\n  }\n\n  if (cookie.secure) {\n    parts.push('Secure');\n  }\n\n  // The delimiter must include a single space after semicolon\n\n  return parts.join('; ');\n}\n\n/**\n * This function can throw because setting document.cookie sometimes throws an error.\n *\n * This function can throw on invalid input, such as when not specifying a required property such as\n * cookie name.\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function write(cookie: Partial<Cookie>) {\n  document.cookie = serialize(cookie);\n}\n\n/**\n * Deletes a cookie.\n *\n * Of the cookie properties, only the name is required. However, some browsers are extremely strict\n * about how a cookie is deleted. To delete a cookie the exact same other properties must also be\n * provided, other than the cookie's value and expiration date. For example, if the cookie to be\n * deleted is secure, then you must specify the secure property to delete the cookie.\n *\n * If the properties do not match, this completes without error and the cookie that was to be\n * deleted still remains.\n *\n * Deleting a cookie is done by setting its value to an empty string and then assigning the full\n * cookie value to document.cookie.\n *\n * This function may throw an error because deleting a cookie is done by assigning a value to\n * document.cookie which can sometimes throw.\n *\n * The expires property is ignored. This sets expiration date to 1970 to force cookie to become\n * expired.\n *\n * Insecure cookies cannot be removed. These are cookies belonging to the http protocol.\n */\nexport function remove(cookie: Partial<Cookie>) {\n  write({ ...cookie, value: '', expires: new Date(0) });\n}\n","import { read } from '@integrabeauty/cookie';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyGoogleClientId(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  const value = getGoogleAnalyticsClientId();\n  if (!value || containsJavascriptScheme(value)) {\n    return;\n  }\n\n  cart.attributes.google_client_id = value;\n}\n\n/**\n * Returns the Google Analytics 4 client id.\n *\n * The preferred and recommended way for accessing this value is via Google Analytics' SDK. However,\n * it turns out that this is not feasible. Ad Block and Apple ITP break the SDK in strange ways,\n * e.g. the gtag callback never runs, so we cannot use the gtag get-property call to get the value.\n * This bug was hard to find because the promise wrapper around the callback ended up being a never-\n * settled promise.\n *\n * This function is infallible (guaranteed not to error), so you do not need to wrap it within a\n * try/catch block.\n *\n * Be wary of when you call this function. Google Analytics initializes the cookie value but the\n * Google Analytics library is not guaranteed to be loaded by the time this function runs. On the\n * very first visit from a device this value may be missing.\n *\n * Be wary of this function breaking at some point in the future. Apple ITP broke the GA SDK. We\n * cannot synchronously load the SDK or ensure it runs early. We are left with no choice but to\n * bypass the SDK and load via cookie. Because we are bypassing the SDK, we are not protected\n * against changes to how GA works. Google probably will not notify us ahead of time either.\n *\n * Be wary of this function not working nicely with the possibility of using a custom client id\n * value some day in the future while we continually migrate to first party cookies to deal with the\n * fallout of Apple ITP violating the basic principles of the last several decades of web\n * development under the guise of privacy.\n */\nfunction getGoogleAnalyticsClientId() {\n  // The value looks like \"GA1.1.1234.5678\" 0 - GA1 is a prefix 1 - unclear 2 - a random number 3 -\n  // epoch in seconds\n  //\n  // The value we want is part 2 and part 3 delimited by a period.\n\n  const value = read('_ga');\n  if (typeof value === 'string' && value.length > 0) {\n    return value.split('.').slice(2).join('.');\n  }\n}\n\nexport function applyGoogleClickId(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  const url = new URL(location.href);\n  const id = url.searchParams.get('gclid');\n  if (!id || containsJavascriptScheme(id)) {\n    return;\n  }\n\n  cart.attributes.google_click_id = id;\n}\n","import { read } from '@integrabeauty/cookie';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyKlaviyoAttributes(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  // Gather the fallible reads first. The assignments at the end cannot throw.\n\n  const id = getKlaviyoId();\n  const clickId = getKlaviyoClickId();\n\n  if (id && !containsJavascriptScheme(id)) {\n    cart.attributes.klaviyo_id = id;\n  }\n\n  if (clickId && !containsJavascriptScheme(clickId)) {\n    cart.attributes.klaviyo_click_id = clickId;\n    cart.attributes.klaviyo_last_click_epoch = Date.now().toString();\n  }\n}\n\n/**\n * Klaviyo leverages this non-standard UTM parameter.\n */\nexport function applyUTMEmail(cart: Cart) {\n  if (!cart.attributes) {\n    cart.attributes = {};\n  }\n\n  const url = new URL(location.href);\n  const utmEmail = url.searchParams.get('utm_email')?.trim();\n  if (!utmEmail || containsJavascriptScheme(utmEmail)) {\n    return;\n  }\n\n  cart.attributes.utm_email = utmEmail;\n}\n\n/**\n * @see https://help.klaviyo.com/hc/en-us/articles/115005076767#h_01HADAYAACVVC4BXQ0ES5Y50TC\n */\nfunction getKlaviyoClickId() {\n  const url = new URL(location.href);\n  const kx = url.searchParams.get('_kx');\n  if (kx) {\n    return kx;\n  }\n}\n\nfunction getKlaviyoId() {\n  const encoded = read('__kla_id');\n  if (encoded) {\n    const decoded = atob(encoded);\n    const parsed: { cid: string; } = JSON.parse(decoded);\n    if (parsed.cid) {\n      return atob(parsed.cid);\n    }\n  }\n}\n","import { read } from '@integrabeauty/cookie';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\n/**\n * @see https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc/\n */\nexport function applyMetaAttributes(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  // Gather all the fallible reads first so that a throw lands before any assignment.\n\n  const url = new URL(location.href);\n  const adId = url.searchParams.get('ad.id')?.trim() || url.searchParams.get('ad_id')?.trim();\n  const clickId = getMetaClickId();\n  const personCookie = read('_fbp');\n  const clickCookie = readMetaClickCookie();\n\n  // Apply, which is now free of anything that can throw.\n\n  if (adId && adId !== '{ad.id}' && adId !== '{{ad.id}}' && adId !== '{ad_id}' &&\n    !containsJavascriptScheme(adId)) {\n    cart.attributes.meta_ad_id = adId;\n  }\n\n  if (clickId && !containsJavascriptScheme(clickId)) {\n    cart.attributes.meta_click_id = clickId;\n  }\n\n  if (personCookie && !containsJavascriptScheme(personCookie)) {\n    cart.attributes.meta_person_cookie = personCookie;\n  }\n\n  if (clickCookie && isValidMetaClickCookie(clickCookie) &&\n    !containsJavascriptScheme(clickCookie)) {\n    cart.attributes.meta_click_cookie = clickCookie;\n  }\n}\n\n/**\n * Sometimes the meta pixel records a junk fbc cookie value. Recognize this invalid value so that we\n * can ignore it. The junk is always in the last part, which is the fbclid value.\n *\n * @see https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc/#2--format-clickid\n */\nfunction isValidMetaClickCookie(value: any) {\n  if (typeof value === 'string') {\n    const parts = value.split('.');\n    return parts.length > 3 && typeof parts[3] === 'string' && !parts[3].startsWith('%25');\n  }\n}\n\nfunction getMetaClickId() {\n  const url = new URL(location.href);\n  const id = url.searchParams.get('fbclid')?.trim();\n\n  // When accessing the fbclid, we have to be careful to ignore the tune redirects that incorrectly\n  // and unconditionally pass through the url parameter macro name.\n\n  // This is different than the cookie code. From what we can surmise, Meta's pixel code reads the\n  // click id from the url during init and then sets the cookie with some extra prefix stuff, so\n  // that code validates the id taking into account the prefix. Here there is no prefix.\n\n  if (id && id !== '{fbclid}' && id !== '{{fbclid}}') {\n    return id;\n  }\n}\n\nfunction readMetaClickCookie() {\n  const value = read('_fbc');\n\n  // Our Facebook advertising uses HasOffers links (now tune.com). HasOffers links are generated\n  // from macros configured within its service. HasOffers has no concept of conditional macros that\n  // only forward url parameters if set. All macros, including unevaluated macros, are relayed. This\n  // means that if we have a macro in the url such as \"&fbclid={fbclid}\", and the visit comes from\n  // facebook.com but not as a result of an ad click that automatically tagged the url with an\n  // fbclid parameter, the meta pixel client detects this from the url and saves the fbc cookie as\n  // containing \"{fbclid}\". That is not a valid id. That cookie should not have been saved. However,\n  // we have no way to intercept the Meta pixel's accessing of the url when it loads. So this junk\n  // gets written into the cookie. This is spectacularly bad because it means we collect garbage\n  // values and we also feed garbage values back to Meta. Now, that said, we can take steps to\n  // mitigate some of the damage. One is that we ensure that all our of unconventional direct cookie\n  // access (instead of via the pixel sdk, which gets ad blocked most of the time anyway and has\n  // loading issues) uses this function to access the internals, we can sort of protect ourselves.\n\n  if (typeof value === 'string' && !value.endsWith('{fbclid}')) {\n    return value;\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyMicrosoftClickId(cart: Cart) {\n  const url = new URL(location.href);\n  const value = url.searchParams.get('msclkid')?.trim();\n\n  if (value && !containsJavascriptScheme(value)) {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.microsoft_click_id = value;\n  }\n}\n","import { read, write } from '@integrabeauty/cookie';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\n/**\n * Reports the url of the current page load as the session entry url. This pins the value: once the\n * key is present it does nothing, so later page loads never overwrite it, which makes this the\n * session entry url rather than a per-page url. See the entry referrer transform for why this reads\n * the live location directly rather than through web storage.\n *\n * We slice to truncate the value to 1000 characters or less so that we avoid issues. I was unable\n * to find any documentation on the maximum size of an attribute.\n */\nexport function applySessionStartUrl(cart: Cart) {\n  if (!cart.attributes) {\n    cart.attributes = {};\n  }\n\n  if ('session_start_url' in cart.attributes) {\n    return;\n  }\n\n  const url = location.href.slice(0, 1000);\n  if (containsJavascriptScheme(url)) {\n    return;\n  }\n\n  cart.attributes.session_start_url = url;\n}\n\n/**\n * Reports the referrer of the current page load as the session entry referrer. This pins the value:\n * once the key is present it does nothing, so later page loads never overwrite it, which makes this\n * the session entry referrer rather than a per-page referrer.\n *\n * We read the referrer directly here rather than through web storage. In-app browser WebViews such\n * as those used by TikTok and Instagram routinely disable DOM storage, and those are a large share\n * of traffic, so a storage backed capture recorded nothing at all in exactly those browsers.\n *\n * An empty referrer is reported as a sentinel so that the value still pins on the first pass.\n * Otherwise a direct visit would leave the attribute absent on the entry page and a later page\n * would fill it with an internal referrer. An empty referrer can mean a direct visit or a stripped\n * referrer (a referrer policy, an https to http downgrade, a privacy mode), so the honest sentinel\n * is \"unknown\" rather than \"(direct)\".\n */\nexport function applySessionReferrer(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  if ('session_referrer' in cart.attributes) {\n    return;\n  }\n\n  const referrer = document.referrer || 'unknown';\n  if (!containsJavascriptScheme(referrer)) {\n    cart.attributes.session_referrer = referrer;\n  }\n}\n\n/**\n * Reports the epoch the session started as the session entry epoch. This pins the value: once the\n * key is present it does nothing, so later page loads never overwrite it, which makes this the\n * entry epoch that survives for the life of the cart rather than a per-load value. The pin is also\n * why this can skip the session cookie work on later loads, since the cookie only exists to feed\n * this value and the value is already established.\n */\nexport function applySessionStartEpoch(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  if ('session_start_epoch' in cart.attributes) {\n    return;\n  }\n\n  const sessionStartEpoch = obtainSessionStartTime();\n  if (!Number.isInteger(sessionStartEpoch)) {\n    return;\n  }\n\n  cart.attributes.session_start_epoch = sessionStartEpoch.toString();\n}\n\n/**\n * Retrieves the date and time the session started in the form of an epoch in milliseconds. As a\n * side effect, if this is the first time, this also memoizes the value for future calls.\n *\n * This is implemented using a \"session\" cookie. By setting a cookie without an expiration date the\n * cookie is automatically deleted when the browser is closed, which is effectively the end of the\n * session.\n *\n * This function is so named because it has to check for the cookie and then either return the value\n * if found or if not found then create the cookie and then return its value.\n *\n * If there is a problem setting the cookie, return's undefined.\n *\n * This suppresses errors thrown by the call to Cookie.write so this should be treated as an\n * infallible function. This means the caller should avoid using a superfluous try/catch.\n */\nfunction obtainSessionStartTime() {\n  let existingValue;\n\n  // If we failed to read, we will still attempt to write.\n  try {\n    existingValue = read('session_start');\n  } catch (error) {\n  }\n\n  if (existingValue) {\n    const epoch = parseInt(existingValue, 10);\n    if (Number.isInteger(epoch) && epoch > 0) {\n      return epoch;\n    } else {\n      console.warn('found session cookie with invalid value', existingValue);\n\n      // This should never happen. I suppose it could if someone manipulates the session storage\n      // state via alternate means and does so carelessly. If this does happen then we fall through\n      // so that we end up overwriting the invalid value.\n    }\n  }\n\n  // First try to obtain the time the page was actually loaded, which might be well before this\n  // script runs. We know this because we can see divergent timing for some events that leverage the\n  // session start time as a session id in downstream analytics. We obtain the page load time from\n  // the data layer queue's non-broadcasted event. That load time is set the moment the js runs on\n  // the page, early in the head, without waiting for any network calls to lazy load other scripts.\n  //\n  // This can often amount to a difference of approximately 3-10 seconds.\n  //\n  // If we did not find the load-time then fallback to now.\n\n  const epoch = cart_lifecycle.script_epoch || Date.now();\n\n  // We are intentionally omitting the expires property here. The logic right now for setting cookie\n  // defaults sets the expired time if not set so for now we are not using the helper.\n\n  try {\n    write({\n      domain: '.langehair.com',\n      name: 'session_start',\n      path: '/',\n      same_site: 'Lax',\n      secure: true,\n      value: epoch.toString()\n    });\n  } catch (error) {\n    // Fall through. We return the epoch even though we did not persist the epoch.\n  }\n\n  return epoch;\n}\n","import { read } from '@integrabeauty/cookie';\n\ntype Limitation = 'CCPA_BLOCK_ALL' | 'GDPR';\n\n/**\n * Represents the state of consent.\n *\n * Sometimes this is serialized into a cookie value. Sometimes it is constructed from a esoteric\n * serialization format.\n */\nexport interface ConsentState {\n  con: {\n    CMP?: {\n      a: '0' | '1';\n      m: '0' | '1';\n      p: '0' | '1';\n      s: '0' | '1';\n    };\n    GDPR?: '' | '0' | '1' | 'no_interaction';\n  };\n  display_banner?: boolean;\n  lim: Limitation[];\n  purposes: {\n    /**\n     * analytics\n     */\n    a: boolean;\n\n    /**\n     * marketing\n     */\n    m: boolean;\n\n    /**\n     * personalization\n     */\n    p: boolean;\n\n    t: boolean;\n  };\n\n  reg: '' | 'CCPA' | 'GDPR' | undefined;\n\n  region?: string;\n\n  sale_of_data_region?: boolean;\n\n  v: string;\n}\n\nexport function parseConsentHash(value: string) {\n  // Check for v3 cookie or session hash. AMPS can be before or after period, all preceding\n  // underscore. lowercase is denied. uppercase is granted. it seems like initially AMPS defaults\n  // are after the period. After interacting with checkboxes, characters move to before the period.\n  //\n  // Format: 3[flags]_[region]_[other data] Where region is a 2 to 6 character code like USCA (US\n  // California) or USIL (US Illinois) I do not know about 6. I do see 5, e.g. GBNIR (Great Britain\n  // Northern Ireland).\n  //\n  // The region can also just be the country. I suppose Shopify is sometimes unable to determine the\n  // subregion.\n  //\n  // The subregion can contain digits.\n  //\n  // The region can be empty.\n  //\n  // The hash can carry trailing underscore-delimited segments after the region that we do not need,\n  // most notably a consent id and, when a metafield is written through setTrackingConsent, an\n  // appended metafield blob in raw or URL encoded JSON. This regex is anchored only at the start\n  // and reads just the flags and region, so any such trailing data is tolerated and ignored. Do not\n  // anchor it at the end or switch to a positional split, which would make those trailing segments\n  // break parsing.\n\n  const matches = /^3([amps]{0,4}\\.?[amps]{0,4})_([A-Z0-9]*)_/i.exec(value);\n\n  if (matches?.length > 2) {\n    const flags = matches[1];\n    const region = matches[2];\n    const state = <ConsentState>{\n      purposes: {},\n      region\n    };\n\n    if (flags.includes('A')) {\n      state.purposes.a = true;\n    } else if (flags.includes('a')) {\n      state.purposes.a = false;\n    }\n\n    if (flags.includes('M')) {\n      state.purposes.m = true;\n    } else if (flags.includes('m')) {\n      state.purposes.m = false;\n    }\n\n    if (flags.includes('P')) {\n      state.purposes.p = true;\n    } else if (flags.includes('p')) {\n      state.purposes.p = false;\n    }\n\n    // note that s appears as t in the object\n    if (flags.includes('S')) {\n      state.purposes.t = true;\n    } else if (flags.includes('s')) {\n      state.purposes.t = false;\n    }\n\n    return state;\n  }\n\n  // Shopify can update the cookie format at any time without coordination and the docs even mention\n  // that parsing the cookie is not advised. Log a warning message so that we get spammed with\n  // warnings about when this happens. We could eventually decide to throw here, but downstream\n  // callers are all written to expect this not to throw. So we leak the error by logging so that\n  // error diagnostics platforms that have hooked console can still observe the error. Beware this\n  // could result in false positives (e.g. genuinely bad input).\n  if (typeof value === 'string' && value && !value.startsWith('{')) {\n    console.warn('Failed to parse _tracking_consent cookie value:', value);\n    return null;\n  }\n\n  // Otherwise, assume it is a v1 or v2 cookie that is a json stringified object.\n\n  let consent: unknown;\n  try {\n    consent = JSON.parse(value);\n  } catch (error) {}\n\n  if (!isConsentState(consent)) {\n    return null;\n  }\n\n  return consent;\n}\n\nexport function readCookie() {\n  let cookie;\n  try {\n    cookie = read('_tracking_consent');\n  } catch (error) {}\n\n  if (!cookie) {\n    return null;\n  }\n\n  return cookie;\n}\n\n/**\n * Reads the consent hash from session storage. Shopify's consent library reads the consent value\n * from the navigation Server-Timing entry and stores it under this key, so the value here is the\n * same one the Server-Timing source provides, just read from Shopify's cache of it.\n */\nexport function readSession() {\n  let session;\n  try {\n    session = sessionStorage.getItem('consentHeader');\n  } catch (error) {}\n\n  if (!session) {\n    return null;\n  }\n\n  return session;\n}\n\n/**\n * Reads the consent hash from the navigation response's Server-Timing entry. Shopify's consent\n * library reads the current consent state from this entry, so this reads the same entry. Shopify\n * also copies this value into session storage, so the session storage source is a cache of this\n * one. The Performance API is the only way for script to observe this header.\n */\nexport function readServerTiming() {\n  try {\n    const entries = performance.getEntriesByType('navigation');\n    const navigation = <PerformanceNavigationTiming>entries[0];\n    if (!navigation) {\n      return null;\n    }\n\n    const timings = navigation.serverTiming;\n    if (!timings) {\n      return null;\n    }\n\n    let header: string | undefined;\n    for (const timing of timings) {\n      if (timing.name === '_cmp') {\n        header = timing.description;\n        break;\n      }\n    }\n\n    if (!header) {\n      return null;\n    }\n\n    // Decoding is best effort. A malformed value is left as is rather than discarded.\n    try {\n      header = decodeURIComponent(header);\n    } catch (error) {}\n\n    return header;\n  } catch (error) {\n    return null;\n  }\n}\n\n/**\n * Resolves the visitor's consent hash. All three sources are read, then the first available value\n * is used in priority order: cookie, then session storage, then the navigation Server-Timing entry.\n * Each source carries the same hash format. Shopify's consent library reads these same sources.\n */\nexport function readCustomerConsent() {\n  // All three are read up front so that every value is available for the diagnostic log below when\n  // none of them resolve. The cookie can be missing when it is not accessible due to same-site\n  // restrictions, in which case session storage or the Server-Timing entry can still carry a value.\n  const cookie = readCookie();\n  const session = readSession();\n  const serverTiming = readServerTiming();\n\n  let consent = cookie;\n  if (!consent) {\n    consent = session;\n  }\n\n  if (!consent) {\n    consent = serverTiming;\n  }\n\n  if (!consent) {\n    console.debug('Unable to determine consent', cookie, session, serverTiming);\n    return null;\n  }\n\n  return parseConsentHash(consent);\n}\n\n/**\n * We use the Shopify consent API to determine if a user has given consent to marketing tracking.\n * Although Shopify does provide a client-side mechanism to load the customer privacy API and\n * supporting methods/functionality, we have made the decision to use the Shopify set\n * \"_tracking_consent\" cookie to prevent pixels from having to wait for an asynchronous call to load\n * the API module.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl\n */\nexport function hasMarketingConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.m === 'boolean') {\n    return cookie.purposes.m;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\n/**\n * We use the Shopify consent API to determine if a user has given consent to analytics tracking.\n * Although Shopify does provide a client-side mechanism to load the customer privacy API and\n * supporting methods/functionality, we have made the decision to use the Shopify set\n * \"_tracking_consent\" cookie to prevent pixels from having to wait for an asynchronous call to load\n * the API module.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl\n */\nexport function hasAnalyticsConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.a === 'boolean') {\n    return cookie.purposes.a;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\nexport function hasSaleOfDataConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.t === 'boolean') {\n    return cookie.purposes.t;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\nexport function isConsentState(value: any): value is ConsentState {\n  return value && 'purposes' in value;\n}\n\n/**\n * Determines the regulation type based on the region code.\n *\n * @param region country and province e.g. \"USCA\", \"DEDE\", \"GBGB\"\n * @returns The applicable regulation type\n */\nexport function determineRegulation(region: string) {\n  // Most regions are 4, but in some cases they can be longer such as FRIDF or GBNIR, and can\n  // sometimes be shorter, such as ROB. A country code is always 2, and there has to be at least one\n  // character for the subregion, so we use 3 as the cutoff for minimal validity. For example, for\n  // ROB, RO is Romania, and B is some subregion.\n  //\n  // The region can also just consist of the country. I am not sure why but I definitely see it in\n  // the wild, where the region value is just \"US\". Therefore, < 2 is the correct condition.\n  //\n  // The region can be the empty string. When the region is completely unknown, we cannot derive the\n  // regulation, so we still want to exit early here.\n\n  if (typeof region !== 'string' || region.length < 2) {\n    return;\n  }\n\n  const regionCode = region.slice(0, 2);\n\n  // All country codes are 2 digits. The province/state are all remaining characters, which is\n  // typically 2 characters, but might be 3, e.g. GBNIR for Great Britain Northern Ireland. This\n  // might not matter that much in practice as we only consider the subregion for CCPA, which only\n  // applies to US, where all subregions are 2 characters in length, but this is more correct.\n  //\n  // There may not be a subregion.\n  const subregionCode = region.length > 2 ? region.slice(2) : undefined;\n\n  // EU countries require GDPR compliance\n  const gdprCountries = [\n    'AT',\n    'BE',\n    'BG',\n    'HR',\n    'CY',\n    'CZ',\n    'DK',\n    'EE',\n    'FI',\n    'FR',\n    'DE',\n    'GR',\n    'HU',\n    'IS',\n    'IE',\n    'IT',\n    'LV',\n    'LI',\n    'LT',\n    'LU',\n    'MT',\n    'NL',\n    'NO',\n    'PL',\n    'PT',\n    'RO',\n    'SI',\n    'SK',\n    'ES',\n    'SE',\n    'GB'\n  ];\n\n  if (gdprCountries.includes(regionCode)) {\n    return 'GDPR';\n  }\n\n  // US states with CCPA-like regulations\n  const ccpaSubregions = ['CA', 'VA'];\n\n  if (regionCode === 'US' && ccpaSubregions.includes(subregionCode)) {\n    return 'CCPA';\n  }\n}\n\n/**\n * Gets the default consent value for a region when no explicit consent is given.\n *\n * @param region 4-character region code\n * @returns Default consent value based on regional privacy laws\n */\nexport function getDefaultConsent(region: string) {\n  const regulation = determineRegulation(region);\n\n  // GDPR requires opt-in consent (default = false)\n  if (regulation === 'GDPR') {\n    return false;\n  }\n\n  // CCPA allows opt-out (default = true, user can opt out). Other regions are generally permissive\n  return true;\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { hasMarketingConsent } from '@integrabeauty/shopify-consent';\n\nexport function applyTrackingConsent(cart: Cart) {\n  const consent = hasMarketingConsent();\n\n  if (typeof consent === 'boolean') {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.tracking_consent = consent.toString();\n  }\n}\n","/**\n * Returns whether the current url looks like a direct link from Tune.\n *\n * Tune (tune.com) owns HasOffers, an affiliate attribution system.\n *\n * Tune can be configured to support direct or indirect links. Direct links bypass Tune's\n * intermediate redirect and result in visitors navigating directly to the destination website\n * without the automatic creation of a transaction id (which is like a click id). Indirect links\n * pass through Tune's intermediate redirect system where Tune appends a transaction_id parameter.\n *\n * When using Tune direct links, we need to capture the direct link somehow so that it can be\n * referenced later for purposes of posting back a conversion event of some kind (e.g. a purchase\n * event). This function helps identify whether the current page load looks like a page visit\n * resulting from a Tune configured offer, which indicates that this url should be captured and\n * later parsed so as to properly post back (send a conversion event linked to this page load).\n *\n * We require that both parameters are present because both are required for post back, so we are\n * ignoring partially specified or invalid page loads.\n *\n * This does not use the Tune JavaScript library. We could, but there is no need.\n *\n * @example \"https://example.com/aff_lsr?adv_sub=test&aff_id=1234&amount=11.11&offer_id=1111\"\n */\nexport function isDirectTuneLink() {\n  const url = new URL(location.href);\n  return url.searchParams.get('aff_id') && url.searchParams.get('offer_id');\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { isDirectTuneLink } from '../../../lib/tune.js';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\n/**\n * ShopMy is an affiliate platform. When a visitor arrives from a ShopMy link, we record the click\n * in cart attributes so that the sale can be credited to the affiliate.\n *\n * A ShopMy link tags utm_source and carries a click id in the smsclickid parameter. Either one on\n * its own is enough for us to capture, because sometimes one of them arrives missing or mangled.\n */\nexport function applyShopMyAttributes(cart: Cart) {\n  const url = new URL(location.href);\n  const isShopMySource = url.searchParams.get('utm_source')?.toLowerCase() === 'shopmy';\n  const clickId = url.searchParams.get('smsclickid')?.trim();\n  if (!isShopMySource && !clickId) {\n    return;\n  }\n\n  cart.attributes = cart.attributes || {};\n\n  const href = location.href;\n  const changed = !containsJavascriptScheme(href) &&\n    cart.attributes.shopmy_click_url !== href;\n  if (changed) {\n    cart.attributes.shopmy_click_url = href;\n  }\n\n  if (clickId && !containsJavascriptScheme(clickId)) {\n    cart.attributes.shopmy_sms_click_id = clickId;\n  }\n\n  cart.attributes.shopmy_click_epoch = Date.now().toString();\n}\n\n/**\n * See shopmy-transforms.md and shopmy-suppression.liquid\n */\nexport function applyShopMySuppression(cart: Cart) {\n  // If there are no attributes, there is nothing to do.\n\n  if (!cart.attributes) {\n    return;\n  }\n\n  // If there is no attribute previously set by ShopMy, there is nothing to do.\n\n  const clickId = cart.attributes.sms_click_id;\n\n  if (!clickId) {\n    return;\n  }\n\n  // If the current page load is not a Tune touch, there is nothing to do.\n\n  const url = new URL(location.href);\n  const tuneTransactionId = url.searchParams.get('transaction_id')?.trim();\n  const isTuneClick = tuneTransactionId || isDirectTuneLink();\n\n  if (!isTuneClick) {\n    return;\n  }\n\n  // Record the two existing attributes that ShopMy sets under new names for posterity, then remove\n  // the existing attributes.\n\n  const clickTime = cart.attributes.sms_click_time;\n\n  cart.attributes.shopmy_suppressed_sms_click_id = clickId;\n  if (clickTime) {\n    cart.attributes.shopmy_suppressed_sms_click_time = clickTime;\n  }\n\n  cart.attributes.sms_click_id = null;\n  cart.attributes.sms_click_time = null;\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applySurveyEmail(cart: Cart) {\n  const url = new URL(location.href);\n  const email = url.searchParams.get('survey_email');\n  if (email && !containsJavascriptScheme(email)) {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.survey_email = email;\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyTiktokClickId(cart: Cart) {\n  const url = new URL(location.href);\n  const id = url.searchParams.get('ttclid');\n\n  if (id?.startsWith('E.C.P.') && !containsJavascriptScheme(id)) {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.tiktok_click_id = id;\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { isDirectTuneLink } from '../../../lib/tune.js';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyAffiliate(cart: Cart) {\n  const url = new URL(location.href);\n  const affiliate = url.searchParams.get('aff');\n\n  if (affiliate && !containsJavascriptScheme(affiliate)) {\n    cart.attributes = cart.attributes || {};\n    cart.attributes.affiliate = affiliate;\n  }\n}\n\nexport function applyTuneAttributes(cart: Cart) {\n  cart.attributes = cart.attributes || {};\n\n  // Gather the fallible reads first, chiefly the URL parse, along with the existing values used to\n  // decide whether the captured urls actually changed. The assignments at the end cannot throw.\n\n  const url = new URL(location.href);\n  const href = location.href;\n  const allowed = !containsJavascriptScheme(href);\n  const transactionId = url.searchParams.get('transaction_id')?.trim();\n  const isDirect = isDirectTuneLink();\n  const existingHasoffers = cart.attributes.hasoffers;\n  const existingTuneUrl = cart.attributes.last_tune_url;\n\n  // This captures Tune indirect redirects.\n\n  if (transactionId) {\n    if (allowed && existingHasoffers !== href) {\n      cart.attributes.hasoffers = href;\n    }\n\n    cart.attributes.hasoffers_updated_at = Date.now().toString();\n  }\n\n  // For an indeterminate period we are capturing indirect Tune HasOffers redirects as well as\n  // direct Tune links, so we have separate attributes, even though there is some redundancy/bloat.\n  // These are \"last\" because we capture last touch, overwriting prior touches.\n\n  if (isDirect) {\n    if (allowed && existingTuneUrl !== href) {\n      cart.attributes.last_tune_url = href;\n    }\n\n    cart.attributes.last_tune_url_epoch = Date.now().toString();\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptScheme } from '../../../lib/untrusted-value.js';\n\nexport function applyUnityAttributes(cart: Cart) {\n  const url = new URL(location.href);\n  const utmSource = url.searchParams.get('utm_source');\n  if (!/unity/i.test(utmSource)) {\n    return;\n  }\n\n  cart.attributes = cart.attributes || {};\n\n  const href = location.href;\n  const changed = !containsJavascriptScheme(href) &&\n    cart.attributes.unity_last_click_url !== href;\n  if (changed) {\n    cart.attributes.unity_last_click_url = href;\n  }\n\n  cart.attributes.unity_last_click_epoch = Date.now().toString();\n}\n","import { type Cart, cartUpdate } from '@integrabeauty/shopify-ajax-api';\nimport { cartFetch } from '../state.js';\nimport { getApplicableDiscountCodes, getDiscountCodes, isWellFormed } from './discount.js';\n\ntype DiscountOverride = typeof cart_lifecycle.discount_overrides[number];\n\n/**\n * Updates a cart's discount codes. This uses a specially enabled feature on the lange Shopify shop\n * that enables the use of the cart update ajax api to modify the cart's discounts. The special\n * feature enables the use of a discount property in the update call.\n *\n * New codes are partially validated before the http request is sent. If any new code is invalid,\n * this throws an error immediately.\n *\n * This will send an update request even if the discount codes have not changed. This does not check\n * whether sending the request is pointless. One of the reasons why this check is not performed is\n * because sometimes callers want to call this function even when it seems pointless because of the\n * risk that client side state is out of sync with server side state. That kind of thing can happen\n * for example when browsing the site in multiple tabs, because client side state is largely managed\n * per tab, so one tab may naively update discounts without awareness of changes made in other tabs.\n * While we attempt to keep state synchronized across tabs, there is no guarantee.\n *\n * The special discount property of the update request body has no documentation. We figured out\n * that it works similar to the checkout links discount url parameter. Multiple codes can be\n * specified as a comma separated value. This also means that codes should not contain commas. We\n * also figured out that it overwrites all discounts. This does not just add new discounts. That is\n * why this requires both the set of existing codes and the new codes.\n *\n * @param cart the state of the cart prior to update\n * @param newCodes the codes that should be applied to the cart in addition to the existing codes\n * @param overrides discount overrides, this is expected to always be defined even if there are no\n * overrides\n * @param postValidate whether to check if the new codes were actually applied after the update\n *\n * @see https://help.shopify.com/en/manual/discounts/managing-discount-codes\n */\nexport async function applyDiscountsHelper(cart: Cart, newCodes: string[],\n  overrides: DiscountOverride[], postValidate = true) {\n  validateCodesBeforeUpdate(newCodes);\n\n  const oldUppers = getCurrentCodes(cart, newCodes.length);\n  const newUppers = newCodes.map(code => code.toUpperCase());\n\n  const retainedOldUppers = oldUppers.filter(code =>\n    !overrides.some(override =>\n      override.old_code === code && newUppers.includes(override.new_code)));\n\n  const mergedUppers = new Set<string>([...retainedOldUppers, ...newUppers]);\n  const mergedCodes = [...mergedUppers];\n\n  const discount = mergedCodes.join(',');\n  await cartUpdate({ discount });\n  const updatedCart = await cartFetch();\n\n  // Only validate if post validation requested. Validation is performed by default but there are\n  // some use cases where discounts are applied even in applicable situations, such as when the cart\n  // is empty (the discount is applied but if you look at the discount object in the discount_codes\n  // array the applicable property is false). By only validating if requested we avoid generating\n  // routine validation errors.\n\n  if (postValidate) {\n    validateCodesAfterUpdate(updatedCart, newCodes);\n  }\n\n  return updatedCart;\n}\n\n/**\n * Obtain the existing discount codes. Generally, we want to retain all codes, including currently\n * inapplicable codes, as those codes may become applicable later and have been deemed applied even\n * though they have no effect. However, retaining all codes leads to the possible accumulation of\n * several inapplicable codes. This leads into a poorly documented issue. Once there are about 5 or\n * 6 codes applied, in either the applicable or inapplicable state, future applications fail without\n * regard to code applicability.\n *\n * Therefore, this needs to take into account the total number of discount codes that will be sent\n * as a part of a cart update. This is why the new codes array is also an input to this function\n * that has only to do with old codes.\n *\n * This is a hacky rushed fix that needs to be revisited. Expired codes stick around. Typo codes\n * stick around. Once we reach more than 5 codes (in any state), all future applications, even of\n * valid codes, incorrectly fail to apply. So in this case, we have to filter down the list. The\n * quickest thing we can do is just drop codes that are applied but in the inapplicable state. We\n * prefer not to do this because we want to support visitors who apply a code, it becomes applied\n * but inapplicable, then add a product, and it becomes applicable.\n *\n * This hacky fix is not perfect because it is not deduplicating, this is an inaccurate and quick\n * fix for \"several\" issues, not all of the issues stemming from bad codes sticking around.\n *\n * There is a limitation on the number of discount codes that can be applied to the same order,\n * including codes in the inapplicable state. According to Shopify, \"Customers can use a maximum of\n * five product or order discount codes and one shipping discount code on the same order.\"\n *\n * @see https://help.shopify.com/en/manual/discounts/combining-discounts/discount-combinations#limitations\n */\nfunction getCurrentCodes(cart: Cart, newCodesCount: number) {\n  const currentCodes = Array.isArray(cart.discount_codes) ? cart.discount_codes : [];\n\n  // We test against what would be the total number of applied codes in determining which codes\n  // should be included in the output. If adding the new codes would exceed the threshold, then we\n  // try our best to trim the number of currently applied codes down to below the threshold. Since\n  // we cannot tell which discount codes are shipping discount codes, we use the threshold of 5\n  // instead of 6.\n\n  // There is currently no reliable additional way of choosing which discount codes to remove. There\n  // is no clear documentation on the order of the codes in the array. So we have to choose an\n  // arbitrary method of ejecting some codes. For now, this basically just gets rid of any codes\n  // that are inapplicable. That will most likely remove typos and expired codes. Most visitors will\n  // not discern the missing codes.\n\n  // This does not guarantee that the output is reduced to below the threshold. If there are 5\n  // applicable codes, then no codes will be removed, and the 6th application can still fail.\n\n  // This is not trying to get down to just below the threshold. This does a whole sweep. We might\n  // want to change this to remove inapplicable codes up until the threshold is no longer exceeded.\n\n  // Drop all inapplicable codes by keeping around only those older codes that are applicable. Try\n  // to retain inapplicable shipping codes.\n\n  if (currentCodes.length + newCodesCount > 5) {\n    return currentCodes\n      .filter(code => code.applicable || /ship/i.test(code.code))\n      .map(code => code.code)\n      .map(code => code.toUpperCase());\n  }\n\n  return getDiscountCodes(cart);\n}\n\nfunction validateCodesBeforeUpdate(newCodes: string[]) {\n  if (newCodes.length < 1) {\n    throw new Error('At least one discount code must be specified');\n  }\n\n  // If any one of the input codes is invalid, reject with a specific error message that mentions\n  // the code. It is important to be explicit here because this error may be visible to users.\n\n  for (const code of newCodes) {\n    if (!isWellFormed(code)) {\n      const error: any = new Error(`Invalid discount code ${code}`);\n      error.discount_code = code;\n      error.discount_codes = newCodes;\n      throw error;\n    }\n  }\n}\n\n/**\n * When validating whether the codes applied, we only validate whether new codes applied. We do not\n * validate whether existing codes applied, because it possible that applying a new code caused a\n * previous code to be removed, because the new code replaces the existing code, for example,\n * because the new code offers a better discount.\n *\n * If a code was applied but in the inapplicable state, then we treat the add as a failure, even\n * though the code persists in the discount_codes array. The rationale is complex. Basically, we\n * must address the UX concern that we must provide feedback that an invalid code was applied, so we\n * must trigger an error. However, some codes, such as shipping codes, can be early applied. Some\n * codes, such as shipping codes, can apply but have no cart level applications or line level\n * allocations, but still be applied, and still be valid, even though the code is not actually\n * affecting the estimated total. We do not want to treat that as an error.\n *\n * For a shipping discount that is applicable, even though it has no effect, it will appear in the\n * discount_codes array with applicable true.\n *\n * For a fictional discount that is inapplicable, it will appear in the discount_codes array with\n * applicable false.\n */\nfunction validateCodesAfterUpdate(cart: Cart, newCodes: string[]) {\n  const updatedNormalCodes = getApplicableDiscountCodes(cart);\n\n  // Downstream analytics sometimes does a breakdown on the error message. Input new codes are in\n  // mixed cases, leading to nearly duplicate messages. To make it easier on analysis, we report on\n  // the normalized discount code, not the actual characters entered by visitors.\n\n  for (const newCode of newCodes) {\n    const upperNewCode = newCode.toUpperCase();\n    if (!updatedNormalCodes.includes(upperNewCode)) {\n      const error: any = new Error(`The discount code ${upperNewCode} was not applied`);\n      error.discount_code = upperNewCode;\n      error.discount_codes = newCodes;\n      throw error;\n    }\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { getDiscountCodes, isWellFormed } from '../../lib/cart/discount/discount.js';\nimport { applyDiscountsHelper } from '../../lib/cart/discount/helper.js';\n\n/**\n * Attempt to apply discount codes parsed from the coupon code url parameter on cart init. This is a\n * best effort operation. Applying the codes can throw, and the caller suppresses it.\n *\n * Previously we started with a length check that exited early when there are no items in cart,\n * because it is not possible to apply any discount to any empty cart. However, this is not entirely\n * accurate. It is possible to apply a discount code to an empty cart. The discount will be applied\n * but in in the inapplicable state. Later, when someone adds to cart, and the requirements for the\n * discount are met, the discount becomes applicable.\n */\nexport async function applyUrlDiscounts(cart: Cart) {\n  const param = getDiscountCodeFromUrl();\n  if (!param) {\n    return cart;\n  }\n\n  const codesToApply = param.split(',');\n\n  // Automatic discount application from a url parameter is a possibly frequent operation. We want\n  // to minimize the number of cart updates, so it is worth checking if there is an opportunity to\n  // noop. When all of the new codes to apply are already applied, do nothing.\n\n  // Get the subset of codes parsed from the url parameter that appear to be valid\n  const validCodes = codesToApply.filter(isWellFormed);\n\n  // Normalize the codes for simple accurate comparison to current codes\n  const normalCodes = validCodes.map(code => code.toUpperCase());\n\n  // Deduplicate\n  const uniques = [...new Set(normalCodes)];\n\n  // Get the codes that are already applied. This includes codes that are applied but in the\n  // inapplicable state because we want to retain those in the update. The getDiscountCodes function\n  // guarantees the codes are uppercase.\n\n  const existingCodes = getDiscountCodes(cart);\n\n  // Get the subset of new codes that are not already applied.\n\n  const deltas = uniques.filter(code => !existingCodes.includes(code.toUpperCase()));\n\n  // If there are no new codes in the subset, return the cart as is.\n\n  if (deltas.length === 0) {\n    return cart;\n  }\n\n  // Apply the new codes. In doing so, disable post validation because ordinarily there is always an\n  // error for an empty cart and because these are automatically applied codes.\n\n  return applyDiscountsHelper(cart, deltas, cart_lifecycle.discount_overrides, false);\n}\n\nfunction getDiscountCodeFromUrl() {\n  const url = new URL(location.href);\n  return url.searchParams.get('couponcode')?.trim().toUpperCase();\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { copy } from '../../../lib/object.js';\nimport { applyIdentityAttributes } from '../identity.js';\nimport { applyAttentiveLastClickEpoch } from './attentive-transforms.js';\nimport { applyShareASaleAttributes } from './awin-transforms.js';\nimport { applyGoogleClickId, applyGoogleClientId } from './google-transforms.js';\nimport { applyKlaviyoAttributes, applyUTMEmail } from './klaviyo-transforms.js';\nimport { applyMetaAttributes } from './meta-transforms.js';\nimport { applyMicrosoftClickId } from './microsoft-transforms.js';\nimport { applySessionReferrer, applySessionStartEpoch, applySessionStartUrl } from './session-transforms.js';\nimport { applyTrackingConsent } from './shopify-transforms.js';\nimport { applyShopMyAttributes, applyShopMySuppression } from './shopmy-transforms.js';\nimport { applySurveyEmail } from './survey-transforms.js';\nimport { applyTiktokClickId } from './tiktok-transforms.js';\nimport { applyAffiliate, applyTuneAttributes } from './tune-transforms.js';\nimport { applyUnityAttributes } from './unity-transforms.js';\n\n/**\n * A cart transform reads the whole cart and mutates it in place to apply the keys it owns, or does\n * nothing when it has nothing to change this page load. Transforms do no IO. The chain clones the\n * fetched cart once and runs every transform against that single working copy, then a comparison of\n * the working copy against the fetched cart yields the minimal set of attributes to write.\n *\n * A transform must not assume cart.attributes exists, so it defines it first if absent. A transform\n * that sets more than one attribute gathers its values into locals first and only assigns them once\n * all the fallible work is done, so a throw partway cannot leave a half-applied set.\n *\n * Each transform owns its own logic, including rejecting unsafe values and, for the ones that read\n * an existing stored value, correcting liquid corruption before comparing. Nothing downstream\n * validates those decisions. The one concern the chain keeps is resilience: it isolates a transform\n * that throws, so a single broken transform cannot abort the rest.\n *\n * A transform only touches keys it owns. The write sends only changed keys, so any key a transform\n * leaves alone survives untouched, which is what keeps the third-party attributes we do not control\n * safe. See docs/cart-lifecycle.md for what those are.\n *\n * There is one deliberate exception, applyShopMySuppression, which removes keys it does not own on\n * purpose. The boundary is meant to be reasonable rather than absolute, so another exception wants\n * a reason as specific as that one has and a comment saying so.\n */\nexport type CartTransform = (cart: Cart) => void;\n\nexport function createTransforms() {\n  const transforms: CartTransform[] = [\n    applySessionStartEpoch,\n    applySessionReferrer,\n    applySessionStartUrl,\n    applyMicrosoftClickId,\n    applyGoogleClientId,\n    applyGoogleClickId,\n    applyMetaAttributes,\n    applyUTMEmail,\n    applyAttentiveLastClickEpoch,\n    applyAffiliate,\n    applySurveyEmail,\n    applyTiktokClickId,\n    applyIdentityAttributes,\n    applyKlaviyoAttributes,\n    applyTrackingConsent,\n    applyTuneAttributes,\n    applyUnityAttributes,\n    applyShareASaleAttributes,\n    applyShopMyAttributes,\n    applyShopMySuppression\n  ];\n\n  return transforms;\n}\n\n/**\n * Clones the cart and runs every transform against that single working copy, returning the mutated\n * copy. The input cart is never mutated, so it stays a clean baseline for the later diff. Each\n * transform is isolated so a single broken one cannot abort the rest.\n */\nexport function applyTransforms(cart: Cart, transforms: CartTransform[]) {\n  const working = copy(cart);\n  for (const transform of transforms) {\n    // Isolate each transform so that a single broken one cannot abort enrichment for the rest. A\n    // throw is treated as no change. This is a coarse backstop for unexpected throws, so it warns.\n    // A transform whose failures are routine still catches those itself at a quieter log level so\n    // they do not surface here and become Sentry events.\n\n    try {\n      transform(working);\n    } catch (error) {\n      console.warn(error);\n    }\n  }\n\n  return working;\n}\n","import { type Cart, isCart } from '@integrabeauty/shopify-ajax-api';\n\n/**\n * Returns whether two carts are equal. This is an approximate check that only considers line items.\n *\n * This does not consider line item properties.\n *\n * This does not consider cart attributes.\n *\n * This does not consider discount applications and allocations.\n *\n * This function exists separate from a full cart equality check because we need a way to consider\n * only certain state changes as meaningful. One of the reasons this is needed is because we have\n * little to no control over third party changes to cart state. There are apps that inject code that\n * does its own cart modifications, e.g. adjusting the cart's attributes on page load, and short of\n * uninstalling them we cannot prevent this. There are even mobile apps that inject content scripts\n * that assume control over the cart state. The decision making to install apps does not always\n * appreciate the complexity of the state comparison case. Given that, we need a way to treat only\n * certain state changes as meaningful, which means being deliberate about which parts of the api\n * contract we honor. This function therefore only considers line items and ignores other state\n * changes because it is intentionally only adhering to one clause of the Shopify cart contract.\n *\n * This is complicated because Shopify's cart mutations generally do not return the updated cart\n * state. They often return junk. The only way to reliably determine the updated cart state is to\n * fetch it separately, which always involves fetching the entire state. Which unfortunately is\n * often the first time that we learn that something else has modified the cart. We have to\n * reconcile the difference between the edits we issued and the actual updated cart state,\n * accounting for unexpected changes. In other words, we are comparing projections of the state\n * rather than the actual state. Since the endpoints do not support projection (e.g. give me only\n * these fields), we do this by selectively comparing only certain cart properties.\n */\nexport function hasSimilarItems(cart1: Cart, cart2: Cart) {\n  // Sanity check the inputs. This should never happen. However, it does allow the caller to be less\n  // careful with guaranteeing null values. Also, we have seen runtime state diverge from statically\n  // typed state in rare cases of bugs and unexpected third party interference, so even though this\n  // is redundant type checking at compile time, it is not redundant at runtime.\n\n  if (!isCart(cart1)) {\n    return false;\n  }\n\n  if (!isCart(cart2)) {\n    return false;\n  }\n\n  // Seeing a new token is unlikely to ever happen and is more of a sanity check. One plausible\n  // situation is cart expiration resulting in cart replacement.\n\n  // We have seen cases where we have partial cart state and the token is missing, so this only does\n  // token comparison when both tokens are known, to still allow for other kinds of cart state\n  // comparison when either is unknown.\n\n  if (cart1.token && cart2.token && cart1.token !== cart2.token) {\n    return false;\n  }\n\n  // The same number of line items should be present in both carts.\n\n  if (cart1.items.length !== cart2.items.length) {\n    return false;\n  }\n\n  // The lines should have the same keys with the same quantities. If any one line does not, break.\n  // Because there are the same number of line items in both carts, finding that every item from\n  // cart1 exists in cart2 implies that every item from cart2 exists in cart1, so we only need to\n  // examine state in one direction.\n\n  // Watch out for careless type inconsistencies, e.g. key as a number on input but key as a string\n  // on output. It is best to only compare output types because those are consistent. The cart api\n  // unfortunately has some Postel's Law like behavior. Avoid comparing a manually constructed cart\n  // to a fetched cart.\n\n  for (const item1 of cart1.items) {\n    let found = false;\n    for (const item2 of cart2.items) {\n      if (item1.key === item2.key && item1.quantity === item2.quantity) {\n        found = true;\n        break;\n      }\n    }\n\n    if (!found) {\n      return false;\n    }\n  }\n\n  return true;\n}\n","import { assert } from '@integrabeauty/assert';\nimport { type Cart, isCart } from '@integrabeauty/shopify-ajax-api';\nimport { hasSimilarItems } from '../../lib/cart/equality.js';\nimport { cartFetch, write } from '../../lib/cart/state.js';\nimport { start, type Transaction } from '../../lib/cart/transaction.js';\n\n// Handle bfcache restoration. This solves the following problem. We cache cart state within the\n// page. So someone visits, shops, then leaves. For example, stops using the Facebook app's in-app\n// browser. When they return, on iOS, the page is restored from the cache. Unfortunately, in between\n// the time they left and the time they returned, the cart state may have changed, so the global\n// cart stale snapshot is stale. In turn, when they remove a line item or modify a line item, they\n// encounter an error, because the call usually fails (e.g. a call based on line item keys uses\n// stale keys). Or perhaps the cart has expired.\n//\n// This issue is already partially mitigated, as often, an iOS user will open multiple tabs, then\n// leave them, and later return to them, leading to cross tab messaging. We already have cross tab\n// messaging built into the cart-state lib.\n//\n// We instrumented Sentry to capture bfcache_stored true on page show if persisted. In the case of\n// cart desync errors (e.g. remove item failed because line item not found), a healthy majority\n// indicate that the page was restored from the bfcache. The expectation is that by running this\n// code, the amount of failures will decrease.\n//\n// This does not wait for dom content to load. pageshow events that are persisted, indicating a\n// restore from the bfcache, are events that are fired the moment page state is restored.\n//\n// This does not use an event replay queue. It is a little counter intuitive. Basically, the event\n// being fired is fired at page state that already initialized. A bfcache restore is not a page\n// load. This module is already loaded. Its listener is already bound.\n\n/**\n * Tracks whether the cart initialized at least once since page loaded.\n */\nlet cartInitialized = false;\n\n/**\n * Tracks whether there are any active transactions.\n *\n * This variable's value is not guaranteed to be accurate at all times due to the complexities of\n * race conditions and errors. This should be treated like a hint.\n */\nlet activeTransactionCount = 0;\n\n/**\n * When a page show event occurs, and the event indicates a bfcache restore, then we need to\n * reconcile the local cart state with the remote cart state. The remote cart state is\n * authoritative.\n *\n * This intentionally does not unregister listeners. The reason is a little counterintuitive at\n * first. The reason is because a browser can repeatedly fire page show events. A page can be\n * restored multiple times after its initial load. If we were to unregister the listeners, only the\n * very first restore would work, and every future needed-restore would be inert.\n */\nfunction onPageShow(event: PageTransitionEvent) {\n  // There are multiple reasons why a page show event occurs. For example, when a user uses the back\n  // button, or when they reopen a site within an in-app browser. We only care about the cases where\n  // the page is restored from the bfcache.\n  //\n  // Non-persisted page show events happen all the time, so we are intentionally not logging\n  // anything here because it is spammy.\n\n  if (!event.persisted) {\n    return;\n  }\n\n  // Unlike in the case of a normal page load, which must take into account url attributes, a\n  // bfcache restore does not require any special handling of the url, because the url has not\n  // changed since the page was frozen. Therefore, this does none of the preliminary work that the\n  // page-show path would normally perform regarding attribute enrichment.\n\n  // A page may have been frozen before the cart was initialized successfully. In that case, there\n  // is already logic. Cart initialization errors have visible error messages and handling. For cart\n  // initialization that is still in progress, it will eventually finish fetching remote cart state\n  // and writing it to page state. So, if the cart is not yet initialized, then we should not do\n  // anything here.\n  //\n  // There is no point to reconciling when the initial cart state does not exist. There is nothing\n  // with which to reconcile.\n  //\n  // A cart is intentionally not initialized in certain contexts. We are implicitly also relying on\n  // that, and not restoring from bfcache in those same contexts. For example, the cart does not\n  // initialize when the user is in the theme editor.\n  //\n  // Once the cart is initialized at least once, we assume it never becomes uninitialized.\n\n  if (!cartInitialized) {\n    console.log('Page restored from bfcache but cart is not yet initialized');\n    return;\n  }\n\n  // If a transaction is in flight, then we expect that the transaction will eventually settle and\n  // update the global cart state cached in the page. Therefore, there is no need for us to fetch\n  // the latest cart state and persist it to page state.\n\n  if (activeTransactionCount > 0) {\n    console.log('Page restored from bfcache but a transaction is in flight');\n    return;\n  }\n\n  // Local cart state within page state should always be present. It is server side rendered in an\n  // intermediate format, and enriched during cart init. It is never unset. If it is missing, or its\n  // shape is unexpected, then we are not operating in expected working conditions, and should just\n  // bail.\n  //\n  // This check is done before starting the transaction so that we avoid triggering any UI updates\n  // in this edge case. Without this, we would start the transaction then almost immediately bail,\n  // causing flicker.\n\n  if (!isCart(cart_lifecycle.cart)) {\n    console.warn('Page restored from bfcache but local cart state is missing');\n    return;\n  }\n\n  // Now we can reconcile. We do not know if remote cart state has changed since the page was\n  // frozen. We have to fetch the latest remote state, compare it to the local state, detect if\n  // state meaningfully changed, and if so, then update the local state.\n\n  console.log('Page restored from bfcache');\n\n  // Open the transaction before the reconcile's fetch, not after. Reconciling exists to guarantee\n  // the cart the shopper acts on is congruent with the server, so leaving the surfaces live during\n  // the fetch would defeat the purpose. The shopper could mutate a cart we are about to discover is\n  // stale. Every interactive cart surface disables itself for the duration of an active\n  // transaction, so opening it here locks the cart for the whole congruence check. This\n  // deliberately slows the resume experience, including on the many resumes where nothing changed,\n  // and that cost is accepted in exchange for never letting the shopper act on unverified state.\n  // The transaction is created synchronously, before the first await, so a second persisted\n  // pageshow that arrives during the fetch sees the active transaction at the entry guard and does\n  // not start a second reconcile.\n\n  const txn = start('bfcacheReconcile');\n\n  // Warn for diagnostics, but deliberately do not escalate a reconcile failure to a user-facing\n  // cart error. Reporting would emit the cart update error event, surfacing an error message and,\n  // for the init and cookie cases, opening the cart drawer, all for a background reconciliation the\n  // shopper did not initiate. That would be wrong here. If this path is ever made to report errors,\n  // weigh that consequence first.\n  reconcile(txn).catch(console.warn).finally(txn.complete);\n}\n\nasync function reconcile(transaction: Transaction) {\n  const localCart = cart_lifecycle.cart;\n  assert(isCart(localCart), 'Local cart state is missing or invalid');\n\n  let fetchedCart: Cart;\n  try {\n    fetchedCart = await cartFetch();\n  } catch (error) {\n    // On failure to fetch we remain silent. Reconciliation is best effort. We do not want to\n    // disrupt the experience with an error now. Some shoppers only add new items and never mutate\n    // existing ones, so despite line keys changing they may never hit a desync. We warn only so the\n    // failure is observable in diagnostics. The finally below still releases the lock.\n\n    console.warn(error);\n    return;\n  }\n\n  // Defensive congruence backstop. Holding the transaction is meant to keep local state still for\n  // the duration of the fetch, since surfaces disable themselves and cross tab applies defer while\n  // a transaction is active. But the transaction service detects overlap rather than preventing it,\n  // so a surface that failed to disable could still have mutated local state underneath us. If\n  // local moved, that write is newer than what we just fetched, so we abandon rather than clobber\n  // it. In normal operation this never fires.\n\n  if (!hasSimilarItems(localCart, cart_lifecycle.cart)) {\n    console.log('Canceling reconciliation on bfcache restore because local state changed');\n    return;\n  }\n\n  // If the fetched cart is practically equivalent to local, there is nothing worth writing. We have\n  // already paid for the lock, but skipping the write still avoids emitting a no-op cart update and\n  // the re-render it would trigger.\n\n  // A changed cart token counts as a difference even when the visible line items match, because a\n  // new token means the server handed us a different cart rather than a changed one. So a rotated\n  // cart flows through to the write below. When the previous cart expired and the replacement is\n  // empty, the write diffs two unrelated cart identities and reports every former line item as\n  // removed. That is surprising but not wrong for this path, and it is not specific to bfcache\n  // restores, so any softening of it belongs in the shared write layer rather than here.\n\n  if (hasSimilarItems(localCart, fetchedCart)) {\n    console.debug('Page restored from bfcache, cart state has not changed');\n    return;\n  }\n\n  // The write here is not the initial init write, so it should not claim to be. This is debatable.\n  // It could in theory be considered an initial write, the cart library did anticipate repeat\n  // initialization. The problem is that higher layer code is currently too naive and assumes one\n  // initial write per page load. Rewriting all of that consuming code before launching this fix is\n  // out of scope. As a compromise we mark this write as not initial.\n  //\n  // Marking it not initial also means the cross-tab coordinator broadcasts it, letting other tabs\n  // know about the reconciled state. That is what we want here. There is no risk of ping ponging\n  // because multiple tabs do not all simultaneously experience a bfcache restore, it is based on\n  // the shopper accessing a single tab.\n  //\n  // This write is not linked to the DOM, so we dispatch from window.\n\n  const isInitial = false;\n\n  write(window, fetchedCart, transaction, 'bfcache-reconcile', isInitial);\n}\n\nfunction onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n  if (event.detail?.is_initial) {\n    cartInitialized = true;\n\n    // We only care about the first cart initialization.\n    removeEventListener('cart-updated', onCartUpdated);\n  }\n}\n\nfunction onTransactionStarted(_event: WindowEventMap['cart-transaction-started']) {\n  activeTransactionCount++;\n}\n\nfunction onTransactionCompleted(_event: WindowEventMap['cart-transaction-completed']) {\n  if (activeTransactionCount > 0) {\n    activeTransactionCount--;\n  } else {\n    // Due to out of order loads with arbitrary delays, this module may start observing in the midst\n    // of an active transaction in another tab. Eventually the transaction will complete, but the\n    // accompanying increment that should have preceded the decrement was not observed.\n  }\n}\n\naddEventListener('cart-updated', onCartUpdated);\naddEventListener('pageshow', onPageShow);\naddEventListener('cart-transaction-started', onTransactionStarted);\naddEventListener('cart-transaction-completed', onTransactionCompleted);\n"],"names":["getRequestId","response","headers","get","error","isResponse","value","ok","status","text","json","decorateError","key","resolveUrl","path","URL","location","protocol","host","async","cartFetch","url","Shopify","routes","root","responseBodyText","responseBodyObject","fetch","cache","href","Error","consumeError","JSON","parse","isCart","cartUpdate","input","init","body","stringify","method","_error$response_body5","_error$response_body6","message","response_body","description","isObject","Array","isArray","items","assert","expression","AssertionError","constructor","super","this","name","rnds8","Uint8Array","byteToHex","i","push","toString","slice","unsafeStringify","arr","offset","toLowerCase","dist_v4","options","buf","crypto","randomUUID","_ref","_options$random","rnds","random","rng","getRandomValues","length","RangeError","_v4","copy","structuredClone","cloned","trustworthy","containsJavascriptScheme","test","isWellFormed","includes","upper","toUpperCase","containsJavascriptWord","getApplicableDiscountCodes","cart","discount_codes","filter","code","applicable","map","getDiscountCodes","write","dispatcher","transaction","scenario","initial","previous","cart_lifecycle","oldVariants","groupByVariant","newVariants","prevCodes","currCodes","event","CustomEvent","detail","discount_codes_added","complement","discount_codes_removed","event_id","is_initial","variants_added","findVariantsAdded","variants_removed","findVariantsRemoved","id","bubbles","composed","target","Element","isConnected","window","dispatchEvent","state_cartFetch","limit","attempts","delays","lastError","delay","resolveAfterTimeout","cartFetchOnceWithTimeout","timeout","timer","timeoutPromise","Promise","_resolve","reject","setTimeout","race","clearTimeout","milliseconds","resolve","output","item","variant","variant_id","property","properties","merged_properties","quantity","image_url","image","price","product_id","product_title","product_type","sku","variant_title","c0map","c1map","increments","Object","keys","decrements","a","b","now","_performance","performance","console","debug","Date","getSharedState","cart_transaction","activeCount","count","timeline","shared","Number","isInteger","start","_len","arguments","args","_key","epoch","completed","startMillis","didTimeOut","complete","snapshot","warn","reportConcurrency","append","type","log","duration","Math","round","undefined","end","_performance3","measure","entry","splice","max","channel","initStatus","cartInitialized","activeTransactionCount","inboundTransaction","onMessage","data","broadcastCartState","_channel","postMessage","decorate_error_decorateError","assignmentError","reason","tag","prototype","call","report","dispatchCartUpdateErred","ignoredCodes","_detail$transaction","_detail$transaction2","inputs","reportError","raise","escalateCartError","compareAttributeValues","value0","value1","keys0","keys1","readLocalStorageItem","localStorage","getItem","writeLocalStorageItem","setItem","removeLocalStorageItem","removeItem","applyIdentityAttributes","attributes","phone","email","identity_phone","identity_email","applyAttentiveLastClickEpoch","_url$searchParams$get","_url$searchParams$get2","utmSource","searchParams","trim","utmMedium","attentive_last_click_epoch","applyShareASaleAttributes","shareasale_click_url","shareasale_click_epoch","safeDecode","decodeURIComponent","dist_read","document","cookie","split","parts","_parts$slice","index","indexOf","encodedValue","readAll","shift","dist_write","encodeURIComponent","domain","expires","getTime","toUTCString","same_site","secure","join","serialize","applyGoogleClientId","getGoogleAnalyticsClientId","google_client_id","applyGoogleClickId","google_click_id","applyKlaviyoAttributes","encoded","decoded","atob","parsed","cid","getKlaviyoId","clickId","kx","getKlaviyoClickId","klaviyo_id","klaviyo_click_id","klaviyo_last_click_epoch","applyUTMEmail","utmEmail","utm_email","applyMetaAttributes","adId","_url$searchParams$get3","getMetaClickId","personCookie","clickCookie","endsWith","readMetaClickCookie","meta_ad_id","meta_click_id","meta_person_cookie","startsWith","isValidMetaClickCookie","meta_click_cookie","applyMicrosoftClickId","microsoft_click_id","applySessionStartUrl","session_start_url","applySessionReferrer","referrer","session_referrer","applySessionStartEpoch","sessionStartEpoch","existingValue","parseInt","script_epoch","obtainSessionStartTime","session_start_epoch","readCustomerConsent","readCookie","session","sessionStorage","readSession","serverTiming","navigation","getEntriesByType","timings","header","timing","readServerTiming","consent","matches","exec","flags","state","purposes","region","m","p","t","isConsentState","parseConsentHash","getDefaultConsent","regulation","regionCode","subregionCode","determineRegulation","applyTrackingConsent","_cookie$purposes","hasMarketingConsent","tracking_consent","isDirectTuneLink","applyShopMyAttributes","isShopMySource","shopmy_click_url","shopmy_sms_click_id","shopmy_click_epoch","applyShopMySuppression","sms_click_id","clickTime","sms_click_time","shopmy_suppressed_sms_click_id","shopmy_suppressed_sms_click_time","applySurveyEmail","survey_email","applyTiktokClickId","tiktok_click_id","applyAffiliate","affiliate","applyTuneAttributes","allowed","transactionId","isDirect","existingHasoffers","hasoffers","existingTuneUrl","last_tune_url","hasoffers_updated_at","last_tune_url_epoch","applyUnityAttributes","unity_last_click_url","unity_last_click_epoch","applyDiscountsHelper","newCodes","overrides","postValidate","discount_code","validateCodesBeforeUpdate","oldUppers","newCodesCount","currentCodes","getCurrentCodes","newUppers","retainedOldUppers","some","override","old_code","new_code","discount","Set","updatedCart","updatedNormalCodes","newCode","upperNewCode","validateCodesAfterUpdate","applyUrlDiscounts","param","getDiscountCodeFromUrl","normalCodes","uniques","existingCodes","deltas","discount_overrides","fetchOptions","hostname","onlineAtStart","navigator","onLine","visibilityAtStart","visibilityState","timesHidden","onVisibilityChange","addEventListener","fetched","transforms","transformed","working","transform","applyTransforms","changes","from","to","source","getAttributeChanges","initUnsafe","country","elapsed_ms","online_at_error","online_at_start","visibility_at_error","visibility_at_start","times_hidden_during_init","removeEventListener","onCartInitRequested","_event","catch","hasSimilarItems","cart1","cart2","token","item1","found","item2","BroadcastChannel","openChannel","page_show_cartInitialized","page_show_activeTransactionCount","page_show_onCartUpdated","_event$detail","persisted","txn","localCart","fetchedCart","reconcile","finally"],"sourceRoot":""}