@vio-live/web-sdk 0.32.0 → 0.33.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.
@@ -841,6 +841,17 @@ var ChannelProduct = class {
841
841
  }
842
842
  }
843
843
  };
844
+ var FILLER_SHIPPING_DESCRIPTIONS = Object.freeze(["by outshifter", "default shipping method"]);
845
+ function shippingDescriptionOf(value, repeats = []) {
846
+ const text3 = typeof value === "string" ? value.trim() : "";
847
+ if (!text3) return "";
848
+ const key = text3.toLowerCase();
849
+ if (FILLER_SHIPPING_DESCRIPTIONS.includes(key)) return "";
850
+ for (const r of repeats) {
851
+ if (typeof r === "string" && r.trim() && r.trim().toLowerCase() === key) return "";
852
+ }
853
+ return text3;
854
+ }
844
855
  function shippingRatesFor(productShipping, country) {
845
856
  if (!Array.isArray(productShipping)) return [];
846
857
  const want = String(country || "").trim().toUpperCase();
@@ -851,13 +862,15 @@ function shippingRatesFor(productShipping, country) {
851
862
  const price = row?.price;
852
863
  const amount = typeof price?.amount_incl_taxes === "number" ? price.amount_incl_taxes : price?.amount;
853
864
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) continue;
865
+ const name = String(s.name ?? "");
866
+ const description = shippingDescriptionOf(s.description, [name]);
854
867
  out.push({
855
868
  id: String(s.id ?? ""),
856
- name: String(s.name ?? ""),
869
+ name,
857
870
  price: amount,
858
871
  currency: String(price?.currency_code ?? ""),
859
872
  default: s.default === true,
860
- description: typeof s.description === "string" ? s.description.trim() : "",
873
+ description,
861
874
  logoUrl: httpsUrlOrEmpty(s.logo_url ?? s.logoUrl)
862
875
  });
863
876
  }
@@ -2014,7 +2027,7 @@ query Config {
2014
2027
  }
2015
2028
  }
2016
2029
  `;
2017
- async function getCartGraphQLOptions(sponsorId) {
2030
+ async function getCartGraphQLOptions(sponsorId, ctx) {
2018
2031
  const facade = globalThis.__VIO_FACADE__;
2019
2032
  if (!Configuration.isInitialized) {
2020
2033
  await Configuration.whenReady();
@@ -2032,14 +2045,31 @@ async function getCartGraphQLOptions(sponsorId) {
2032
2045
  const anyCfg = cfg;
2033
2046
  let commerceKey = anyCfg.apiKey;
2034
2047
  let sponsorResolved = false;
2035
- if (sponsorId && typeof facade?.findSponsor === "function") {
2048
+ let channelId;
2049
+ let channelMissing = false;
2050
+ const askedChannel = ctx?.channelId === null || ctx?.channelId === void 0 ? "" : String(ctx.channelId).trim();
2051
+ if (sponsorId && typeof facade?.sponsorCommerce === "function") {
2052
+ const sponsor = facade.sponsorCommerce(sponsorId, ctx ?? {});
2053
+ if (sponsor?.commerce?.apiKey) {
2054
+ commerceKey = sponsor.commerce.apiKey;
2055
+ sponsorResolved = true;
2056
+ const ch = sponsor.commerce.channelId;
2057
+ if (ch !== null && ch !== void 0 && String(ch).trim()) channelId = String(ch).trim();
2058
+ } else if (askedChannel && facade.bootstrapCache) {
2059
+ channelMissing = true;
2060
+ }
2061
+ } else if (sponsorId && !askedChannel && typeof facade?.findSponsor === "function") {
2036
2062
  const sponsor = facade.findSponsor(sponsorId);
2037
2063
  if (sponsor?.commerce?.apiKey) {
2038
2064
  commerceKey = sponsor.commerce.apiKey;
2039
2065
  sponsorResolved = true;
2040
2066
  }
2041
2067
  }
2042
- if (sponsorId && !sponsorResolved && typeof console !== "undefined") {
2068
+ if (channelMissing && typeof console !== "undefined") {
2069
+ console.warn(
2070
+ `[Vio] channel ${askedChannel} of sponsor ${sponsorId} is no longer in the bootstrap \u2014 not calling it with another channel's key`
2071
+ );
2072
+ } else if (sponsorId && !sponsorResolved && typeof console !== "undefined") {
2043
2073
  console.warn(
2044
2074
  `[Vio] no commerce key for sponsor ${sponsorId} \u2014 falling back to the platform apiKey, which the commerce API will reject. Is the sponsor configured, and did Vio.init run before this call?`
2045
2075
  );
@@ -2048,7 +2078,9 @@ async function getCartGraphQLOptions(sponsorId) {
2048
2078
  endpoint: anyCfg.graphQLBase || "https://graph-ql-dev.vio.live",
2049
2079
  apiKey: anyCfg.apiKey,
2050
2080
  commerceKey,
2051
- sponsorResolved
2081
+ sponsorResolved,
2082
+ ...channelId ? { channelId } : {},
2083
+ ...channelMissing ? { channelMissing } : {}
2052
2084
  };
2053
2085
  }
2054
2086
  var CUSTOMER_SESSION_KEY = "vio.customer_session_id.v1";
@@ -2311,7 +2343,18 @@ async function getVippsStatus(variables, options) {
2311
2343
  }
2312
2344
 
2313
2345
  // src/core/cart/cart-manager.ts
2314
- var STORAGE_KEY = "vio.cart.v1";
2346
+ var CART_STORAGE_PREFIX = "vio.cart.v1";
2347
+ function cartStorageKey(scope) {
2348
+ return scope ? `${CART_STORAGE_PREFIX}:${scope}` : CART_STORAGE_PREFIX;
2349
+ }
2350
+ function cartScopeOf(environment, apiKey) {
2351
+ let h = 2166136261;
2352
+ for (let i = 0; i < apiKey.length; i++) {
2353
+ h ^= apiKey.charCodeAt(i);
2354
+ h = Math.imul(h, 16777619) >>> 0;
2355
+ }
2356
+ return `${environment || "production"}:${h.toString(16).padStart(8, "0")}`;
2357
+ }
2315
2358
  function finalTarget(sync) {
2316
2359
  return sync.steps.reduce((quantity, step) => quantity + step.delta, sync.sent);
2317
2360
  }
@@ -2339,6 +2382,17 @@ function queueStep(sync, target, origin) {
2339
2382
  else sync.steps.push({ delta: -left });
2340
2383
  }
2341
2384
  }
2385
+ function cartCampaignOf(cart) {
2386
+ const items = cart?.items ?? [];
2387
+ for (let i = items.length - 1; i >= 0; i--) {
2388
+ const origins = items[i]?.origins ?? [];
2389
+ for (let j = origins.length - 1; j >= 0; j--) {
2390
+ const id = Number(origins[j]?.campaignId);
2391
+ if (Number.isInteger(id) && id > 0) return id;
2392
+ }
2393
+ }
2394
+ return void 0;
2395
+ }
2342
2396
  function lineId() {
2343
2397
  return "li_" + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
2344
2398
  }
@@ -2349,7 +2403,7 @@ function normId(val) {
2349
2403
  return String(val).trim();
2350
2404
  }
