@drip-apex/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/bundle-report.json +584 -226
- package/dist/define.cjs +123 -0
- package/dist/define.cjs.map +7 -0
- package/dist/define.mjs +102 -0
- package/dist/define.mjs.map +7 -0
- package/dist/drip.cjs +19262 -0
- package/dist/drip.cjs.map +7 -0
- package/dist/drip.debug.js +1 -1
- package/dist/drip.debug.js.map +1 -1
- package/dist/drip.heatmap.js +1 -1
- package/dist/drip.heatmap.js.map +1 -1
- package/dist/drip.js +1 -1
- package/dist/drip.js.map +1 -1
- package/dist/drip.live.js +1 -1
- package/dist/drip.live.js.map +1 -1
- package/dist/drip.mjs +19246 -0
- package/dist/drip.mjs.map +7 -0
- package/dist/drip.replay-recorder.js +2 -0
- package/dist/drip.replay-recorder.js.map +1 -0
- package/dist/editor.js +147 -142
- package/dist/editor.js.map +4 -4
- package/dist/flags.cjs +1788 -0
- package/dist/flags.cjs.map +7 -0
- package/dist/flags.mjs +1765 -0
- package/dist/flags.mjs.map +7 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +2 -2
- package/dist/metafiles/define.cjs.meta.json +28 -0
- package/dist/metafiles/define.mjs.meta.json +32 -0
- package/dist/metafiles/drip.cjs.meta.json +3193 -0
- package/dist/metafiles/drip.debug.meta.json +2859 -574
- package/dist/metafiles/drip.heatmap.meta.json +2732 -460
- package/dist/metafiles/drip.live.meta.json +2732 -460
- package/dist/metafiles/drip.meta.json +2859 -574
- package/dist/metafiles/drip.mjs.meta.json +3235 -0
- package/dist/metafiles/drip.replay-recorder.meta.json +82 -0
- package/dist/metafiles/editor.meta.json +67 -27
- package/dist/metafiles/flags.cjs.meta.json +220 -0
- package/dist/metafiles/flags.mjs.meta.json +225 -0
- package/dist/metafiles/index.meta.json +2717 -491
- package/dist/metafiles/index.mjs.meta.json +14 -4
- package/dist/react/index.js +16621 -13539
- package/dist/react/index.js.map +4 -4
- package/dist/sri.json +20 -15
- package/dist/types/activation-runtime.d.ts +91 -0
- package/dist/types/analytics-runtime.d.ts +166 -0
- package/dist/types/antiflicker.d.ts +9 -3
- package/dist/types/assignment-cookie-sync.d.ts +31 -0
- package/dist/types/assignment-runtime.d.ts +257 -0
- package/dist/types/assignment-store.d.ts +4 -8
- package/dist/types/bucket.d.ts +1 -2
- package/dist/types/compact-store.d.ts +1 -0
- package/dist/types/config-runtime.d.ts +313 -0
- package/dist/types/consent-evaluation.d.ts +27 -0
- package/dist/types/css.d.ts +5 -3
- package/dist/types/debug-loader.d.ts +2 -0
- package/dist/types/decide-assignments.d.ts +31 -0
- package/dist/types/delivery-timing.d.ts +8 -0
- package/dist/types/early-interaction-capture.d.ts +79 -0
- package/dist/types/early-reassert.d.ts +32 -0
- package/dist/types/edge-runtime.d.ts +11 -0
- package/dist/types/editor/api.d.ts +3 -0
- package/dist/types/editor/controller.d.ts +3 -1
- package/dist/types/editor/publication-result.d.ts +11 -0
- package/dist/types/editor/types.d.ts +2 -1
- package/dist/types/editor/view-kit.d.ts +1 -1
- package/dist/types/endpoint.d.ts +8 -0
- package/dist/types/exclusion-groups.d.ts +4 -7
- package/dist/types/flags.d.ts +2 -0
- package/dist/types/goal-eligibility.d.ts +4 -0
- package/dist/types/goals.d.ts +10 -1
- package/dist/types/guards.d.ts +7 -7
- package/dist/types/handover.d.ts +12 -1
- package/dist/types/hash.d.ts +8 -4
- package/dist/types/hydration.d.ts +2 -1
- package/dist/types/index.d.ts +24 -128
- package/dist/types/init-test-helpers.d.ts +48 -0
- package/dist/types/inline-bootstrap.d.ts +26 -0
- package/dist/types/js.d.ts +3 -1
- package/dist/types/money.d.ts +12 -0
- package/dist/types/mutate.d.ts +45 -6
- package/dist/types/performance.d.ts +2 -0
- package/dist/types/persistence-decoders.d.ts +12 -0
- package/dist/types/public-api.d.ts +209 -0
- package/dist/types/qa-assistant-state.d.ts +34 -0
- package/dist/types/react/index.d.ts +28 -3
- package/dist/types/reassert-sentinel.d.ts +6 -0
- package/dist/types/redirect.d.ts +48 -0
- package/dist/types/replay-recorder-entry.d.ts +1 -0
- package/dist/types/replay-signal-state.d.ts +6 -0
- package/dist/types/replay-signals.d.ts +13 -0
- package/dist/types/replay.d.ts +110 -0
- package/dist/types/router.d.ts +12 -2
- package/dist/types/runtime-state.d.ts +166 -0
- package/dist/types/sanitize.d.ts +1 -0
- package/dist/types/shadow.d.ts +3 -0
- package/dist/types/shopify-cart.d.ts +25 -1
- package/dist/types/storefront-vitals.d.ts +24 -0
- package/dist/types/targeting-diagnostics.d.ts +1 -0
- package/dist/types/targeting.d.ts +16 -2
- package/dist/types/track.d.ts +15 -1
- package/dist/types/types.d.ts +115 -9
- package/dist/types/wait.d.ts +14 -1
- package/dist/types/weekday-schedule.d.ts +3 -0
- package/package.json +17 -8
- package/src/activation-runtime.ts +2737 -0
- package/src/analytics-runtime.ts +2175 -0
- package/src/antiflicker.test.ts +216 -44
- package/src/antiflicker.ts +103 -28
- package/src/artifact-concurrency.test.ts +1091 -0
- package/src/assignment-cookie-sync.test.ts +202 -0
- package/src/assignment-cookie-sync.ts +189 -0
- package/src/assignment-runtime.ts +3204 -0
- package/src/assignment-store.test.ts +56 -0
- package/src/assignment-store.ts +7 -38
- package/src/bucket.ts +3 -18
- package/src/commerce-analytics-lifecycle.test.ts +75 -15
- package/src/commerce-config-lifecycle.test.ts +81 -1
- package/src/commerce-price-preserve.test.ts +133 -0
- package/src/commerce-price.test.ts +97 -0
- package/src/commerce-price.ts +74 -20
- package/src/commerce-shipping.test.ts +53 -0
- package/src/commerce-shipping.ts +5 -11
- package/src/compact-store.ts +15 -0
- package/src/config-fetch.test.ts +480 -9
- package/src/config-runtime.ts +2070 -0
- package/src/consent-evaluation-cookiescript.test.ts +50 -0
- package/src/consent-evaluation.ts +138 -0
- package/src/css.test.ts +30 -0
- package/src/css.ts +120 -6
- package/src/cwv.test.ts +60 -77
- package/src/debug-loader.test.ts +214 -0
- package/src/debug-loader.ts +41 -0
- package/src/decide-assignments.test.ts +196 -0
- package/src/decide-assignments.ts +204 -0
- package/src/delivery-timing.test.ts +104 -0
- package/src/delivery-timing.ts +89 -0
- package/src/early-interaction-capture.test.ts +451 -0
- package/src/early-interaction-capture.ts +230 -0
- package/src/early-reassert.test.ts +207 -0
- package/src/early-reassert.ts +152 -0
- package/src/edge-adoption.test.ts +435 -0
- package/src/edge-runtime.ts +30 -0
- package/src/editor/api.test.ts +109 -0
- package/src/editor/api.ts +34 -3
- package/src/editor/controller.test.ts +297 -14
- package/src/editor/controller.ts +69 -5
- package/src/editor/index.test.ts +2 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/publication-result.ts +58 -0
- package/src/editor/types.ts +10 -1
- package/src/editor/view-kit.ts +2 -1
- package/src/editor/view.test.ts +108 -0
- package/src/editor/view.ts +49 -7
- package/src/endpoint.test.ts +15 -0
- package/src/endpoint.ts +10 -0
- package/src/exclusion-groups.ts +8 -18
- package/src/flags.test.ts +88 -1
- package/src/flags.ts +29 -29
- package/src/global-runtime-reconciler.test.ts +271 -0
- package/src/goal-eligibility.test.ts +29 -0
- package/src/goal-eligibility.ts +17 -0
- package/src/goals.test.ts +144 -1
- package/src/goals.ts +97 -18
- package/src/guards.test.ts +99 -1
- package/src/guards.ts +34 -15
- package/src/handover-cache-order.test.ts +6 -3
- package/src/handover.test.ts +154 -8
- package/src/handover.ts +122 -12
- package/src/hash.ts +29 -54
- package/src/holdout.test.ts +26 -0
- package/src/hydration.test.ts +73 -2
- package/src/hydration.ts +18 -9
- package/src/index.ts +130 -7287
- package/src/init-analytics.test.ts +1694 -0
- package/src/init-anti-flicker.test.ts +1211 -0
- package/src/init-cart-attributes.test.ts +2003 -0
- package/src/init-config-resolution.test.ts +999 -0
- package/src/init-consent.test.ts +971 -0
- package/src/init-empty.test.ts +11 -5406
- package/src/init-integrations.test.ts +846 -0
- package/src/init-page-activation.test.ts +5064 -0
- package/src/init-qa-loading.test.ts +740 -0
- package/src/init-redirects.test.ts +2156 -0
- package/src/init-test-helpers.ts +287 -0
- package/src/inline-bootstrap.test.ts +184 -0
- package/src/inline-bootstrap.ts +73 -0
- package/src/js.test.ts +51 -1
- package/src/js.ts +86 -2
- package/src/money.test.ts +55 -1
- package/src/money.ts +104 -2
- package/src/money.web-parity.contract.test.ts +85 -0
- package/src/mutate.test.ts +474 -10
- package/src/mutate.ts +739 -230
- package/src/page-trigger.test.ts +218 -8
- package/src/performance.ts +10 -0
- package/src/persistence-decoders.ts +18 -0
- package/src/public-api.ts +1828 -0
- package/src/qa-assistant-state.test.ts +249 -3
- package/src/qa-assistant-state.ts +146 -6
- package/src/qa-assistant-view.test.ts +220 -2
- package/src/qa-assistant-view.ts +102 -5
- package/src/react/index.test.tsx +37 -0
- package/src/react/index.tsx +19 -3
- package/src/reassert-sentinel.test.ts +155 -1
- package/src/reassert-sentinel.ts +168 -8
- package/src/redirect.test.ts +541 -0
- package/src/redirect.ts +572 -0
- package/src/replay-recorder-entry.ts +115 -0
- package/src/replay-signal-state.ts +100 -0
- package/src/replay-signals.test.ts +249 -0
- package/src/replay-signals.ts +151 -0
- package/src/replay.test.ts +764 -0
- package/src/replay.ts +514 -0
- package/src/router.test.ts +61 -2
- package/src/router.ts +50 -12
- package/src/runtime-state.ts +507 -0
- package/src/runtime.test.ts +174 -7
- package/src/runtime.ts +88 -14
- package/src/sanitize.test.ts +34 -0
- package/src/sanitize.ts +23 -12
- package/src/shadow.test.ts +187 -0
- package/src/shadow.ts +99 -0
- package/src/shopify-cart.test.ts +733 -23
- package/src/shopify-cart.ts +402 -49
- package/src/storefront-vitals.test.ts +217 -0
- package/src/storefront-vitals.ts +206 -0
- package/src/surface-signals.ts +6 -0
- package/src/targeting-conformance.test.ts +9 -0
- package/src/targeting-diagnostics.ts +38 -8
- package/src/targeting-parity-corpus.json +559 -0
- package/src/targeting-parity-corpus.test.ts +18 -0
- package/src/targeting.detailed.test.ts +105 -0
- package/src/targeting.test.ts +211 -0
- package/src/targeting.ts +88 -207
- package/src/track.test.ts +147 -0
- package/src/track.ts +125 -26
- package/src/types.ts +120 -13
- package/src/wait.test.ts +23 -0
- package/src/wait.ts +129 -13
- package/src/weekday-delivery.test.ts +56 -0
- package/src/weekday-schedule-parity-corpus.json +92 -0
- package/src/weekday-schedule.test.ts +69 -0
- package/src/weekday-schedule.ts +20 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/shopify-cart-events.ts", "../src/shopify-cart.ts", "../src/commerce-gift.ts", "../src/module.ts"],
|
|
4
|
-
"sourcesContent": ["export type ShopifyCartMutationHandler = () => void;\n\nconst handlers = new Set<ShopifyCartMutationHandler>();\nlet originalFetch: typeof window.fetch | null = null;\nlet wrappedFetch: typeof window.fetch | null = null;\nlet suppressNotifications = 0;\nlet formListenerInstalled = false;\n\nfunction requestUrl(input: RequestInfo | URL): URL | null {\n try {\n const value = typeof input === \"string\" || input instanceof URL\n ? String(input)\n : input.url;\n return new URL(value, location.href);\n } catch {\n return null;\n }\n}\n\nfunction isShopifyCartMutation(input: RequestInfo | URL): boolean {\n const url = requestUrl(input);\n if (!url || url.origin !== location.origin) return false;\n const configuredRoot = (window as Window & {\n Shopify?: { routes?: { root?: string } };\n }).Shopify?.routes?.root;\n let root = \"\";\n if (typeof configuredRoot === \"string\") {\n try {\n const parsed = new URL(configuredRoot, location.origin);\n if (parsed.origin === location.origin && !parsed.search && !parsed.hash) {\n root = parsed.pathname.replace(/\\/$/, \"\");\n }\n } catch {\n root = \"\";\n }\n }\n const path = url.pathname.replace(/\\/$/, \"\");\n return [\"add\", \"change\", \"update\", \"clear\"].some(\n (action) => path === `${root}/cart/${action}` || path === `${root}/cart/${action}.js`,\n );\n}\n\nfunction notifyHandlers(): void {\n for (const handler of handlers) {\n try {\n handler();\n } catch {\n // Cart observers must never affect the storefront's own mutation flow.\n }\n }\n}\n\nfunction handleFormSubmit(event: Event): void {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !isShopifyCartMutation(form.action)) return;\n queueMicrotask(notifyHandlers);\n}\n\nfunction install(): void {\n if (originalFetch || typeof window.fetch !== \"function\") return;\n originalFetch = window.fetch;\n const nativeFetch = originalFetch.bind(window);\n wrappedFetch = function(...args: Parameters<typeof fetch>) {\n const shouldNotify = suppressNotifications === 0 && isShopifyCartMutation(args[0]);\n const result = nativeFetch(...args);\n if (shouldNotify) {\n result.then((response) => {\n if (response.ok) notifyHandlers();\n }, () => {\n // Observe without changing or consuming the caller's rejection.\n });\n }\n return result;\n };\n window.fetch = wrappedFetch;\n document.addEventListener(\"submit\", handleFormSubmit, true);\n formListenerInstalled = true;\n}\n\nfunction uninstall(): void {\n if (formListenerInstalled) {\n document.removeEventListener(\"submit\", handleFormSubmit, true);\n formListenerInstalled = false;\n }\n if (originalFetch && window.fetch === wrappedFetch) window.fetch = originalFetch;\n wrappedFetch = null;\n originalFetch = null;\n}\n\nexport function onShopifyCartMutation(handler: ShopifyCartMutationHandler): () => void {\n handlers.add(handler);\n install();\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) uninstall();\n };\n}\n\n/**\n * Use for Apex's own cart-attribute write so it does not recursively look like\n * a shopper/theme mutation to subscribers of the shared fetch wrapper.\n */\nexport function fetchWithoutShopifyCartMutationNotification(\n ...args: Parameters<typeof fetch>\n): ReturnType<typeof fetch> {\n suppressNotifications += 1;\n try {\n const activeFetch = typeof window.fetch === \"function\"\n ? window.fetch\n : globalThis.fetch;\n return activeFetch.call(window, ...args);\n } finally {\n suppressNotifications -= 1;\n }\n}\n", "import { fetchWithoutShopifyCartMutationNotification } from \"./shopify-cart-events\";\n\nconst CART_ATTRIBUTE_SYNC_KEY = \"drip_cart_attr\";\n\nexport type CartAssignment = {\n experimentId: string;\n variationId: string;\n assignmentEpoch?: number;\n commercePriceOnly?: boolean;\n};\n\nexport type CartAttributeSyncInput = {\n assignments: CartAssignment[];\n requiredAssignments?: CartAssignment[];\n visitorId: string;\n sessionId: string;\n shopId?: string;\n endpoint?: string;\n ingestSignature?: string;\n url?: string;\n environment?: string;\n cartToken?: string;\n offersAuthorizationRevision?: number;\n offersExperimentIds?: string[];\n shippingAuthorizationRevision?: number;\n shippingExperimentIds?: string[];\n priceAuthorizationRevision?: number;\n priceExperimentIds?: string[];\n adoptOffersAssignments?: (assignments: CartAssignment[]) => boolean;\n /** Pre-write feasibility check; must not apply anything. */\n canAdoptOffersAssignments?: (assignments: CartAssignment[]) => boolean;\n /** Headless capture hook: stage a validated offers adoption for {@link confirmCartAttributesWritten}. */\n stageOffersAdoptionAssignments?: (assignments: CartAssignment[]) => void;\n adoptPriceAssignments?: (assignments: CartAssignment[]) => boolean;\n authorizePriceDisplayAssignments?: (assignments: CartAssignment[]) => void;\n authorizeShippingDisplayAssignments?: (assignments: CartAssignment[]) => void;\n authorizeOffersDisplayAssignments?: (assignments: CartAssignment[]) => void;\n /** Receives the parsed /cart/update.js response body after a successful stamp write. */\n onCartSnapshot?: (cart: unknown) => void;\n exclusionGroupSelections?: Array<{\n groupId: string;\n experimentId: string;\n epoch: number;\n }>;\n};\n\nexport type CartAttributeEntry = { key: string; value: string };\n\nexport type CartAttributeSyncResult = {\n status: \"sent\" | \"skipped\" | \"failed\";\n reason?: string;\n};\n\nexport type CartAttributeSyncOptions = {\n /** Re-write after a real cart mutation even if this assignment was stamped earlier. */\n force?: boolean;\n};\n\nconst inFlightBySignature = new Map<string, Promise<CartAttributeSyncResult>>();\nconst validatedAuthorizationSignatures = new Set<string>();\nlet authorizedAssignmentsBySignature: {\n price: Map<string, CartAssignment[]>;\n shipping: Map<string, CartAssignment[]>;\n offers: Map<string, CartAssignment[]>;\n} | null = null;\nlet latestSyncGeneration = 0;\n\n/** Prevent an obsolete authorization response from reaching the cart write. */\nexport function invalidateShopifyCartAttributeSync(): void {\n latestSyncGeneration += 1;\n}\n\nfunction ensureAuthorizedAssignmentCache() {\n return authorizedAssignmentsBySignature ??= {\n price: new Map<string, CartAssignment[]>(),\n shipping: new Map<string, CartAssignment[]>(),\n offers: new Map<string, CartAssignment[]>(),\n };\n}\n\nfunction requiresCommerceAuthorization(input: CartAttributeSyncInput): boolean {\n return !!input.requiredAssignments?.length\n || input.offersAuthorizationRevision != null\n || input.shippingAuthorizationRevision != null\n || input.priceAuthorizationRevision != null;\n}\n\nfunction hasActivePriceAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.priceAuthorizationRevision != null && !!input.priceExperimentIds?.length;\n}\n\nfunction hasActiveOffersAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.offersAuthorizationRevision != null && !!input.offersExperimentIds?.length;\n}\n\nfunction offersExperimentIds(input: CartAttributeSyncInput): Set<string> {\n return new Set(input.offersExperimentIds ?? []);\n}\n\nfunction shippingExperimentIds(input: CartAttributeSyncInput): Set<string> {\n if (input.shippingExperimentIds?.length) return new Set(input.shippingExperimentIds);\n const priceExperimentIds = new Set(input.priceExperimentIds ?? []);\n return new Set(\n input.requiredAssignments\n ?.filter((assignment) => !priceExperimentIds.has(assignment.experimentId))\n .map((assignment) => assignment.experimentId) ?? [],\n );\n}\n\nfunction hasActiveShippingAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.shippingAuthorizationRevision != null && shippingExperimentIds(input).size > 0;\n}\n\n// Clearing a stale grant is privilege-REDUCING, so shipping- and offers-only\n// shops may clear without a signed token; the price plane always needs one.\nfunction isUnsignedCommerceClearCandidate(input: CartAttributeSyncInput): boolean {\n return input.priceAuthorizationRevision == null &&\n (input.shippingAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null) &&\n !input.requiredAssignments?.length;\n}\n\nexport function cannotRequestRequiredCommerceAuthorization(\n input: CartAttributeSyncInput,\n): boolean {\n return (\n !!input.assignments[0] ||\n input.shippingAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null ||\n hasActivePriceAuthorizationPlane(input)\n )\n && requiresCommerceAuthorization(input)\n && (!input.shopId || !input.endpoint || !input.ingestSignature);\n}\n\nexport function isShopifyStorefront(): boolean {\n return typeof window !== \"undefined\" && !!(window as unknown as { Shopify?: unknown }).Shopify;\n}\n\nfunction readStampedSignature(): string | null {\n try {\n return sessionStorage.getItem(CART_ATTRIBUTE_SYNC_KEY);\n } catch {\n return null;\n }\n}\n\nfunction writeStampedSignature(signature: string): void {\n try {\n sessionStorage.setItem(CART_ATTRIBUTE_SYNC_KEY, signature);\n } catch {\n // Storage can be unavailable in strict privacy modes; the cart write is still best-effort.\n }\n}\n\nfunction normalizeTargetingUrl(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n try {\n // The Worker evaluates targeting through URL with the same fallback base.\n // Persist that effective value so equivalent URL spellings share a cache\n // entry while a meaningful SPA route change requests a fresh price pin.\n return new URL(trimmed, \"https://_\").href;\n } catch {\n // The Worker still receives invalid-but-non-empty strings and fails their\n // URL targeting closed, so retain the trimmed value as part of the key.\n return trimmed;\n }\n}\n\nfunction syncSignature(input: CartAttributeSyncInput): string {\n return JSON.stringify({\n assignments: input.assignments\n .map((assignment) => [\n assignment.experimentId,\n assignment.variationId,\n assignment.assignmentEpoch ?? null,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n requiredAssignments: input.requiredAssignments\n ?.map((assignment) => [\n assignment.experimentId,\n assignment.variationId,\n assignment.assignmentEpoch ?? null,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n visitorId: input.visitorId,\n sessionId: input.sessionId,\n url: requiresCommerceAuthorization(input)\n ? normalizeTargetingUrl(input.url)\n : undefined,\n cartToken: normalizeCartToken(input.cartToken),\n ...(input.offersAuthorizationRevision != null || input.offersExperimentIds?.length\n ? {\n offersAuthorizationRevision: input.offersAuthorizationRevision ?? null,\n offersExperimentIds: input.offersExperimentIds?.slice().sort(),\n }\n : {}),\n shippingAuthorizationRevision: input.shippingAuthorizationRevision ?? null,\n shippingExperimentIds: input.shippingExperimentIds?.slice().sort(),\n priceAuthorizationRevision: input.priceAuthorizationRevision ?? null,\n exclusionGroupSelections: input.exclusionGroupSelections\n ?.map((selection) => [\n selection.groupId,\n selection.experimentId,\n selection.epoch,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n });\n}\n\n/** Validated locale-aware storefront root (\"/fr/\" on localized markets, \"/\" otherwise). */\nexport function shopifyRoutesRoot(): string {\n const configuredRoot = (window as Window & {\n Shopify?: { routes?: { root?: string } };\n }).Shopify?.routes?.root;\n if (typeof configuredRoot !== \"string\") return \"/\";\n try {\n const storefrontOrigin = window.location?.origin;\n if (!storefrontOrigin) return \"/\";\n const parsed = new URL(configuredRoot, storefrontOrigin);\n if (parsed.origin !== storefrontOrigin || parsed.search || parsed.hash) return \"/\";\n return parsed.pathname.endsWith(\"/\") ? parsed.pathname : `${parsed.pathname}/`;\n } catch {\n return \"/\";\n }\n}\n\nexport function shopifyCartPath(): string {\n return `${shopifyRoutesRoot()}cart.js`;\n}\n\nfunction normalizeCartToken(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const withoutQuery = value.trim().split(\"?\", 1)[0] ?? \"\";\n const token = withoutQuery.startsWith(\"gid://shopify/Cart/\")\n ? withoutQuery.slice(\"gid://shopify/Cart/\".length)\n : withoutQuery;\n return token.length > 0 && token.length <= 4096 ? token : null;\n}\n\nfunction normalizeProofCartToken(value: unknown): string | null {\n return typeof value === \"string\" && normalizeCartToken(value) ? value.trim() : null;\n}\n\nasync function resolveChargeAuthorizationCartToken(\n input: CartAttributeSyncInput,\n): Promise<string | null> {\n const supplied = normalizeProofCartToken(input.cartToken);\n if (supplied) return supplied;\n if (\n input.priceAuthorizationRevision == null &&\n input.offersAuthorizationRevision == null\n ) return null;\n if (!isShopifyStorefront()) return null;\n try {\n const response = await fetchWithoutShopifyCartMutationNotification(shopifyCartPath(), {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n cache: \"no-store\",\n });\n if (!response.ok) return null;\n return normalizeProofCartToken(((await response.json()) as { token?: unknown }).token);\n } catch {\n return null;\n }\n}\n\nexport function syncShopifyCartAttributes(\n input: CartAttributeSyncInput,\n options: CartAttributeSyncOptions = {},\n): Promise<CartAttributeSyncResult> {\n try {\n if (!isShopifyStorefront()) return Promise.resolve({ status: \"skipped\", reason: \"not_shopify\" });\n if (typeof fetch !== \"function\") return Promise.resolve({ status: \"skipped\", reason: \"fetch_missing\" });\n if (!input.assignments[0] && !requiresCommerceAuthorization(input)) {\n return Promise.resolve({ status: \"skipped\", reason: \"no_assignments\" });\n }\n if (cannotRequestRequiredCommerceAuthorization(input)) {\n return Promise.resolve({ status: \"failed\", reason: \"authorization_failed\" });\n }\n\n const signature = syncSignature(input);\n const existingInFlight = inFlightBySignature.get(signature);\n if (existingInFlight) {\n if (!options.force) return existingInFlight;\n // A forced sync follows a real cart mutation, so cart identity may have\n // changed underneath the pending request. Invalidate it immediately;\n // ordinary overlaps still share the existing request.\n latestSyncGeneration += 1;\n return existingInFlight.then(() => syncShopifyCartAttributes(input, options));\n }\n const generation = ++latestSyncGeneration;\n const requiresCurrentAssignmentProof = requiresCommerceAuthorization(input);\n if (\n !options.force &&\n readStampedSignature() === signature &&\n (!requiresCurrentAssignmentProof || validatedAuthorizationSignatures.has(signature))\n ) {\n if (hasActivePriceAuthorizationPlane(input)) {\n input.authorizePriceDisplayAssignments?.(\n authorizedAssignmentsBySignature?.price.get(signature) ?? [],\n );\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n input.authorizeShippingDisplayAssignments?.(\n authorizedAssignmentsBySignature?.shipping.get(signature) ?? [],\n );\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n input.authorizeOffersDisplayAssignments?.(\n authorizedAssignmentsBySignature?.offers.get(signature) ?? [],\n );\n }\n return Promise.resolve({ status: \"sent\", reason: \"already_stamped\" });\n }\n\n if (requiresCurrentAssignmentProof) validatedAuthorizationSignatures.delete(signature);\n let authorizedSignature = signature;\n const pending = requestCommerceAuthorization(input, generation)\n .then(async ({\n completed,\n cartWriteAllowed,\n token,\n assignments,\n offersAdoptionAssignments,\n }): Promise<{\n response: Response | null;\n authorizationCompleted: boolean;\n authorizedSignature: string;\n authorizedPriceAssignments: CartAssignment[];\n authorizedShippingAssignments: CartAssignment[];\n authorizedOffersAssignments: CartAssignment[];\n offersAdoptionAssignments?: CartAssignment[];\n }> => {\n if (!cartWriteAllowed || generation !== latestSyncGeneration) {\n return {\n response: null,\n authorizationCompleted: false,\n authorizedSignature,\n authorizedPriceAssignments: [],\n authorizedShippingAssignments: [],\n authorizedOffersAssignments: [],\n };\n }\n const authorizedInput = { ...input, assignments };\n authorizedSignature = syncSignature(authorizedInput);\n const authorizedPriceAssignments = hasActivePriceAuthorizationPlane(input)\n ? parseSignedPriceAuthorizationAssignments(\n token,\n new Set(input.priceExperimentIds ?? []),\n ) ?? []\n : [];\n const authorizedShippingAssignments = hasActiveShippingAuthorizationPlane(input)\n ? parseSignedShippingAuthorizationAssignments(\n token,\n shippingExperimentIds(input),\n ) ?? []\n : [];\n const authorizedOffersAssignments = hasActiveOffersAuthorizationPlane(input)\n ? parseSignedOffersAuthorizationAssignments(\n token,\n offersExperimentIds(input),\n ) ?? []\n : [];\n const attributes = Object.fromEntries(\n buildCartAttributeEntries(authorizedInput, token).map((entry) => [entry.key, entry.value]),\n );\n const response = await fetchWithoutShopifyCartMutationNotification(\"/cart/update.js\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ attributes }),\n keepalive: true,\n });\n return {\n response,\n authorizationCompleted: completed,\n authorizedSignature,\n authorizedPriceAssignments,\n authorizedShippingAssignments,\n authorizedOffersAssignments,\n offersAdoptionAssignments,\n };\n })\n .then(({\n response,\n authorizationCompleted,\n authorizedSignature: completedSignature,\n authorizedPriceAssignments,\n authorizedShippingAssignments,\n authorizedOffersAssignments,\n offersAdoptionAssignments,\n }) => {\n if (!authorizationCompleted) {\n return { status: \"failed\", reason: \"authorization_failed\" } as const;\n }\n if (!response) {\n return { status: \"failed\", reason: \"cart_update_missing\" } as const;\n }\n if (!response.ok) {\n return { status: \"failed\", reason: `cart_update_${response.status}` } as const;\n }\n if (generation === latestSyncGeneration) {\n // The stamp is on the cart; only now may the adopted offer arm reach\n // display/persist/tracking. On failure, skip the signature records so\n // the next sync re-reconciles instead of treating this cart as done.\n if (\n offersAdoptionAssignments &&\n !input.adoptOffersAssignments?.(offersAdoptionAssignments)\n ) {\n return { status: \"failed\", reason: \"offers_adoption_failed\" } as const;\n }\n if (input.onCartSnapshot) {\n // The stamp-write response body is the full updated cart \u2014 a free\n // snapshot consumers use to rediscover Apex-managed lines without\n // an extra request (best-effort; never blocks the sync result).\n // Handed out only for the accepted generation AND after a required\n // adoption succeeded: reconciling against local assignments the\n // stamped envelope does not authorize could retain or insert a\n // gift the Function will not discount.\n void response.clone().json()\n .then((cart) => input.onCartSnapshot?.(cart))\n .catch(() => {});\n }\n writeStampedSignature(completedSignature);\n if (requiresCurrentAssignmentProof) {\n validatedAuthorizationSignatures.clear();\n validatedAuthorizationSignatures.add(completedSignature);\n }\n if (hasActivePriceAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().price;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedPriceAssignments,\n );\n input.authorizePriceDisplayAssignments?.(authorizedPriceAssignments);\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().shipping;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedShippingAssignments,\n );\n input.authorizeShippingDisplayAssignments?.(authorizedShippingAssignments);\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().offers;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedOffersAssignments,\n );\n input.authorizeOffersDisplayAssignments?.(authorizedOffersAssignments);\n }\n }\n return { status: \"sent\" } as const;\n })\n .catch((error) => ({\n status: \"failed\",\n reason: error instanceof Error ? error.message : String(error),\n }) as const)\n .finally(() => {\n if (inFlightBySignature.get(signature) === pending) {\n inFlightBySignature.delete(signature);\n }\n });\n inFlightBySignature.set(signature, pending);\n return pending;\n } catch {\n // Shopify cart attribution must never block or break assignment rendering.\n return Promise.resolve({ status: \"failed\", reason: \"sync_exception\" });\n }\n}\n\nasync function requestCommerceAuthorization(\n input: CartAttributeSyncInput,\n generation?: number,\n): Promise<{\n completed: boolean;\n cartWriteAllowed: boolean;\n token: string | null;\n assignments: CartAssignment[];\n /** Adoption validated pre-write; the caller applies it only after the cart write is confirmed. */\n offersAdoptionAssignments?: CartAssignment[];\n}> {\n const priceWriteRequiresProof = hasActivePriceAuthorizationPlane(input);\n const offersWriteRequiresProof = hasActiveOffersAuthorizationPlane(input);\n const configuredOffersExperimentIds = offersExperimentIds(input);\n const enforceOffersPlaneProof = input.offersAuthorizationRevision != null;\n let requiredOffersAssignments = enforceOffersPlaneProof\n ? input.requiredAssignments?.filter((assignment) =>\n configuredOffersExperimentIds.has(assignment.experimentId)\n ) ?? []\n : [];\n const configuredShippingExperimentIds = shippingExperimentIds(input);\n const enforceShippingPlaneProof = input.shippingAuthorizationRevision != null &&\n (!!input.shippingExperimentIds?.length || hasActivePriceAuthorizationPlane(input));\n const requiredShippingAssignments = enforceShippingPlaneProof\n ? input.requiredAssignments?.filter((assignment) =>\n configuredShippingExperimentIds.has(assignment.experimentId)\n ) ?? []\n : [];\n let hasAuthoritativePriceProof = false;\n if (\n !input.assignments[0] &&\n input.shippingAuthorizationRevision == null &&\n input.offersAuthorizationRevision == null &&\n !hasActivePriceAuthorizationPlane(input)\n ) {\n return {\n completed: true,\n cartWriteAllowed: true,\n token: null,\n assignments: input.assignments,\n };\n }\n if (!input.shopId || !input.endpoint || !input.ingestSignature) {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: true,\n token: null,\n assignments: input.assignments,\n };\n }\n const endpoint = input.endpoint.replace(/\\/+$/, \"\").replace(/\\/events$/, \"\");\n try {\n const cartToken = await resolveChargeAuthorizationCartToken(input);\n if (\n (input.priceAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null) &&\n !cartToken\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n const requestedExperimentIds = [...new Set([\n ...input.assignments.map((assignment) => assignment.experimentId),\n ...(hasActivePriceAuthorizationPlane(input) ? input.priceExperimentIds ?? [] : []),\n ...(hasActiveOffersAuthorizationPlane(input) ? input.offersExperimentIds ?? [] : []),\n ])];\n const response = await fetch(`${endpoint}/v1/assign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Drip-Shop\": input.shopId,\n \"X-Drip-Signature\": input.ingestSignature,\n },\n body: JSON.stringify({\n shop_id: input.shopId,\n user_id: input.visitorId,\n url: input.url,\n environment: input.environment,\n ...(cartToken ? { cart_token: cartToken } : {}),\n experiments: requestedExperimentIds,\n exclusion_group_selections: input.exclusionGroupSelections?.map((selection) => ({\n group_id: selection.groupId,\n experiment_id: selection.experimentId,\n epoch: selection.epoch,\n })),\n }),\n keepalive: true,\n });\n if (!response.ok) {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof &&\n !isUnsignedCommerceClearCandidate(input),\n token: null,\n assignments: input.assignments,\n };\n }\n const payload = (await response.json()) as {\n assignments?: unknown;\n commerce_authorization?: unknown;\n };\n const token =\n typeof payload.commerce_authorization === \"string\" &&\n payload.commerce_authorization\n ? payload.commerce_authorization\n : null;\n if (\n !input.assignments[0] &&\n isUnsignedCommerceClearCandidate(input) &&\n // With configured offer experiments, a nonempty response is the\n // cart-pinned offer arm returning under a new identity/off-target page \u2014\n // it must flow to the offers proof/adoption path below, not be rejected\n // as a failed stale-clear. Empty-response teardown clears are unaffected.\n !input.offersExperimentIds?.length &&\n (!Array.isArray(payload.assignments) || payload.assignments.length !== 0)\n ) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n let authorizedAssignments = input.assignments;\n let requiredAssignments = input.requiredAssignments;\n let resolvedPriceAssignments: ReturnType<\n typeof resolveAuthoritativePriceAssignments\n > = null;\n let resolvedOffersAssignments: ReturnType<\n typeof resolveAuthoritativeOffersAssignments\n > = null;\n if (input.priceAuthorizationRevision != null) {\n const priceExperimentIds = new Set(input.priceExperimentIds ?? []);\n const evaluatedPriceExperimentIds = parseEvaluatedPriceExperimentIds(\n token,\n priceExperimentIds,\n );\n if (!evaluatedPriceExperimentIds) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n if (priceExperimentIds.size > 0) {\n resolvedPriceAssignments = resolveAuthoritativePriceAssignments(\n payload.assignments,\n input.assignments,\n evaluatedPriceExperimentIds,\n );\n if (!resolvedPriceAssignments) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n authorizedAssignments = resolvedPriceAssignments.assignments;\n requiredAssignments = input.requiredAssignments?.flatMap((required) => {\n if (!priceExperimentIds.has(required.experimentId)) return [required];\n // A browser-only price experiment outside the Worker's evaluated set\n // cannot be signed. Keep its local assignment/cart attribution, but do\n // not demand impossible price proof: display eligibility is separately\n // restricted to assignments actually present in the signed price plane.\n if (!evaluatedPriceExperimentIds.has(required.experimentId)) return [];\n const authoritative = resolvedPriceAssignments?.priceAssignments.find(\n (assignment) => assignment.experimentId === required.experimentId,\n );\n return authoritative ? [authoritative] : [];\n });\n }\n const requiredPriceAssignments = requiredAssignments?.filter((assignment) =>\n priceExperimentIds.has(assignment.experimentId)\n ) ?? [];\n const requiresNonPriceAssignment = requiredAssignments?.some((assignment) =>\n !priceExperimentIds.has(assignment.experimentId)\n );\n const pricePlane = parsePriceAuthorizationPlane(token);\n hasAuthoritativePriceProof =\n resolvedPriceAssignments?.priceAssignments.length === 0\n ? hasSignedEmptyPriceAuthorizationEnvelope(token)\n : !!pricePlane && hasSignedAssignmentProof(\n pricePlane.a,\n resolvedPriceAssignments?.priceAssignments ?? [],\n );\n // Round 17 permits an unsigned successful response to clear shipping proof\n // because removing a shipping grant is privilege-reducing. Price differs:\n // replacing a signed price pin changes what checkout charges, so an active\n // price plane must have a signed assignment or signed-empty envelope before\n // any combined cart-attribute write can touch the existing pin.\n if (\n (priceWriteRequiresProof && !hasAuthoritativePriceProof) ||\n (requiredPriceAssignments.length > 0 &&\n (!pricePlane || !hasSignedAssignmentProof(pricePlane.a, requiredPriceAssignments))) ||\n (resolvedPriceAssignments?.adoptionRequired &&\n !hasAuthoritativePriceProof) ||\n (!token && requiresNonPriceAssignment)\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n } else if (requiresCommerceAuthorization(input) && !token) {\n const isLegitimateEmptyShippingResponse =\n isUnsignedCommerceClearCandidate(input) &&\n Array.isArray(payload.assignments) &&\n (!!input.assignments[0] || payload.assignments.length === 0);\n if (!isLegitimateEmptyShippingResponse) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n // Clearing a stale shipping discount grant is privilege-reducing and needs no\n // signature; unlike conferring a grant, the successful parsed response is enough.\n }\n if (enforceOffersPlaneProof && configuredOffersExperimentIds.size > 0) {\n resolvedOffersAssignments = resolveAuthoritativeOffersAssignments(\n payload.assignments,\n authorizedAssignments,\n configuredOffersExperimentIds,\n );\n if (!resolvedOffersAssignments) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n authorizedAssignments = resolvedOffersAssignments.assignments;\n requiredOffersAssignments = resolvedOffersAssignments.offersAssignments;\n requiredAssignments = requiredAssignments?.flatMap((required) => {\n if (!configuredOffersExperimentIds.has(required.experimentId)) return [required];\n const authoritative = resolvedOffersAssignments?.offersAssignments.find(\n (assignment) => assignment.experimentId === required.experimentId,\n );\n return authoritative ? [authoritative] : [];\n });\n }\n if (requiredOffersAssignments.length > 0) {\n const offersPlane = parseAuthorizationPlane(token, \"offers\");\n const hasOffersProof = !!offersPlane && hasSignedAssignmentProof(\n offersPlane.a,\n requiredOffersAssignments,\n );\n if (!hasOffersProof) {\n if (!priceWriteRequiresProof || !hasAuthoritativePriceProof) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n const configuredPriceExperimentIds = new Set(input.priceExperimentIds ?? []);\n authorizedAssignments = authorizedAssignments.filter((assignment) =>\n !configuredOffersExperimentIds.has(assignment.experimentId) ||\n configuredPriceExperimentIds.has(assignment.experimentId)\n );\n }\n }\n if (requiredShippingAssignments.length > 0) {\n const shippingPlane = parseAuthorizationPlane(token, \"shipping\");\n const hasShippingProof = !!shippingPlane && hasSignedAssignmentProof(\n shippingPlane.a,\n requiredShippingAssignments,\n );\n if (!hasShippingProof) {\n if (!priceWriteRequiresProof || !hasAuthoritativePriceProof) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n // A proven price plane may proceed independently. Remove shipping-only\n // assignments from the stamp; assignments shared with price remain because\n // checkout validates each plane from its own signature.\n const configuredPriceExperimentIds = new Set(input.priceExperimentIds ?? []);\n authorizedAssignments = authorizedAssignments.filter((assignment) =>\n !configuredShippingExperimentIds.has(assignment.experimentId) ||\n configuredPriceExperimentIds.has(assignment.experimentId)\n );\n }\n }\n if (!hasRequiredAssignmentProof(payload.assignments, requiredAssignments)) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n if (\n resolvedPriceAssignments?.adoptionRequired &&\n ((generation !== undefined && generation !== latestSyncGeneration) ||\n !input.adoptPriceAssignments?.(\n authorizedAssignments.filter((assignment) =>\n input.priceExperimentIds?.includes(assignment.experimentId)\n ),\n ))\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n let offersAdoptionAssignments: CartAssignment[] | undefined;\n if (resolvedOffersAssignments?.adoptionRequired) {\n const adoptable = authorizedAssignments.filter((assignment) =>\n configuredOffersExperimentIds.has(assignment.experimentId)\n );\n // Only validate feasibility here. Applying the adoption (DOM, persist,\n // tracking) before the cart write is confirmed would let the shopper see\n // and record a treatment Shopify never stamped an authorization for \u2014\n // the caller applies it post-write (theme) or post-confirm (headless).\n if (\n (generation !== undefined && generation !== latestSyncGeneration) ||\n !input.canAdoptOffersAssignments?.(adoptable)\n ) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n offersAdoptionAssignments = adoptable;\n }\n return {\n completed: true,\n cartWriteAllowed: true,\n token,\n assignments: authorizedAssignments,\n offersAdoptionAssignments,\n };\n } catch {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof &&\n !isUnsignedCommerceClearCandidate(input),\n token: null,\n assignments: input.assignments,\n };\n }\n}\n\nfunction hasSignedEmptyPriceAuthorizationEnvelope(token: string | null): boolean {\n if (!token) return false;\n try {\n const envelope = JSON.parse(token) as Record<string, unknown> | null;\n if (\n !envelope ||\n typeof envelope !== \"object\" ||\n envelope.v !== 2 ||\n !envelope.p ||\n typeof envelope.p !== \"object\"\n ) return false;\n const price = (envelope.p as Record<string, unknown>).price;\n if (!price || typeof price !== \"object\") return false;\n const plane = price as Record<string, unknown>;\n return plane.v === 1 &&\n Number.isSafeInteger(plane.r) && (plane.r as number) > 0 &&\n typeof plane.c === \"string\" && /^[a-f0-9]{64}$/.test(plane.c) &&\n typeof plane.x === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(plane.x) &&\n Array.isArray(plane.a) && plane.a.length === 0 &&\n typeof plane.cartHash === \"string\" && /^[a-f0-9]{64}$/.test(plane.cartHash) &&\n typeof plane.s === \"string\" && /^[a-f0-9]{64}$/.test(plane.s);\n } catch {\n return false;\n }\n}\n\nfunction parsePriceAuthorizationPlane(\n token: string | null,\n): Record<string, unknown> | null {\n if (!token) return null;\n try {\n const candidate = JSON.parse(token) as Record<string, unknown> | null;\n if (!candidate || typeof candidate !== \"object\") return null;\n if (\n candidate.v === 1 &&\n typeof candidate.cartHash === \"string\" &&\n candidate.cartHash\n ) {\n return candidate;\n }\n if (candidate.v !== 2 || !candidate.p || typeof candidate.p !== \"object\") return null;\n const price = (candidate.p as Record<string, unknown>).price;\n return price && typeof price === \"object\"\n ? price as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseAuthorizationPlane(\n token: string | null,\n plane: \"shipping\" | \"offers\",\n): Record<string, unknown> | null {\n if (!token) return null;\n try {\n const candidate = JSON.parse(token) as Record<string, unknown> | null;\n if (candidate?.v !== 2 || !candidate.p || typeof candidate.p !== \"object\") return null;\n const value = (candidate.p as Record<string, unknown>)[plane];\n return value && typeof value === \"object\"\n ? value as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseSignedPriceAuthorizationAssignments(\n token: string | null,\n configuredPriceExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const pricePlane = parsePriceAuthorizationPlane(token);\n if (!pricePlane || !Array.isArray(pricePlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of pricePlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredPriceExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseSignedShippingAuthorizationAssignments(\n token: string | null,\n configuredShippingExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const shippingPlane = parseAuthorizationPlane(token, \"shipping\");\n if (!shippingPlane || !Array.isArray(shippingPlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of shippingPlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredShippingExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseSignedOffersAuthorizationAssignments(\n token: string | null,\n configuredOffersExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const offersPlane = parseAuthorizationPlane(token, \"offers\");\n if (!offersPlane || !Array.isArray(offersPlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of offersPlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredOffersExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseEvaluatedPriceExperimentIds(\n token: string | null,\n configuredPriceExperimentIds: ReadonlySet<string>,\n): Set<string> | null {\n const rolloutFallback = () => new Set(configuredPriceExperimentIds);\n if (!token) return rolloutFallback();\n try {\n const envelope = JSON.parse(token) as Record<string, unknown> | null;\n if (!envelope || typeof envelope !== \"object\" || envelope.v !== 2) {\n return rolloutFallback();\n }\n if (!Object.prototype.hasOwnProperty.call(envelope, \"e\")) {\n // Rollout ordering matters: deploy the Worker before this SDK. Until every\n // response declares `e`, an older V2 envelope must retain round-14 behavior\n // and treat every configured price experiment as evaluated.\n return rolloutFallback();\n }\n if (!Array.isArray(envelope.e)) return null;\n const evaluated = new Set<string>();\n for (const experimentId of envelope.e) {\n if (\n typeof experimentId !== \"string\" ||\n !experimentId ||\n !configuredPriceExperimentIds.has(experimentId) ||\n evaluated.has(experimentId)\n ) return null;\n evaluated.add(experimentId);\n }\n return evaluated;\n } catch {\n return rolloutFallback();\n }\n}\n\nfunction hasSignedAssignmentProof(\n signedAssignments: unknown,\n requiredAssignments: NonNullable<CartAttributeSyncInput[\"requiredAssignments\"]>,\n): boolean {\n if (!Array.isArray(signedAssignments)) return false;\n return requiredAssignments.every((required) =>\n signedAssignments.some((candidate) => {\n if (!candidate || typeof candidate !== \"object\") return false;\n const assignment = candidate as Record<string, unknown>;\n return assignment.e === required.experimentId &&\n assignment.v === required.variationId &&\n assignment.ep === required.assignmentEpoch;\n })\n );\n}\n\nfunction parseResponsePriceAssignments(\n responseAssignments: unknown,\n priceExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n if (!Array.isArray(responseAssignments)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of responseAssignments) {\n if (!candidate || typeof candidate !== \"object\") continue;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.experiment_id !== \"string\" ||\n !priceExperimentIds.has(assignment.experiment_id)\n ) {\n continue;\n }\n if (\n seen.has(assignment.experiment_id) ||\n typeof assignment.variation_id !== \"string\" ||\n !assignment.variation_id ||\n !Number.isSafeInteger(assignment.assignment_epoch) ||\n (assignment.assignment_epoch as number) < 1\n ) return null;\n seen.add(assignment.experiment_id);\n assignments.push({\n experimentId: assignment.experiment_id,\n variationId: assignment.variation_id,\n assignmentEpoch: assignment.assignment_epoch as number,\n ...(assignment.commerce_price_only === true ? { commercePriceOnly: true } : {}),\n });\n }\n return assignments;\n}\n\nfunction resolveAuthoritativePriceAssignments(\n responseAssignments: unknown,\n localAssignments: CartAttributeSyncInput[\"assignments\"],\n evaluatedPriceExperimentIds: ReadonlySet<string>,\n): {\n assignments: CartAssignment[];\n priceAssignments: CartAssignment[];\n adoptionRequired: boolean;\n} | null {\n const priceAssignments = parseResponsePriceAssignments(\n responseAssignments,\n evaluatedPriceExperimentIds,\n );\n if (!priceAssignments) return null;\n const localPriceAssignments = localAssignments.filter((assignment) =>\n evaluatedPriceExperimentIds.has(assignment.experimentId)\n );\n const authoritativeByExperimentId = new Map(\n priceAssignments.map((assignment) => [assignment.experimentId, assignment]),\n );\n const adoptionRequired = localPriceAssignments.some(\n (assignment) => !authoritativeByExperimentId.has(assignment.experimentId),\n ) || priceAssignments.some((assignment) => {\n const local = localPriceAssignments.find(\n (candidate) => candidate.experimentId === assignment.experimentId,\n );\n return local?.variationId !== assignment.variationId ||\n local.assignmentEpoch !== assignment.assignmentEpoch ||\n local.commercePriceOnly !== assignment.commercePriceOnly;\n });\n const remainingPriceAssignments = new Map(authoritativeByExperimentId);\n const assignments = localAssignments.map((assignment) => {\n if (!evaluatedPriceExperimentIds.has(assignment.experimentId)) return assignment;\n const authoritative = remainingPriceAssignments.get(assignment.experimentId);\n if (!authoritative) return null;\n remainingPriceAssignments.delete(assignment.experimentId);\n return authoritative;\n }).filter((assignment): assignment is CartAssignment => assignment !== null);\n for (const assignment of priceAssignments) {\n if (remainingPriceAssignments.has(assignment.experimentId)) {\n assignments.push(assignment);\n remainingPriceAssignments.delete(assignment.experimentId);\n }\n }\n return { assignments, priceAssignments, adoptionRequired };\n}\n\nfunction resolveAuthoritativeOffersAssignments(\n responseAssignments: unknown,\n localAssignments: CartAttributeSyncInput[\"assignments\"],\n configuredOffersExperimentIds: ReadonlySet<string>,\n): {\n assignments: CartAssignment[];\n offersAssignments: CartAssignment[];\n adoptionRequired: boolean;\n} | null {\n const offersAssignments = parseResponsePriceAssignments(\n responseAssignments,\n configuredOffersExperimentIds,\n );\n if (!offersAssignments) return null;\n const resolved = resolveAuthoritativePriceAssignments(\n responseAssignments,\n localAssignments,\n configuredOffersExperimentIds,\n );\n return resolved && {\n assignments: resolved.assignments,\n offersAssignments,\n adoptionRequired: resolved.adoptionRequired,\n };\n}\n\nexport async function buildSignedCartAttributeEntries(\n input: CartAttributeSyncInput,\n): Promise<CartAttributeEntry[]> {\n const generation = ++latestSyncGeneration;\n const authorization = await requestCommerceAuthorization(input, generation);\n if (generation !== latestSyncGeneration || !authorization.completed) return [];\n if (authorization.offersAdoptionAssignments) {\n // Headless integrators own the cart write; hand the validated adoption to\n // the caller so it applies only after confirmCartAttributesWritten.\n input.stageOffersAdoptionAssignments?.(authorization.offersAdoptionAssignments);\n }\n if (hasActivePriceAuthorizationPlane(input)) {\n const authorizedPriceAssignments = parseSignedPriceAuthorizationAssignments(\n authorization.token,\n new Set(input.priceExperimentIds ?? []),\n );\n if (!authorizedPriceAssignments) return [];\n input.authorizePriceDisplayAssignments?.(authorizedPriceAssignments);\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n const authorizedShippingAssignments = parseSignedShippingAuthorizationAssignments(\n authorization.token,\n shippingExperimentIds(input),\n ) ?? [];\n input.authorizeShippingDisplayAssignments?.(authorizedShippingAssignments);\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n const authorizedOffersAssignments = parseSignedOffersAuthorizationAssignments(\n authorization.token,\n offersExperimentIds(input),\n ) ?? [];\n input.authorizeOffersDisplayAssignments?.(authorizedOffersAssignments);\n }\n return buildCartAttributeEntries(\n { ...input, assignments: authorization.assignments },\n authorization.token,\n );\n}\n\nfunction hasRequiredAssignmentProof(\n responseAssignments: unknown,\n requiredAssignments: CartAttributeSyncInput[\"requiredAssignments\"],\n): boolean {\n if (!requiredAssignments?.length) return true;\n if (!Array.isArray(responseAssignments)) return false;\n\n return requiredAssignments.every((required) => {\n if (!Number.isInteger(required.assignmentEpoch) || (required.assignmentEpoch ?? 0) < 1) {\n return false;\n }\n return responseAssignments.some((candidate) => {\n if (!candidate || typeof candidate !== \"object\") return false;\n const assignment = candidate as Record<string, unknown>;\n return assignment.experiment_id === required.experimentId\n && assignment.variation_id === required.variationId\n && assignment.assignment_epoch === required.assignmentEpoch;\n });\n });\n}\n\nexport function buildCartAttributeEntries(\n input: CartAttributeSyncInput,\n commerceAuthorization: string | null = null,\n): CartAttributeEntry[] {\n const primaryAssignment = input.assignments[0];\n const entries: CartAttributeEntry[] = [\n {\n key: \"apex_assignments\",\n value: JSON.stringify(\n input.assignments.map((assignment) => ({\n e: assignment.experimentId,\n v: assignment.variationId,\n ep: assignment.assignmentEpoch,\n })),\n ),\n },\n {\n key: \"apex_assignment_authorization\",\n value: commerceAuthorization ?? \"\",\n },\n ];\n if (!primaryAssignment) {\n return [\n ...entries,\n { key: \"apex_experiment_id\", value: \"\" },\n { key: \"apex_variation_id\", value: \"\" },\n { key: \"apex_assignment_epoch\", value: \"\" },\n { key: \"apex_visitor_id\", value: input.visitorId },\n { key: \"apex_session_id\", value: input.sessionId },\n ];\n }\n return [\n ...entries,\n { key: \"apex_experiment_id\", value: primaryAssignment.experimentId },\n { key: \"apex_variation_id\", value: primaryAssignment.variationId },\n {\n key: \"apex_assignment_epoch\",\n value: String(primaryAssignment.assignmentEpoch ?? \"\"),\n },\n { key: \"apex_visitor_id\", value: input.visitorId },\n { key: \"apex_session_id\", value: input.sessionId },\n ];\n}\n", "import { fetchWithoutShopifyCartMutationNotification } from \"./shopify-cart-events\";\nimport {\n isShopifyStorefront,\n shopifyCartPath,\n shopifyRoutesRoot,\n type CartAssignment,\n} from \"./shopify-cart\";\nimport type { CommerceOffersSettings } from \"./types\";\n\nconst MARKER = \"_apex_gwp\";\nconst DECLINE_PREFIX = \"drip_gwp_declined:\";\nconst INSERTED_PREFIX = \"drip_gwp_inserted:\";\n\ntype CartLine = {\n key?: unknown;\n quantity?: unknown;\n variant_id?: unknown;\n final_line_price?: unknown;\n line_price?: unknown;\n properties?: unknown;\n};\ntype ShopifyCart = { token?: unknown; currency?: unknown; items?: unknown };\n\n// Record value when the cart's identity is unknown \u2014 matches any cart.\nconst ANY_CART = \"*\";\n\nexport type GiftSyncOptions = {\n /** Per-arm proof from the CURRENT stamped offers plane. Insertion requires\n * it: config membership alone can cover an assignment whose offers proof\n * was refused (e.g. shared price+offers experiment with price-only proof),\n * and the Function would charge that \"gift\" at full price. */\n isArmProven?: (assignment: CartAssignment) => boolean;\n /** A cart mutation invalidates the once-per-page teardown check. */\n recheckTeardown?: boolean;\n /** Fail-closed reconciliation after a FAILED stamp sync: privilege-reducing\n * cleanup only (threshold/currency/orphan removals, quantity normalization)\n * \u2014 never insert, because no fresh proof exists for a new line. */\n suppressInsertion?: boolean;\n};\n\nlet inFlight: Promise<void> | null = null;\nlet queuedArgs: {\n settings: CommerceOffersSettings | undefined;\n assignments: CartAssignment[];\n options: GiftSyncOptions | undefined;\n generation: number;\n} | null = null;\n// In-memory fallback when localStorage is unavailable (strict privacy modes).\nconst insertedThisSession = new Map<string, string>();\nconst selectedGiftOptions = new Map<string, { gid: string; tierThreshold: string; pending?: boolean }>();\nconst dispatchedChoices = new Set<string>();\n// The choice tier currently applicable per experiment \u2014 selections bind to\n// THIS tier, not to whichever prompts were dispatched earlier in the session.\nconst activeChoiceTier = new Map<string, string>();\n// Bumped whenever new sync arguments are accepted: an in-flight pass built\n// on older arguments must not repopulate tier liveness after the clear.\nlet choiceTierGeneration = 0;\nlet latestArgs: { settings: CommerceOffersSettings | undefined; assignments: CartAssignment[]; options?: GiftSyncOptions } | null = null;\n\ntype GiftTier = { threshold: string; gift_variant_gid: string; gift_choice_variant_gids?: string[] };\nexport type PendingGiftChoice = {\n experimentId: string;\n threshold: string;\n options: string[];\n};\nfunction giftTiers(variant: Record<string, unknown>): GiftTier[] | null {\n const raw = Array.isArray(variant.tiers) && variant.tiers.length > 0\n ? variant.tiers\n : [{ threshold: variant.threshold, gift_variant_gid: variant.gift_variant_gid }];\n const tiers: GiftTier[] = [];\n for (const item of raw) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return null;\n const tier = item as Record<string, unknown>;\n if (typeof tier.threshold !== \"string\" || !/^\\d+$/.test(tier.threshold) ||\n typeof tier.gift_variant_gid !== \"string\" ||\n // Malformed fixed-tier GIDs would reach choice dispatch with a null\n // giftId and abort reconciliation mid-run; money-path config fails\n // closed at parse time instead.\n numericVariantId(tier.gift_variant_gid) === null) return null;\n const choices = tier.gift_choice_variant_gids;\n if (choices !== undefined && (!Array.isArray(choices) || choices.length < 2 || choices.length > 4 ||\n !choices.every((gid) => typeof gid === \"string\" && numericVariantId(gid) !== null))) return null;\n tiers.push({ threshold: tier.threshold, gift_variant_gid: tier.gift_variant_gid,\n ...(choices ? { gift_choice_variant_gids: choices as string[] } : {}) });\n }\n if (tiers.length < 1 || tiers.length > 3 || tiers.some((tier, index) =>\n !Number.isSafeInteger(Number(tier.threshold)) ||\n (index > 0 && Number(tier.threshold) <= Number(tiers[index - 1].threshold)))) return null;\n return tiers;\n}\n\nfunction declined(experimentId: string): boolean {\n try { return sessionStorage.getItem(`${DECLINE_PREFIX}${experimentId}`) === \"1\"; } catch { return false; }\n}\n\nfunction recordDecline(experimentId: string): void {\n try { sessionStorage.setItem(`${DECLINE_PREFIX}${experimentId}`, \"1\"); } catch { /* best effort */ }\n}\n\n// Insertion state persists in LOCAL storage: the Shopify cart itself is\n// durable across browser sessions, so the record of \"Apex added this line\"\n// must outlive the session too \u2014 otherwise a shopper returning after the\n// offer stopped would keep a no-longer-discounted gift (teardown cleanup\n// could not know to look), and a manual removal in a fresh session would\n// read as \"never inserted\" and re-add the gift. The record VALUE is the cart\n// token the insertion happened on, so a normal cart transition (checkout\n// emptied the old cart) is not misread as a manual decline on the new one.\n// This is charge-correctness state (experiment id + cart token), like the\n// cart attribute stamp, not analytics. Decline flags stay session-scoped.\nfunction insertionRecord(experimentId: string): string | null {\n try {\n return localStorage.getItem(`${INSERTED_PREFIX}${experimentId}`)\n ?? insertedThisSession.get(experimentId) ?? null;\n } catch { return insertedThisSession.get(experimentId) ?? null; }\n}\n\nfunction recordCart(record: string): string {\n if (!record.startsWith(\"{\")) return record;\n try { const parsed = JSON.parse(record) as { cart?: unknown }; return typeof parsed.cart === \"string\" ? parsed.cart : record; } catch { return record; }\n}\nfunction recordVariantGid(record: string): string | null {\n if (!record.startsWith(\"{\")) return null;\n try {\n const parsed = JSON.parse(record) as { variantGid?: unknown };\n return typeof parsed.variantGid === \"string\" ? parsed.variantGid : null;\n } catch { return null; }\n}\nfunction recordTierThreshold(record: string): string | null {\n if (!record.startsWith(\"{\")) return null;\n try {\n const parsed = JSON.parse(record) as { tierThreshold?: unknown };\n return typeof parsed.tierThreshold === \"string\" ? parsed.tierThreshold : null;\n } catch { return null; }\n}\nfunction insertionMatchesCart(record: string, cartToken: string | null): boolean {\n const token = recordCart(record);\n return token === ANY_CART || cartToken == null || token === cartToken;\n}\n\nfunction recordInserted(experimentId: string, cartToken: string | null, variantGid?: string, tierThreshold?: string): void {\n const token = cartToken ?? ANY_CART;\n const value = variantGid || tierThreshold ? JSON.stringify({ cart: token, variantGid, tierThreshold }) : token;\n insertedThisSession.set(experimentId, value);\n try { localStorage.setItem(`${INSERTED_PREFIX}${experimentId}`, value); } catch { /* best effort */ }\n // The durable record landing IS the settle point for a pending selection \u2014\n // waiting for another reconciliation pass leaves a last-pass add pending\n // forever and re-opens the cross-tab revert.\n const selection = selectedGiftOptions.get(experimentId);\n if (selection?.pending && selection.gid === variantGid) {\n selectedGiftOptions.set(experimentId, {\n gid: selection.gid,\n tierThreshold: selection.tierThreshold,\n });\n }\n}\n\nfunction clearInserted(experimentId: string): void {\n insertedThisSession.delete(experimentId);\n try { localStorage.removeItem(`${INSERTED_PREFIX}${experimentId}`); } catch { /* best effort */ }\n}\n\nfunction clearDispatchedChoices(experimentId: string): void {\n dispatchedChoices.forEach((key) => {\n if (key.startsWith(`${experimentId}:`)) dispatchedChoices.delete(key);\n });\n}\n\nfunction listInsertionRecords(): string[] {\n const ids = new Set<string>(insertedThisSession.keys());\n try {\n for (let i = 0; i < localStorage.length; i += 1) {\n const key = localStorage.key(i);\n if (key?.startsWith(INSERTED_PREFIX)) ids.add(key.slice(INSERTED_PREFIX.length));\n }\n } catch { /* fall through */ }\n return [...ids];\n}\n\nfunction hasInsertionRecords(): boolean {\n return listInsertionRecords().length > 0;\n}\n\n// The teardown read (offers plane tombstoned to an empty experiments list)\n// runs once per PAGE LOAD \u2014 not per session \u2014 so a login/customer-account\n// cart merge that lands a marked line on a later page is still discovered;\n// in-page cart mutations reset the flag via the tombstone observer.\nlet teardownChecked = false;\n\nfunction markTeardownChecked(): void {\n teardownChecked = true;\n}\n\nfunction numericVariantId(gid: string): number | null {\n const match = /^gid:\\/\\/shopify\\/ProductVariant\\/(\\d+)$/.exec(gid);\n if (!match) return null;\n const id = Number(match[1]);\n return Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n\nfunction marker(line: CartLine): string | null {\n if (!line.properties || typeof line.properties !== \"object\" || Array.isArray(line.properties)) return null;\n const value = (line.properties as Record<string, unknown>)[MARKER];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction lineVariantId(line: CartLine): string | null {\n if (typeof line.variant_id === \"number\" && Number.isSafeInteger(line.variant_id)) return String(line.variant_id);\n if (typeof line.variant_id === \"string\" && /^\\d+$/.test(line.variant_id)) return line.variant_id;\n return null;\n}\n\nfunction lineAmount(line: CartLine): number | null {\n // The offer Function compares each non-gift line's cost.subtotalAmount \u2014\n // the PRE-line-discount amount. Ajax `line_price` is that same basis;\n // `final_line_price` is post-discount and would disagree on discounted\n // carts, removing a gift the Function still authorizes.\n const value = line.line_price;\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 ? value : null;\n}\n\nasync function mutate(path: string, body: unknown): Promise<boolean> {\n // Never throws: a network-level rejection mid-pass must not discard the\n // pass's accumulated mutation state (earlier successful removals still\n // warrant the fixpoint retry and the cart-changed signal).\n try {\n const response = await fetchWithoutShopifyCartMutationNotification(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/** Returns true when this pass mutated the cart (caller re-runs to a fixpoint). */\nasync function run(\n settings: CommerceOffersSettings | undefined,\n assignments: CartAssignment[],\n options: GiftSyncOptions | undefined,\n generation: number,\n): Promise<boolean> {\n if (!isShopifyStorefront() || typeof fetch !== \"function\") return false;\n let response: Response;\n try {\n response = await fetchWithoutShopifyCartMutationNotification(shopifyCartPath(), {\n method: \"GET\", headers: { Accept: \"application/json\" }, cache: \"no-store\",\n });\n } catch { return false; }\n if (!response.ok) return false;\n const cart = await response.json() as ShopifyCart;\n if (!Array.isArray(cart.items) || typeof cart.currency !== \"string\") return false;\n let cartMutated = false;\n const lines = cart.items.filter((item): item is CartLine => !!item && typeof item === \"object\");\n const cartToken = typeof cart.token === \"string\" && cart.token ? cart.token : null;\n const assigned = new Map(assignments.map((item) => [item.experimentId, item.variationId]));\n // Charge-only pins authorize the cart stamp for an off-target page but are\n // deliberately excluded from DOM, persistence, and exposure \u2014 inserting a\n // physical gift for them would give away inventory the shopper was never\n // shown. They still count as ACTIVE for removal/keep decisions.\n const chargeOnlyExperimentIds = new Set(\n assignments.filter((item) => item.commercePriceOnly).map((item) => item.experimentId),\n );\n const experiments = settings?.experiments ?? [];\n // Only experiments covered by the signed offers authorization may INSERT:\n // without it the Function cannot discount the added line, so the \"gift\"\n // would be charged at full price. Removal/maintenance stays unrestricted.\n const authorizedOfferExperimentIds = new Set(\n settings?.authorization?.experimentIds ?? [],\n );\n const root = shopifyRoutesRoot();\n const changePath = `${root}cart/change.js`;\n // Tracks gift variants inserted within THIS run: two parallel GWP tests\n // configured with the same gift variant must share one line (the Function\n // consolidates both candidates onto the first matching line), or the\n // second marked line would be charged at full price.\n const insertedVariantIdsThisRun = new Set<string>();\n // Tracks lines removed within THIS run so presence checks against the\n // immutable snapshot do not see a line that is already gone \u2014 otherwise a\n // still-qualifying experiment sharing the removed gift's variant would\n // silently skip its own insertion.\n const removedLineKeys = new Set<string>();\n const removeLine = async (line: CartLine): Promise<boolean> => {\n if (typeof line.key !== \"string\") return false;\n if (!(await mutate(changePath, { id: line.key, quantity: 0 }))) return false;\n removedLineKeys.add(line.key);\n cartMutated = true;\n return true;\n };\n const linePresent = (line: CartLine): boolean =>\n typeof line.key === \"string\" ? !removedLineKeys.has(line.key) : true;\n\n // Orphan cleanup first: a marked line whose experiment left the offers\n // projection (stopped, archived, kill-switched), whose assignment is gone,\n // or whose assigned variant no longer describes a GWP offer (relaunched\n // under a different offer type) will never be authorized by the discount\n // Function again \u2014 the shopper would be charged full price for it. Remove\n // it before any insertion logic; keep the insertion record when removal\n // fails so the next sync retries instead of abandoning the line.\n const activeGiftExperiments = new Set(\n experiments\n .filter((experiment) => {\n const variationId = assigned.get(experiment.experiment_id);\n const variant = variationId ? experiment.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") return false;\n // With arm proof available, an assignment absent from the CURRENT\n // stamped offers plane is not active: the Function will not discount\n // its line, so keeping it would charge the shopper.\n if (options?.isArmProven) {\n const assignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (!assignment || !options.isArmProven(assignment)) return false;\n }\n if (\"control\" in variant && variant.control === true) return true;\n return \"offer_type\" in variant && variant.offer_type === \"gwp_threshold\"\n && giftTiers(variant as Record<string, unknown>) !== null;\n })\n .map((experiment) => experiment.experiment_id),\n );\n for (const line of lines) {\n const mark = marker(line);\n if (mark == null || activeGiftExperiments.has(mark)) continue;\n // Seed the durable record BEFORE attempting removal: if the mutation\n // fails transiently, the record keeps later syncs (and later sessions)\n // retrying even after this session's tombstone read is throttled.\n recordInserted(mark, cartToken);\n if (await removeLine(line)) clearInserted(mark);\n }\n // A record whose experiment is inactive AND whose line is already gone\n // (checkout consumed the cart, or the shopper removed it before teardown)\n // has nothing left to reconcile \u2014 clear it, or it would trigger a cart\n // read on every later apply and misread a relaunch as a manual decline.\n for (const experimentId of listInsertionRecords()) {\n if (activeGiftExperiments.has(experimentId)) continue;\n if (lines.some((line) => marker(line) === experimentId && linePresent(line))) continue;\n clearInserted(experimentId);\n }\n // Throttle only once the cart has actually been read this session; failed\n // orphan removals keep their seeded records, which bypass the throttle.\n if (settings && experiments.length === 0) markTeardownChecked();\n\n for (const experiment of experiments) {\n // Not live until this pass proves the choice prompt eligible: every\n // early exit (unassigned, control, currency mismatch, unproven arm,\n // suppression, decline) leaves the tier cleared so a delayed\n // selectGiftOption callback cannot bind to a phantom prompt.\n activeChoiceTier.delete(experiment.experiment_id);\n const variationId = assigned.get(experiment.experiment_id);\n if (!variationId) continue;\n const variant = experiment.variants[variationId];\n if (!variant || typeof variant !== \"object\") continue;\n // ALL marked lines for this experiment: concurrent tabs during a\n // relaunch can leave several (e.g. old and new gift variants), and the\n // Function discounts at most one unit of the current variant.\n const managedLines = () => lines.filter(\n (line) => marker(line) === experiment.experiment_id && linePresent(line),\n );\n // Removes every managed line; true when none that could be removed\n // remains (lines without a usable key were never removable and follow\n // the historical clear-anyway behavior).\n const removeAllManaged = async (): Promise<boolean> => {\n let allRemoved = true;\n for (const line of managedLines()) {\n if (typeof line.key !== \"string\") continue;\n if (!(await removeLine(line))) {\n allRemoved = false;\n // A failed REQUIRED removal (below-threshold, control, currency)\n // is unresolved work: the Function no longer discounts the line,\n // so force another fixpoint pass to retry within this\n // reconciliation instead of waiting for an unrelated cart event.\n cartMutated = true;\n }\n }\n return allRemoved;\n };\n if (\"control\" in variant && variant.control === true) {\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n if (!(\"offer_type\" in variant) || variant.offer_type !== \"gwp_threshold\") continue;\n const tiers = giftTiers(variant as Record<string, unknown>);\n if (!tiers) continue;\n if (cart.currency.toUpperCase() !== experiment.currency.toUpperCase()) {\n // The Function refuses cross-currency carts, so a market/currency switch\n // strands an existing managed line at full price \u2014 fail closed by\n // removing it, not just by skipping insertion. Not a decline: the gift\n // may re-insert if the shopper switches back.\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n const allGiftIds = new Set(tiers.flatMap((tier) => tier.gift_choice_variant_gids ?? [tier.gift_variant_gid])\n .map(numericVariantId).filter((id): id is number => id !== null).map(String));\n let subtotal = 0;\n let valid = true;\n for (const line of lines) {\n if (!linePresent(line) || allGiftIds.has(lineVariantId(line) ?? \"\")) continue;\n const amount = lineAmount(line);\n if (amount == null || !Number.isSafeInteger(subtotal + amount)) { valid = false; break; }\n subtotal += amount;\n }\n const applicableTier = valid ? tiers.reduce<GiftTier | null>((chosen, tier) => {\n const threshold = Number(tier.threshold);\n return Number.isSafeInteger(threshold) && threshold >= 0 && subtotal >= threshold ? tier : chosen;\n }, null) : null;\n if (!valid || tiers.some((tier) => !Number.isSafeInteger(Number(tier.threshold)))) {\n // Fail closed: an unrepresentable threshold means the Function cannot\n // legitimately discount this gift \u2014 an Apex-managed line left from an\n // earlier (lower) threshold would charge full price. Remove every\n // managed line for this experiment before skipping it.\n for (const line of managedLines()) {\n if (typeof line.key !== \"string\") continue;\n await removeLine(line);\n }\n continue;\n }\n if (!applicableTier) {\n clearDispatchedChoices(experiment.experiment_id);\n activeChoiceTier.delete(experiment.experiment_id);\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n const choiceGids = applicableTier.gift_choice_variant_gids;\n // Every applicable-tier transition re-arms the chooser: keys that do not\n // belong to the CURRENT tier (including all keys while a fixed tier\n // applies) are stale. Only the current batch may mutate dedup state.\n if (generation === choiceTierGeneration) {\n const currentKey = choiceGids\n ? `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids.join(\",\")}`\n : null;\n dispatchedChoices.forEach((key) => {\n if (key.startsWith(`${experiment.experiment_id}:`) && key !== currentKey) {\n dispatchedChoices.delete(key);\n }\n });\n }\n const markChoicePromptLive = () => {\n if (\n choiceGids &&\n !options?.suppressInsertion &&\n authorizedOfferExperimentIds.has(experiment.experiment_id) &&\n !declined(experiment.experiment_id)\n ) {\n const assignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (\n (!options?.isArmProven || (assignment && options.isArmProven(assignment))) &&\n generation === choiceTierGeneration\n ) {\n activeChoiceTier.set(experiment.experiment_id, applicableTier.threshold);\n }\n }\n };\n const extendedRecord = Array.isArray((variant as Record<string, unknown>).tiers);\n const persistedRecord = insertionRecord(experiment.experiment_id);\n const persistedGid = persistedRecord && insertionMatchesCart(persistedRecord, cartToken)\n ? recordVariantGid(persistedRecord)\n : null;\n // A recorded tier that DIFFERS from the applicable tier is a stale\n // lower-tier gift: entering a choice tier must present the choice. An\n // ABSENT tier (snapshot-seeded cross-device record, legacy plain-token\n // record) has no provenance: recover it only while the applicable tier\n // is the LOWEST choice tier containing that gid \u2014 once the subtotal has\n // crossed into a higher choice tier sharing the GID, the new tier's\n // prompt must not be suppressed by a tierless record.\n const lowestChoiceTierThresholdFor = (gid: string | null | undefined) =>\n gid == null\n ? undefined\n : tiers.find((tier) => tier.gift_choice_variant_gids?.includes(gid))?.threshold;\n const persistedTierThreshold =\n persistedRecord === null ? null : recordTierThreshold(persistedRecord);\n const tierlessRecoveryAllowed = (gid: string | null | undefined) =>\n lowestChoiceTierThresholdFor(gid) === applicableTier.threshold;\n // In-memory selections are tier-bound: a choice made for another tier\n // (tier crossing, or a selectGiftOption call while a different tier\n // applied) must not suppress the new tier's choice prompt.\n const inMemorySelection = selectedGiftOptions.get(experiment.experiment_id);\n let inMemoryGid = inMemorySelection?.tierThreshold === applicableTier.threshold\n ? inMemorySelection.gid\n : undefined;\n const markedCurrentTierGid = choiceGids?.find((gid) => {\n const variantId = numericVariantId(gid);\n return variantId !== null && lines.some((line) =>\n linePresent(line) && marker(line) === experiment.experiment_id &&\n lineVariantId(line) === String(variantId));\n });\n const persistedChoiceIsCurrentTier = persistedRecord !== null &&\n (persistedTierThreshold === applicableTier.threshold ||\n (persistedTierThreshold === null &&\n tierlessRecoveryAllowed(persistedGid ?? markedCurrentTierGid)));\n // Cross-tab arbitration: the durable record is the source of truth once\n // this tab's own selection has been persisted. A PENDING selection (made\n // here, not yet recorded) still wins \u2014 clobbering the user's fresh click\n // with an older record would be worse. Once our record lands (gids\n // match), pending clears; a later differing durable record means another\n // tab chose \u2014 drop the stale local selection instead of reverting theirs.\n if (inMemorySelection && inMemoryGid) {\n const persistedCurrentTierGid =\n persistedChoiceIsCurrentTier && choiceGids?.includes(persistedGid ?? \"\")\n ? persistedGid\n : null;\n if (inMemorySelection.pending && persistedCurrentTierGid === inMemoryGid) {\n selectedGiftOptions.set(experiment.experiment_id, {\n gid: inMemorySelection.gid,\n tierThreshold: inMemorySelection.tierThreshold,\n });\n } else if (\n !inMemorySelection.pending &&\n persistedCurrentTierGid &&\n persistedCurrentTierGid !== inMemoryGid\n ) {\n selectedGiftOptions.delete(experiment.experiment_id);\n inMemoryGid = undefined;\n }\n }\n const selectedGid = choiceGids?.includes(inMemoryGid ?? \"\")\n ? inMemoryGid\n : persistedChoiceIsCurrentTier && choiceGids?.includes(persistedGid ?? \"\") ? persistedGid!\n : persistedChoiceIsCurrentTier ? markedCurrentTierGid : undefined;\n if (selectedGid) {\n const priorPending =\n selectedGiftOptions.get(experiment.experiment_id)?.gid === selectedGid &&\n selectedGiftOptions.get(experiment.experiment_id)?.pending === true;\n selectedGiftOptions.set(experiment.experiment_id, {\n gid: selectedGid,\n tierThreshold: applicableTier.threshold,\n ...(priorPending ? { pending: true } : {}),\n });\n }\n const desiredGid = choiceGids ? selectedGid : applicableTier.gift_variant_gid;\n const giftId = desiredGid ? numericVariantId(desiredGid) : null;\n const allowedCurrentIds = new Set((choiceGids ?? [applicableTier.gift_variant_gid])\n .map(numericVariantId).filter((id): id is number => id !== null).map(String));\n // Keep at most ONE line carrying the current gift variant; every other\n // marked line (stale variant after a relaunch, or a concurrent-tab\n // duplicate) is removed \u2014 the Function will not discount it. Removal\n // failures skip the experiment: falling through could misread the\n // shopper's cart as a manual decline.\n let managed: CartLine | undefined;\n let staleRemovalFailed = false;\n let removedStaleForExperiment = false;\n for (const line of managedLines()) {\n if (!managed && giftId !== null && lineVariantId(line) === String(giftId)) {\n managed = line;\n continue;\n }\n if (typeof line.key !== \"string\") continue;\n if (await removeLine(line)) removedStaleForExperiment = true;\n else staleRemovalFailed = true;\n }\n // Any failed stale removal is unresolved work: force another fixpoint\n // pass so the retry happens now, not at the next unrelated cart event \u2014\n // the Function discounts at most the one current-variant unit.\n if (staleRemovalFailed) cartMutated = true;\n if (staleRemovalFailed && !managed) continue;\n if (!managed && removedStaleForExperiment) {\n // OUR stale-variant removal, not a shopper decline of the new gift:\n // clear the record so the decline logic below cannot misread it and\n // the current gift may insert.\n clearInserted(experiment.experiment_id);\n }\n if (managed && lines.some((line) =>\n line !== managed && linePresent(line) && marker(line) !== experiment.experiment_id &&\n allowedCurrentIds.has(lineVariantId(line) ?? \"\"))) {\n // The Function picks one allowed option by cart-line id, not by Apex's\n // marker. If a shopper or another experiment added an allowed option,\n // our marked gift could therefore become a full-price line. Relinquish\n // Apex's line and let that other option satisfy the tier.\n if (typeof managed.key === \"string\" && await removeLine(managed)) {\n clearInserted(experiment.experiment_id);\n } else {\n cartMutated = true;\n }\n continue;\n }\n if (!desiredGid || giftId === null) {\n clearInserted(experiment.experiment_id);\n markChoicePromptLive();\n if (!options?.suppressInsertion && authorizedOfferExperimentIds.has(experiment.experiment_id) && !declined(experiment.experiment_id)) {\n const assignment = assignments.find((candidate) => candidate.experimentId === experiment.experiment_id);\n if (!options?.isArmProven || (assignment && options.isArmProven(assignment))) {\n const eventKey = `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids!.join(\",\")}`;\n // Only the current batch may dispatch or record dedup state: a\n // stale batch consuming the key would mute the queued batch's\n // valid prompt while its own event points at dead liveness.\n if (generation === choiceTierGeneration) {\n if (!dispatchedChoices.has(eventKey) && typeof window !== \"undefined\") {\n dispatchedChoices.add(eventKey);\n // Detail carries a COPY: chooser code mutating the array must\n // not corrupt the server-authored Function configuration that\n // selectGiftOption validates against.\n emitChoicePrompt({ experimentId: experiment.experiment_id, threshold: applicableTier.threshold, options: [...choiceGids!] });\n }\n }\n }\n }\n continue;\n }\n if (managed) {\n markChoicePromptLive();\n if (options?.suppressInsertion) {\n // Fail-closed pass after a failed stamp refresh: keep the line only\n // when durable evidence ties it to THIS cart \u2014 after a cart-identity\n // change (customer-cart merge) the old proof does not cover the new\n // cart and the Function will not discount the line.\n const record = insertionRecord(experiment.experiment_id);\n if (!record || !insertionMatchesCart(record, cartToken)) {\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n } else {\n // A correctly marked line is always Apex-inserted (shoppers cannot\n // set our line property), so seed the insertion record when a fresh\n // session discovers one \u2014 a later disappearance must count as a\n // manual decline.\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n }\n // The Function only zeroes a single unit; extra units on the managed\n // line would be charged, so normalize a shopper/theme increment to one.\n if (\n typeof managed.key === \"string\" &&\n typeof managed.quantity === \"number\" &&\n managed.quantity !== 1\n ) {\n // An off-model quantity always warrants another fixpoint pass \u2014 even\n // when this normalize fails, the re-read retries within the run\n // instead of leaving an extra charged unit until the next mutation.\n cartMutated = true;\n await mutate(changePath, { id: managed.key, quantity: 1 });\n }\n continue;\n }\n // Off-target charge-only pins never insert, and their line's absence on\n // this page is not decline evidence \u2014 the targeted page owns both calls.\n if (chargeOnlyExperimentIds.has(experiment.experiment_id)) {\n // But when WE removed this experiment's shared gift line in this run,\n // clear the co-owner evidence now: removedByUsThisRun does not survive\n // the pass, and a later targeted page would misread the absent line as\n // a shopper decline and never reinsert the still-eligible gift.\n if (\n insertionRecord(experiment.experiment_id) != null &&\n lines.some(\n (line) => !linePresent(line) && allowedCurrentIds.has(lineVariantId(line) ?? \"\"),\n )\n ) {\n clearInserted(experiment.experiment_id);\n }\n continue;\n }\n // A shopper-owned copy of the gift satisfies the offer without giving\n // Apex ownership of it \u2014 as does a copy just inserted for a parallel\n // GWP test with the same gift variant (the Function consolidates both\n // experiments' candidates onto that one line). This MUST precede the\n // decline logic: a co-owner's record with the shared line still present\n // is not a removal.\n const satisfyingLine = lines.find(\n (line) => linePresent(line) && allowedCurrentIds.has(lineVariantId(line) ?? \"\"),\n );\n const insertedAllowedId = [...insertedVariantIdsThisRun].find((id) => allowedCurrentIds.has(id));\n if (satisfyingLine || insertedAllowedId) {\n markChoicePromptLive();\n // Sharing an APEX-INSERTED line makes this experiment a co-owner:\n // record insertion state so the shopper's later removal declines every\n // experiment the line represents, not just the one in its marker.\n // Unmarked shopper-owned copies stay unrecorded \u2014 removing one's own\n // purchase is not a gift decline.\n if (\n insertedAllowedId ||\n (satisfyingLine && marker(satisfyingLine) != null)\n ) {\n const satisfyingGid = satisfyingLine\n ? choiceGids?.find((gid) => String(numericVariantId(gid)) === lineVariantId(satisfyingLine))\n : choiceGids?.find((gid) => String(numericVariantId(gid)) === insertedAllowedId);\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? satisfyingGid ?? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n }\n continue;\n }\n const record = insertionRecord(experiment.experiment_id);\n if (record != null) {\n clearInserted(experiment.experiment_id);\n // Only a missing line on the SAME cart the gift was inserted on is a\n // manual decline. A new cart (checkout consumed the old one) starts\n // clean, and a shared line WE removed this run (its owner entered\n // control or lost eligibility) lets a still-qualifying co-owner take\n // ownership instead of being marked declined.\n const removedByUsThisRun = lines.some(\n (line) => !linePresent(line) && lineVariantId(line) === String(giftId),\n );\n if (!removedByUsThisRun && insertionMatchesCart(record, cartToken)) {\n recordDecline(experiment.experiment_id);\n continue;\n }\n }\n if (declined(experiment.experiment_id)) continue;\n if (options?.suppressInsertion) continue;\n if (!authorizedOfferExperimentIds.has(experiment.experiment_id)) continue;\n if (options?.isArmProven) {\n const localAssignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (!localAssignment || !options.isArmProven(localAssignment)) continue;\n }\n const addResponse = await fetchWithoutShopifyCartMutationNotification(`${root}cart/add.js`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n items: [{ id: giftId, quantity: 1, properties: { [MARKER]: experiment.experiment_id } }],\n }),\n }).catch(() => null);\n if (!addResponse?.ok) {\n // A failed add for an explicit CHOICE dead-ends the shopper: the\n // pending selection would keep retrying the same variant while the\n // cleared tier liveness rejects any new selectGiftOption call. Drop\n // the failed selection and re-arm the prompt so another option can\n // be chosen.\n const failedSelection = selectedGiftOptions.get(experiment.experiment_id);\n if (choiceGids && failedSelection?.gid === desiredGid) {\n selectedGiftOptions.delete(experiment.experiment_id);\n clearDispatchedChoices(experiment.experiment_id);\n // Re-arm immediately: the pass-start tier clear plus a failed add\n // with no cart mutation means no follow-up pass runs, so the chooser\n // would stay dead (selection ignored, no pending prompts) until an\n // unrelated reconciliation. Restore tier liveness and re-dispatch so\n // an alternative option can be chosen right away.\n markChoicePromptLive();\n if (generation === choiceTierGeneration && typeof window !== \"undefined\") {\n const retryKey = `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids.join(\",\")}`;\n if (!dispatchedChoices.has(retryKey)) {\n dispatchedChoices.add(retryKey);\n emitChoicePrompt({ experimentId: experiment.experiment_id, threshold: applicableTier.threshold, options: [...choiceGids] });\n }\n }\n }\n continue;\n }\n if (addResponse?.ok) {\n cartMutated = true;\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n insertedVariantIdsThisRun.add(String(giftId));\n // Concurrent tabs can both add; Shopify consolidates identical\n // variant+property lines by bumping quantity while the Function only\n // discounts one unit. Verify from the add response and normalize.\n try {\n const body = await addResponse.json() as { items?: unknown };\n const addedLine = Array.isArray(body.items)\n ? body.items.find((item): item is CartLine =>\n !!item && typeof item === \"object\" &&\n marker(item as CartLine) === experiment.experiment_id)\n : undefined;\n if (\n addedLine && typeof addedLine.key === \"string\" &&\n typeof addedLine.quantity === \"number\" && addedLine.quantity > 1\n ) {\n // Detected consolidation always forces a follow-up fixpoint pass:\n // even if this normalize fails, the re-read sees quantity > 1 on\n // the managed line and retries rather than leaving a charged unit.\n cartMutated = true;\n await mutate(changePath, { id: addedLine.key, quantity: 1 });\n }\n } catch { /* best effort */ }\n }\n }\n return cartMutated;\n}\n\n/**\n * Seed durable insertion records from any cart snapshot that passes through\n * the SDK (e.g. the /cart/update.js stamp-write response). Marked lines can\n * only originate from Apex, so seeing one IS the insertion evidence \u2014 this is\n * how a fresh device or a cleared-storage session over a persistent (or\n * customer-synced) cart rediscovers gifts it must reconcile or tear down.\n * Returns true when a marked line was found.\n */\nexport function noteCartSnapshotForGifts(cart: unknown): boolean {\n const snapshot = cart as { token?: unknown; items?: unknown } | null;\n if (!Array.isArray(snapshot?.items)) return false;\n const cartToken = typeof snapshot.token === \"string\" && snapshot.token ? snapshot.token : null;\n let found = false;\n for (const item of snapshot.items) {\n if (!item || typeof item !== \"object\") continue;\n const mark = marker(item as CartLine);\n if (mark == null) continue;\n const variantId = lineVariantId(item as CartLine);\n // A snapshot write must not erase tier ownership an in-flow record\n // already carries: overwriting a tier-bound choice with a variant-only\n // record would let a later tier crossing adopt it without re-prompting.\n const existing = insertionRecord(mark);\n const existingTier = existing && insertionMatchesCart(existing, cartToken)\n ? recordTierThreshold(existing)\n : null;\n recordInserted(mark, cartToken, variantId\n ? `gid://shopify/ProductVariant/${variantId}`\n : undefined, existingTier ?? undefined);\n found = true;\n }\n return found;\n}\n\nexport function syncGiftWithPurchase(\n settings: CommerceOffersSettings | undefined,\n assignments: CartAssignment[],\n options?: GiftSyncOptions,\n): Promise<void> {\n latestArgs = { settings, assignments, options };\n installChoiceRequestListener();\n // Liveness derives from the PREVIOUS eligibility state until the async\n // reconciliation below re-proves it; clear synchronously and advance the\n // generation so neither a chooser callback nor an in-flight pass built on\n // the old arguments can resurrect a stale proven tier.\n choiceTierGeneration += 1;\n const batchGeneration = choiceTierGeneration;\n activeChoiceTier.clear();\n if (options?.recheckTeardown) teardownChecked = false;\n if (!settings?.experiments.length && !hasInsertionRecords()) {\n // No offers plane at all: nothing to reconcile (shops that never ran\n // offers have no tombstone). A present-but-empty plane is the teardown\n // tombstone \u2014 read the cart once per page load (reset on cart mutations)\n // so an orphaned gift is discovered even when local insertion records\n // are gone (cleared storage, another device over a customer-synced cart).\n if (!settings || teardownChecked) return Promise.resolve();\n }\n if (inFlight) {\n // The active run may have read the cart before the mutation that\n // triggered this call \u2014 queue one trailing run (latest args win) instead\n // of dropping the reconciliation.\n queuedArgs = { settings, assignments, options, generation: batchGeneration };\n return inFlight;\n }\n // Any pass that mutates the cart re-reads and re-verifies its decisions\n // (bounded): the cart may have changed between the read and the mutation\n // (e.g. the subtotal dropped below threshold before the gift add landed),\n // and shared-line decisions must be order-independent across experiments.\n const runToFixpoint = async (\n nextSettings: CommerceOffersSettings | undefined,\n nextAssignments: CartAssignment[],\n nextOptions: GiftSyncOptions | undefined,\n // Fixed per argument batch: a later batch bumps the global counter, and\n // every remaining pass of THIS batch must keep failing the liveness\n // check rather than re-reading the now-current generation.\n generation: number,\n ) => {\n let anyMutated = false;\n for (let pass = 0; pass < 3; pass += 1) {\n const mutated = await run(nextSettings, nextAssignments, nextOptions, generation).catch(() => false);\n if (mutated) anyMutated = true;\n if (!mutated) break;\n }\n if (anyMutated && typeof window !== \"undefined\") {\n // Our mutations suppress the SDK's own cart observer, so themes whose\n // drawers render from their original add/change responses would show a\n // stale cart. Publish a documented signal for themes/integrators to\n // re-render from /cart.js (checkout charges are correct regardless \u2014\n // the backend cart is authoritative; this is display freshness).\n try {\n window.dispatchEvent(\n new CustomEvent(\"apex:cart:changed\", { detail: { source: \"gwp_gift\" } }),\n );\n } catch { /* best effort */ }\n }\n };\n const pending = (async () => {\n await runToFixpoint(settings, assignments, options, batchGeneration);\n while (queuedArgs) {\n const next = queuedArgs;\n queuedArgs = null;\n await runToFixpoint(next.settings, next.assignments, next.options, next.generation);\n }\n if (choiceReplayRequested) getPendingGiftChoices().forEach(emitChoicePrompt);\n choiceReplayRequested = false;\n })().finally(() => {\n if (inFlight === pending) inFlight = null;\n if (!inFlight && queuedArgs) {\n const next = queuedArgs;\n queuedArgs = null;\n void syncGiftWithPurchase(next.settings, next.assignments, next.options);\n }\n });\n inFlight = pending;\n return pending;\n}\n\n/** Selects an option previously offered by `apex:gwp:choice`. The next\n * reconciliation validates the current signed arm/tier before mutating. */\nexport function selectGiftOption(experimentId: string, variantGid: string): Promise<void> {\n if (!latestArgs || numericVariantId(variantGid) === null) return Promise.resolve();\n const experiment = latestArgs.settings?.experiments.find((item) => item.experiment_id === experimentId);\n const variationId = latestArgs.assignments.find((item) => item.experimentId === experimentId)?.variationId;\n const variant = variationId ? experiment?.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") return Promise.resolve();\n const tiers = giftTiers(variant as Record<string, unknown>);\n const containingTiers = tiers?.filter((tier) =>\n tier.gift_choice_variant_gids?.includes(variantGid)) ?? [];\n // The same GID can be a valid choice in several tiers: bind the selection\n // to the tier that is applicable right now (tracked each reconciliation\n // pass), so answering the live prompt records the live tier \u2014 dispatched\n // keys from earlier tier crossings must not decide this.\n const applicableThreshold = activeChoiceTier.get(experimentId);\n // No live choice tier (shopper dropped below threshold, or moved to a\n // fixed tier) \u2192 a delayed chooser callback must be ignored, not recorded\n // against a tier that is no longer prompting.\n const owningTier = containingTiers.find(\n (tier) => tier.threshold === applicableThreshold,\n );\n if (!owningTier) return Promise.resolve();\n selectedGiftOptions.set(experimentId, {\n gid: variantGid,\n tierThreshold: owningTier.threshold,\n pending: true,\n });\n clearDispatchedChoices(experimentId);\n return syncGiftWithPurchase(latestArgs.settings, latestArgs.assignments, latestArgs.options);\n}\n\nfunction emitChoicePrompt(detail: PendingGiftChoice): void {\n dispatchEvent(new CustomEvent(\"apex:gwp:choice\", { detail }));\n}\n\n// Documented replay contract: dispatching apex:gwp:choice:request makes the\n// SDK re-emit apex:gwp:choice for every live, unanswered prompt, so choosers\n// mounted after the initial dispatch can recover it. Registered lazily from\n// the sync path so importing this module (e.g. via the @drip-apex/sdk/chooser\n// entry) stays free of runtime side effects.\nlet choiceRequestListenerInstalled = false;\nlet choiceReplayRequested = false;\nfunction installChoiceRequestListener(): void {\n if (choiceRequestListenerInstalled || typeof window === \"undefined\") return;\n choiceRequestListenerInstalled = true;\n window.addEventListener(\"apex:gwp:choice:request\", () => {\n if (inFlight) choiceReplayRequested = true;\n getPendingGiftChoices().forEach(emitChoicePrompt);\n });\n}\n\n/** Returns live, unanswered gift-choice prompts. This replayable query lets a\n * chooser mounted after `apex:gwp:choice` was dispatched discover the same\n * prompt; pass an experiment id to restrict the result. Returned option\n * arrays are copies and may be safely modified by caller code. */\nexport function getPendingGiftChoices(experimentId?: string): PendingGiftChoice[] {\n if (!latestArgs) return [];\n const prompts: PendingGiftChoice[] = [];\n for (const [activeExperimentId, threshold] of activeChoiceTier) {\n if (experimentId !== undefined && activeExperimentId !== experimentId) continue;\n const selected = selectedGiftOptions.get(activeExperimentId);\n if (selected?.tierThreshold === threshold) continue;\n const experiment = latestArgs.settings?.experiments.find(\n (item) => item.experiment_id === activeExperimentId,\n );\n const variationId = latestArgs.assignments.find(\n (item) => item.experimentId === activeExperimentId,\n )?.variationId;\n const variant = variationId ? experiment?.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") continue;\n const tier = giftTiers(variant as Record<string, unknown>)?.find(\n (candidate) => candidate.threshold === threshold,\n );\n if (!tier?.gift_choice_variant_gids) continue;\n prompts.push({\n experimentId: activeExperimentId,\n threshold,\n options: [...tier.gift_choice_variant_gids],\n });\n }\n return prompts;\n}\n\nexport const __giftManagerInternals = {\n reset() {\n inFlight = null;\n queuedArgs = null;\n insertedThisSession.clear();\n selectedGiftOptions.clear();\n activeChoiceTier.clear();\n dispatchedChoices.clear();\n latestArgs = null;\n teardownChecked = false;\n },\n};\n", "// Keep npm module exports separate from the browser IIFE entry. The IIFE is\n// embedded into the Worker and exposed through the Drip global, while npm\n// consumers need real ESM bindings from the package root.\n//\n// Deliberately NO star re-export of ./index: evaluating the full runtime\n// entry would bootstrap browser side effects for consumers who only want\n// the chooser APIs. And because the hosted drip.js runtime owns the live\n// gift state, these wrappers DELEGATE to window.drip when it is present \u2014\n// an ESM chooser bundled separately must never operate on a second copy of\n// the module state.\nimport {\n getPendingGiftChoices as localGetPendingGiftChoices,\n selectGiftOption as localSelectGiftOption,\n type PendingGiftChoice,\n} from \"./commerce-gift\";\n\ntype ChooserRuntime = {\n getPendingGiftChoices?: (experimentId?: string) => PendingGiftChoice[];\n selectGiftOption?: (\n experimentId: string,\n variantGid: string,\n ) => Promise<void>;\n};\n\nfunction activeRuntime(): ChooserRuntime | undefined {\n return typeof window === \"undefined\"\n ? undefined\n : (window as Window & { drip?: ChooserRuntime }).drip;\n}\n\nexport function getPendingGiftChoices(\n experimentId?: string,\n): PendingGiftChoice[] {\n const runtime = activeRuntime();\n return runtime?.getPendingGiftChoices\n ? runtime.getPendingGiftChoices(experimentId)\n : localGetPendingGiftChoices(experimentId);\n}\n\nexport function selectGiftOption(\n experimentId: string,\n variantGid: string,\n): Promise<void> {\n const runtime = activeRuntime();\n return runtime?.selectGiftOption\n ? runtime.selectGiftOption(experimentId, variantGid)\n : localSelectGiftOption(experimentId, variantGid);\n}\n\nexport type { PendingGiftChoice };\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["export type ShopifyCartMutationHandler = () => void;\n\nconst handlers = new Set<ShopifyCartMutationHandler>();\nlet originalFetch: typeof window.fetch | null = null;\nlet wrappedFetch: typeof window.fetch | null = null;\nlet suppressNotifications = 0;\nlet formListenerInstalled = false;\n\nfunction requestUrl(input: RequestInfo | URL): URL | null {\n try {\n const value = typeof input === \"string\" || input instanceof URL\n ? String(input)\n : input.url;\n return new URL(value, location.href);\n } catch {\n return null;\n }\n}\n\nfunction isShopifyCartMutation(input: RequestInfo | URL): boolean {\n const url = requestUrl(input);\n if (!url || url.origin !== location.origin) return false;\n const configuredRoot = (window as Window & {\n Shopify?: { routes?: { root?: string } };\n }).Shopify?.routes?.root;\n let root = \"\";\n if (typeof configuredRoot === \"string\") {\n try {\n const parsed = new URL(configuredRoot, location.origin);\n if (parsed.origin === location.origin && !parsed.search && !parsed.hash) {\n root = parsed.pathname.replace(/\\/$/, \"\");\n }\n } catch {\n root = \"\";\n }\n }\n const path = url.pathname.replace(/\\/$/, \"\");\n return [\"add\", \"change\", \"update\", \"clear\"].some(\n (action) => path === `${root}/cart/${action}` || path === `${root}/cart/${action}.js`,\n );\n}\n\nfunction notifyHandlers(): void {\n for (const handler of handlers) {\n try {\n handler();\n } catch {\n // Cart observers must never affect the storefront's own mutation flow.\n }\n }\n}\n\nfunction handleFormSubmit(event: Event): void {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !isShopifyCartMutation(form.action)) return;\n queueMicrotask(notifyHandlers);\n}\n\nfunction install(): void {\n if (originalFetch || typeof window.fetch !== \"function\") return;\n originalFetch = window.fetch;\n const nativeFetch = originalFetch.bind(window);\n wrappedFetch = function(...args: Parameters<typeof fetch>) {\n const shouldNotify = suppressNotifications === 0 && isShopifyCartMutation(args[0]);\n const result = nativeFetch(...args);\n if (shouldNotify) {\n result.then((response) => {\n if (response.ok) notifyHandlers();\n }, () => {\n // Observe without changing or consuming the caller's rejection.\n });\n }\n return result;\n };\n window.fetch = wrappedFetch;\n document.addEventListener(\"submit\", handleFormSubmit, true);\n formListenerInstalled = true;\n}\n\nfunction uninstall(): void {\n if (formListenerInstalled) {\n document.removeEventListener(\"submit\", handleFormSubmit, true);\n formListenerInstalled = false;\n }\n if (originalFetch && window.fetch === wrappedFetch) window.fetch = originalFetch;\n wrappedFetch = null;\n originalFetch = null;\n}\n\nexport function onShopifyCartMutation(handler: ShopifyCartMutationHandler): () => void {\n handlers.add(handler);\n install();\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) uninstall();\n };\n}\n\n/**\n * Use for Apex's own cart-attribute write so it does not recursively look like\n * a shopper/theme mutation to subscribers of the shared fetch wrapper.\n */\nexport function fetchWithoutShopifyCartMutationNotification(\n ...args: Parameters<typeof fetch>\n): ReturnType<typeof fetch> {\n suppressNotifications += 1;\n try {\n const activeFetch = typeof window.fetch === \"function\"\n ? window.fetch\n : globalThis.fetch;\n return activeFetch.call(window, ...args);\n } finally {\n suppressNotifications -= 1;\n }\n}\n", "import { fetchWithoutShopifyCartMutationNotification } from \"./shopify-cart-events\";\nimport { normalizeEndpoint } from \"./endpoint\";\n\nconst CART_ATTRIBUTE_SYNC_KEY = \"drip_cart_attr\";\nconst CART_ATTRIBUTE_WRITE_CIRCUIT_BREAK_KEY = \"drip_cart_attr_write_405\";\nconst CART_WRITE_TIMEOUT_MS = 10_000;\n\nexport type CartAssignment = {\n experimentId: string;\n variationId: string;\n assignmentEpoch?: number;\n commercePriceOnly?: boolean;\n pageActive?: boolean;\n};\n\nexport type CartAttributeSyncInput = {\n assignments: CartAssignment[];\n /** Allows an explicit empty control stamp to clear prior treatment attributes. */\n controlStamp?: boolean;\n /** Writes only storefront identity without disturbing existing treatment attributes. */\n identityOnly?: boolean;\n requiredAssignments?: CartAssignment[];\n visitorId: string;\n sessionId: string;\n shopId?: string;\n endpoint?: string;\n ingestSignature?: string;\n url?: string;\n environment?: string;\n platform?: string;\n onAttributionEnvelope?: (envelope: string) => void;\n cartToken?: string;\n offersAuthorizationRevision?: number;\n offersExperimentIds?: string[];\n shippingAuthorizationRevision?: number;\n shippingExperimentIds?: string[];\n priceAuthorizationRevision?: number;\n priceExperimentIds?: string[];\n adoptOffersAssignments?: (assignments: CartAssignment[]) => boolean;\n /** Pre-write feasibility check; must not apply anything. */\n canAdoptOffersAssignments?: (assignments: CartAssignment[]) => boolean;\n /** Headless capture hook: stage a validated offers adoption for {@link confirmCartAttributesWritten}. */\n stageOffersAdoptionAssignments?: (assignments: CartAssignment[]) => void;\n adoptPriceAssignments?: (assignments: CartAssignment[]) => boolean;\n authorizePriceDisplayAssignments?: (assignments: CartAssignment[]) => void;\n authorizeShippingDisplayAssignments?: (assignments: CartAssignment[]) => void;\n authorizeOffersDisplayAssignments?: (assignments: CartAssignment[]) => void;\n /** Receives the normalized Shopify cart token already observed by this sync. */\n onCartToken?: (cartToken: string) => void;\n /** Receives the parsed /cart/update.js response body after a successful stamp write. */\n onCartSnapshot?: (cart: unknown) => void;\n exclusionGroupSelections?: Array<{\n groupId: string;\n experimentId: string;\n epoch: number;\n }>;\n};\n\nexport type CartAttributeEntry = { key: string; value: string };\nexport type CartMetafieldInput = {\n key: string;\n type: string;\n value: string;\n};\n\nexport type CartMetafieldKeyInput = Pick<CartMetafieldInput, \"key\">;\n\nconst APEX_CART_ATTRIBUTE_KEY_VALUES = [\n \"apex_assignments\",\n \"apex_assignment_authorization\",\n \"apex_experiment_id\",\n \"apex_variation_id\",\n \"apex_assignment_epoch\",\n \"apex_visitor_id\",\n \"apex_session_id\",\n] as const;\n\ntype ApexCartAttributeKey = (typeof APEX_CART_ATTRIBUTE_KEY_VALUES)[number];\n\n/** Every Shopify cart attribute key owned and replaced by Apex. */\nexport const APEX_CART_ATTRIBUTE_KEYS: ReadonlySet<string> = new Set(\n APEX_CART_ATTRIBUTE_KEY_VALUES,\n);\n\n/** Normalize Shopify metafield namespaces accepted by the Apex cart mirror. */\nexport function normalizeCartMetafieldNamespace(namespace?: string): string {\n const requestedNamespace = namespace?.trim();\n return requestedNamespace && /^[A-Za-z0-9_-]{2,255}$/.test(requestedNamespace)\n ? requestedNamespace\n : \"apex\";\n}\n\n/** Return every Apex cart-metafield key missing from the supplied mirror. */\nexport function getCartMetafieldStaleKeys(\n currentMetafields: readonly CartMetafieldKeyInput[],\n namespace = \"apex\",\n): string[] {\n const normalizedNamespace = normalizeCartMetafieldNamespace(namespace);\n const currentKeys = new Set(currentMetafields.map(({ key }) => key));\n return APEX_CART_ATTRIBUTE_KEY_VALUES\n .map((key) => `${normalizedNamespace}.${key}`)\n .filter((key) => !currentKeys.has(key));\n}\n\nfunction apexCartAttributeEntry(\n key: ApexCartAttributeKey,\n value: string,\n): CartAttributeEntry {\n return { key, value };\n}\n\nexport type CartAttributeSyncResult = {\n status: \"sent\" | \"skipped\" | \"failed\";\n reason?: string;\n};\n\nexport type CartAttributeSyncOptions = {\n /** Re-write after a real cart mutation even if this assignment was stamped earlier. */\n force?: boolean;\n};\n\nconst inFlightBySignature = new Map<string, {\n generation: number;\n promise: Promise<CartAttributeSyncResult>;\n}>();\nconst validatedAuthorizationSignatures = new Set<string>();\nlet authorizedAssignmentsBySignature: {\n price: Map<string, CartAssignment[]>;\n shipping: Map<string, CartAssignment[]>;\n offers: Map<string, CartAssignment[]>;\n} | null = null;\nlet latestSyncGeneration = 0;\nlet pendingCartAttributeWrite: {\n generation: number;\n submit: (signal: AbortSignal | undefined) => Promise<Response>;\n resolve: (outcome: CartAttributeWriteOutcome) => void;\n} | null = null;\ntype CartAttributeWriterState =\n | { status: \"idle\" }\n | {\n status: \"in-flight\";\n generation: number;\n controller: AbortController | null;\n abortReason: \"cart_write_aborted\" | \"cart_write_timeout\" | null;\n };\nlet cartAttributeWriterState: CartAttributeWriterState = { status: \"idle\" };\nlet cartAttributeWriteCircuitBrokenInMemory = false;\n\n/** Prevent an obsolete authorization response or pending request from reaching the cart. */\nexport function invalidateShopifyCartAttributeSync(): void {\n latestSyncGeneration += 1;\n pendingCartAttributeWrite?.resolve({ status: \"superseded\" });\n pendingCartAttributeWrite = null;\n if (cartAttributeWriterState.status === \"in-flight\") {\n cartAttributeWriterState.abortReason = \"cart_write_aborted\";\n cartAttributeWriterState.controller?.abort();\n }\n}\n\nfunction ensureAuthorizedAssignmentCache() {\n return authorizedAssignmentsBySignature ??= {\n price: new Map<string, CartAssignment[]>(),\n shipping: new Map<string, CartAssignment[]>(),\n offers: new Map<string, CartAssignment[]>(),\n };\n}\n\nfunction requiresCommerceAuthorization(input: CartAttributeSyncInput): boolean {\n return !!input.requiredAssignments?.length\n || input.offersAuthorizationRevision != null\n || input.shippingAuthorizationRevision != null\n || input.priceAuthorizationRevision != null;\n}\n\nfunction hasActivePriceAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.priceAuthorizationRevision != null && !!input.priceExperimentIds?.length;\n}\n\nfunction hasActiveOffersAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.offersAuthorizationRevision != null && !!input.offersExperimentIds?.length;\n}\n\nfunction offersExperimentIds(input: CartAttributeSyncInput): Set<string> {\n return new Set(input.offersExperimentIds ?? []);\n}\n\nfunction shippingExperimentIds(input: CartAttributeSyncInput): Set<string> {\n if (input.shippingExperimentIds?.length) return new Set(input.shippingExperimentIds);\n const priceExperimentIds = new Set(input.priceExperimentIds ?? []);\n return new Set(\n input.requiredAssignments\n ?.filter((assignment) => !priceExperimentIds.has(assignment.experimentId))\n .map((assignment) => assignment.experimentId) ?? [],\n );\n}\n\nfunction hasActiveShippingAuthorizationPlane(input: CartAttributeSyncInput): boolean {\n return input.shippingAuthorizationRevision != null && shippingExperimentIds(input).size > 0;\n}\n\n// Clearing a stale grant is privilege-REDUCING, so shipping- and offers-only\n// shops may clear without a signed token; the price plane always needs one.\nfunction isUnsignedCommerceClearCandidate(input: CartAttributeSyncInput): boolean {\n return input.priceAuthorizationRevision == null &&\n (input.shippingAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null) &&\n !input.requiredAssignments?.length;\n}\n\nexport function cannotRequestRequiredCommerceAuthorization(\n input: CartAttributeSyncInput,\n): boolean {\n return (\n !!input.assignments[0] ||\n input.shippingAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null ||\n hasActivePriceAuthorizationPlane(input)\n )\n && requiresCommerceAuthorization(input)\n && (!input.shopId || !input.endpoint || !input.ingestSignature);\n}\n\nexport function isShopifyStorefront(): boolean {\n return typeof window !== \"undefined\" && !!(window as unknown as { Shopify?: unknown }).Shopify;\n}\n\nfunction readStampedSignature(): string | null {\n try {\n return sessionStorage.getItem(CART_ATTRIBUTE_SYNC_KEY);\n } catch {\n return null;\n }\n}\n\nfunction writeStampedSignature(signature: string): void {\n try {\n sessionStorage.setItem(CART_ATTRIBUTE_SYNC_KEY, signature);\n } catch {\n // Storage can be unavailable in strict privacy modes; the cart write is still best-effort.\n }\n}\n\nfunction cartAttributeWriteCircuitBroken(): boolean {\n if (cartAttributeWriteCircuitBrokenInMemory) return true;\n try {\n cartAttributeWriteCircuitBrokenInMemory =\n sessionStorage.getItem(CART_ATTRIBUTE_WRITE_CIRCUIT_BREAK_KEY) === \"1\";\n return cartAttributeWriteCircuitBrokenInMemory;\n } catch {\n return false;\n }\n}\n\nfunction breakCartAttributeWriteCircuit(): void {\n cartAttributeWriteCircuitBrokenInMemory = true;\n try {\n sessionStorage.setItem(CART_ATTRIBUTE_WRITE_CIRCUIT_BREAK_KEY, \"1\");\n } catch {\n // Storage persistence is best-effort; the module-level breaker remains active.\n }\n}\n\nfunction normalizeTargetingUrl(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n try {\n // The Worker evaluates targeting through URL with the same fallback base.\n // Persist that effective value so equivalent URL spellings share a cache\n // entry while a meaningful SPA route change requests a fresh price pin.\n return new URL(trimmed, \"https://_\").href;\n } catch {\n // The Worker still receives invalid-but-non-empty strings and fails their\n // URL targeting closed, so retain the trimmed value as part of the key.\n return trimmed;\n }\n}\n\nfunction syncSignature(input: CartAttributeSyncInput): string {\n return JSON.stringify({\n ...(input.controlStamp\n ? { mode: \"control\" }\n : input.identityOnly\n ? { mode: \"identity\" }\n : {}),\n assignments: input.assignments\n .map((assignment) => [\n assignment.experimentId,\n assignment.variationId,\n assignment.assignmentEpoch ?? null,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n requiredAssignments: input.requiredAssignments\n ?.map((assignment) => [\n assignment.experimentId,\n assignment.variationId,\n assignment.assignmentEpoch ?? null,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n visitorId: input.visitorId,\n sessionId: input.sessionId,\n url: requiresCommerceAuthorization(input)\n ? normalizeTargetingUrl(input.url)\n : undefined,\n cartToken: normalizeCartToken(input.cartToken),\n ...(input.offersAuthorizationRevision != null || input.offersExperimentIds?.length\n ? {\n offersAuthorizationRevision: input.offersAuthorizationRevision ?? null,\n offersExperimentIds: input.offersExperimentIds?.slice().sort(),\n }\n : {}),\n shippingAuthorizationRevision: input.shippingAuthorizationRevision ?? null,\n shippingExperimentIds: input.shippingExperimentIds?.slice().sort(),\n priceAuthorizationRevision: input.priceAuthorizationRevision ?? null,\n exclusionGroupSelections: input.exclusionGroupSelections\n ?.map((selection) => [\n selection.groupId,\n selection.experimentId,\n selection.epoch,\n ])\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),\n });\n}\n\n/** Validated locale-aware storefront root (\"/fr/\" on localized markets, \"/\" otherwise). */\nexport function shopifyRoutesRoot(): string {\n const configuredRoot = (window as Window & {\n Shopify?: { routes?: { root?: string } };\n }).Shopify?.routes?.root;\n if (typeof configuredRoot !== \"string\") return \"/\";\n try {\n const storefrontOrigin = window.location?.origin;\n if (!storefrontOrigin) return \"/\";\n const parsed = new URL(configuredRoot, storefrontOrigin);\n if (parsed.origin !== storefrontOrigin || parsed.search || parsed.hash) return \"/\";\n return parsed.pathname.endsWith(\"/\") ? parsed.pathname : `${parsed.pathname}/`;\n } catch {\n return \"/\";\n }\n}\n\nexport function shopifyCartPath(): string {\n return `${shopifyRoutesRoot()}cart.js`;\n}\n\nfunction normalizeCartToken(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const withoutQuery = value.trim().split(\"?\", 1)[0] ?? \"\";\n const token = withoutQuery.startsWith(\"gid://shopify/Cart/\")\n ? withoutQuery.slice(\"gid://shopify/Cart/\".length)\n : withoutQuery;\n return token.length > 0 && token.length <= 4096 ? token : null;\n}\n\nfunction normalizeProofCartToken(value: unknown): string | null {\n return typeof value === \"string\" && normalizeCartToken(value) ? value.trim() : null;\n}\n\nfunction observeCartToken(\n input: CartAttributeSyncInput,\n value: unknown,\n): string | null {\n const proofToken = normalizeProofCartToken(value);\n const cartToken = normalizeCartToken(proofToken);\n if (cartToken) {\n try {\n input.onCartToken?.(cartToken);\n } catch {\n // Cart-token observers must not affect cart authorization or writes.\n }\n }\n return proofToken;\n}\n\nasync function resolveChargeAuthorizationCartToken(\n input: CartAttributeSyncInput,\n): Promise<string | null> {\n const supplied = observeCartToken(input, input.cartToken);\n if (supplied) return supplied;\n if (\n input.priceAuthorizationRevision == null &&\n input.offersAuthorizationRevision == null\n ) return null;\n if (!isShopifyStorefront()) return null;\n try {\n const response = await fetchWithoutShopifyCartMutationNotification(shopifyCartPath(), {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n cache: \"no-store\",\n });\n if (!response.ok) return null;\n return observeCartToken(input, ((await response.json()) as { token?: unknown }).token);\n } catch {\n return null;\n }\n}\n\nexport function syncShopifyCartAttributes(\n input: CartAttributeSyncInput,\n options: CartAttributeSyncOptions = {},\n): Promise<CartAttributeSyncResult> {\n try {\n // An empty visitor ID is the fail-closed sentinel for the identity-free\n // consent-pending input built by index.ts.\n const armOnly = input.assignments.length > 0 && !input.visitorId;\n if (!isShopifyStorefront()) return Promise.resolve({ status: \"skipped\", reason: \"not_shopify\" });\n if (typeof fetch !== \"function\") return Promise.resolve({ status: \"skipped\", reason: \"fetch_missing\" });\n if (cartAttributeWriteCircuitBroken()) {\n return Promise.resolve({ status: \"skipped\", reason: \"cart_update_405_circuit_break\" });\n }\n if (\n !input.assignments[0] &&\n !input.controlStamp &&\n !input.identityOnly &&\n !requiresCommerceAuthorization(input)\n ) {\n return Promise.resolve({ status: \"skipped\", reason: \"no_assignments\" });\n }\n if (cannotRequestRequiredCommerceAuthorization(input)) {\n return Promise.resolve({ status: \"failed\", reason: \"authorization_failed\" });\n }\n\n const signature = syncSignature(input);\n const existingInFlight = inFlightBySignature.get(signature);\n if (existingInFlight) {\n if (!options.force && existingInFlight.generation === latestSyncGeneration) {\n return existingInFlight.promise;\n }\n if (!options.force) {\n // A -> B -> A must not attach the final A to the first A. B may write\n // after that request, so enqueue a fresh A once the original settles.\n const continuationGeneration = latestSyncGeneration;\n return existingInFlight.promise.then(() =>\n continuationGeneration === latestSyncGeneration\n ? syncShopifyCartAttributes(input, options)\n : { status: \"skipped\", reason: \"superseded\" },\n );\n }\n // A forced sync follows a real cart mutation, so cart identity may have\n // changed underneath the pending request. Invalidate it immediately;\n // ordinary overlaps still share the existing request.\n latestSyncGeneration += 1;\n const continuationGeneration = latestSyncGeneration;\n return existingInFlight.promise.then(() =>\n continuationGeneration === latestSyncGeneration\n ? syncShopifyCartAttributes(input, options)\n : { status: \"skipped\", reason: \"superseded\" },\n );\n }\n const generation = ++latestSyncGeneration;\n const requiresCurrentAssignmentProof = requiresCommerceAuthorization(input);\n if (\n !options.force &&\n !armOnly &&\n readStampedSignature() === signature &&\n (!requiresCurrentAssignmentProof || validatedAuthorizationSignatures.has(signature))\n ) {\n if (hasActivePriceAuthorizationPlane(input)) {\n input.authorizePriceDisplayAssignments?.(\n authorizedAssignmentsBySignature?.price.get(signature) ?? [],\n );\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n input.authorizeShippingDisplayAssignments?.(\n authorizedAssignmentsBySignature?.shipping.get(signature) ?? [],\n );\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n input.authorizeOffersDisplayAssignments?.(\n authorizedAssignmentsBySignature?.offers.get(signature) ?? [],\n );\n }\n return Promise.resolve({ status: \"sent\", reason: \"already_stamped\" });\n }\n\n if (requiresCurrentAssignmentProof) validatedAuthorizationSignatures.delete(signature);\n let authorizedSignature = signature;\n const pending = requestCommerceAuthorization(input, generation)\n .then(async ({\n completed,\n cartWriteAllowed,\n token,\n assignments,\n offersAdoptionAssignments,\n }): Promise<{\n response: Response | null;\n authorizationCompleted: boolean;\n writeCircuitBroken: boolean;\n authorizedSignature: string;\n authorizedPriceAssignments: CartAssignment[];\n authorizedShippingAssignments: CartAssignment[];\n authorizedOffersAssignments: CartAssignment[];\n offersAdoptionAssignments?: CartAssignment[];\n writeOutcome: CartAttributeWriteOutcome;\n }> => {\n if (generation !== latestSyncGeneration) {\n return {\n response: null,\n authorizationCompleted: completed,\n writeCircuitBroken: false,\n authorizedSignature,\n authorizedPriceAssignments: [],\n authorizedShippingAssignments: [],\n authorizedOffersAssignments: [],\n writeOutcome: { status: \"superseded\" },\n };\n }\n if (!cartWriteAllowed) {\n return {\n response: null,\n authorizationCompleted: false,\n writeCircuitBroken: false,\n authorizedSignature,\n authorizedPriceAssignments: [],\n authorizedShippingAssignments: [],\n authorizedOffersAssignments: [],\n writeOutcome: { status: \"failed\", reason: \"authorization_failed\" },\n };\n }\n const authorizedInput = { ...input, assignments };\n authorizedSignature = syncSignature(authorizedInput);\n const authorizedPriceAssignments = hasActivePriceAuthorizationPlane(input)\n ? parseSignedPriceAuthorizationAssignments(\n token,\n new Set(input.priceExperimentIds ?? []),\n ) ?? []\n : [];\n const authorizedShippingAssignments = hasActiveShippingAuthorizationPlane(input)\n ? parseSignedShippingAuthorizationAssignments(\n token,\n shippingExperimentIds(input),\n ) ?? []\n : [];\n const authorizedOffersAssignments = hasActiveOffersAuthorizationPlane(input)\n ? parseSignedOffersAuthorizationAssignments(\n token,\n offersExperimentIds(input),\n ) ?? []\n : [];\n const attributes = Object.fromEntries(\n buildCartAttributeEntries(authorizedInput, token).map((entry) => [entry.key, entry.value]),\n );\n if (cartAttributeWriteCircuitBroken()) {\n return {\n response: null,\n authorizationCompleted: completed,\n writeCircuitBroken: true,\n authorizedSignature,\n authorizedPriceAssignments,\n authorizedShippingAssignments,\n authorizedOffersAssignments,\n offersAdoptionAssignments,\n writeOutcome: { status: \"failed\", reason: \"cart_update_405_circuit_break\" },\n };\n }\n const writeOutcome = await submitNewestCartAttributeWrite(generation, (signal) =>\n fetchWithoutShopifyCartMutationNotification(\"/cart/update.js\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ attributes }),\n keepalive: true,\n signal,\n })\n );\n const response = writeOutcome.status === \"sent\" ? writeOutcome.response : null;\n if (response?.status === 405) breakCartAttributeWriteCircuit();\n return {\n response,\n authorizationCompleted: completed,\n writeCircuitBroken: false,\n authorizedSignature,\n authorizedPriceAssignments,\n authorizedShippingAssignments,\n authorizedOffersAssignments,\n offersAdoptionAssignments,\n writeOutcome,\n };\n })\n .then(({\n response,\n authorizationCompleted,\n writeCircuitBroken,\n authorizedSignature: completedSignature,\n authorizedPriceAssignments,\n authorizedShippingAssignments,\n authorizedOffersAssignments,\n offersAdoptionAssignments,\n writeOutcome,\n }) => {\n if (writeOutcome.status === \"superseded\") {\n return { status: \"skipped\", reason: \"superseded\" } as const;\n }\n if (writeCircuitBroken) {\n return { status: \"skipped\", reason: \"cart_update_405_circuit_break\" } as const;\n }\n if (writeOutcome.status === \"aborted\" || writeOutcome.status === \"timeout\") {\n return { status: \"failed\", reason: writeOutcome.reason } as const;\n }\n if (writeOutcome.status === \"failed\") {\n return { status: \"failed\", reason: writeOutcome.reason } as const;\n }\n if (!authorizationCompleted) {\n return { status: \"failed\", reason: \"authorization_failed\" } as const;\n }\n if (!response) {\n return { status: \"failed\", reason: \"cart_update_missing\" } as const;\n }\n if (!response.ok) {\n return { status: \"failed\", reason: `cart_update_${response.status}` } as const;\n }\n if (generation === latestSyncGeneration) {\n // The stamp is on the cart; only now may the adopted offer arm reach\n // display/persist/tracking. On failure, skip the signature records so\n // the next sync re-reconciles instead of treating this cart as done.\n if (\n offersAdoptionAssignments &&\n !input.adoptOffersAssignments?.(offersAdoptionAssignments)\n ) {\n return { status: \"failed\", reason: \"offers_adoption_failed\" } as const;\n }\n if (input.onCartSnapshot || input.onCartToken) {\n // The stamp-write response body is the full updated cart \u2014 a free\n // snapshot consumers use to rediscover Apex-managed lines without\n // an extra request (best-effort; never blocks the sync result).\n // Handed out only for the accepted generation AND after a required\n // adoption succeeded: reconciling against local assignments the\n // stamped envelope does not authorize could retain or insert a\n // gift the Function will not discount.\n void response.clone().json()\n .then((cart) => {\n observeCartToken(input, (cart as { token?: unknown })?.token);\n input.onCartSnapshot?.(cart);\n })\n .catch(() => {});\n }\n if (!armOnly) writeStampedSignature(completedSignature);\n if (requiresCurrentAssignmentProof) {\n validatedAuthorizationSignatures.clear();\n validatedAuthorizationSignatures.add(completedSignature);\n }\n if (hasActivePriceAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().price;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedPriceAssignments,\n );\n input.authorizePriceDisplayAssignments?.(authorizedPriceAssignments);\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().shipping;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedShippingAssignments,\n );\n input.authorizeShippingDisplayAssignments?.(authorizedShippingAssignments);\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n const cache = ensureAuthorizedAssignmentCache().offers;\n cache.clear();\n cache.set(\n completedSignature,\n authorizedOffersAssignments,\n );\n input.authorizeOffersDisplayAssignments?.(authorizedOffersAssignments);\n }\n }\n return { status: \"sent\" } as const;\n })\n .catch((error) => ({\n status: \"failed\",\n reason: error instanceof Error ? error.message : String(error),\n }) as const)\n .finally(() => {\n if (inFlightBySignature.get(signature)?.promise === pending) {\n inFlightBySignature.delete(signature);\n }\n });\n inFlightBySignature.set(signature, { generation, promise: pending });\n return pending;\n } catch {\n // Shopify cart attribution must never block or break assignment rendering.\n return Promise.resolve({ status: \"failed\", reason: \"sync_exception\" });\n }\n}\n\nfunction submitNewestCartAttributeWrite(\n generation: number,\n submit: (signal: AbortSignal | undefined) => Promise<Response>,\n): Promise<CartAttributeWriteOutcome> {\n const outcome = new Promise<CartAttributeWriteOutcome>((resolve) => {\n const requested = { generation, submit, resolve };\n if (!pendingCartAttributeWrite || generation >= pendingCartAttributeWrite.generation) {\n pendingCartAttributeWrite?.resolve({ status: \"superseded\" });\n pendingCartAttributeWrite = requested;\n } else {\n resolve({ status: \"superseded\" });\n }\n });\n if (cartAttributeWriterState.status === \"idle\") drainCartAttributeWrites();\n return outcome;\n}\n\nfunction drainCartAttributeWrites(): void {\n if (cartAttributeWriterState.status !== \"idle\") return;\n const requested = pendingCartAttributeWrite;\n pendingCartAttributeWrite = null;\n if (!requested) return;\n if (requested.generation !== latestSyncGeneration) {\n requested.resolve({ status: \"superseded\" });\n drainCartAttributeWrites();\n return;\n }\n\n const controller = typeof AbortController === \"undefined\" ? null : new AbortController();\n const inFlight: Extract<CartAttributeWriterState, { status: \"in-flight\" }> = {\n status: \"in-flight\",\n generation: requested.generation,\n controller,\n abortReason: null,\n };\n cartAttributeWriterState = inFlight;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timeout = setTimeout(() => {\n inFlight.abortReason = \"cart_write_timeout\";\n controller?.abort();\n reject(new Error(\"cart_write_timeout\"));\n }, CART_WRITE_TIMEOUT_MS);\n controller?.signal.addEventListener(\"abort\", () => {\n reject(new Error(inFlight.abortReason ?? \"cart_write_aborted\"));\n }, { once: true });\n });\n\n Promise.race([requested.submit(controller?.signal), deadline])\n .then((response) => {\n requested.resolve(requested.generation === latestSyncGeneration\n ? { status: \"sent\", response }\n : { status: \"superseded\" });\n })\n .catch((error: unknown) => {\n if (inFlight.abortReason === \"cart_write_aborted\") {\n requested.resolve({ status: \"aborted\", reason: \"cart_write_aborted\" });\n } else if (inFlight.abortReason === \"cart_write_timeout\") {\n requested.resolve({ status: \"timeout\", reason: \"cart_write_timeout\" });\n } else {\n requested.resolve({\n status: \"failed\",\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n })\n .finally(() => {\n if (timeout !== undefined) clearTimeout(timeout);\n // Publish idle and consume the pending slot synchronously. An enqueue can\n // therefore observe either this in-flight state or an idle writer that it\n // starts itself, never an unowned pending request.\n cartAttributeWriterState = { status: \"idle\" };\n drainCartAttributeWrites();\n });\n}\n\ntype CartAttributeWriteOutcome =\n | { status: \"sent\"; response: Response }\n | { status: \"superseded\" }\n | { status: \"aborted\"; reason: \"cart_write_aborted\" }\n | { status: \"timeout\"; reason: \"cart_write_timeout\" }\n | { status: \"failed\"; reason: string };\n\nexport async function mintShopwareAttributionEnvelope(input: CartAttributeSyncInput): Promise<void> {\n await requestCommerceAuthorization({ ...input, platform: \"shopware\" });\n}\n\nasync function requestCommerceAuthorization(\n input: CartAttributeSyncInput,\n generation?: number,\n): Promise<{\n completed: boolean;\n cartWriteAllowed: boolean;\n token: string | null;\n assignments: CartAssignment[];\n /** Adoption validated pre-write; the caller applies it only after the cart write is confirmed. */\n offersAdoptionAssignments?: CartAssignment[];\n}> {\n // Page-inactive assignments remain attribution state. Keep them visible\n // while reconciling the Worker's cart pin so the same arm does not look\n // like a new commerce-only assignment that needs adoption.\n let authorizedAssignments = input.assignments;\n const priceWriteRequiresProof = hasActivePriceAuthorizationPlane(input);\n const offersWriteRequiresProof = hasActiveOffersAuthorizationPlane(input);\n const configuredOffersExperimentIds = offersExperimentIds(input);\n const enforceOffersPlaneProof = input.offersAuthorizationRevision != null;\n let requiredOffersAssignments = enforceOffersPlaneProof\n ? input.requiredAssignments?.filter((assignment) =>\n configuredOffersExperimentIds.has(assignment.experimentId)\n ) ?? []\n : [];\n const configuredShippingExperimentIds = shippingExperimentIds(input);\n const enforceShippingPlaneProof = input.shippingAuthorizationRevision != null &&\n (!!input.shippingExperimentIds?.length || hasActivePriceAuthorizationPlane(input));\n const requiredShippingAssignments = enforceShippingPlaneProof\n ? input.requiredAssignments?.filter((assignment) =>\n configuredShippingExperimentIds.has(assignment.experimentId)\n ) ?? []\n : [];\n let hasAuthoritativePriceProof = false;\n if (\n !input.assignments[0] &&\n input.shippingAuthorizationRevision == null &&\n input.offersAuthorizationRevision == null &&\n !hasActivePriceAuthorizationPlane(input)\n ) {\n return {\n completed: true,\n cartWriteAllowed: true,\n token: null,\n assignments: input.assignments,\n };\n }\n if (!input.shopId || !input.endpoint || !input.ingestSignature) {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: true,\n token: null,\n assignments: input.assignments,\n };\n }\n const endpoint = normalizeEndpoint(input.endpoint);\n try {\n const cartToken = await resolveChargeAuthorizationCartToken(input);\n if (\n (input.priceAuthorizationRevision != null ||\n input.offersAuthorizationRevision != null) &&\n !cartToken\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n const requestedExperimentIds = [...new Set([\n ...authorizedAssignments.map((assignment) => assignment.experimentId),\n ...(hasActivePriceAuthorizationPlane(input) ? input.priceExperimentIds ?? [] : []),\n ...(hasActiveOffersAuthorizationPlane(input) ? input.offersExperimentIds ?? [] : []),\n ])];\n const response = await fetch(`${endpoint}/v1/assign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Drip-Shop\": input.shopId,\n \"X-Drip-Signature\": input.ingestSignature,\n },\n body: JSON.stringify({\n shop_id: input.shopId,\n user_id: input.visitorId,\n url: input.url,\n environment: input.environment,\n platform: input.platform,\n ...(cartToken\n ? {\n cart_token: cartToken,\n // Ask the authenticated Worker to persist its signed decision in\n // the Function-owned cart plane before the client fallback runs.\n persist_function_handoff: true,\n }\n : {}),\n experiments: requestedExperimentIds,\n exclusion_group_selections: input.exclusionGroupSelections?.map((selection) => ({\n group_id: selection.groupId,\n experiment_id: selection.experimentId,\n epoch: selection.epoch,\n })),\n }),\n keepalive: true,\n });\n if (!response.ok) {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof &&\n !isUnsignedCommerceClearCandidate(input),\n token: null,\n assignments: input.assignments,\n };\n }\n const payload = (await response.json()) as {\n assignments?: unknown;\n commerce_authorization?: unknown;\n handoff_persisted?: boolean;\n handoff_stale_risk?: boolean;\n attribution_envelope?: unknown;\n };\n if (typeof payload.attribution_envelope === \"string\" && payload.attribution_envelope.length <= 4096) {\n input.onAttributionEnvelope?.(payload.attribution_envelope);\n }\n const token =\n typeof payload.commerce_authorization === \"string\" &&\n payload.commerce_authorization\n ? payload.commerce_authorization\n : null;\n if (\n !input.assignments[0] &&\n isUnsignedCommerceClearCandidate(input) &&\n // With configured offer experiments, a nonempty response is the\n // cart-pinned offer arm returning under a new identity/off-target page \u2014\n // it must flow to the offers proof/adoption path below, not be rejected\n // as a failed stale-clear. Empty-response teardown clears are unaffected.\n !input.offersExperimentIds?.length &&\n (!Array.isArray(payload.assignments) || payload.assignments.length !== 0)\n ) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n let requiredAssignments = input.requiredAssignments;\n let resolvedPriceAssignments: ReturnType<\n typeof resolveAuthoritativePriceAssignments\n > = null;\n let resolvedOffersAssignments: ReturnType<\n typeof resolveAuthoritativeOffersAssignments\n > = null;\n if (input.priceAuthorizationRevision != null) {\n const priceExperimentIds = new Set(input.priceExperimentIds ?? []);\n const evaluatedPriceExperimentIds = parseEvaluatedPriceExperimentIds(\n token,\n priceExperimentIds,\n );\n if (!evaluatedPriceExperimentIds) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n if (priceExperimentIds.size > 0) {\n resolvedPriceAssignments = resolveAuthoritativePriceAssignments(\n payload.assignments,\n authorizedAssignments,\n evaluatedPriceExperimentIds,\n );\n if (!resolvedPriceAssignments) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n authorizedAssignments = resolvedPriceAssignments.assignments;\n requiredAssignments = input.requiredAssignments?.flatMap((required) => {\n if (!priceExperimentIds.has(required.experimentId)) return [required];\n // A browser-only price experiment outside the Worker's evaluated set\n // cannot be signed. Keep its local assignment/cart attribution, but do\n // not demand impossible price proof: display eligibility is separately\n // restricted to assignments actually present in the signed price plane.\n if (!evaluatedPriceExperimentIds.has(required.experimentId)) return [];\n const authoritative = resolvedPriceAssignments?.priceAssignments.find(\n (assignment) => assignment.experimentId === required.experimentId,\n );\n return authoritative ? [authoritative] : [];\n });\n }\n const requiredPriceAssignments = requiredAssignments?.filter((assignment) =>\n priceExperimentIds.has(assignment.experimentId)\n ) ?? [];\n const requiresNonPriceAssignment = requiredAssignments?.some((assignment) =>\n !priceExperimentIds.has(assignment.experimentId)\n );\n const pricePlane = parsePriceAuthorizationPlane(token);\n hasAuthoritativePriceProof =\n resolvedPriceAssignments?.priceAssignments.length === 0\n ? hasSignedEmptyPriceAuthorizationEnvelope(token)\n : !!pricePlane && hasSignedAssignmentProof(\n pricePlane.a,\n resolvedPriceAssignments?.priceAssignments ?? [],\n );\n // Round 17 permits an unsigned successful response to clear shipping proof\n // because removing a shipping grant is privilege-reducing. Price differs:\n // replacing a signed price pin changes what checkout charges, so an active\n // price plane must have a signed assignment or signed-empty envelope before\n // any combined cart-attribute write can touch the existing pin.\n if (\n (priceWriteRequiresProof && !hasAuthoritativePriceProof) ||\n (requiredPriceAssignments.length > 0 &&\n (!pricePlane || !hasSignedAssignmentProof(pricePlane.a, requiredPriceAssignments))) ||\n (resolvedPriceAssignments?.adoptionRequired &&\n !hasAuthoritativePriceProof) ||\n (!token && requiresNonPriceAssignment)\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n } else if (requiresCommerceAuthorization(input) && !token) {\n const isLegitimateEmptyShippingResponse =\n isUnsignedCommerceClearCandidate(input) &&\n Array.isArray(payload.assignments) &&\n (!!input.assignments[0] || payload.assignments.length === 0);\n if (!isLegitimateEmptyShippingResponse) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n // Clearing a stale shipping discount grant is privilege-reducing and needs no\n // signature; unlike conferring a grant, the successful parsed response is enough.\n }\n if (enforceOffersPlaneProof && configuredOffersExperimentIds.size > 0) {\n resolvedOffersAssignments = resolveAuthoritativeOffersAssignments(\n payload.assignments,\n authorizedAssignments,\n configuredOffersExperimentIds,\n );\n if (!resolvedOffersAssignments) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n authorizedAssignments = resolvedOffersAssignments.assignments;\n requiredOffersAssignments = resolvedOffersAssignments.offersAssignments;\n requiredAssignments = requiredAssignments?.flatMap((required) => {\n if (!configuredOffersExperimentIds.has(required.experimentId)) return [required];\n const authoritative = resolvedOffersAssignments?.offersAssignments.find(\n (assignment) => assignment.experimentId === required.experimentId,\n );\n return authoritative ? [authoritative] : [];\n });\n }\n if (requiredOffersAssignments.length > 0) {\n const offersPlane = parseAuthorizationPlane(token, \"offers\");\n const hasOffersProof = !!offersPlane && hasSignedAssignmentProof(\n offersPlane.a,\n requiredOffersAssignments,\n );\n if (!hasOffersProof) {\n if (!priceWriteRequiresProof || !hasAuthoritativePriceProof) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n const configuredPriceExperimentIds = new Set(input.priceExperimentIds ?? []);\n authorizedAssignments = authorizedAssignments.filter((assignment) =>\n !configuredOffersExperimentIds.has(assignment.experimentId) ||\n configuredPriceExperimentIds.has(assignment.experimentId)\n );\n }\n }\n if (requiredShippingAssignments.length > 0) {\n const shippingPlane = parseAuthorizationPlane(token, \"shipping\");\n const hasShippingProof = !!shippingPlane && hasSignedAssignmentProof(\n shippingPlane.a,\n requiredShippingAssignments,\n );\n if (!hasShippingProof) {\n if (!priceWriteRequiresProof || !hasAuthoritativePriceProof) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n // A proven price plane may proceed independently. Remove shipping-only\n // assignments from the stamp; assignments shared with price remain because\n // checkout validates each plane from its own signature.\n const configuredPriceExperimentIds = new Set(input.priceExperimentIds ?? []);\n authorizedAssignments = authorizedAssignments.filter((assignment) =>\n !configuredShippingExperimentIds.has(assignment.experimentId) ||\n configuredPriceExperimentIds.has(assignment.experimentId)\n );\n }\n }\n if (!hasRequiredAssignmentProof(payload.assignments, requiredAssignments)) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n if (\n resolvedPriceAssignments?.adoptionRequired &&\n ((generation !== undefined && generation !== latestSyncGeneration) ||\n !input.adoptPriceAssignments?.(\n authorizedAssignments.filter((assignment) =>\n input.priceExperimentIds?.includes(assignment.experimentId)\n ),\n ))\n ) {\n return {\n completed: false,\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof,\n token: null,\n assignments: input.assignments,\n };\n }\n let offersAdoptionAssignments: CartAssignment[] | undefined;\n if (resolvedOffersAssignments?.adoptionRequired) {\n const adoptable = authorizedAssignments.filter((assignment) =>\n configuredOffersExperimentIds.has(assignment.experimentId)\n );\n // Only validate feasibility here. Applying the adoption (DOM, persist,\n // tracking) before the cart write is confirmed would let the shopper see\n // and record a treatment Shopify never stamped an authorization for \u2014\n // the caller applies it post-write (theme) or post-confirm (headless).\n if (\n (generation !== undefined && generation !== latestSyncGeneration) ||\n !input.canAdoptOffersAssignments?.(adoptable)\n ) {\n return {\n completed: false,\n cartWriteAllowed: false,\n token: null,\n assignments: input.assignments,\n };\n }\n offersAdoptionAssignments = adoptable;\n }\n return {\n completed: true,\n cartWriteAllowed: true,\n token,\n assignments: authorizedAssignments,\n offersAdoptionAssignments,\n };\n } catch {\n return {\n completed: !requiresCommerceAuthorization(input),\n cartWriteAllowed: !priceWriteRequiresProof && !offersWriteRequiresProof &&\n !isUnsignedCommerceClearCandidate(input),\n token: null,\n assignments: input.assignments,\n };\n }\n}\n\nfunction hasSignedEmptyPriceAuthorizationEnvelope(token: string | null): boolean {\n if (!token) return false;\n try {\n const envelope = JSON.parse(token) as Record<string, unknown> | null;\n if (\n !envelope ||\n typeof envelope !== \"object\" ||\n envelope.v !== 2 ||\n !envelope.p ||\n typeof envelope.p !== \"object\"\n ) return false;\n const price = (envelope.p as Record<string, unknown>).price;\n if (!price || typeof price !== \"object\") return false;\n const plane = price as Record<string, unknown>;\n return plane.v === 1 &&\n Number.isSafeInteger(plane.r) && (plane.r as number) > 0 &&\n typeof plane.c === \"string\" && /^[a-f0-9]{64}$/.test(plane.c) &&\n typeof plane.x === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(plane.x) &&\n Array.isArray(plane.a) && plane.a.length === 0 &&\n typeof plane.cartHash === \"string\" && /^[a-f0-9]{64}$/.test(plane.cartHash) &&\n typeof plane.s === \"string\" && /^[a-f0-9]{64}$/.test(plane.s);\n } catch {\n return false;\n }\n}\n\nfunction parsePriceAuthorizationPlane(\n token: string | null,\n): Record<string, unknown> | null {\n if (!token) return null;\n try {\n const candidate = JSON.parse(token) as Record<string, unknown> | null;\n if (!candidate || typeof candidate !== \"object\") return null;\n if (\n candidate.v === 1 &&\n typeof candidate.cartHash === \"string\" &&\n candidate.cartHash\n ) {\n return candidate;\n }\n if (candidate.v !== 2 || !candidate.p || typeof candidate.p !== \"object\") return null;\n const price = (candidate.p as Record<string, unknown>).price;\n return price && typeof price === \"object\"\n ? price as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseAuthorizationPlane(\n token: string | null,\n plane: \"shipping\" | \"offers\",\n): Record<string, unknown> | null {\n if (!token) return null;\n try {\n const candidate = JSON.parse(token) as Record<string, unknown> | null;\n if (candidate?.v !== 2 || !candidate.p || typeof candidate.p !== \"object\") return null;\n const value = (candidate.p as Record<string, unknown>)[plane];\n return value && typeof value === \"object\"\n ? value as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseSignedPriceAuthorizationAssignments(\n token: string | null,\n configuredPriceExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const pricePlane = parsePriceAuthorizationPlane(token);\n if (!pricePlane || !Array.isArray(pricePlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of pricePlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredPriceExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseSignedShippingAuthorizationAssignments(\n token: string | null,\n configuredShippingExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const shippingPlane = parseAuthorizationPlane(token, \"shipping\");\n if (!shippingPlane || !Array.isArray(shippingPlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of shippingPlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredShippingExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseSignedOffersAuthorizationAssignments(\n token: string | null,\n configuredOffersExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n const offersPlane = parseAuthorizationPlane(token, \"offers\");\n if (!offersPlane || !Array.isArray(offersPlane.a)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of offersPlane.a) {\n if (!candidate || typeof candidate !== \"object\") return null;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.e !== \"string\" ||\n !configuredOffersExperimentIds.has(assignment.e) ||\n seen.has(assignment.e) ||\n typeof assignment.v !== \"string\" ||\n !assignment.v ||\n !Number.isSafeInteger(assignment.ep) ||\n (assignment.ep as number) < 1\n ) return null;\n seen.add(assignment.e);\n assignments.push({\n experimentId: assignment.e,\n variationId: assignment.v,\n assignmentEpoch: assignment.ep as number,\n });\n }\n return assignments;\n}\n\nfunction parseEvaluatedPriceExperimentIds(\n token: string | null,\n configuredPriceExperimentIds: ReadonlySet<string>,\n): Set<string> | null {\n const rolloutFallback = () => new Set(configuredPriceExperimentIds);\n if (!token) return rolloutFallback();\n try {\n const envelope = JSON.parse(token) as Record<string, unknown> | null;\n if (!envelope || typeof envelope !== \"object\" || envelope.v !== 2) {\n return rolloutFallback();\n }\n if (!Object.prototype.hasOwnProperty.call(envelope, \"e\")) {\n // Rollout ordering matters: deploy the Worker before this SDK. Until every\n // response declares `e`, an older V2 envelope must retain round-14 behavior\n // and treat every configured price experiment as evaluated.\n return rolloutFallback();\n }\n if (!Array.isArray(envelope.e)) return null;\n const evaluated = new Set<string>();\n for (const experimentId of envelope.e) {\n if (\n typeof experimentId !== \"string\" ||\n !experimentId ||\n !configuredPriceExperimentIds.has(experimentId) ||\n evaluated.has(experimentId)\n ) return null;\n evaluated.add(experimentId);\n }\n return evaluated;\n } catch {\n return rolloutFallback();\n }\n}\n\nfunction hasSignedAssignmentProof(\n signedAssignments: unknown,\n requiredAssignments: NonNullable<CartAttributeSyncInput[\"requiredAssignments\"]>,\n): boolean {\n if (!Array.isArray(signedAssignments)) return false;\n return requiredAssignments.every((required) =>\n signedAssignments.some((candidate) => {\n if (!candidate || typeof candidate !== \"object\") return false;\n const assignment = candidate as Record<string, unknown>;\n return assignment.e === required.experimentId &&\n assignment.v === required.variationId &&\n assignment.ep === required.assignmentEpoch;\n })\n );\n}\n\nfunction parseResponsePriceAssignments(\n responseAssignments: unknown,\n priceExperimentIds: ReadonlySet<string>,\n): CartAssignment[] | null {\n if (!Array.isArray(responseAssignments)) return null;\n const assignments: CartAssignment[] = [];\n const seen = new Set<string>();\n for (const candidate of responseAssignments) {\n if (!candidate || typeof candidate !== \"object\") continue;\n const assignment = candidate as Record<string, unknown>;\n if (\n typeof assignment.experiment_id !== \"string\" ||\n !priceExperimentIds.has(assignment.experiment_id)\n ) {\n continue;\n }\n if (\n seen.has(assignment.experiment_id) ||\n typeof assignment.variation_id !== \"string\" ||\n !assignment.variation_id ||\n !Number.isSafeInteger(assignment.assignment_epoch) ||\n (assignment.assignment_epoch as number) < 1\n ) return null;\n seen.add(assignment.experiment_id);\n assignments.push({\n experimentId: assignment.experiment_id,\n variationId: assignment.variation_id,\n assignmentEpoch: assignment.assignment_epoch as number,\n ...(assignment.commerce_price_only === true ? { commercePriceOnly: true } : {}),\n });\n }\n return assignments;\n}\n\nfunction resolveAuthoritativePriceAssignments(\n responseAssignments: unknown,\n localAssignments: CartAttributeSyncInput[\"assignments\"],\n evaluatedPriceExperimentIds: ReadonlySet<string>,\n): {\n assignments: CartAssignment[];\n priceAssignments: CartAssignment[];\n adoptionRequired: boolean;\n} | null {\n const priceAssignments = parseResponsePriceAssignments(\n responseAssignments,\n evaluatedPriceExperimentIds,\n );\n if (!priceAssignments) return null;\n const localPriceAssignments = localAssignments.filter((assignment) =>\n evaluatedPriceExperimentIds.has(assignment.experimentId)\n );\n const authoritativeByExperimentId = new Map(\n priceAssignments.map((assignment) => [assignment.experimentId, assignment]),\n );\n const adoptionRequired = localPriceAssignments.some(\n (assignment) => assignment.pageActive !== false &&\n !authoritativeByExperimentId.has(assignment.experimentId),\n ) || priceAssignments.some((assignment) => {\n const local = localPriceAssignments.find(\n (candidate) => candidate.experimentId === assignment.experimentId,\n );\n return local?.variationId !== assignment.variationId ||\n local.assignmentEpoch !== assignment.assignmentEpoch ||\n (local?.pageActive !== false &&\n local.commercePriceOnly !== assignment.commercePriceOnly);\n });\n const remainingPriceAssignments = new Map(authoritativeByExperimentId);\n const assignments = localAssignments.map((assignment) => {\n if (!evaluatedPriceExperimentIds.has(assignment.experimentId)) return assignment;\n const authoritative = remainingPriceAssignments.get(assignment.experimentId);\n if (!authoritative) return assignment.pageActive === false ? assignment : null;\n remainingPriceAssignments.delete(assignment.experimentId);\n return assignment.pageActive === undefined\n ? authoritative\n : { ...authoritative, pageActive: assignment.pageActive };\n }).filter((assignment): assignment is CartAssignment => assignment !== null);\n for (const assignment of priceAssignments) {\n if (remainingPriceAssignments.has(assignment.experimentId)) {\n assignments.push(assignment);\n remainingPriceAssignments.delete(assignment.experimentId);\n }\n }\n return { assignments, priceAssignments, adoptionRequired };\n}\n\nfunction resolveAuthoritativeOffersAssignments(\n responseAssignments: unknown,\n localAssignments: CartAttributeSyncInput[\"assignments\"],\n configuredOffersExperimentIds: ReadonlySet<string>,\n): {\n assignments: CartAssignment[];\n offersAssignments: CartAssignment[];\n adoptionRequired: boolean;\n} | null {\n const offersAssignments = parseResponsePriceAssignments(\n responseAssignments,\n configuredOffersExperimentIds,\n );\n if (!offersAssignments) return null;\n const resolved = resolveAuthoritativePriceAssignments(\n responseAssignments,\n localAssignments,\n configuredOffersExperimentIds,\n );\n return resolved && {\n assignments: resolved.assignments,\n offersAssignments,\n adoptionRequired: resolved.adoptionRequired,\n };\n}\n\nexport async function buildSignedCartAttributeEntries(\n input: CartAttributeSyncInput,\n): Promise<CartAttributeEntry[]> {\n const generation = ++latestSyncGeneration;\n const authorization = await requestCommerceAuthorization(input, generation);\n if (generation !== latestSyncGeneration || !authorization.completed) return [];\n if (authorization.offersAdoptionAssignments) {\n // Headless integrators own the cart write; hand the validated adoption to\n // the caller so it applies only after confirmCartAttributesWritten.\n input.stageOffersAdoptionAssignments?.(authorization.offersAdoptionAssignments);\n }\n if (hasActivePriceAuthorizationPlane(input)) {\n const authorizedPriceAssignments = parseSignedPriceAuthorizationAssignments(\n authorization.token,\n new Set(input.priceExperimentIds ?? []),\n );\n if (!authorizedPriceAssignments) return [];\n input.authorizePriceDisplayAssignments?.(authorizedPriceAssignments);\n }\n if (hasActiveShippingAuthorizationPlane(input)) {\n const authorizedShippingAssignments = parseSignedShippingAuthorizationAssignments(\n authorization.token,\n shippingExperimentIds(input),\n ) ?? [];\n input.authorizeShippingDisplayAssignments?.(authorizedShippingAssignments);\n }\n if (hasActiveOffersAuthorizationPlane(input)) {\n const authorizedOffersAssignments = parseSignedOffersAuthorizationAssignments(\n authorization.token,\n offersExperimentIds(input),\n ) ?? [];\n input.authorizeOffersDisplayAssignments?.(authorizedOffersAssignments);\n }\n return buildCartAttributeEntries(\n { ...input, assignments: authorization.assignments },\n authorization.token,\n );\n}\n\nfunction hasRequiredAssignmentProof(\n responseAssignments: unknown,\n requiredAssignments: CartAttributeSyncInput[\"requiredAssignments\"],\n): boolean {\n if (!requiredAssignments?.length) return true;\n if (!Array.isArray(responseAssignments)) return false;\n\n return requiredAssignments.every((required) => {\n if (!Number.isInteger(required.assignmentEpoch) || (required.assignmentEpoch ?? 0) < 1) {\n return false;\n }\n return responseAssignments.some((candidate) => {\n if (!candidate || typeof candidate !== \"object\") return false;\n const assignment = candidate as Record<string, unknown>;\n return assignment.experiment_id === required.experimentId\n && assignment.variation_id === required.variationId\n && assignment.assignment_epoch === required.assignmentEpoch;\n });\n });\n}\n\nexport function buildCartAttributeEntries(\n input: CartAttributeSyncInput,\n commerceAuthorization: string | null = null,\n): CartAttributeEntry[] {\n // An empty visitor ID marks the identity-free consent-pending stamp. Empty\n // named keys actively replace identity left by an earlier consented visit.\n if (input.assignments.length > 0 && !input.visitorId) {\n return [\n apexCartAttributeEntry(\n \"apex_assignments\",\n JSON.stringify(input.assignments.map((assignment) => ({\n e: assignment.experimentId,\n v: assignment.variationId,\n ep: assignment.assignmentEpoch,\n }))),\n ),\n apexCartAttributeEntry(\"apex_assignment_authorization\", \"\"),\n apexCartAttributeEntry(\"apex_experiment_id\", \"\"),\n apexCartAttributeEntry(\"apex_variation_id\", \"\"),\n apexCartAttributeEntry(\"apex_assignment_epoch\", \"\"),\n apexCartAttributeEntry(\"apex_visitor_id\", \"\"),\n apexCartAttributeEntry(\"apex_session_id\", \"\"),\n ];\n }\n if (input.identityOnly) {\n return [apexCartAttributeEntry(\"apex_visitor_id\", input.visitorId)];\n }\n const primaryAssignment = input.assignments[0];\n const entries: CartAttributeEntry[] = [\n apexCartAttributeEntry(\n \"apex_assignments\",\n JSON.stringify(\n input.assignments.map((assignment) => ({\n e: assignment.experimentId,\n v: assignment.variationId,\n ep: assignment.assignmentEpoch,\n })),\n ),\n ),\n apexCartAttributeEntry(\"apex_assignment_authorization\", commerceAuthorization ?? \"\"),\n ];\n if (!primaryAssignment) {\n return [\n ...entries,\n apexCartAttributeEntry(\"apex_experiment_id\", \"\"),\n apexCartAttributeEntry(\"apex_variation_id\", \"\"),\n apexCartAttributeEntry(\"apex_assignment_epoch\", \"\"),\n apexCartAttributeEntry(\"apex_visitor_id\", input.visitorId),\n apexCartAttributeEntry(\"apex_session_id\", input.sessionId),\n ];\n }\n return [\n ...entries,\n apexCartAttributeEntry(\"apex_experiment_id\", primaryAssignment.experimentId),\n apexCartAttributeEntry(\"apex_variation_id\", primaryAssignment.variationId),\n apexCartAttributeEntry(\n \"apex_assignment_epoch\",\n String(primaryAssignment.assignmentEpoch ?? \"\"),\n ),\n apexCartAttributeEntry(\"apex_visitor_id\", input.visitorId),\n apexCartAttributeEntry(\"apex_session_id\", input.sessionId),\n ];\n}\n\n/** Build a Storefront API cart-metafield mirror of the canonical attribution stamp. */\nexport function buildCartMetafieldInputs(\n input: CartAttributeSyncInput,\n namespace = \"apex\",\n): CartMetafieldInput[] {\n const normalizedNamespace = normalizeCartMetafieldNamespace(namespace);\n return buildCartAttributeEntries(input)\n .filter(({ value }) => value !== \"\")\n .map(({ key, value }) => ({\n key: `${normalizedNamespace}.${key}`,\n type: key === \"apex_assignments\" ? \"json\" : \"single_line_text_field\",\n value,\n }));\n}\n", "import { fetchWithoutShopifyCartMutationNotification } from \"./shopify-cart-events\";\nimport {\n isShopifyStorefront,\n shopifyCartPath,\n shopifyRoutesRoot,\n type CartAssignment,\n} from \"./shopify-cart\";\nimport type { CommerceOffersSettings } from \"./types\";\n\nconst MARKER = \"_apex_gwp\";\nconst DECLINE_PREFIX = \"drip_gwp_declined:\";\nconst INSERTED_PREFIX = \"drip_gwp_inserted:\";\n\ntype CartLine = {\n key?: unknown;\n quantity?: unknown;\n variant_id?: unknown;\n final_line_price?: unknown;\n line_price?: unknown;\n properties?: unknown;\n};\ntype ShopifyCart = { token?: unknown; currency?: unknown; items?: unknown };\n\n// Record value when the cart's identity is unknown \u2014 matches any cart.\nconst ANY_CART = \"*\";\n\nexport type GiftSyncOptions = {\n /** Per-arm proof from the CURRENT stamped offers plane. Insertion requires\n * it: config membership alone can cover an assignment whose offers proof\n * was refused (e.g. shared price+offers experiment with price-only proof),\n * and the Function would charge that \"gift\" at full price. */\n isArmProven?: (assignment: CartAssignment) => boolean;\n /** A cart mutation invalidates the once-per-page teardown check. */\n recheckTeardown?: boolean;\n /** Fail-closed reconciliation after a FAILED stamp sync: privilege-reducing\n * cleanup only (threshold/currency/orphan removals, quantity normalization)\n * \u2014 never insert, because no fresh proof exists for a new line. */\n suppressInsertion?: boolean;\n};\n\nlet inFlight: Promise<void> | null = null;\nlet queuedArgs: {\n settings: CommerceOffersSettings | undefined;\n assignments: CartAssignment[];\n options: GiftSyncOptions | undefined;\n generation: number;\n} | null = null;\n// In-memory fallback when localStorage is unavailable (strict privacy modes).\nconst insertedThisSession = new Map<string, string>();\nconst selectedGiftOptions = new Map<string, { gid: string; tierThreshold: string; pending?: boolean }>();\nconst dispatchedChoices = new Set<string>();\n// The choice tier currently applicable per experiment \u2014 selections bind to\n// THIS tier, not to whichever prompts were dispatched earlier in the session.\nconst activeChoiceTier = new Map<string, string>();\n// Bumped whenever new sync arguments are accepted: an in-flight pass built\n// on older arguments must not repopulate tier liveness after the clear.\nlet choiceTierGeneration = 0;\nlet latestArgs: { settings: CommerceOffersSettings | undefined; assignments: CartAssignment[]; options?: GiftSyncOptions } | null = null;\n\ntype GiftTier = { threshold: string; gift_variant_gid: string; gift_choice_variant_gids?: string[] };\nexport type PendingGiftChoice = {\n experimentId: string;\n threshold: string;\n options: string[];\n};\nfunction giftTiers(variant: Record<string, unknown>): GiftTier[] | null {\n const raw = Array.isArray(variant.tiers) && variant.tiers.length > 0\n ? variant.tiers\n : [{ threshold: variant.threshold, gift_variant_gid: variant.gift_variant_gid }];\n const tiers: GiftTier[] = [];\n for (const item of raw) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return null;\n const tier = item as Record<string, unknown>;\n if (typeof tier.threshold !== \"string\" || !/^\\d+$/.test(tier.threshold) ||\n typeof tier.gift_variant_gid !== \"string\" ||\n // Malformed fixed-tier GIDs would reach choice dispatch with a null\n // giftId and abort reconciliation mid-run; money-path config fails\n // closed at parse time instead.\n numericVariantId(tier.gift_variant_gid) === null) return null;\n const choices = tier.gift_choice_variant_gids;\n if (choices !== undefined && (!Array.isArray(choices) || choices.length < 2 || choices.length > 4 ||\n !choices.every((gid) => typeof gid === \"string\" && numericVariantId(gid) !== null))) return null;\n tiers.push({ threshold: tier.threshold, gift_variant_gid: tier.gift_variant_gid,\n ...(choices ? { gift_choice_variant_gids: choices as string[] } : {}) });\n }\n if (tiers.length < 1 || tiers.length > 3 || tiers.some((tier, index) =>\n !Number.isSafeInteger(Number(tier.threshold)) ||\n (index > 0 && Number(tier.threshold) <= Number(tiers[index - 1].threshold)))) return null;\n return tiers;\n}\n\nfunction declined(experimentId: string): boolean {\n try { return sessionStorage.getItem(`${DECLINE_PREFIX}${experimentId}`) === \"1\"; } catch { return false; }\n}\n\nfunction recordDecline(experimentId: string): void {\n try { sessionStorage.setItem(`${DECLINE_PREFIX}${experimentId}`, \"1\"); } catch { /* best effort */ }\n}\n\n// Insertion state persists in LOCAL storage: the Shopify cart itself is\n// durable across browser sessions, so the record of \"Apex added this line\"\n// must outlive the session too \u2014 otherwise a shopper returning after the\n// offer stopped would keep a no-longer-discounted gift (teardown cleanup\n// could not know to look), and a manual removal in a fresh session would\n// read as \"never inserted\" and re-add the gift. The record VALUE is the cart\n// token the insertion happened on, so a normal cart transition (checkout\n// emptied the old cart) is not misread as a manual decline on the new one.\n// This is charge-correctness state (experiment id + cart token), like the\n// cart attribute stamp, not analytics. Decline flags stay session-scoped.\nfunction insertionRecord(experimentId: string): string | null {\n try {\n return localStorage.getItem(`${INSERTED_PREFIX}${experimentId}`)\n ?? insertedThisSession.get(experimentId) ?? null;\n } catch { return insertedThisSession.get(experimentId) ?? null; }\n}\n\nfunction recordCart(record: string): string {\n if (!record.startsWith(\"{\")) return record;\n try { const parsed = JSON.parse(record) as { cart?: unknown }; return typeof parsed.cart === \"string\" ? parsed.cart : record; } catch { return record; }\n}\nfunction recordVariantGid(record: string): string | null {\n if (!record.startsWith(\"{\")) return null;\n try {\n const parsed = JSON.parse(record) as { variantGid?: unknown };\n return typeof parsed.variantGid === \"string\" ? parsed.variantGid : null;\n } catch { return null; }\n}\nfunction recordTierThreshold(record: string): string | null {\n if (!record.startsWith(\"{\")) return null;\n try {\n const parsed = JSON.parse(record) as { tierThreshold?: unknown };\n return typeof parsed.tierThreshold === \"string\" ? parsed.tierThreshold : null;\n } catch { return null; }\n}\nfunction insertionMatchesCart(record: string, cartToken: string | null): boolean {\n const token = recordCart(record);\n return token === ANY_CART || cartToken == null || token === cartToken;\n}\n\nfunction recordInserted(experimentId: string, cartToken: string | null, variantGid?: string, tierThreshold?: string): void {\n const token = cartToken ?? ANY_CART;\n const value = variantGid || tierThreshold ? JSON.stringify({ cart: token, variantGid, tierThreshold }) : token;\n insertedThisSession.set(experimentId, value);\n try { localStorage.setItem(`${INSERTED_PREFIX}${experimentId}`, value); } catch { /* best effort */ }\n // The durable record landing IS the settle point for a pending selection \u2014\n // waiting for another reconciliation pass leaves a last-pass add pending\n // forever and re-opens the cross-tab revert.\n const selection = selectedGiftOptions.get(experimentId);\n if (selection?.pending && selection.gid === variantGid) {\n selectedGiftOptions.set(experimentId, {\n gid: selection.gid,\n tierThreshold: selection.tierThreshold,\n });\n }\n}\n\nfunction clearInserted(experimentId: string): void {\n insertedThisSession.delete(experimentId);\n try { localStorage.removeItem(`${INSERTED_PREFIX}${experimentId}`); } catch { /* best effort */ }\n}\n\nfunction clearDispatchedChoices(experimentId: string): void {\n dispatchedChoices.forEach((key) => {\n if (key.startsWith(`${experimentId}:`)) dispatchedChoices.delete(key);\n });\n}\n\nfunction listInsertionRecords(): string[] {\n const ids = new Set<string>(insertedThisSession.keys());\n try {\n for (let i = 0; i < localStorage.length; i += 1) {\n const key = localStorage.key(i);\n if (key?.startsWith(INSERTED_PREFIX)) ids.add(key.slice(INSERTED_PREFIX.length));\n }\n } catch { /* fall through */ }\n return [...ids];\n}\n\nfunction hasInsertionRecords(): boolean {\n return listInsertionRecords().length > 0;\n}\n\n// The teardown read (offers plane tombstoned to an empty experiments list)\n// runs once per PAGE LOAD \u2014 not per session \u2014 so a login/customer-account\n// cart merge that lands a marked line on a later page is still discovered;\n// in-page cart mutations reset the flag via the tombstone observer.\nlet teardownChecked = false;\n\nfunction markTeardownChecked(): void {\n teardownChecked = true;\n}\n\nfunction numericVariantId(gid: string): number | null {\n const match = /^gid:\\/\\/shopify\\/ProductVariant\\/(\\d+)$/.exec(gid);\n if (!match) return null;\n const id = Number(match[1]);\n return Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n\nfunction marker(line: CartLine): string | null {\n if (!line.properties || typeof line.properties !== \"object\" || Array.isArray(line.properties)) return null;\n const value = (line.properties as Record<string, unknown>)[MARKER];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction lineVariantId(line: CartLine): string | null {\n if (typeof line.variant_id === \"number\" && Number.isSafeInteger(line.variant_id)) return String(line.variant_id);\n if (typeof line.variant_id === \"string\" && /^\\d+$/.test(line.variant_id)) return line.variant_id;\n return null;\n}\n\nfunction lineAmount(line: CartLine): number | null {\n // The offer Function compares each non-gift line's cost.subtotalAmount \u2014\n // the PRE-line-discount amount. Ajax `line_price` is that same basis;\n // `final_line_price` is post-discount and would disagree on discounted\n // carts, removing a gift the Function still authorizes.\n const value = line.line_price;\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 ? value : null;\n}\n\nasync function mutate(path: string, body: unknown): Promise<boolean> {\n // Never throws: a network-level rejection mid-pass must not discard the\n // pass's accumulated mutation state (earlier successful removals still\n // warrant the fixpoint retry and the cart-changed signal).\n try {\n const response = await fetchWithoutShopifyCartMutationNotification(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/** Returns true when this pass mutated the cart (caller re-runs to a fixpoint). */\nasync function run(\n settings: CommerceOffersSettings | undefined,\n assignments: CartAssignment[],\n options: GiftSyncOptions | undefined,\n generation: number,\n): Promise<boolean> {\n if (!isShopifyStorefront() || typeof fetch !== \"function\") return false;\n let response: Response;\n try {\n response = await fetchWithoutShopifyCartMutationNotification(shopifyCartPath(), {\n method: \"GET\", headers: { Accept: \"application/json\" }, cache: \"no-store\",\n });\n } catch { return false; }\n if (!response.ok) return false;\n const cart = await response.json() as ShopifyCart;\n if (!Array.isArray(cart.items) || typeof cart.currency !== \"string\") return false;\n let cartMutated = false;\n const lines = cart.items.filter((item): item is CartLine => !!item && typeof item === \"object\");\n const cartToken = typeof cart.token === \"string\" && cart.token ? cart.token : null;\n const assigned = new Map(assignments.map((item) => [item.experimentId, item.variationId]));\n // Charge-only pins authorize the cart stamp for an off-target page but are\n // deliberately excluded from DOM, persistence, and exposure \u2014 inserting a\n // physical gift for them would give away inventory the shopper was never\n // shown. They still count as ACTIVE for removal/keep decisions.\n const chargeOnlyExperimentIds = new Set(\n assignments.filter((item) => item.commercePriceOnly).map((item) => item.experimentId),\n );\n const experiments = settings?.experiments ?? [];\n // Only experiments covered by the signed offers authorization may INSERT:\n // without it the Function cannot discount the added line, so the \"gift\"\n // would be charged at full price. Removal/maintenance stays unrestricted.\n const authorizedOfferExperimentIds = new Set(\n settings?.authorization?.experimentIds ?? [],\n );\n const root = shopifyRoutesRoot();\n const changePath = `${root}cart/change.js`;\n // Tracks gift variants inserted within THIS run: two parallel GWP tests\n // configured with the same gift variant must share one line (the Function\n // consolidates both candidates onto the first matching line), or the\n // second marked line would be charged at full price.\n const insertedVariantIdsThisRun = new Set<string>();\n // Tracks lines removed within THIS run so presence checks against the\n // immutable snapshot do not see a line that is already gone \u2014 otherwise a\n // still-qualifying experiment sharing the removed gift's variant would\n // silently skip its own insertion.\n const removedLineKeys = new Set<string>();\n const removeLine = async (line: CartLine): Promise<boolean> => {\n if (typeof line.key !== \"string\") return false;\n if (!(await mutate(changePath, { id: line.key, quantity: 0 }))) return false;\n removedLineKeys.add(line.key);\n cartMutated = true;\n return true;\n };\n const linePresent = (line: CartLine): boolean =>\n typeof line.key === \"string\" ? !removedLineKeys.has(line.key) : true;\n\n // Orphan cleanup first: a marked line whose experiment left the offers\n // projection (stopped, archived, kill-switched), whose assignment is gone,\n // or whose assigned variant no longer describes a GWP offer (relaunched\n // under a different offer type) will never be authorized by the discount\n // Function again \u2014 the shopper would be charged full price for it. Remove\n // it before any insertion logic; keep the insertion record when removal\n // fails so the next sync retries instead of abandoning the line.\n const activeGiftExperiments = new Set(\n experiments\n .filter((experiment) => {\n const variationId = assigned.get(experiment.experiment_id);\n const variant = variationId ? experiment.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") return false;\n // With arm proof available, an assignment absent from the CURRENT\n // stamped offers plane is not active: the Function will not discount\n // its line, so keeping it would charge the shopper.\n if (options?.isArmProven) {\n const assignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (!assignment || !options.isArmProven(assignment)) return false;\n }\n if (\"control\" in variant && variant.control === true) return true;\n return \"offer_type\" in variant && variant.offer_type === \"gwp_threshold\"\n && giftTiers(variant as Record<string, unknown>) !== null;\n })\n .map((experiment) => experiment.experiment_id),\n );\n for (const line of lines) {\n const mark = marker(line);\n if (mark == null || activeGiftExperiments.has(mark)) continue;\n // Seed the durable record BEFORE attempting removal: if the mutation\n // fails transiently, the record keeps later syncs (and later sessions)\n // retrying even after this session's tombstone read is throttled.\n recordInserted(mark, cartToken);\n if (await removeLine(line)) clearInserted(mark);\n }\n // A record whose experiment is inactive AND whose line is already gone\n // (checkout consumed the cart, or the shopper removed it before teardown)\n // has nothing left to reconcile \u2014 clear it, or it would trigger a cart\n // read on every later apply and misread a relaunch as a manual decline.\n for (const experimentId of listInsertionRecords()) {\n if (activeGiftExperiments.has(experimentId)) continue;\n if (lines.some((line) => marker(line) === experimentId && linePresent(line))) continue;\n clearInserted(experimentId);\n }\n // Throttle only once the cart has actually been read this session; failed\n // orphan removals keep their seeded records, which bypass the throttle.\n if (settings && experiments.length === 0) markTeardownChecked();\n\n for (const experiment of experiments) {\n // Not live until this pass proves the choice prompt eligible: every\n // early exit (unassigned, control, currency mismatch, unproven arm,\n // suppression, decline) leaves the tier cleared so a delayed\n // selectGiftOption callback cannot bind to a phantom prompt.\n activeChoiceTier.delete(experiment.experiment_id);\n const variationId = assigned.get(experiment.experiment_id);\n if (!variationId) continue;\n const variant = experiment.variants[variationId];\n if (!variant || typeof variant !== \"object\") continue;\n // ALL marked lines for this experiment: concurrent tabs during a\n // relaunch can leave several (e.g. old and new gift variants), and the\n // Function discounts at most one unit of the current variant.\n const managedLines = () => lines.filter(\n (line) => marker(line) === experiment.experiment_id && linePresent(line),\n );\n // Removes every managed line; true when none that could be removed\n // remains (lines without a usable key were never removable and follow\n // the historical clear-anyway behavior).\n const removeAllManaged = async (): Promise<boolean> => {\n let allRemoved = true;\n for (const line of managedLines()) {\n if (typeof line.key !== \"string\") continue;\n if (!(await removeLine(line))) {\n allRemoved = false;\n // A failed REQUIRED removal (below-threshold, control, currency)\n // is unresolved work: the Function no longer discounts the line,\n // so force another fixpoint pass to retry within this\n // reconciliation instead of waiting for an unrelated cart event.\n cartMutated = true;\n }\n }\n return allRemoved;\n };\n if (\"control\" in variant && variant.control === true) {\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n if (!(\"offer_type\" in variant) || variant.offer_type !== \"gwp_threshold\") continue;\n const tiers = giftTiers(variant as Record<string, unknown>);\n if (!tiers) continue;\n if (cart.currency.toUpperCase() !== experiment.currency.toUpperCase()) {\n // The Function refuses cross-currency carts, so a market/currency switch\n // strands an existing managed line at full price \u2014 fail closed by\n // removing it, not just by skipping insertion. Not a decline: the gift\n // may re-insert if the shopper switches back.\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n const allGiftIds = new Set(tiers.flatMap((tier) => tier.gift_choice_variant_gids ?? [tier.gift_variant_gid])\n .map(numericVariantId).filter((id): id is number => id !== null).map(String));\n let subtotal = 0;\n let valid = true;\n for (const line of lines) {\n if (!linePresent(line) || allGiftIds.has(lineVariantId(line) ?? \"\")) continue;\n const amount = lineAmount(line);\n if (amount == null || !Number.isSafeInteger(subtotal + amount)) { valid = false; break; }\n subtotal += amount;\n }\n const applicableTier = valid ? tiers.reduce<GiftTier | null>((chosen, tier) => {\n const threshold = Number(tier.threshold);\n return Number.isSafeInteger(threshold) && threshold >= 0 && subtotal >= threshold ? tier : chosen;\n }, null) : null;\n if (!valid || tiers.some((tier) => !Number.isSafeInteger(Number(tier.threshold)))) {\n // Fail closed: an unrepresentable threshold means the Function cannot\n // legitimately discount this gift \u2014 an Apex-managed line left from an\n // earlier (lower) threshold would charge full price. Remove every\n // managed line for this experiment before skipping it.\n for (const line of managedLines()) {\n if (typeof line.key !== \"string\") continue;\n await removeLine(line);\n }\n continue;\n }\n if (!applicableTier) {\n clearDispatchedChoices(experiment.experiment_id);\n activeChoiceTier.delete(experiment.experiment_id);\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n const choiceGids = applicableTier.gift_choice_variant_gids;\n // Every applicable-tier transition re-arms the chooser: keys that do not\n // belong to the CURRENT tier (including all keys while a fixed tier\n // applies) are stale. Only the current batch may mutate dedup state.\n if (generation === choiceTierGeneration) {\n const currentKey = choiceGids\n ? `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids.join(\",\")}`\n : null;\n dispatchedChoices.forEach((key) => {\n if (key.startsWith(`${experiment.experiment_id}:`) && key !== currentKey) {\n dispatchedChoices.delete(key);\n }\n });\n }\n const markChoicePromptLive = () => {\n if (\n choiceGids &&\n !options?.suppressInsertion &&\n authorizedOfferExperimentIds.has(experiment.experiment_id) &&\n !declined(experiment.experiment_id)\n ) {\n const assignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (\n (!options?.isArmProven || (assignment && options.isArmProven(assignment))) &&\n generation === choiceTierGeneration\n ) {\n activeChoiceTier.set(experiment.experiment_id, applicableTier.threshold);\n }\n }\n };\n const extendedRecord = Array.isArray((variant as Record<string, unknown>).tiers);\n const persistedRecord = insertionRecord(experiment.experiment_id);\n const persistedGid = persistedRecord && insertionMatchesCart(persistedRecord, cartToken)\n ? recordVariantGid(persistedRecord)\n : null;\n // A recorded tier that DIFFERS from the applicable tier is a stale\n // lower-tier gift: entering a choice tier must present the choice. An\n // ABSENT tier (snapshot-seeded cross-device record, legacy plain-token\n // record) has no provenance: recover it only while the applicable tier\n // is the LOWEST choice tier containing that gid \u2014 once the subtotal has\n // crossed into a higher choice tier sharing the GID, the new tier's\n // prompt must not be suppressed by a tierless record.\n const lowestChoiceTierThresholdFor = (gid: string | null | undefined) =>\n gid == null\n ? undefined\n : tiers.find((tier) => tier.gift_choice_variant_gids?.includes(gid))?.threshold;\n const persistedTierThreshold =\n persistedRecord === null ? null : recordTierThreshold(persistedRecord);\n const tierlessRecoveryAllowed = (gid: string | null | undefined) =>\n lowestChoiceTierThresholdFor(gid) === applicableTier.threshold;\n // In-memory selections are tier-bound: a choice made for another tier\n // (tier crossing, or a selectGiftOption call while a different tier\n // applied) must not suppress the new tier's choice prompt.\n const inMemorySelection = selectedGiftOptions.get(experiment.experiment_id);\n let inMemoryGid = inMemorySelection?.tierThreshold === applicableTier.threshold\n ? inMemorySelection.gid\n : undefined;\n const markedCurrentTierGid = choiceGids?.find((gid) => {\n const variantId = numericVariantId(gid);\n return variantId !== null && lines.some((line) =>\n linePresent(line) && marker(line) === experiment.experiment_id &&\n lineVariantId(line) === String(variantId));\n });\n const persistedChoiceIsCurrentTier = persistedRecord !== null &&\n (persistedTierThreshold === applicableTier.threshold ||\n (persistedTierThreshold === null &&\n tierlessRecoveryAllowed(persistedGid ?? markedCurrentTierGid)));\n // Cross-tab arbitration: the durable record is the source of truth once\n // this tab's own selection has been persisted. A PENDING selection (made\n // here, not yet recorded) still wins \u2014 clobbering the user's fresh click\n // with an older record would be worse. Once our record lands (gids\n // match), pending clears; a later differing durable record means another\n // tab chose \u2014 drop the stale local selection instead of reverting theirs.\n if (inMemorySelection && inMemoryGid) {\n const persistedCurrentTierGid =\n persistedChoiceIsCurrentTier && choiceGids?.includes(persistedGid ?? \"\")\n ? persistedGid\n : null;\n if (inMemorySelection.pending && persistedCurrentTierGid === inMemoryGid) {\n selectedGiftOptions.set(experiment.experiment_id, {\n gid: inMemorySelection.gid,\n tierThreshold: inMemorySelection.tierThreshold,\n });\n } else if (\n !inMemorySelection.pending &&\n persistedCurrentTierGid &&\n persistedCurrentTierGid !== inMemoryGid\n ) {\n selectedGiftOptions.delete(experiment.experiment_id);\n inMemoryGid = undefined;\n }\n }\n const selectedGid = choiceGids?.includes(inMemoryGid ?? \"\")\n ? inMemoryGid\n : persistedChoiceIsCurrentTier && choiceGids?.includes(persistedGid ?? \"\") ? persistedGid!\n : persistedChoiceIsCurrentTier ? markedCurrentTierGid : undefined;\n if (selectedGid) {\n const priorPending =\n selectedGiftOptions.get(experiment.experiment_id)?.gid === selectedGid &&\n selectedGiftOptions.get(experiment.experiment_id)?.pending === true;\n selectedGiftOptions.set(experiment.experiment_id, {\n gid: selectedGid,\n tierThreshold: applicableTier.threshold,\n ...(priorPending ? { pending: true } : {}),\n });\n }\n const desiredGid = choiceGids ? selectedGid : applicableTier.gift_variant_gid;\n const giftId = desiredGid ? numericVariantId(desiredGid) : null;\n const allowedCurrentIds = new Set((choiceGids ?? [applicableTier.gift_variant_gid])\n .map(numericVariantId).filter((id): id is number => id !== null).map(String));\n // Keep at most ONE line carrying the current gift variant; every other\n // marked line (stale variant after a relaunch, or a concurrent-tab\n // duplicate) is removed \u2014 the Function will not discount it. Removal\n // failures skip the experiment: falling through could misread the\n // shopper's cart as a manual decline.\n let managed: CartLine | undefined;\n let staleRemovalFailed = false;\n let removedStaleForExperiment = false;\n for (const line of managedLines()) {\n if (!managed && giftId !== null && lineVariantId(line) === String(giftId)) {\n managed = line;\n continue;\n }\n if (typeof line.key !== \"string\") continue;\n if (await removeLine(line)) removedStaleForExperiment = true;\n else staleRemovalFailed = true;\n }\n // Any failed stale removal is unresolved work: force another fixpoint\n // pass so the retry happens now, not at the next unrelated cart event \u2014\n // the Function discounts at most the one current-variant unit.\n if (staleRemovalFailed) cartMutated = true;\n if (staleRemovalFailed && !managed) continue;\n if (!managed && removedStaleForExperiment) {\n // OUR stale-variant removal, not a shopper decline of the new gift:\n // clear the record so the decline logic below cannot misread it and\n // the current gift may insert.\n clearInserted(experiment.experiment_id);\n }\n if (managed && lines.some((line) =>\n line !== managed && linePresent(line) && marker(line) !== experiment.experiment_id &&\n allowedCurrentIds.has(lineVariantId(line) ?? \"\"))) {\n // The Function picks one allowed option by cart-line id, not by Apex's\n // marker. If a shopper or another experiment added an allowed option,\n // our marked gift could therefore become a full-price line. Relinquish\n // Apex's line and let that other option satisfy the tier.\n if (typeof managed.key === \"string\" && await removeLine(managed)) {\n clearInserted(experiment.experiment_id);\n } else {\n cartMutated = true;\n }\n continue;\n }\n if (!desiredGid || giftId === null) {\n clearInserted(experiment.experiment_id);\n markChoicePromptLive();\n if (!options?.suppressInsertion && authorizedOfferExperimentIds.has(experiment.experiment_id) && !declined(experiment.experiment_id)) {\n const assignment = assignments.find((candidate) => candidate.experimentId === experiment.experiment_id);\n if (!options?.isArmProven || (assignment && options.isArmProven(assignment))) {\n const eventKey = `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids!.join(\",\")}`;\n // Only the current batch may dispatch or record dedup state: a\n // stale batch consuming the key would mute the queued batch's\n // valid prompt while its own event points at dead liveness.\n if (generation === choiceTierGeneration) {\n if (!dispatchedChoices.has(eventKey) && typeof window !== \"undefined\") {\n dispatchedChoices.add(eventKey);\n // Detail carries a COPY: chooser code mutating the array must\n // not corrupt the server-authored Function configuration that\n // selectGiftOption validates against.\n emitChoicePrompt({ experimentId: experiment.experiment_id, threshold: applicableTier.threshold, options: [...choiceGids!] });\n }\n }\n }\n }\n continue;\n }\n if (managed) {\n markChoicePromptLive();\n if (options?.suppressInsertion) {\n // Fail-closed pass after a failed stamp refresh: keep the line only\n // when durable evidence ties it to THIS cart \u2014 after a cart-identity\n // change (customer-cart merge) the old proof does not cover the new\n // cart and the Function will not discount the line.\n const record = insertionRecord(experiment.experiment_id);\n if (!record || !insertionMatchesCart(record, cartToken)) {\n if (await removeAllManaged()) clearInserted(experiment.experiment_id);\n continue;\n }\n } else {\n // A correctly marked line is always Apex-inserted (shoppers cannot\n // set our line property), so seed the insertion record when a fresh\n // session discovers one \u2014 a later disappearance must count as a\n // manual decline.\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n }\n // The Function only zeroes a single unit; extra units on the managed\n // line would be charged, so normalize a shopper/theme increment to one.\n if (\n typeof managed.key === \"string\" &&\n typeof managed.quantity === \"number\" &&\n managed.quantity !== 1\n ) {\n // An off-model quantity always warrants another fixpoint pass \u2014 even\n // when this normalize fails, the re-read retries within the run\n // instead of leaving an extra charged unit until the next mutation.\n cartMutated = true;\n await mutate(changePath, { id: managed.key, quantity: 1 });\n }\n continue;\n }\n // Off-target charge-only pins never insert, and their line's absence on\n // this page is not decline evidence \u2014 the targeted page owns both calls.\n if (chargeOnlyExperimentIds.has(experiment.experiment_id)) {\n // But when WE removed this experiment's shared gift line in this run,\n // clear the co-owner evidence now: removedByUsThisRun does not survive\n // the pass, and a later targeted page would misread the absent line as\n // a shopper decline and never reinsert the still-eligible gift.\n if (\n insertionRecord(experiment.experiment_id) != null &&\n lines.some(\n (line) => !linePresent(line) && allowedCurrentIds.has(lineVariantId(line) ?? \"\"),\n )\n ) {\n clearInserted(experiment.experiment_id);\n }\n continue;\n }\n // A shopper-owned copy of the gift satisfies the offer without giving\n // Apex ownership of it \u2014 as does a copy just inserted for a parallel\n // GWP test with the same gift variant (the Function consolidates both\n // experiments' candidates onto that one line). This MUST precede the\n // decline logic: a co-owner's record with the shared line still present\n // is not a removal.\n const satisfyingLine = lines.find(\n (line) => linePresent(line) && allowedCurrentIds.has(lineVariantId(line) ?? \"\"),\n );\n const insertedAllowedId = [...insertedVariantIdsThisRun].find((id) => allowedCurrentIds.has(id));\n if (satisfyingLine || insertedAllowedId) {\n markChoicePromptLive();\n // Sharing an APEX-INSERTED line makes this experiment a co-owner:\n // record insertion state so the shopper's later removal declines every\n // experiment the line represents, not just the one in its marker.\n // Unmarked shopper-owned copies stay unrecorded \u2014 removing one's own\n // purchase is not a gift decline.\n if (\n insertedAllowedId ||\n (satisfyingLine && marker(satisfyingLine) != null)\n ) {\n const satisfyingGid = satisfyingLine\n ? choiceGids?.find((gid) => String(numericVariantId(gid)) === lineVariantId(satisfyingLine))\n : choiceGids?.find((gid) => String(numericVariantId(gid)) === insertedAllowedId);\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? satisfyingGid ?? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n }\n continue;\n }\n const record = insertionRecord(experiment.experiment_id);\n if (record != null) {\n clearInserted(experiment.experiment_id);\n // Only a missing line on the SAME cart the gift was inserted on is a\n // manual decline. A new cart (checkout consumed the old one) starts\n // clean, and a shared line WE removed this run (its owner entered\n // control or lost eligibility) lets a still-qualifying co-owner take\n // ownership instead of being marked declined.\n const removedByUsThisRun = lines.some(\n (line) => !linePresent(line) && lineVariantId(line) === String(giftId),\n );\n if (!removedByUsThisRun && insertionMatchesCart(record, cartToken)) {\n recordDecline(experiment.experiment_id);\n continue;\n }\n }\n if (declined(experiment.experiment_id)) continue;\n if (options?.suppressInsertion) continue;\n if (!authorizedOfferExperimentIds.has(experiment.experiment_id)) continue;\n if (options?.isArmProven) {\n const localAssignment = assignments.find(\n (candidate) => candidate.experimentId === experiment.experiment_id,\n );\n if (!localAssignment || !options.isArmProven(localAssignment)) continue;\n }\n const addResponse = await fetchWithoutShopifyCartMutationNotification(`${root}cart/add.js`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n items: [{ id: giftId, quantity: 1, properties: { [MARKER]: experiment.experiment_id } }],\n }),\n }).catch(() => null);\n if (!addResponse?.ok) {\n // A failed add for an explicit CHOICE dead-ends the shopper: the\n // pending selection would keep retrying the same variant while the\n // cleared tier liveness rejects any new selectGiftOption call. Drop\n // the failed selection and re-arm the prompt so another option can\n // be chosen.\n const failedSelection = selectedGiftOptions.get(experiment.experiment_id);\n if (choiceGids && failedSelection?.gid === desiredGid) {\n selectedGiftOptions.delete(experiment.experiment_id);\n clearDispatchedChoices(experiment.experiment_id);\n // Re-arm immediately: the pass-start tier clear plus a failed add\n // with no cart mutation means no follow-up pass runs, so the chooser\n // would stay dead (selection ignored, no pending prompts) until an\n // unrelated reconciliation. Restore tier liveness and re-dispatch so\n // an alternative option can be chosen right away.\n markChoicePromptLive();\n if (generation === choiceTierGeneration && typeof window !== \"undefined\") {\n const retryKey = `${experiment.experiment_id}:${applicableTier.threshold}:${choiceGids.join(\",\")}`;\n if (!dispatchedChoices.has(retryKey)) {\n dispatchedChoices.add(retryKey);\n emitChoicePrompt({ experimentId: experiment.experiment_id, threshold: applicableTier.threshold, options: [...choiceGids] });\n }\n }\n }\n continue;\n }\n if (addResponse?.ok) {\n cartMutated = true;\n recordInserted(experiment.experiment_id, cartToken, extendedRecord ? desiredGid : undefined, extendedRecord ? applicableTier.threshold : undefined);\n insertedVariantIdsThisRun.add(String(giftId));\n // Concurrent tabs can both add; Shopify consolidates identical\n // variant+property lines by bumping quantity while the Function only\n // discounts one unit. Verify from the add response and normalize.\n try {\n const body = await addResponse.json() as { items?: unknown };\n const addedLine = Array.isArray(body.items)\n ? body.items.find((item): item is CartLine =>\n !!item && typeof item === \"object\" &&\n marker(item as CartLine) === experiment.experiment_id)\n : undefined;\n if (\n addedLine && typeof addedLine.key === \"string\" &&\n typeof addedLine.quantity === \"number\" && addedLine.quantity > 1\n ) {\n // Detected consolidation always forces a follow-up fixpoint pass:\n // even if this normalize fails, the re-read sees quantity > 1 on\n // the managed line and retries rather than leaving a charged unit.\n cartMutated = true;\n await mutate(changePath, { id: addedLine.key, quantity: 1 });\n }\n } catch { /* best effort */ }\n }\n }\n return cartMutated;\n}\n\n/**\n * Seed durable insertion records from any cart snapshot that passes through\n * the SDK (e.g. the /cart/update.js stamp-write response). Marked lines can\n * only originate from Apex, so seeing one IS the insertion evidence \u2014 this is\n * how a fresh device or a cleared-storage session over a persistent (or\n * customer-synced) cart rediscovers gifts it must reconcile or tear down.\n * Returns true when a marked line was found.\n */\nexport function noteCartSnapshotForGifts(cart: unknown): boolean {\n const snapshot = cart as { token?: unknown; items?: unknown } | null;\n if (!Array.isArray(snapshot?.items)) return false;\n const cartToken = typeof snapshot.token === \"string\" && snapshot.token ? snapshot.token : null;\n let found = false;\n for (const item of snapshot.items) {\n if (!item || typeof item !== \"object\") continue;\n const mark = marker(item as CartLine);\n if (mark == null) continue;\n const variantId = lineVariantId(item as CartLine);\n // A snapshot write must not erase tier ownership an in-flow record\n // already carries: overwriting a tier-bound choice with a variant-only\n // record would let a later tier crossing adopt it without re-prompting.\n const existing = insertionRecord(mark);\n const existingTier = existing && insertionMatchesCart(existing, cartToken)\n ? recordTierThreshold(existing)\n : null;\n recordInserted(mark, cartToken, variantId\n ? `gid://shopify/ProductVariant/${variantId}`\n : undefined, existingTier ?? undefined);\n found = true;\n }\n return found;\n}\n\nexport function syncGiftWithPurchase(\n settings: CommerceOffersSettings | undefined,\n assignments: CartAssignment[],\n options?: GiftSyncOptions,\n): Promise<void> {\n latestArgs = { settings, assignments, options };\n installChoiceRequestListener();\n // Liveness derives from the PREVIOUS eligibility state until the async\n // reconciliation below re-proves it; clear synchronously and advance the\n // generation so neither a chooser callback nor an in-flight pass built on\n // the old arguments can resurrect a stale proven tier.\n choiceTierGeneration += 1;\n const batchGeneration = choiceTierGeneration;\n activeChoiceTier.clear();\n if (options?.recheckTeardown) teardownChecked = false;\n if (!settings?.experiments.length && !hasInsertionRecords()) {\n // No offers plane at all: nothing to reconcile (shops that never ran\n // offers have no tombstone). A present-but-empty plane is the teardown\n // tombstone \u2014 read the cart once per page load (reset on cart mutations)\n // so an orphaned gift is discovered even when local insertion records\n // are gone (cleared storage, another device over a customer-synced cart).\n if (!settings || teardownChecked) return Promise.resolve();\n }\n if (inFlight) {\n // The active run may have read the cart before the mutation that\n // triggered this call \u2014 queue one trailing run (latest args win) instead\n // of dropping the reconciliation.\n queuedArgs = { settings, assignments, options, generation: batchGeneration };\n return inFlight;\n }\n // Any pass that mutates the cart re-reads and re-verifies its decisions\n // (bounded): the cart may have changed between the read and the mutation\n // (e.g. the subtotal dropped below threshold before the gift add landed),\n // and shared-line decisions must be order-independent across experiments.\n const runToFixpoint = async (\n nextSettings: CommerceOffersSettings | undefined,\n nextAssignments: CartAssignment[],\n nextOptions: GiftSyncOptions | undefined,\n // Fixed per argument batch: a later batch bumps the global counter, and\n // every remaining pass of THIS batch must keep failing the liveness\n // check rather than re-reading the now-current generation.\n generation: number,\n ) => {\n let anyMutated = false;\n for (let pass = 0; pass < 3; pass += 1) {\n const mutated = await run(nextSettings, nextAssignments, nextOptions, generation).catch(() => false);\n if (mutated) anyMutated = true;\n if (!mutated) break;\n }\n if (anyMutated && typeof window !== \"undefined\") {\n // Our mutations suppress the SDK's own cart observer, so themes whose\n // drawers render from their original add/change responses would show a\n // stale cart. Publish a documented signal for themes/integrators to\n // re-render from /cart.js (checkout charges are correct regardless \u2014\n // the backend cart is authoritative; this is display freshness).\n try {\n window.dispatchEvent(\n new CustomEvent(\"apex:cart:changed\", { detail: { source: \"gwp_gift\" } }),\n );\n } catch { /* best effort */ }\n }\n };\n const pending = (async () => {\n await runToFixpoint(settings, assignments, options, batchGeneration);\n while (queuedArgs) {\n const next = queuedArgs;\n queuedArgs = null;\n await runToFixpoint(next.settings, next.assignments, next.options, next.generation);\n }\n if (choiceReplayRequested) getPendingGiftChoices().forEach(emitChoicePrompt);\n choiceReplayRequested = false;\n })().finally(() => {\n if (inFlight === pending) inFlight = null;\n if (!inFlight && queuedArgs) {\n const next = queuedArgs;\n queuedArgs = null;\n void syncGiftWithPurchase(next.settings, next.assignments, next.options);\n }\n });\n inFlight = pending;\n return pending;\n}\n\n/** Selects an option previously offered by `apex:gwp:choice`. The next\n * reconciliation validates the current signed arm/tier before mutating. */\nexport function selectGiftOption(experimentId: string, variantGid: string): Promise<void> {\n if (!latestArgs || numericVariantId(variantGid) === null) return Promise.resolve();\n const experiment = latestArgs.settings?.experiments.find((item) => item.experiment_id === experimentId);\n const variationId = latestArgs.assignments.find((item) => item.experimentId === experimentId)?.variationId;\n const variant = variationId ? experiment?.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") return Promise.resolve();\n const tiers = giftTiers(variant as Record<string, unknown>);\n const containingTiers = tiers?.filter((tier) =>\n tier.gift_choice_variant_gids?.includes(variantGid)) ?? [];\n // The same GID can be a valid choice in several tiers: bind the selection\n // to the tier that is applicable right now (tracked each reconciliation\n // pass), so answering the live prompt records the live tier \u2014 dispatched\n // keys from earlier tier crossings must not decide this.\n const applicableThreshold = activeChoiceTier.get(experimentId);\n // No live choice tier (shopper dropped below threshold, or moved to a\n // fixed tier) \u2192 a delayed chooser callback must be ignored, not recorded\n // against a tier that is no longer prompting.\n const owningTier = containingTiers.find(\n (tier) => tier.threshold === applicableThreshold,\n );\n if (!owningTier) return Promise.resolve();\n selectedGiftOptions.set(experimentId, {\n gid: variantGid,\n tierThreshold: owningTier.threshold,\n pending: true,\n });\n clearDispatchedChoices(experimentId);\n return syncGiftWithPurchase(latestArgs.settings, latestArgs.assignments, latestArgs.options);\n}\n\nfunction emitChoicePrompt(detail: PendingGiftChoice): void {\n dispatchEvent(new CustomEvent(\"apex:gwp:choice\", { detail }));\n}\n\n// Documented replay contract: dispatching apex:gwp:choice:request makes the\n// SDK re-emit apex:gwp:choice for every live, unanswered prompt, so choosers\n// mounted after the initial dispatch can recover it. Registered lazily from\n// the sync path so importing this module (e.g. via the @drip-apex/sdk/chooser\n// entry) stays free of runtime side effects.\nlet choiceRequestListenerInstalled = false;\nlet choiceReplayRequested = false;\nfunction installChoiceRequestListener(): void {\n if (choiceRequestListenerInstalled || typeof window === \"undefined\") return;\n choiceRequestListenerInstalled = true;\n window.addEventListener(\"apex:gwp:choice:request\", () => {\n if (inFlight) choiceReplayRequested = true;\n getPendingGiftChoices().forEach(emitChoicePrompt);\n });\n}\n\n/** Returns live, unanswered gift-choice prompts. This replayable query lets a\n * chooser mounted after `apex:gwp:choice` was dispatched discover the same\n * prompt; pass an experiment id to restrict the result. Returned option\n * arrays are copies and may be safely modified by caller code. */\nexport function getPendingGiftChoices(experimentId?: string): PendingGiftChoice[] {\n if (!latestArgs) return [];\n const prompts: PendingGiftChoice[] = [];\n for (const [activeExperimentId, threshold] of activeChoiceTier) {\n if (experimentId !== undefined && activeExperimentId !== experimentId) continue;\n const selected = selectedGiftOptions.get(activeExperimentId);\n if (selected?.tierThreshold === threshold) continue;\n const experiment = latestArgs.settings?.experiments.find(\n (item) => item.experiment_id === activeExperimentId,\n );\n const variationId = latestArgs.assignments.find(\n (item) => item.experimentId === activeExperimentId,\n )?.variationId;\n const variant = variationId ? experiment?.variants[variationId] : undefined;\n if (!variant || typeof variant !== \"object\") continue;\n const tier = giftTiers(variant as Record<string, unknown>)?.find(\n (candidate) => candidate.threshold === threshold,\n );\n if (!tier?.gift_choice_variant_gids) continue;\n prompts.push({\n experimentId: activeExperimentId,\n threshold,\n options: [...tier.gift_choice_variant_gids],\n });\n }\n return prompts;\n}\n\nexport const __giftManagerInternals = {\n reset() {\n inFlight = null;\n queuedArgs = null;\n insertedThisSession.clear();\n selectedGiftOptions.clear();\n activeChoiceTier.clear();\n dispatchedChoices.clear();\n latestArgs = null;\n teardownChecked = false;\n },\n};\n", "// Keep npm module exports separate from the browser IIFE entry. The IIFE is\n// embedded into the Worker and exposed through the Drip global, while npm\n// consumers need real ESM bindings from the package root.\n//\n// Deliberately NO star re-export of ./index: evaluating the full runtime\n// entry would bootstrap browser side effects for consumers who only want\n// the chooser APIs. And because the hosted drip.js runtime owns the live\n// gift state, these wrappers DELEGATE to window.drip when it is present \u2014\n// an ESM chooser bundled separately must never operate on a second copy of\n// the module state.\nimport {\n getPendingGiftChoices as localGetPendingGiftChoices,\n selectGiftOption as localSelectGiftOption,\n type PendingGiftChoice,\n} from \"./commerce-gift\";\n\ntype ChooserRuntime = {\n getPendingGiftChoices?: (experimentId?: string) => PendingGiftChoice[];\n selectGiftOption?: (\n experimentId: string,\n variantGid: string,\n ) => Promise<void>;\n};\n\nfunction activeRuntime(): ChooserRuntime | undefined {\n return typeof window === \"undefined\"\n ? undefined\n : (window as Window & { drip?: ChooserRuntime }).drip;\n}\n\nexport function getPendingGiftChoices(\n experimentId?: string,\n): PendingGiftChoice[] {\n const runtime = activeRuntime();\n return runtime?.getPendingGiftChoices\n ? runtime.getPendingGiftChoices(experimentId)\n : localGetPendingGiftChoices(experimentId);\n}\n\nexport function selectGiftOption(\n experimentId: string,\n variantGid: string,\n): Promise<void> {\n const runtime = activeRuntime();\n return runtime?.selectGiftOption\n ? runtime.selectGiftOption(experimentId, variantGid)\n : localSelectGiftOption(experimentId, variantGid);\n}\n\nexport type { PendingGiftChoice };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["record", "getPendingGiftChoices", "selectGiftOption"]
|
|
7
7
|
}
|