2351
2405
  var CartManager = class extends EventTarget {
2352
- constructor() {
2406
+ constructor(scope = "") {
2353
2407
  super();
2354
2408
  /** In-flight CreateCart per sponsor — two quick adds must share ONE backend
2355
2409
  * cart (two CreateCarts split the items and one silently disappears). */
@@ -2373,6 +2427,21 @@ var CartManager = class extends EventTarget {
2373
2427
  * in parallel; each response re-applied the whole backend cart, so an
2374
2428
  * older one landing after a newer optimistic change rolled the line back.) */
2375
2429
  this.qtySyncs = /* @__PURE__ */ new Map();
2430
+ /**
2431
+ * Bumped per sponsor each time its backend cart is dropped because its
2432
+ * channel went away: the lines as they stood then (`carryOver`) go to the
2433
+ * new backend cart, so an add whose local units were already counted in
2434
+ * them must not send its own AddItem as well (see `addManual`).
2435
+ */
2436
+ this.rebuildEpoch = /* @__PURE__ */ new Map();
2437
+ /** The lines to give the next backend cart of a sponsor, after its channel went away. */
2438
+ this.carryOver = /* @__PURE__ */ new Map();
2439
+ /**
2440
+ * Whether this is the page's connection now. A manager left behind by a
2441
+ * re-init (another key) still finishes its requests and keeps its own
2442
+ * carts, but says nothing to the page: no `vio:cart-*` events.
2443
+ */
2444
+ this.active = true;
2376
2445
  /** Quantity intents for lines whose backend id hasn't arrived yet — replayed
2377
2446
  * when the AddItem response reconciles (keyed by product::variant). */
2378
2447
  this.pendingQtyIntents = /* @__PURE__ */ new Map();
@@ -2386,7 +2455,7 @@ var CartManager = class extends EventTarget {
2386
2455
  * that changed (`source: 'storage'`); nothing when nothing did.
2387
2456
  */
2388
2457
  this.onStorage = (e) => {
2389
- if (e.key !== null && e.key !== STORAGE_KEY) return;
2458
+ if (e.key !== null && e.key !== this.storageKey) return;
2390
2459
  const stored = this.loadFromStorage();
2391
2460
  const changed = [];
2392
2461
  for (const sponsorId of /* @__PURE__ */ new Set([...this.cartsBySponsor.keys(), ...stored.keys()])) {
@@ -2402,11 +2471,28 @@ var CartManager = class extends EventTarget {
2402
2471
  this.emit();
2403
2472
  for (const sponsorId of changed) this.emitCartUpdated(sponsorId, "storage");
2404
2473
  };
2474
+ this.storageKey = cartStorageKey(scope);
2405
2475
  this.cartsBySponsor = this.loadFromStorage();
2476
+ if (scope && this.cartsBySponsor.size === 0) {
2477
+ const legacy = this.loadFromStorage(CART_STORAGE_PREFIX);
2478
+ if (legacy.size > 0) {
2479
+ this.cartsBySponsor = legacy;
2480
+ this.persist();
2481
+ storageRemove("local", CART_STORAGE_PREFIX);
2482
+ }
2483
+ }
2406
2484
  if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
2407
2485
  window.addEventListener("storage", this.onStorage);
2408
2486
  }
2409
2487
  }
2488
+ /** The page's connection is (or is no longer) this one; see `active`. */
2489
+ setActive(active) {
2490
+ this.active = active;
2491
+ }
2492
+ /** Tell the listeners what this connection's carts hold now (after the page switched to it). */
2493
+ announce() {
2494
+ this.emit();
2495
+ }
2410
2496
  nextSeq(sponsorId) {
2411
2497
  const n = (this.mutationSeq.get(sponsorId) ?? 0) + 1;
2412
2498
  this.mutationSeq.set(sponsorId, n);
@@ -2500,15 +2586,118 @@ var CartManager = class extends EventTarget {
2500
2586
  emitError(message, err) {
2501
2587
  if (typeof console !== "undefined") console.warn("[CartManager]", message, err ?? "");
2502
2588
  this.dispatchEvent(new CustomEvent("error", { detail: { message } }));
2503
- if (typeof document !== "undefined") {
2589
+ if (this.active && typeof document !== "undefined") {
2504
2590
  document.dispatchEvent(
2505
2591
  new CustomEvent("vio:cart-error", { bubbles: true, detail: { message } })
2506
2592
  );
2507
2593
  }
2508
2594
  }
2509
2595
  // MARK: - Backend cart sync (Vio Commerce GraphQL)
2510
- /** Ensure the sponsor cart exists server-side; returns its backend cart id. */
2511
- async ensureCartId(sponsorId, currency, shippingCountry) {
2596
+ /**
2597
+ * The options of a sponsor's backend cart: the key of the channel it was
2598
+ * created on (`channelMissing` when the bootstrap no longer lists it). A
2599
+ * cart from before channels were remembered keeps the sponsor's first
2600
+ * block, as when it was created, and remembers that channel from now on.
2601
+ */
2602
+ async cartOptions(sponsorId) {
2603
+ const cart = this.cartsBySponsor.get(sponsorId);
2604
+ if (cart?.cartId && cart.channelId) {
2605
+ return getCartGraphQLOptions(sponsorId, { channelId: cart.channelId });
2606
+ }
2607
+ if (!cart?.cartId) return getCartGraphQLOptions(sponsorId);
2608
+ const opts = await getCartGraphQLOptions(sponsorId, { campaignId: null });
2609
+ const now = this.cartsBySponsor.get(sponsorId);
2610
+ if (now?.cartId && !now.channelId && opts.sponsorResolved && opts.channelId) {
2611
+ now.channelId = opts.channelId;
2612
+ this.persist();
2613
+ }
2614
+ return opts;
2615
+ }
2616
+ /**
2617
+ * The backend cart to call for a sponsor, with the key of its channel. When
2618
+ * the bootstrap no longer lists that channel the stale cart is dropped and a
2619
+ * new one is created on the sponsor's current channel with the same lines
2620
+ * (`dropStaleBackendCart` + `ensureCartId`) — never a call with a key of
2621
+ * another channel. `undefined` when there is no backend cart (or none could
2622
+ * be made).
2623
+ */
2624
+ async liveCart(sponsorId) {
2625
+ const cart = this.cartsBySponsor.get(sponsorId);
2626
+ const cartId = cart?.cartId;
2627
+ if (!cart || !cartId) return void 0;
2628
+ const opts = await this.cartOptions(sponsorId);
2629
+ if (!opts.channelMissing) {
2630
+ const current = this.cartsBySponsor.get(sponsorId)?.cartId;
2631
+ if (current && current !== cartId) return { cartId: current, opts: await this.cartOptions(sponsorId) };
2632
+ return { cartId, opts };
2633
+ }
2634
+ const now = this.cartsBySponsor.get(sponsorId);
2635
+ if (now?.cartId === cartId) this.dropStaleBackendCart(sponsorId);
2636
+ const rebuilt = await this.ensureCartId(sponsorId, now?.currency || void 0, now?.shippingCountry || void 0);
2637
+ if (!rebuilt) return void 0;
2638
+ return { cartId: rebuilt, opts: await this.cartOptions(sponsorId) };
2639
+ }
2640
+ /**
2641
+ * The channel of a sponsor's cart is gone from the bootstrap (the campaign
2642
+ * moved, a refresh after hours): forget the backend cart and its line ids,
2643
+ * keep the lines, and remember them as they stand now for the next backend
2644
+ * cart (`carryOver`). Queued quantity syncs of the old cart are dropped —
2645
+ * their quantities are in the lines carried.
2646
+ */
2647
+ dropStaleBackendCart(sponsorId) {
2648
+ const cart = this.cartsBySponsor.get(sponsorId);
2649
+ if (!cart?.cartId) return;
2650
+ const carried = cart.items.filter((item) => item.quantity > 0).map((item) => ({ productId: item.productId, variantId: item.variantId, origins: lineOrigins(item) }));
2651
+ delete cart.cartId;
2652
+ delete cart.channelId;
2653
+ for (const item of cart.items) delete item.cartItemId;
2654
+ for (const key of [...this.qtySyncs.keys()]) {
2655
+ if (key.startsWith(`${sponsorId}:`)) this.qtySyncs.delete(key);
2656
+ }
2657
+ this.carryOver.set(sponsorId, carried);
2658
+ this.rebuildEpoch.set(sponsorId, (this.rebuildEpoch.get(sponsorId) ?? 0) + 1);
2659
+ this.persist();
2660
+ }
2661
+ /** Give a new backend cart the lines of the one whose channel went away — each unit with its own origin. */
2662
+ async replayCarriedLines(sponsorId, cartId, carried, opts) {
2663
+ let last;
2664
+ const seq = this.nextSeq(sponsorId);
2665
+ try {
2666
+ const privacyMode = await currentPrivacyMode();
2667
+ for (const line of carried) {
2668
+ for (const origin of line.origins) {
2669
+ if (!(origin.quantity > 0)) continue;
2670
+ const { quantity, ...place } = origin;
2671
+ const wire = Object.keys(place).length ? lineOriginWire(normalizeOrigin(place), { page: currentOriginPage(), privacyMode }) : void 0;
2672
+ last = await addItem(
2673
+ cartId,
2674
+ [
2675
+ {
2676
+ product_id: Number(line.productId || 0),
2677
+ quantity,
2678
+ variant_id: line.variantId != null ? Number(line.variantId) : null,
2679
+ ...wire ? { origin: wire } : {}
2680
+ }
2681
+ ],
2682
+ opts
2683
+ );
2684
+ }
2685
+ }
2686
+ } catch (err) {
2687
+ this.emitError("moving the cart to the current channel failed \u2014 some lines may not be in it", err);
2688
+ }
2689
+ const res = last;
2690
+ if (res && Array.isArray(res.line_items)) this.applyBackendCart(sponsorId, res, seq);
2691
+ }
2692
+ /**
2693
+ * Ensure the sponsor cart exists server-side; returns its backend cart id.
2694
+ * A new backend cart is created on the channel of the campaign in context —
2695
+ * `ctx.campaignId` (the add's origin), else the campaign the cart's lines
2696
+ * came from, else `Vio.init`'s — and remembers it (`channelId`). An existing
2697
+ * one whose channel the bootstrap no longer lists is created again on the
2698
+ * current channel, with its lines (`liveCart`).
2699
+ */
2700
+ async ensureCartId(sponsorId, currency, shippingCountry, ctx = {}) {
2512
2701
  const activeCurrency = currency || getGlobalCurrency();
2513
2702
  const activeCountry = shippingCountry || getGlobalCountryCode();
2514
2703
  let cart = this.cartsBySponsor.get(sponsorId);
@@ -2518,11 +2707,15 @@ var CartManager = class extends EventTarget {
2518
2707
  } else {
2519
2708
  cart.shippingCountry = activeCountry;
2520
2709
  }
2521
- if (cart.cartId) return cart.cartId;
2710
+ if (cart.cartId) {
2711
+ if (!cart.channelId) return cart.cartId;
2712
+ return (await this.liveCart(sponsorId))?.cartId;
2713
+ }
2522
2714
  const inFlight = this.cartIdPromises.get(sponsorId);
2523
2715
  if (inFlight) return inFlight;
2524
2716
  const creation = (async () => {
2525
- const opts = await getCartGraphQLOptions(sponsorId);
2717
+ const campaignId = ctx.campaignId ?? cartCampaignOf(cart);
2718
+ const opts = await getCartGraphQLOptions(sponsorId, campaignId !== void 0 ? { campaignId } : void 0);
2526
2719
  if (!opts.sponsorResolved) {
2527
2720
  this.emitError(`no commerce key resolved for sponsor ${sponsorId} \u2014 cart not synced`);
2528
2721
  return void 0;
@@ -2537,6 +2730,8 @@ var CartManager = class extends EventTarget {
2537
2730
  );
2538
2731
  if (backendCart?.cart_id) {
2539
2732
  cart.cartId = backendCart.cart_id;
2733
+ if (opts.channelId) cart.channelId = opts.channelId;
2734
+ else delete cart.channelId;
2540
2735
  if (Array.isArray(backendCart.line_items) && backendCart.line_items.length > 0) {
2541
2736
  this.updateFromBackendCart(sponsorId, backendCart);
2542
2737
  } else {
@@ -2550,6 +2745,11 @@ var CartManager = class extends EventTarget {
2550
2745
  this.persist();
2551
2746
  this.emit();
2552
2747
  }
2748
+ const carried = this.carryOver.get(sponsorId);
2749
+ if (carried) {
2750
+ this.carryOver.delete(sponsorId);
2751
+ if (carried.length > 0) await this.replayCarriedLines(sponsorId, backendCart.cart_id, carried, opts);
2752
+ }
2553
2753
  }
2554
2754
  } catch (err) {
2555
2755
  this.emitError("createCart failed", err);
@@ -2642,7 +2842,7 @@ var CartManager = class extends EventTarget {
2642
2842
  this.emit();
2643
2843
  const syncedCartId = cart.cartId;
2644
2844
  if (syncedCartId) {
2645
- for (const id of removedBeforeSync) this.syncQuantity(sponsorId, syncedCartId, id, 0);
2845
+ for (const id of removedBeforeSync) this.syncQuantity(sponsorId, syncedCartId, id, 0, void 0, cart.channelId);
2646
2846
  }
2647
2847
  if (this.pendingQtyIntents.size > 0) {
2648
2848
  const currentCart = this.cartsBySponsor.get(sponsorId);
@@ -2734,13 +2934,17 @@ var CartManager = class extends EventTarget {
2734
2934
  this.emit();
2735
2935
  const addedItem = existing ?? cart.items[cart.items.length - 1];
2736
2936
  const addedLineId = addedItem.id;
2937
+ const epochAtAdd = this.rebuildEpoch.get(opts.sponsorId) ?? 0;
2737
2938
  const seq = this.nextSeq(opts.sponsorId);
2738
2939
  let settling;
2739
2940
  const mutation = (async () => {
2740
2941
  try {
2741
- const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
2942
+ const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0, void 0, {
2943
+ campaignId: opts.origin?.campaignId
2944
+ });
2945
+ if ((this.rebuildEpoch.get(opts.sponsorId) ?? 0) !== epochAtAdd) return;
2742
2946
  if (cartId) {
2743
- const gqlOpts = await getCartGraphQLOptions(opts.sponsorId);
2947
+ const gqlOpts = await this.cartOptions(opts.sponsorId);
2744
2948
  const origin = opts.origin ? lineOriginWire(normalizeOrigin(opts.origin), {
2745
2949
  page: currentOriginPage(),
2746
2950
  privacyMode: await currentPrivacyMode()
@@ -2809,7 +3013,7 @@ var CartManager = class extends EventTarget {
2809
3013
  const addedOrigin = increase ? latestOrigin(item) : void 0;
2810
3014
  if (target <= 0) {
2811
3015
  cart.items = cart.items.filter((i) => i !== item);
2812
- if (typeof document !== "undefined") {
3016
+ if (this.active && typeof document !== "undefined") {
2813
3017
  document.dispatchEvent(
2814
3018
  new CustomEvent("vio:removed-from-cart", {
2815
3019
  bubbles: true,
@@ -2836,12 +3040,17 @@ var CartManager = class extends EventTarget {
2836
3040
  if (cart.items.length === 0) this.cartsBySponsor.delete(sponsorId);
2837
3041
  this.persist();
2838
3042
  this.emit();
2839
- if (!cart.cartId) return;
3043
+ if (!cart.cartId) {
3044
+ if (this.cartIdPromises.has(sponsorId)) {
3045
+ this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
3046
+ }
3047
+ return;
3048
+ }
2840
3049
  if (!targetItemId) {
2841
3050
  this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
2842
3051
  return;
2843
3052
  }
2844
- this.syncQuantity(sponsorId, cart.cartId, targetItemId, target, addedOrigin);
3053
+ this.syncQuantity(sponsorId, cart.cartId, targetItemId, target, addedOrigin, cart.channelId);
2845
3054
  }
2846
3055
  /** Send a line's target quantity to the backend — ONE request in flight per
2847
3056
  * line. Targets that arrive while one is in flight queue behind it as
@@ -2853,7 +3062,7 @@ var CartManager = class extends EventTarget {
2853
3062
  * optimistic quantity through any backend response (see
2854
3063
  * `updateFromBackendCart`); a rejection drops what was queued and
2855
3064
  * reconciles the cart from the server. */
2856
- syncQuantity(sponsorId, backendCartId, cartItemId, target, origin) {
3065
+ syncQuantity(sponsorId, backendCartId, cartItemId, target, origin, channelId) {
2857
3066
  const key = this.qtySyncKey(sponsorId, cartItemId);
2858
3067
  const active = this.qtySyncs.get(key);
2859
3068
  if (active) {
@@ -2868,9 +3077,19 @@ var CartManager = class extends EventTarget {
2868
3077
  let nextOrigin = origin;
2869
3078
  let removalRetried = false;
2870
3079
  while (next !== void 0) {
3080
+ let gqlOpts;
3081
+ if (this.cartsBySponsor.get(sponsorId)?.cartId) {
3082
+ const live = await this.liveCart(sponsorId);
3083
+ if (!live || live.cartId !== backendCartId) {
3084
+ if (this.qtySyncs.get(key) === sync) this.qtySyncs.delete(key);
3085
+ break;
3086
+ }
3087
+ gqlOpts = live.opts;
3088
+ } else {
3089
+ gqlOpts = await getCartGraphQLOptions(sponsorId, channelId ? { channelId, retired: true } : void 0);
3090
+ }
2871
3091
  const seq = this.nextSeq(sponsorId);
2872
3092
  try {
2873
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2874
3093
  const originWire = next > 0 && nextOrigin ? lineOriginWire(nextOrigin, { page: currentOriginPage(), privacyMode: await currentPrivacyMode() }) : void 0;
2875
3094
  const resCart = next <= 0 ? await deleteItem(backendCartId, cartItemId, gqlOpts) : originWire ? await updateItem(backendCartId, cartItemId, null, next, gqlOpts, { origin: originWire }) : await updateItem(backendCartId, cartItemId, null, next, gqlOpts);
2876
3095
  const step = sync.steps.shift();
@@ -2930,10 +3149,9 @@ var CartManager = class extends EventTarget {
2930
3149
  }
2931
3150
  /** Refresh the local cart from the backend (source of truth). */
2932
3151
  async fetchCartFromBackend(sponsorId) {
2933
- const cart = this.cartsBySponsor.get(sponsorId);
2934
- if (!cart?.cartId) return null;
2935
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2936
- const resCart = await getCart(cart.cartId, gqlOpts);
3152
+ const live = await this.liveCart(sponsorId);
3153
+ if (!live) return null;
3154
+ const resCart = await getCart(live.cartId, live.opts);
2937
3155
  if (resCart) this.updateFromBackendCart(sponsorId, resCart);
2938
3156
  return resCart;
2939
3157
  }
@@ -2957,8 +3175,9 @@ var CartManager = class extends EventTarget {
2957
3175
  if (!cart?.cartId) return cart;
2958
3176
  let resCart;
2959
3177
  try {
2960
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2961
- resCart = await getCart(cart.cartId, gqlOpts);
3178
+ const live = await this.liveCart(sponsorId);
3179
+ if (!live) return this.cartsBySponsor.get(sponsorId);
3180
+ resCart = await getCart(live.cartId, live.opts);
2962
3181
  } catch (err) {
2963
3182
  if (typeof console !== "undefined") {
2964
3183
  console.warn("[CartManager] GetCart before the checkout failed \u2014 the local cart stands", err);
@@ -2977,7 +3196,7 @@ var CartManager = class extends EventTarget {
2977
3196
  }
2978
3197
  /** `vio:cart-updated` on `document` — see `CartUpdatedDetail`. */
2979
3198
  emitCartUpdated(sponsorId, source) {
2980
- if (typeof document === "undefined") return;
3199
+ if (!this.active || typeof document === "undefined") return;
2981
3200
  const cart = this.cartsBySponsor.get(sponsorId);
2982
3201
  const items = (cart?.items ?? []).map((item) => ({ ...item }));
2983
3202
  const detail = {
@@ -2993,17 +3212,15 @@ var CartManager = class extends EventTarget {
2993
3212
  }
2994
3213
  /** Line items grouped by supplier (shipping is per-supplier server-side). */
2995
3214
  async fetchLineItemsBySupplier(sponsorId) {
2996
- const cart = this.cartsBySponsor.get(sponsorId);
2997
- if (!cart?.cartId) return null;
2998
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2999
- return getLineItemsBySupplier(cart.cartId, gqlOpts);
3215
+ const live = await this.liveCart(sponsorId);
3216
+ if (!live) return null;
3217
+ return getLineItemsBySupplier(live.cartId, live.opts);
3000
3218
  }
3001
3219
  /** Set the chosen shipping option per supplier on the backend cart. */
3002
3220
  async updateShippingsBySupplier(sponsorId, data) {
3003
- const cart = this.cartsBySponsor.get(sponsorId);
3004
- if (!cart?.cartId) return null;
3005
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
3006
- const resCart = await updateShippingsBySupplier(cart.cartId, data, gqlOpts);
3221
+ const live = await this.liveCart(sponsorId);
3222
+ if (!live) return null;
3223
+ const resCart = await updateShippingsBySupplier(live.cartId, data, live.opts);
3007
3224
  if (resCart && Array.isArray(resCart.line_items)) {
3008
3225
  this.updateFromBackendCart(sponsorId, resCart);
3009
3226
  }
@@ -3053,13 +3270,13 @@ var CartManager = class extends EventTarget {
3053
3270
  try {
3054
3271
  const obj = {};
3055
3272
  for (const [k, v] of this.cartsBySponsor) obj[String(k)] = v;
3056
- storageSet("local", STORAGE_KEY, JSON.stringify(obj), { memory: false });
3273
+ storageSet("local", this.storageKey, JSON.stringify(obj), { memory: false });
3057
3274
  } catch {
3058
3275
  }
3059
3276
  }
3060
- loadFromStorage() {
3277
+ loadFromStorage(key = this.storageKey) {
3061
3278
  try {
3062
- const raw = storageGet("local", STORAGE_KEY);
3279
+ const raw = storageGet("local", key);
3063
3280
  if (!raw) return /* @__PURE__ */ new Map();
3064
3281
  const obj = JSON.parse(raw);
3065
3282
  const map = /* @__PURE__ */ new Map();
@@ -3210,6 +3427,56 @@ function findSponsorIn(boot, sponsorId) {
3210
3427
  }
3211
3428
  return void 0;
3212
3429
  }
3430
+ function channelIdOf(value) {
3431
+ if (value === null || value === void 0) return void 0;
3432
+ const s = String(value).trim();
3433
+ return s ? s : void 0;
3434
+ }
3435
+ function sells(s) {
3436
+ const commerce = s?.commerce;
3437
+ if (!commerce || typeof commerce !== "object") return false;
3438
+ const key = commerce.apiKey;
3439
+ return typeof key === "string" && key.trim() !== "";
3440
+ }
3441
+ function sellingBlocksIn(boot, sponsorId) {
3442
+ const id = toSponsorId(sponsorId);
3443
+ const out = [];
3444
+ if (id === void 0 || !boot || typeof boot !== "object") return out;
3445
+ const take = (s, campaignId) => {
3446
+ if (!s || typeof s !== "object" || Number(s.id) !== id || !sells(s)) return;
3447
+ out.push({ campaignId: toCampaignId(campaignId), sponsor: s });
3448
+ };
3449
+ try {
3450
+ const defaultId = boot.campaign?.id;
3451
+ take(boot.primarySponsor, defaultId);
3452
+ if (Array.isArray(boot.secondarySponsors)) for (const s of boot.secondarySponsors) take(s, defaultId);
3453
+ const campaigns = Array.isArray(boot.campaigns) ? boot.campaigns : [];
3454
+ for (const c of campaigns) {
3455
+ if (!c || typeof c !== "object") continue;
3456
+ take(c.primarySponsor, c.id);
3457
+ if (Array.isArray(c.secondarySponsors)) for (const s of c.secondarySponsors) take(s, c.id);
3458
+ }
3459
+ } catch {
3460
+ }
3461
+ return out;
3462
+ }
3463
+ function sponsorCommerceIn(boot, sponsorId, ctx = {}) {
3464
+ try {
3465
+ const blocks = sellingBlocksIn(boot, sponsorId);
3466
+ if (blocks.length === 0) return void 0;
3467
+ const channel = channelIdOf(ctx?.channelId);
3468
+ if (channel !== void 0) {
3469
+ return blocks.find((b) => channelIdOf(b.sponsor.commerce.channelId) === channel)?.sponsor;
3470
+ }
3471
+ for (const campaignId of campaignCandidates(ctx?.campaignId)) {
3472
+ const found = blocks.find((b) => b.campaignId === campaignId);
3473
+ if (found) return found.sponsor;
3474
+ }
3475
+ return blocks[0].sponsor;
3476
+ } catch {
3477
+ return void 0;
3478
+ }
3479
+ }
3213
3480
  function sponsorIdentityFrom(boot, sponsorId) {
3214
3481
  try {
3215
3482
  const id = toSponsorId(sponsorId);
@@ -3255,7 +3522,8 @@ function normalizeEntry(raw) {
3255
3522
  const checkoutId = typeof r.checkoutId === "string" ? r.checkoutId.trim() : "";
3256
3523
  const sponsorId = toSponsorId(r.sponsorId);
3257
3524
  if (!checkoutId || sponsorId === void 0) return null;
3258
- return { checkoutId, sponsorId, name: nameOf(r.name), logoUrl: urlOrNull(r.logoUrl) };
3525
+ const channelId = channelIdOf(r.channelId);
3526
+ return { checkoutId, sponsorId, name: nameOf(r.name), logoUrl: urlOrNull(r.logoUrl), ...channelId ? { channelId } : {} };
3259
3527
  }
3260
3528
  function readEntries() {
3261
3529
  const store = storageRefusedWrite ? null : sessionStore();
@@ -3295,8 +3563,9 @@ function rememberCheckoutSponsor(checkoutId, sponsor) {
3295
3563
  name = previous.name;
3296
3564
  logoUrl = previous.logoUrl;
3297
3565
  }
3566
+ const channelId = channelIdOf(sponsor.channelId) ?? (previous && previous.sponsorId === sponsorId ? previous.channelId : void 0);
3298
3567
  const next = entries.filter((e) => e.checkoutId !== id);
3299
- next.push({ checkoutId: id, sponsorId, name, logoUrl });
3568
+ next.push({ checkoutId: id, sponsorId, name, logoUrl, ...channelId ? { channelId } : {} });
3300
3569
  writeEntries(next.slice(-CHECKOUT_SPONSOR_MEMORY_MAX));
3301
3570
  } catch {
3302
3571
  }
@@ -3535,7 +3804,7 @@ async function prepareApplePay(config) {
3535
3804
  }
3536
3805
 
3537
3806
  // src/core/version.ts
3538
- var SDK_VERSION = "0.32.0";
3807
+ var SDK_VERSION = "0.33.0";
3539
3808
 
3540
3809
  // src/core/checkout/payments/stripe-embedded.ts
3541
3810
  var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
@@ -5104,8 +5373,8 @@ function resolveHttpsReturnUrl(overrideUrl, params = null) {
5104
5373
  }
5105
5374
  return url;
5106
5375
  }
5107
- async function executeKlarnaGraphQL(query, variables, sponsorId) {
5108
- return executeCartGraphQL(query, variables, await getCartGraphQLOptions(sponsorId));
5376
+ async function executeKlarnaGraphQL(query, variables, options) {
5377
+ return executeCartGraphQL(query, variables, options);
5109
5378
  }
5110
5379
  function usesStripe(methods) {
5111
5380
  if (!Array.isArray(methods)) return false;
@@ -5188,8 +5457,44 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5188
5457
  return this.currentStateValue;
5189
5458
  }
5190
5459
  set state(next) {
5460
+ if (next?.checkoutId && next.sponsorId && !next.channelId) {
5461
+ const remembered = rememberedCheckoutSponsor(next.checkoutId);
5462
+ const channelId = (remembered?.sponsorId === next.sponsorId ? remembered.channelId : void 0) ?? this.cartManager.getCart(next.sponsorId)?.channelId;
5463
+ if (channelId) next = { ...next, channelId };
5464
+ }
5191
5465
  this.currentStateValue = next;
5192
- if (next?.checkoutId && next.sponsorId) this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId);
5466
+ if (next?.checkoutId && next.sponsorId) {
5467
+ this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId, next.channelId);
5468
+ }
5469
+ }
5470
+ /**
5471
+ * The options of a call for a sponsor's cart or checkout: the key of the
5472
+ * channel the checkout lives on — what this tab remembered for the
5473
+ * checkout (a return from a redirect, after the cart was cleared), the open
5474
+ * checkout's, else the cart's — even if a bootstrap refresh no longer lists
5475
+ * it (an open checkout keeps paying with its own key). Without any, the
5476
+ * sponsor's block as `getCartGraphQLOptions` resolves it.
5477
+ */
5478
+ async gqlOptions(sponsorId, checkoutId) {
5479
+ const channelId = this.channelOf(sponsorId, checkoutId);
5480
+ return getCartGraphQLOptions(sponsorId, channelId ? { channelId, retired: true } : void 0);
5481
+ }
5482
+ /** See `gqlOptions`. */
5483
+ channelOf(sponsorId, checkoutId) {
5484
+ try {
5485
+ if (!sponsorId) return void 0;
5486
+ const state = this.state?.sponsorId === sponsorId ? this.state : null;
5487
+ const id = checkoutId ?? state?.checkoutId;
5488
+ if (id) {
5489
+ const remembered = rememberedCheckoutSponsor(id);
5490
+ if (remembered?.channelId && remembered.sponsorId === sponsorId) return remembered.channelId;
5491
+ if (state?.checkoutId === id && state.channelId) return state.channelId;
5492
+ }
5493
+ const cart = this.cartManager.getCart(sponsorId);
5494
+ return cart?.cartId ? cart.channelId : void 0;
5495
+ } catch {
5496
+ return void 0;
5497
+ }
5193
5498
  }
5194
5499
  /** The open checkout's lines as a signature, to tell a real change from an echo. */
5195
5500
  cartSignature(sponsorId) {
@@ -5264,7 +5569,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5264
5569
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${sponsorId}`);
5265
5570
  }
5266
5571
  }
5267
- const opts = await getCartGraphQLOptions(sponsorId);
5572
+ const opts = await this.gqlOptions(sponsorId);
5268
5573
  const attribution = await this.attributionFor(sponsorId);
5269
5574
  const checkout = attribution ? await createCheckout(cartId, opts, { attribution }) : await createCheckout(cartId, opts);
5270
5575
  if (checkout?.id) {
@@ -5347,7 +5652,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5347
5652
  /** Update the backend checkout (terms are re-asserted unless overridden; the attribution rides along unless the caller gives its own). */
5348
5653
  async updateCheckout(variables, sponsorId) {
5349
5654
  const spId = sponsorId ?? this.state?.sponsorId;
5350
- const opts = await getCartGraphQLOptions(spId);
5655
+ const opts = await this.gqlOptions(spId);
5351
5656
  const attribution = "attribution" in variables ? void 0 : await this.attributionFor(spId);
5352
5657
  const fullVars = {
5353
5658
  buyerAcceptsPurchaseConditions: true,
@@ -5363,7 +5668,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5363
5668
  return updated;
5364
5669
  }
5365
5670
  async getCheckout(checkoutId, sponsorId) {
5366
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
5671
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5367
5672
  return getCheckout(checkoutId, opts);
5368
5673
  }
5369
5674
  /**
@@ -5386,8 +5691,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5386
5691
  if (!request) {
5387
5692
  const asked = (async () => {
5388
5693
  try {
5389
- const opts = await getCartGraphQLOptions(
5390
- sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId
5694
+ const opts = await this.gqlOptions(
5695
+ sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId,
5696
+ checkoutId
5391
5697
  );
5392
5698
  return await getCheckoutReference(checkoutId, opts);
5393
5699
  } catch {
@@ -5440,7 +5746,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5440
5746
  * sponsor's name and logo as known now — so a confirmation after a redirect
5441
5747
  * or a reload can name the brand. Written again only when something changed.
5442
5748
  */
5443
- rememberSponsorOfCheckout(checkoutId, sponsorId) {
5749
+ rememberSponsorOfCheckout(checkoutId, sponsorId, channelId) {
5444
5750
  try {
5445
5751
  let identity = null;
5446
5752
  try {
@@ -5450,10 +5756,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5450
5756
  }
5451
5757
  const name = identity?.name || this.cartManager.getCart(sponsorId)?.sponsorName || "";
5452
5758
  const logoUrl = identity?.logoUrl ?? null;
5453
- const key = JSON.stringify([checkoutId, sponsorId, name, logoUrl]);
5759
+ const key = JSON.stringify([checkoutId, sponsorId, name, logoUrl, channelId ?? null]);
5454
5760
  if (key === this.lastRememberedSponsor) return;
5455
5761
  this.lastRememberedSponsor = key;
5456
- rememberCheckoutSponsor(checkoutId, { sponsorId, name, logoUrl });
5762
+ rememberCheckoutSponsor(checkoutId, { sponsorId, name, logoUrl, channelId });
5457
5763
  } catch {
5458
5764
  }
5459
5765
  }
@@ -5471,7 +5777,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5471
5777
  if (inFlight) return inFlight;
5472
5778
  const promise = (async () => {
5473
5779
  try {
5474
- const opts = await getCartGraphQLOptions(spId);
5780
+ const opts = await this.gqlOptions(spId);
5475
5781
  const res = await getAvailablePaymentMethods(opts);
5476
5782
  if (res) this.paymentMethodsCache.set(spId, res);
5477
5783
  if (usesStripe(res)) preloadStripeJs();
@@ -5508,7 +5814,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5508
5814
  cartId = await this.cartManager.ensureCartId(spId);
5509
5815
  }
5510
5816
  if (!cartId) return [];
5511
- const opts = await getCartGraphQLOptions(spId);
5817
+ const opts = await this.gqlOptions(spId);
5512
5818
  const supplierGroups = await getLineItemsBySupplier(cartId, opts);
5513
5819
  this.shippingsResolved = Array.isArray(supplierGroups);
5514
5820
  const firstGroup = Array.isArray(supplierGroups) ? supplierGroups[0] : null;
@@ -5562,7 +5868,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5562
5868
  if (!spId) return null;
5563
5869
  const cart = this.cartManager.getCart(spId);
5564
5870
  if (!cart?.cartId) return null;
5565
- const opts = await getCartGraphQLOptions(spId);
5871
+ const opts = await this.gqlOptions(spId);
5566
5872
  const updated = await updateShippingsBySupplier(cart.cartId, data, opts);
5567
5873
  if (updated) {
5568
5874
  this.cartManager.updateFromBackendCart(spId, updated);
@@ -5597,7 +5903,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5597
5903
  if (!cartId) {
5598
5904
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5599
5905
  }
5600
- const opts = await getCartGraphQLOptions(spId);
5906
+ const opts = await this.gqlOptions(spId);
5601
5907
  const attribution = await this.attributionFor(spId);
5602
5908
  let checkoutId = this.state?.checkoutId;
5603
5909
  if (!checkoutId) {
@@ -5756,7 +6062,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5756
6062
  /** Vipps' own payment state for the checkout — see getVippsStatus in
5757
6063
  * cart-queries.ts for why this exists instead of trusting GetCheckout. */
5758
6064
  async getVippsStatus(checkoutId, sponsorId) {
5759
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6065
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5760
6066
  return getVippsStatus({ checkoutId }, opts);
5761
6067
  }
5762
6068
  /**
@@ -5773,7 +6079,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5773
6079
  if (!cartId) {
5774
6080
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5775
6081
  }
5776
- const opts = await getCartGraphQLOptions(spId);
6082
+ const opts = await this.gqlOptions(spId);
5777
6083
  let checkoutId = this.state?.checkoutId;
5778
6084
  if (!checkoutId) {
5779
6085
  const checkoutRes = await this.createCheckout(spId, "kustom");
@@ -5819,7 +6125,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5819
6125
  const spId = sponsorId ?? this.state?.sponsorId;
5820
6126
  const checkoutId = this.state?.checkoutId;
5821
6127
  if (!spId || !checkoutId) return null;
5822
- const opts = await getCartGraphQLOptions(spId);
6128
+ const opts = await this.gqlOptions(spId);
5823
6129
  suspendKustom();
5824
6130
  try {
5825
6131
  return await syncPaymentKustom(checkoutId, opts);
@@ -5829,7 +6135,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5829
6135
  }
5830
6136
  /** Re-read a Kustom order — used on the confirmation return trip. */
5831
6137
  async getKustomOrder(orderId, sponsorId) {
5832
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6138
+ const opts = await this.gqlOptions(sponsorId ?? this.state?.sponsorId);
5833
6139
  return getKustomOrder(orderId, opts);
5834
6140
  }
5835
6141
  /** Render a Kustom html_snippet (e.g. the confirmation receipt) into a container. */
@@ -5849,7 +6155,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5849
6155
  if (!cartId) {
5850
6156
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5851
6157
  }
5852
- const opts = await getCartGraphQLOptions(spId);
6158
+ const opts = await this.gqlOptions(spId);
5853
6159
  let checkoutId = this.state?.checkoutId;
5854
6160
  if (!checkoutId) {
5855
6161
  const checkoutRes = await this.createCheckout(spId, "qliro");
@@ -5919,7 +6225,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5919
6225
  }
5920
6226
  /** Re-read the Qliro order owned by a checkout — the confirmation trip. */
5921
6227
  async getQliroOrder(checkoutId, sponsorId) {
5922
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6228
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5923
6229
  return getQliroOrder(checkoutId, opts);
5924
6230
  }
5925
6231
  /**
@@ -5937,7 +6243,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5937
6243
  if (!cartId) {
5938
6244
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5939
6245
  }
5940
- const opts = await getCartGraphQLOptions(spId);
6246
+ const opts = await this.gqlOptions(spId);
5941
6247
  let checkoutId = this.state?.checkoutId;
5942
6248
  if (!checkoutId) {
5943
6249
  const checkoutRes = await this.createCheckout(spId, "walley");
@@ -5974,7 +6280,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5974
6280
  }
5975
6281
  /** Re-read the Walley session owned by a checkout — the confirmation trip. */
5976
6282
  async getWalleyOrder(checkoutId, sponsorId) {
5977
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6283
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5978
6284
  return getWalleyOrder(checkoutId, opts);
5979
6285
  }
5980
6286
  /**
@@ -6000,7 +6306,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6000
6306
  if (!cartId) {
6001
6307
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
6002
6308
  }
6003
- const opts = await getCartGraphQLOptions(spId);
6309
+ const opts = await this.gqlOptions(spId);
6004
6310
  const stored = resume ? readNexiPending() : null;
6005
6311
  const pending2 = stored && stored.paymentId === resume?.paymentId ? stored : null;
6006
6312
  let checkoutId = this.state?.checkoutId;
@@ -6234,7 +6540,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6234
6540
  }
6235
6541
  /** Re-read the Nexi payment owned by a checkout. */
6236
6542
  async getNexiOrder(checkoutId, sponsorId) {
6237
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6543
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
6238
6544
  return getNexiOrder(checkoutId, opts);
6239
6545
  }
6240
6546
  /**
@@ -6312,7 +6618,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6312
6618
  * finalised on the server.
6313
6619
  */
6314
6620
  async getAdyenPayment(checkoutId, proof = {}, sponsorId) {
6315
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6621
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
6316
6622
  return getAdyenPayment({ checkoutId, ...proof }, opts);
6317
6623
  }
6318
6624
  /* eslint-enable @typescript-eslint/no-explicit-any */
@@ -6591,7 +6897,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6591
6897
  console.warn("[VioCheckout] updateCheckout before Apple Pay confirm failed:", err);
6592
6898
  }
6593
6899
  }
6594
- const gqlOpts = await getCartGraphQLOptions(spId);
6900
+ const gqlOpts = await this.gqlOptions(spId);
6595
6901
  const createRes = await createPaymentApplePay({ checkoutId }, gqlOpts);
6596
6902
  const gatewayId = String(createRes?.gateway_merchant_id ?? "");
6597
6903
  if (gatewayId.startsWith("pk_")) this.applePayPublishableKey = gatewayId;
@@ -6843,7 +7149,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6843
7149
  const json = await executeKlarnaGraphQL(
6844
7150
  CREATE_PAYMENT_KLARNA_NATIVE_MUTATION,
6845
7151
  variables,
6846
- sponsorId
7152
+ await this.gqlOptions(sponsorId, checkoutId)
6847
7153
  );
6848
7154
  const nativeRes = json?.data?.Payment?.CreatePaymentKlarnaNative;
6849
7155
  if (!nativeRes || !nativeRes.client_token) {
@@ -6993,7 +7299,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6993
7299
  const json = await executeKlarnaGraphQL(
6994
7300
  CREATE_PAYMENT_KLARNA_NATIVE_MUTATION,
6995
7301
  variables,
6996
- sponsorId
7302
+ await this.gqlOptions(sponsorId, checkoutId)
6997
7303
  );
6998
7304
  const nativeRes = json?.data?.Payment?.CreatePaymentKlarnaNative;
6999
7305
  if (!nativeRes || !nativeRes.client_token) {
@@ -7135,12 +7441,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
7135
7441
  shippingAddress: buildKlarnaAddress(address),
7136
7442
  customer: buildKlarnaCustomer(address)
7137
7443
  };
7444
+ const klarnaSponsorId = ctx?.sponsorId ?? this.state?.sponsorId;
7445
+ const klarnaOptions = await this.gqlOptions(klarnaSponsorId, ctx?.checkoutId ?? this.state?.checkoutId);
7138
7446
  const json = await withBuyerRef(
7139
- (withRef) => executeKlarnaGraphQL(
7140
- confirmPaymentKlarnaNativeMutation(withRef),
7141
- variables,
7142
- ctx?.sponsorId ?? this.state?.sponsorId
7143
- )
7447
+ (withRef) => executeKlarnaGraphQL(confirmPaymentKlarnaNativeMutation(withRef), variables, klarnaOptions)
7144
7448
  );
7145
7449
  const confirmRes = json?.data?.Payment?.ConfirmPaymentKlarnaNative;
7146
7450
  if (!confirmRes) {
@@ -8664,6 +8968,8 @@ function cut3(value, max) {
8664
8968
 
8665
8969
  // src/core/client.ts
8666
8970
  var SLOT_DECLARATION_DEBOUNCE_MS = 1e3;
8971
+ var BOOTSTRAP_MAX_AGE_MS = 5 * 6e4;
8972
+ var BOOTSTRAP_REFRESH_BACKOFF_MS = 6e4;
8667
8973
  var PRIVACY_MODE_TIMEOUT_MS = 1500;
8668
8974
  var PRIVACY_UNKNOWN_TTL_MS = 6e4;
8669
8975
  function privacyModeOf(value) {
@@ -8679,9 +8985,23 @@ function privacyModeOfSurface(res) {
8679
8985
  var VioFacade = class {
8680
8986
  constructor() {
8681
8987
  this.apiInstance = null;
8988
+ /** Commerce clients by sponsor AND key: one sponsor may sell on several channels (one per campaign). */
8682
8989
  this.commerceClients = /* @__PURE__ */ new Map();
8683
8990
  this.bootstrapPromise = null;
8684
8991
  this.cachedBootstrap = null;
8992
+ /** When the cached bootstrap arrived (ms epoch). */
8993
+ this.bootstrapAt = 0;
8994
+ /** A refresh of the bootstrap in flight (`refreshStaleBootstrap`). */
8995
+ this.bootstrapRefresh = null;
8996
+ this.bootstrapRefreshFailedAt = 0;
8997
+ /** The `visibilitychange` listener is installed once per page. */
8998
+ this.visibilityHooked = false;
8999
+ /**
9000
+ * Every selling block this page has seen, by `sponsorId:channelId` — so a
9001
+ * checkout opened on a channel keeps its key after a bootstrap refresh no
9002
+ * longer lists that channel (`sponsorCommerce(…, { retired: true })`).
9003
+ */
9004
+ this.seenChannels = /* @__PURE__ */ new Map();
8685
9005
  /** Last bootstrap failure — short backoff so cart mutations don't hammer a
8686
9006
  * dead endpoint on every call. */
8687
9007
  this.bootstrapFailedAt = 0;
@@ -8705,7 +9025,17 @@ var VioFacade = class {
8705
9025
  this.warnedDeclarationCap = false;
8706
9026
  // Client-side managers — created lazily on first access. They don't
8707
9027
  // need Vio.init() to work (state is purely local).
8708
- this.cartManager = null;
9028
+ /**
9029
+ * One cart manager per connection (environment + key), each with its own
9030
+ * storage entry; `Vio.cart` is a stable front for the current one, so a
9031
+ * re-init with another key switches carts without the page re-subscribing.
9032
+ * A manager left behind keeps its carts (they come back if the page switches
9033
+ * back) and finishes its requests into ITS carts, never the new one's.
9034
+ */
9035
+ this.cartManagers = /* @__PURE__ */ new Map();
9036
+ this.cartScope = "";
9037
+ this.cartFront = null;
9038
+ this.cartEvents = new EventTarget();
8709
9039
  this.checkoutManager = null;
8710
9040
  this.analyticsManager = null;
8711
9041
  /** What the surface answered of its privacy mode (`GET /v2/mobile/surface`) and when; `unknown` expires after a minute. */
@@ -8715,10 +9045,19 @@ var VioFacade = class {
8715
9045
  }
8716
9046
  init(config) {
8717
9047
  Configuration.init(config);
9048
+ try {
9049
+ const resolved = Configuration.get();
9050
+ this.switchCartScope(cartScopeOf(resolved.environment, resolved.apiKey));
9051
+ } catch {
9052
+ }
8718
9053
  this.apiInstance = null;
8719
9054
  this.commerceClients.clear();
8720
9055
  this.bootstrapPromise = null;
8721
9056
  this.cachedBootstrap = null;
9057
+ this.bootstrapAt = 0;
9058
+ this.bootstrapRefresh = null;
9059
+ this.bootstrapRefreshFailedAt = 0;
9060
+ this.seenChannels.clear();
8722
9061
  this.bootstrapFailedAt = 0;
8723
9062
  this.generation++;
8724
9063
  this.surfaceMode = null;
@@ -8766,8 +9105,66 @@ var VioFacade = class {
8766
9105
  // MARK: - Client-side managers (no init required)
8767
9106
  /** Multi-sponsor cart manager. Reactive — listen via addEventListener('change', ...). */
8768
9107
  get cart() {
8769
- if (!this.cartManager) this.cartManager = new CartManager();
8770
- return this.cartManager;
9108
+ if (!this.cartFront) this.cartFront = this.makeCartFront();
9109
+ return this.cartFront;
9110
+ }
9111
+ /** The cart manager of the page's connection now (created on first use). */
9112
+ currentCartManager() {
9113
+ const scope = this.cartScope;
9114
+ let manager = this.cartManagers.get(scope);
9115
+ if (!manager) {
9116
+ const created = new CartManager(scope);
9117
+ for (const type of ["change", "error"]) {
9118
+ created.addEventListener(type, (e) => {
9119
+ if (this.cartManagers.get(this.cartScope) !== created) return;
9120
+ this.cartEvents.dispatchEvent(new CustomEvent(type, { detail: e.detail }));
9121
+ });
9122
+ }
9123
+ this.cartManagers.set(scope, created);
9124
+ manager = created;
9125
+ }
9126
+ return manager;
9127
+ }
9128
+ /**
9129
+ * `Vio.cart`: every property and method is the current connection's
9130
+ * manager's; the listeners live on the front, so they survive a switch.
9131
+ */
9132
+ makeCartFront() {
9133
+ const events = this.cartEvents;
9134
+ const listenerMethods = {
9135
+ addEventListener: events.addEventListener.bind(events),
9136
+ removeEventListener: events.removeEventListener.bind(events),
9137
+ dispatchEvent: events.dispatchEvent.bind(events)
9138
+ };
9139
+ return new Proxy(Object.create(CartManager.prototype), {
9140
+ get: (_target, prop) => {
9141
+ if (typeof prop === "string" && prop in listenerMethods) return listenerMethods[prop];
9142
+ const manager = this.currentCartManager();
9143
+ const value = Reflect.get(manager, prop, manager);
9144
+ if (typeof value !== "function" || Object.prototype.hasOwnProperty.call(manager, prop)) return value;
9145
+ return value.bind(manager);
9146
+ },
9147
+ set: (_target, prop, value) => Reflect.set(this.currentCartManager(), prop, value),
9148
+ has: (_target, prop) => Reflect.has(this.currentCartManager(), prop),
9149
+ // A property defined on Vio.cart (a test's spy, a host's patch) lands on
9150
+ // the current manager, where its own methods find it.
9151
+ defineProperty: (_target, prop, descriptor) => Reflect.defineProperty(this.currentCartManager(), prop, descriptor),
9152
+ deleteProperty: (_target, prop) => Reflect.deleteProperty(this.currentCartManager(), prop),
9153
+ getOwnPropertyDescriptor: (_target, prop) => {
9154
+ const own = Reflect.getOwnPropertyDescriptor(this.currentCartManager(), prop);
9155
+ return own ? { ...own, configurable: true } : void 0;
9156
+ }
9157
+ });
9158
+ }
9159
+ /** After `init`: the carts are the new connection's. Says `change` when that switched them. */
9160
+ switchCartScope(scope) {
9161
+ if (scope === this.cartScope) return;
9162
+ this.cartManagers.get(this.cartScope)?.setActive(false);
9163
+ this.cartScope = scope;
9164
+ if (!this.cartFront) return;
9165
+ const manager = this.currentCartManager();
9166
+ manager.setActive(true);
9167
+ manager.announce();
8771
9168
  }
8772
9169
  /** Checkout flow manager. Subscribes to cart state via the CartManager; asks here for the order's attribution. */
8773
9170
  get checkout() {
@@ -8815,7 +9212,7 @@ var VioFacade = class {
8815
9212
  const promise = this.api.bootstrap(Configuration.get().platform).then(
8816
9213
  (res) => {
8817
9214
  if (generation !== this.generation) return this.bootstrap();
8818
- this.cachedBootstrap = res;
9215
+ this.adoptBootstrap(res);
8819
9216
  this.bootstrapFailedAt = 0;
8820
9217
  if (this.slotsDirty) this.scheduleSlotDeclaration();
8821
9218
  this.preloadPrivacyMode();
@@ -8835,6 +9232,85 @@ var VioFacade = class {
8835
9232
  get bootstrapCache() {
8836
9233
  return this.cachedBootstrap;
8837
9234
  }
9235
+ /** A bootstrap becomes the page's: when it arrived, the channels it lists, and the tab-visible refresh. */
9236
+ adoptBootstrap(res) {
9237
+ this.cachedBootstrap = res;
9238
+ this.bootstrapAt = Date.now();
9239
+ try {
9240
+ const ids = /* @__PURE__ */ new Set();
9241
+ const add = (s) => {
9242
+ const id = toSponsorId(s?.id);
9243
+ if (id !== void 0) ids.add(id);
9244
+ };
9245
+ add(res?.primarySponsor);
9246
+ if (Array.isArray(res?.secondarySponsors)) res.secondarySponsors.forEach(add);
9247
+ if (Array.isArray(res?.campaigns)) {
9248
+ for (const c of res.campaigns) {
9249
+ if (!c || typeof c !== "object") continue;
9250
+ add(c.primarySponsor);
9251
+ if (Array.isArray(c.secondarySponsors)) c.secondarySponsors.forEach(add);
9252
+ }
9253
+ }
9254
+ for (const id of ids) {
9255
+ for (const { sponsor } of sellingBlocksIn(res, id)) {
9256
+ const channel = channelIdOf(sponsor.commerce.channelId);
9257
+ if (channel !== void 0) this.seenChannels.set(`${id}:${channel}`, sponsor);
9258
+ }
9259
+ }
9260
+ } catch {
9261
+ }
9262
+ this.hookVisibility();
9263
+ }
9264
+ /**
9265
+ * The bootstrap is cached for the page's life, but a tab can stay open for
9266
+ * days: when it becomes visible again and the cached one is older than
9267
+ * five minutes, it is fetched again (new campaigns, channels). The old one
9268
+ * stays until the new one arrives — and stays if the refresh fails. Carts
9269
+ * keep their lines; a cart whose channel the new bootstrap no longer lists
9270
+ * is moved to the current channel the next time it is used (CartManager),
9271
+ * and an open checkout keeps paying with the key of its own channel.
9272
+ */
9273
+ hookVisibility() {
9274
+ if (this.visibilityHooked) return;
9275
+ if (typeof document === "undefined" || typeof document.addEventListener !== "function") return;
9276
+ this.visibilityHooked = true;
9277
+ document.addEventListener("visibilitychange", () => {
9278
+ try {
9279
+ if (document.visibilityState === "visible") void this.refreshStaleBootstrap();
9280
+ } catch {
9281
+ }
9282
+ });
9283
+ }
9284
+ /** Fetch the bootstrap again when the cached one is older than `maxAgeMs`; never throws, never clears the cache. */
9285
+ refreshStaleBootstrap(maxAgeMs = BOOTSTRAP_MAX_AGE_MS) {
9286
+ try {
9287
+ if (this.bootstrapRefresh) return this.bootstrapRefresh;
9288
+ if (!this.cachedBootstrap || this.bootstrapPromise || !Configuration.isInitialized) return Promise.resolve();
9289
+ const now = Date.now();
9290
+ if (now - this.bootstrapAt < maxAgeMs) return Promise.resolve();
9291
+ if (this.bootstrapRefreshFailedAt && now - this.bootstrapRefreshFailedAt < BOOTSTRAP_REFRESH_BACKOFF_MS) {
9292
+ return Promise.resolve();
9293
+ }
9294
+ const generation = this.generation;
9295
+ const refresh = this.api.bootstrap(Configuration.get().platform).then(
9296
+ (res) => {
9297
+ if (generation !== this.generation || !res || typeof res !== "object") return;
9298
+ this.adoptBootstrap(res);
9299
+ this.bootstrapRefreshFailedAt = 0;
9300
+ if (this.slotsDirty) this.scheduleSlotDeclaration();
9301
+ },
9302
+ () => {
9303
+ if (generation === this.generation) this.bootstrapRefreshFailedAt = Date.now();
9304
+ }
9305
+ ).finally(() => {
9306
+ if (this.bootstrapRefresh === refresh) this.bootstrapRefresh = null;
9307
+ });
9308
+ this.bootstrapRefresh = refresh;
9309
+ return refresh;
9310
+ } catch {
9311
+ return Promise.resolve();
9312
+ }
9313
+ }
8838
9314
  /**
8839
9315
  * Who a sponsor is to a shopper — `{ id, name, logoUrl, avatarUrl }` — by
8840
9316
  * the ONE rule every element uses (`sponsorIdentityFrom`): the live
@@ -8869,22 +9345,60 @@ var VioFacade = class {
8869
9345
  }
8870
9346
  }
8871
9347
  /**
8872
- * Get a CommerceClient scoped to a specific sponsor (via bootstrap).
8873
- * Throws if bootstrap hasn't been called or the sponsor is unknown.
9348
+ * The block a sponsor sells with — its `commerce.apiKey` and `channelId` —
9349
+ * by the rule one article = one campaign = one channel = one sponsor (see
9350
+ * `sponsorCommerceIn`): `ctx.channelId` → that channel's block only (a cart
9351
+ * created on a channel is only called with its key); else the campaign in
9352
+ * context — `ctx.campaignId` candidates (the click's, the element's
9353
+ * `vio-campaign`, an ancestor's `data-vio-campaign`), then
9354
+ * `Vio.init({ campaignId })` — when the sponsor takes part in it; else the
9355
+ * sponsor's first block that sells (the behaviour before). `campaignId:
9356
+ * null` means no campaign at all, not even `Vio.init`'s. `retired: true`
9357
+ * also answers a channel the current bootstrap no longer lists with the
9358
+ * block this page saw for it (an open checkout after a bootstrap refresh).
9359
+ * Blocks with `commerce: null` are skipped. `null` when none; never throws.
8874
9360
  */
8875
- commerceFor(sponsorId) {
9361
+ sponsorCommerce(sponsorId, ctx = {}) {
9362
+ try {
9363
+ const id = toSponsorId(sponsorId);
9364
+ if (id === void 0) return null;
9365
+ const channel = channelIdOf(ctx?.channelId);
9366
+ if (channel !== void 0) {
9367
+ const listed = sponsorCommerceIn(this.cachedBootstrap, id, { channelId: channel });
9368
+ if (listed) return listed;
9369
+ return ctx.retired ? this.seenChannels.get(`${id}:${channel}`) ?? null : null;
9370
+ }
9371
+ const campaignId = ctx?.campaignId === null ? [] : this.pageCampaignCandidates(ctx?.campaignId);
9372
+ return sponsorCommerceIn(this.cachedBootstrap, id, { campaignId }) ?? null;
9373
+ } catch {
9374
+ return null;
9375
+ }
9376
+ }
9377
+ /**
9378
+ * Get a CommerceClient scoped to a sponsor (via bootstrap) — on the
9379
+ * channel of the campaign in context (`ctx.campaignId`, the element's
9380
+ * candidates first; see `sponsorCommerce`) or of `ctx.channelId`. Without
9381
+ * `ctx`, the page's campaign (`Vio.init`) when the sponsor takes part in
9382
+ * it, else the sponsor's first selling block, as before. Clients are kept
9383
+ * per sponsor and key. Throws if bootstrap hasn't been called or nothing of
9384
+ * the sponsor sells there.
9385
+ */
9386
+ commerceFor(sponsorId, ctx) {
8876
9387
  sponsorId = Number(sponsorId);
8877
- const existing = this.commerceClients.get(sponsorId);
8878
- if (existing) return existing;
8879
- const sponsor = this.findSponsor(sponsorId);
9388
+ const sponsor = this.sponsorCommerce(sponsorId, ctx ?? {});
8880
9389
  if (!sponsor) {
9390
+ const channel = channelIdOf(ctx?.channelId);
8881
9391
  throw new Error(
8882
- `[Vio] Sponsor ${sponsorId} not found in bootstrap. Call Vio.bootstrap() first or pass commerce-key directly.`
9392
+ channel !== void 0 ? `[Vio] Channel ${channel} of sponsor ${sponsorId} not found in bootstrap.` : `[Vio] Sponsor ${sponsorId} not found in bootstrap. Call Vio.bootstrap() first or pass commerce-key directly.`
8883
9393
  );
8884
9394
  }
9395
+ const apiKey = sponsor.commerce.apiKey;
9396
+ const cacheKey = `${sponsorId}:${apiKey}`;
9397
+ const existing = this.commerceClients.get(cacheKey);
9398
+ if (existing) return existing;
8885
9399
  const cfg = Configuration.get();
8886
- const client = createCommerceClient({ endpoint: cfg.graphQLBase, apiKey: sponsor.commerce.apiKey });
8887
- this.commerceClients.set(sponsorId, client);
9400
+ const client = createCommerceClient({ endpoint: cfg.graphQLBase, apiKey });
9401
+ this.commerceClients.set(cacheKey, client);
8888
9402
  return client;
8889
9403
  }
8890
9404
  /** Get a CommerceClient using an explicit commerce apiKey (skip bootstrap). */
@@ -9223,9 +9737,10 @@ var VioFacade = class {
9223
9737
  * The attribution of the order a sponsor's cart is about to become
9224
9738
  * (ADR-0035 phase B) — what `CreateCheckout` / `UpdateCheckout` send as
9225
9739
  * `attribution` and shopcart writes on the order: the surface (its SDK
9226
- * key), the page's campaign (`pageCampaign()`; without one, the campaign
9227
- * that explains most units of the cart under the same rule `purchase` is
9228
- * split by — `cartSlices`) and, on a surface KNOWN to be `standard`
9740
+ * key), the campaign of the order — always one the cart's sponsor takes part
9741
+ * in (`orderCampaign`: the cart lines' own campaign, else the page's;
9742
+ * without one, the campaign that explains most units of the cart under the
9743
+ * same rule `purchase` is split by — `cartSlices`) and, on a surface KNOWN to be `standard`
9229
9744
  * (`privacyModeNow`), the page (`content()`'s url as origin + path, and
9230
9745
  * title) and where the session came in: the landing the session REMEMBERS
9231
9746
  * (`storedLanding`, else the one analytics resolved on this page) — never
@@ -9237,10 +9752,13 @@ var VioFacade = class {
9237
9752
  try {
9238
9753
  const config = Configuration.isInitialized ? Configuration.get() : void 0;
9239
9754
  const content = this.content();
9755
+ const sponsor = toSponsorId(sponsorId);
9756
+ const belongs = (campaignId) => campaignId === void 0 || campaignTakesSponsor(this.cachedBootstrap, campaignId, sponsor);
9240
9757
  return checkoutAttributionOf({
9241
9758
  surfaceKey: config?.apiKey,
9242
- pageCampaignId: toNumber(this.pageCampaign()?.id),
9243
- slices: this.cartSlices(sponsorId),
9759
+ pageCampaignId: this.orderCampaign(sponsor),
9760
+ // The fallback campaign of the slices, too, only when it is the sponsor's.
9761
+ slices: this.cartSlices(sponsorId).filter((slice) => belongs(slice.campaignId)),
9244
9762
  content: { url: content.url, title: content.title },
9245
9763
  landing: this.rememberedLanding(),
9246
9764
  privacyMode: this.privacyModeNow()
@@ -9249,6 +9767,29 @@ var VioFacade = class {
9249
9767
  return void 0;
9250
9768
  }
9251
9769
  }
9770
+ /**
9771
+ * The campaign an order of `sponsorId` is credited to — one article = one
9772
+ * campaign = one sponsor, so it is always one the sponsor takes part in:
9773
+ * the campaign the cart's own lines were added from (their origins; the one
9774
+ * that explains most units), else the page's — the elements' campaigns on
9775
+ * the page (`vio-campaign` / `data-vio-campaign`, in document order), then
9776
+ * `Vio.init({ campaignId })` — the first one active with that sponsor.
9777
+ * `undefined` when neither says one (the slices then decide). Without a
9778
+ * sponsor, the page's campaign as before.
9779
+ */
9780
+ orderCampaign(sponsorId) {
9781
+ const boot = this.cachedBootstrap;
9782
+ if (sponsorId !== void 0) {
9783
+ const lines = [...this.cart.getCart(sponsorId)?.items ?? []];
9784
+ const fromLines = dominantCampaignOf(
9785
+ cartSlicesFrom(lines, () => void 0).filter(
9786
+ (slice) => slice.campaignId !== void 0 && campaignTakesSponsor(boot, slice.campaignId, sponsorId)
9787
+ )
9788
+ );
9789
+ if (fromLines !== void 0) return fromLines;
9790
+ }
9791
+ return toNumber(activeCampaignEntry(boot, this.pageCampaignCandidates(documentCampaignHints()), sponsorId)?.id);
9792
+ }
9252
9793
  /** The landing the session remembers: what the first page stored, else what analytics resolved on this page load (consent pending); null otherwise. */
9253
9794
  rememberedLanding() {
9254
9795
  try {
@@ -9350,6 +9891,35 @@ var VioFacade = class {
9350
9891
  return findSponsorIn(this.cachedBootstrap, sponsorId);
9351
9892
  }
9352
9893
  };
9894
+ function campaignTakesSponsor(boot, campaignId, sponsorId) {
9895
+ if (sponsorId === void 0 || !boot) return true;
9896
+ try {
9897
+ const listed = (Array.isArray(boot.campaigns) ? boot.campaigns : []).filter(
9898
+ (c) => !!c && typeof c === "object" && toNumber(c.id) === campaignId
9899
+ );
9900
+ if (listed.length > 0) return listed.some((c) => campaignSponsorIds(c).includes(sponsorId));
9901
+ if (toNumber(boot.campaign?.id) === campaignId) {
9902
+ const entry = defaultCampaignEntry(boot);
9903
+ return !!entry && campaignSponsorIds(entry).includes(sponsorId);
9904
+ }
9905
+ return true;
9906
+ } catch {
9907
+ return true;
9908
+ }
9909
+ }
9910
+ function documentCampaignHints() {
9911
+ try {
9912
+ if (typeof document === "undefined" || typeof document.querySelectorAll !== "function") return [];
9913
+ const out = [];
9914
+ document.querySelectorAll("[vio-campaign], [data-vio-campaign]").forEach((el) => {
9915
+ const value = el.getAttribute("vio-campaign") || el.getAttribute("data-vio-campaign");
9916
+ if (value) out.push(value);
9917
+ });
9918
+ return out;
9919
+ } catch {
9920
+ return [];
9921
+ }
9922
+ }
9353
9923
  function warn(...args) {
9354
9924
  if (typeof console !== "undefined") console.warn("[Vio]", ...args);
9355
9925
  }
@@ -9408,6 +9978,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
9408
9978
  };
9409
9979
  }
9410
9980
 
9411
- export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CHECKOUT_SPONSOR_STORAGE_KEY, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, INTERNAL_KEY, INTERNAL_PARAM, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, checkoutSponsorOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCountryCode, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, isValidPhone, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readInternal, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveInternal, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storageGet, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature, writeInternal };
9412
- //# sourceMappingURL=chunk-IR4XYTZL.js.map
9413
- //# sourceMappingURL=chunk-IR4XYTZL.js.map
9981
+ export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CHECKOUT_SPONSOR_STORAGE_KEY, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, INTERNAL_KEY, INTERNAL_PARAM, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, checkoutSponsorOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCountryCode, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, isValidPhone, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readInternal, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveInternal, resolveLanding, resolveSlotCampaign, sameOrigin, shippingDescriptionOf, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storageGet, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature, writeInternal };
9982
+ //# sourceMappingURL=chunk-ZORYG4W3.js.map
9983
+ //# sourceMappingURL=chunk-ZORYG4W3.js.map