@vio-live/web-sdk 0.23.0 → 0.24.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.
@@ -11,6 +11,245 @@ var __decorateClass = (decorators, target, key, kind) => {
11
11
  return result;
12
12
  };
13
13
 
14
+ // src/core/slots.ts
15
+ var SLOT_ID_MAX_LENGTH = 100;
16
+ var SLOT_DECLARATION_TTL_MS = 24 * 60 * 60 * 1e3;
17
+ var SLOT_DECLARATION_FAILURE_TTL_MS = 60 * 60 * 1e3;
18
+ var MAX_LOCATIONS_PER_DECLARATION = 100;
19
+ var MAX_DECLARATIONS_PER_LOAD = 3;
20
+ function resolveSlotCampaign(boot, slot, now = Date.now(), platform, pageCampaignId, sponsorId) {
21
+ if (!boot) return null;
22
+ const page = activeCampaignEntry(boot, pageCampaignId);
23
+ const assignment = findSlotAssignmentDetail(boot, slot, now, platform);
24
+ const sponsor = toNumber(sponsorId);
25
+ if (assignment && assignmentApplies(assignment, page, sponsor)) return assignment.campaign;
26
+ return activeCampaignEntry(boot, pageCampaignId, sponsor) ?? (sponsor !== void 0 ? sponsorCampaignEntry(boot, sponsor) : null) ?? defaultCampaignEntry(boot);
27
+ }
28
+ function sponsorCampaignEntry(boot, sponsorId) {
29
+ const id = toNumber(sponsorId);
30
+ if (!boot || id === void 0) return null;
31
+ try {
32
+ const matches = activeCampaigns(boot).filter((c) => campaignSponsorIds(c).includes(id));
33
+ if (matches.length === 0) return null;
34
+ const defaultId = toNumber(boot.campaign?.id);
35
+ return matches.find((c) => toNumber(c.id) === defaultId) ?? matches[0] ?? null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function assignmentApplies(assignment, page, sponsorId) {
41
+ if (assignment.slot.source !== "page") return true;
42
+ if (isStalePageAssignment(assignment, page)) return false;
43
+ return sponsorId === void 0 || campaignSponsorIds(assignment.campaign).includes(sponsorId);
44
+ }
45
+ function isStalePageAssignment(assignment, page) {
46
+ return !!page && assignment.slot.source === "page" && toNumber(page.id) !== toNumber(assignment.campaign.id);
47
+ }
48
+ function toCampaignId(value) {
49
+ let n;
50
+ if (typeof value === "number") n = value;
51
+ else if (typeof value === "string" && /^\s*\d+\s*$/.test(value)) n = Number(value);
52
+ else return void 0;
53
+ return Number.isSafeInteger(n) && n > 0 ? n : void 0;
54
+ }
55
+ function campaignCandidates(hint) {
56
+ const raw = Array.isArray(hint) ? hint : [hint];
57
+ const out = [];
58
+ for (const v of raw) {
59
+ const id = toCampaignId(v);
60
+ if (id !== void 0 && !out.includes(id)) out.push(id);
61
+ }
62
+ return out;
63
+ }
64
+ function activeCampaignEntry(boot, hint, sponsorId) {
65
+ if (!boot) return null;
66
+ try {
67
+ const active = activeCampaigns(boot);
68
+ for (const id of campaignCandidates(hint)) {
69
+ const entry = active.find((c) => toNumber(c.id) === id);
70
+ if (!entry) continue;
71
+ if (sponsorId !== void 0 && !campaignSponsorIds(entry).includes(sponsorId)) continue;
72
+ return entry;
73
+ }
74
+ } catch {
75
+ }
76
+ return null;
77
+ }
78
+ function campaignSponsorIds(campaign) {
79
+ const ids = [];
80
+ const primary = toNumber(campaign.primarySponsor?.id);
81
+ if (primary !== void 0) ids.push(primary);
82
+ const secondaries = Array.isArray(campaign.secondarySponsors) ? campaign.secondarySponsors : [];
83
+ for (const s of secondaries) {
84
+ const id = toNumber(s?.id);
85
+ if (id !== void 0) ids.push(id);
86
+ }
87
+ return ids;
88
+ }
89
+ function findSlotAssignmentDetail(boot, slot, now = Date.now(), platform) {
90
+ const name = typeof slot === "string" ? slot.trim() : "";
91
+ if (!boot || !name) return null;
92
+ try {
93
+ const matches = [];
94
+ for (const campaign of activeCampaigns(boot)) {
95
+ const match = slotsOf(campaign).find((s) => assignmentMatches(s, name, now, platform));
96
+ if (match) matches.push({ campaign, slot: match });
97
+ }
98
+ if (matches.length === 0) return null;
99
+ const defaultId = toNumber(boot.campaign?.id);
100
+ return matches.find((m) => toNumber(m.campaign.id) === defaultId) ?? matches[0] ?? null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ function defaultCampaignEntry(boot) {
106
+ if (!boot?.campaign || typeof boot.campaign !== "object") return null;
107
+ const defaultId = toNumber(boot.campaign.id);
108
+ if (defaultId === void 0) return null;
109
+ try {
110
+ const listed = wellFormedCampaigns(boot).find((c) => toNumber(c.id) === defaultId);
111
+ if (listed) return listed;
112
+ } catch {
113
+ }
114
+ return {
115
+ ...boot.campaign,
116
+ isDefault: true,
117
+ primarySponsor: boot.primarySponsor ?? null,
118
+ secondarySponsors: Array.isArray(boot.secondarySponsors) ? boot.secondarySponsors : [],
119
+ slots: []
120
+ };
121
+ }
122
+ function wellFormedCampaigns(boot) {
123
+ const list = Array.isArray(boot.campaigns) ? boot.campaigns : [];
124
+ return list.filter((c) => !!c && typeof c === "object");
125
+ }
126
+ function activeCampaigns(boot) {
127
+ return wellFormedCampaigns(boot).filter((c) => c.status === "active");
128
+ }
129
+ function slotsOf(campaign) {
130
+ const slots = Array.isArray(campaign.slots) ? campaign.slots : [];
131
+ return slots.filter((s) => !!s && typeof s === "object");
132
+ }
133
+ function assignmentMatches(s, name, now, platform) {
134
+ if (typeof s.name !== "string" || s.name.trim() !== name) return false;
135
+ if (platform && s.platform != null && s.platform !== platform) return false;
136
+ const start = parseTime(s.startDate);
137
+ if (start !== void 0 && start > now) return false;
138
+ const end = parseTime(s.endDate);
139
+ if (end !== void 0 && now >= end) return false;
140
+ return true;
141
+ }
142
+ function parseTime(value) {
143
+ if (value == null || value === "") return void 0;
144
+ const t = Date.parse(String(value));
145
+ return Number.isFinite(t) ? t : void 0;
146
+ }
147
+ function toNumber(value) {
148
+ if (value === void 0 || value === null || value === "") return void 0;
149
+ const n = Number(value);
150
+ return Number.isFinite(n) ? n : void 0;
151
+ }
152
+ function normalizeSlotId(value) {
153
+ if (typeof value !== "string") return "";
154
+ return value.trim().slice(0, SLOT_ID_MAX_LENGTH).trim();
155
+ }
156
+ function normalizeSlotLocations(slots) {
157
+ const out = [];
158
+ const seen = /* @__PURE__ */ new Set();
159
+ if (!Array.isArray(slots)) return out;
160
+ for (const raw of slots) {
161
+ const id = normalizeSlotId(typeof raw === "string" ? raw : raw?.id);
162
+ if (!id || seen.has(id)) continue;
163
+ seen.add(id);
164
+ const displayName = raw && typeof raw === "object" && typeof raw.displayName === "string" ? raw.displayName.trim() : "";
165
+ const campaignId = raw && typeof raw === "object" ? toCampaignId(raw.campaignId) : void 0;
166
+ const location2 = { id };
167
+ if (displayName) location2.displayName = displayName;
168
+ if (campaignId !== void 0) location2.campaignId = campaignId;
169
+ out.push(location2);
170
+ }
171
+ return out;
172
+ }
173
+ function autoSlotId(kind, ordinal, scope) {
174
+ const tail = `${kind}-${ordinal + 1}`;
175
+ let prefix = normalizeScope(scope);
176
+ const room = SLOT_ID_MAX_LENGTH - tail.length - 1;
177
+ if (prefix.length > room) prefix = prefix.slice(0, Math.max(0, room)).replace(/-+$/, "");
178
+ return `${prefix ? `${prefix}-` : ""}${tail}`;
179
+ }
180
+ function autoSlotDisplayName(kind, ordinal, scopeName) {
181
+ const label = `${kind.charAt(0).toUpperCase()}${kind.slice(1)} ${ordinal + 1}`;
182
+ const name = typeof scopeName === "string" ? scopeName.trim() : "";
183
+ return name ? `${label} \xB7 ${name}` : label;
184
+ }
185
+ function normalizeScope(scope) {
186
+ if (typeof scope !== "string") return "";
187
+ return scope.trim().replace(/\s+/g, "-");
188
+ }
189
+ var SLOT_DECLARATION_MEMORY_CAP = 500;
190
+ function slotDeclarationKey(location2, pageCampaignId) {
191
+ const campaignId = toCampaignId(location2.campaignId) ?? toCampaignId(pageCampaignId);
192
+ return location2.id + (location2.displayName ? `${location2.displayName}` : "") + (campaignId !== void 0 ? `${campaignId}` : "");
193
+ }
194
+ function pendingSlotKeys(memory, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
195
+ return keys.filter((key) => {
196
+ const at = memory?.[key];
197
+ if (typeof at !== "number" || !Number.isFinite(at)) return true;
198
+ return now - at >= ttl || now < at;
199
+ });
200
+ }
201
+ function slotKeyId(key) {
202
+ const cut = key.search(/[\u001f\u001e]/);
203
+ return cut >= 0 ? key.slice(0, cut) : key;
204
+ }
205
+ function rememberSlotKeys(memory, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
206
+ const next = { ...memory ?? {} };
207
+ const ids = new Set(keys.map(slotKeyId));
208
+ for (const existing of Object.keys(next)) {
209
+ if (ids.has(slotKeyId(existing)) && !keys.includes(existing)) delete next[existing];
210
+ }
211
+ for (const key of keys) next[key] = at;
212
+ const entries = Object.entries(next);
213
+ if (entries.length <= cap) return next;
214
+ entries.sort((a, b) => b[1] - a[1]);
215
+ return Object.fromEntries(entries.slice(0, cap));
216
+ }
217
+ function slotDeclarationStorageKey(platform, apiKey) {
218
+ return `vio.slots.${platform}.${fnv1a(apiKey)}`;
219
+ }
220
+ function fnv1a(input) {
221
+ let hash = 2166136261;
222
+ for (let i = 0; i < input.length; i++) {
223
+ hash ^= input.charCodeAt(i);
224
+ hash = Math.imul(hash, 16777619) >>> 0;
225
+ }
226
+ return hash.toString(16).padStart(8, "0");
227
+ }
228
+ function readSlotDeclaration(storageKey) {
229
+ try {
230
+ if (typeof localStorage === "undefined") return {};
231
+ const raw = localStorage.getItem(storageKey);
232
+ if (!raw) return {};
233
+ const parsed = JSON.parse(raw);
234
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
235
+ if (Array.isArray(parsed.keys)) return {};
236
+ const out = {};
237
+ for (const [key, at] of Object.entries(parsed)) {
238
+ if (typeof at === "number" && Number.isFinite(at)) out[key] = at;
239
+ }
240
+ return out;
241
+ } catch {
242
+ return {};
243
+ }
244
+ }
245
+ function writeSlotDeclaration(storageKey, memory) {
246
+ try {
247
+ if (typeof localStorage === "undefined") return;
248
+ localStorage.setItem(storageKey, JSON.stringify(memory));
249
+ } catch {
250
+ }
251
+ }
252
+
14
253
  // src/core/configuration.ts
15
254
  var DEFAULT_URLS = {
16
255
  // The Vio Backend development environment was removed on 2026-09-16; its REST
@@ -49,6 +288,8 @@ var ConfigurationSingleton = class {
49
288
  platform: input.platform ?? "web",
50
289
  contentId: input.contentId,
51
290
  contentTitle: input.contentTitle,
291
+ campaignId: toCampaignId(input.campaignId),
292
+ debug: input.debug === true,
52
293
  apiBase: input.apiBase ?? defaults.apiBase,
53
294
  graphQLBase: input.graphQLBase ?? defaults.graphQLBase,
54
295
  eventsBase: input.eventsBase ?? defaults.eventsBase,
@@ -1659,6 +1900,18 @@ var CartManager = class extends EventTarget {
1659
1900
  await Promise.allSettled([...set]);
1660
1901
  }
1661
1902
  }
1903
+ /**
1904
+ * True while an AddItem/UpdateItem/DeleteItem of this sponsor OTHER than
1905
+ * `except` has not settled. A mutation applying its own answer passes
1906
+ * itself: its response is current for what IT sent — only the others make
1907
+ * it stale.
1908
+ */
1909
+ hasMutationsInFlight(sponsorId, except) {
1910
+ const set = this.inFlightMutations.get(sponsorId);
1911
+ if (!set || set.size === 0) return false;
1912
+ for (const m of set) if (m !== except) return true;
1913
+ return false;
1914
+ }
1662
1915
  /** Register a backend mutation so `waitForCartMutations` can await it. */
1663
1916
  trackMutation(sponsorId, mutation) {
1664
1917
  let set = this.inFlightMutations.get(sponsorId);
@@ -1687,10 +1940,10 @@ var CartManager = class extends EventTarget {
1687
1940
  }
1688
1941
  /** Apply a backend response only if no newer response was applied already.
1689
1942
  * Returns false when it was discarded as stale. */
1690
- applyBackendCart(sponsorId, backendCart, seq) {
1943
+ applyBackendCart(sponsorId, backendCart, seq, settling) {
1691
1944
  if (seq < (this.appliedSeq.get(sponsorId) ?? 0)) return false;
1692
1945
  this.appliedSeq.set(sponsorId, seq);
1693
- this.updateFromBackendCart(sponsorId, backendCart);
1946
+ this.updateFromBackendCart(sponsorId, backendCart, { settling });
1694
1947
  return true;
1695
1948
  }
1696
1949
  /** The final answer of a line's sync lost the per-sponsor seq race (another
@@ -1698,7 +1951,7 @@ var CartManager = class extends EventTarget {
1698
1951
  * is the latest word. Copy its quantity (and price) so a server-side clamp
1699
1952
  * — stock — doesn't leave the optimistic value behind for good, with the
1700
1953
  * checkout / Apple Pay total built on it. */
1701
- reconcileLineFromResponse(sponsorId, cartItemId, resCart) {
1954
+ reconcileLineFromResponse(sponsorId, cartItemId, resCart, settling) {
1702
1955
  const cart = this.cartsBySponsor.get(sponsorId);
1703
1956
  const line = cart?.items.find((i) => i.cartItemId === cartItemId || i.id === cartItemId);
1704
1957
  const server = resCart.line_items?.find((li) => li.id === cartItemId);
@@ -1707,7 +1960,9 @@ var CartManager = class extends EventTarget {
1707
1960
  let changed = false;
1708
1961
  if (server.quantity !== line.quantity) {
1709
1962
  line.quantity = server.quantity;
1710
- line.origins = reconciledOrigins(line.origins, server.quantity);
1963
+ if (!this.hasMutationsInFlight(sponsorId, settling)) {
1964
+ line.origins = reconciledOrigins(line.origins, server.quantity);
1965
+ }
1711
1966
  changed = true;
1712
1967
  }
1713
1968
  if (price != null && price !== line.unitPrice) {
@@ -1783,8 +2038,12 @@ var CartManager = class extends EventTarget {
1783
2038
  this.cartIdPromises.set(sponsorId, creation);
1784
2039
  return creation;
1785
2040
  }
1786
- /** Overwrite local cart state with the backend cart response (source of truth). */
1787
- updateFromBackendCart(sponsorId, backendCart) {
2041
+ /**
2042
+ * Overwrite local cart state with the backend cart response (source of
2043
+ * truth). `opts.settling` is the mutation whose answer this is: it does not
2044
+ * count as "in flight" for the origins (see `hasMutationsInFlight`).
2045
+ */
2046
+ updateFromBackendCart(sponsorId, backendCart, opts = {}) {
1788
2047
  const cart = this.cartsBySponsor.get(sponsorId);
1789
2048
  if (!cart) return;
1790
2049
  if (backendCart.cart_id) cart.cartId = backendCart.cart_id;
@@ -1840,9 +2099,12 @@ var CartManager = class extends EventTarget {
1840
2099
  currency: li.price?.currency_code || backendCart.currency || matchedLocal?.currency || "NOK",
1841
2100
  imageUrl,
1842
2101
  quantity: intended ?? li.quantity,
1843
- // The server knows nothing about origins: keep the local ones,
1844
- // squared with the quantity it answered.
1845
- origins: matchedLocal ? reconciledOrigins(matchedLocal.origins, intended ?? li.quantity) : void 0,
2102
+ // The server knows nothing about origins: keep the local ones. They
2103
+ // are squared with its quantity (a stock clamp) only when no OTHER
2104
+ // mutation is in flight for this sponsor — an AddItem answering
2105
+ // while another is pending reports a quantity that is already old
2106
+ // (see `lineOrigins`).
2107
+ origins: matchedLocal ? this.hasMutationsInFlight(sponsorId, opts.settling) ? matchedLocal.origins : reconciledOrigins(matchedLocal.origins, intended ?? li.quantity) : void 0,
1846
2108
  shipping: li.shipping,
1847
2109
  availableShippings: li.available_shippings
1848
2110
  };
@@ -1946,6 +2208,7 @@ var CartManager = class extends EventTarget {
1946
2208
  const addedItem = existing ?? cart.items[cart.items.length - 1];
1947
2209
  const addedLineId = addedItem.id;
1948
2210
  const seq = this.nextSeq(opts.sponsorId);
2211
+ let settling;
1949
2212
  const mutation = (async () => {
1950
2213
  try {
1951
2214
  const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
@@ -1960,7 +2223,7 @@ var CartManager = class extends EventTarget {
1960
2223
  ];
1961
2224
  const resCart = await addItem(cartId, lineItemsInput, gqlOpts);
1962
2225
  if (resCart && Array.isArray(resCart.line_items)) {
1963
- this.applyBackendCart(opts.sponsorId, resCart, seq);
2226
+ this.applyBackendCart(opts.sponsorId, resCart, seq, settling);
1964
2227
  }
1965
2228
  }
1966
2229
  } catch (err) {
@@ -1968,7 +2231,7 @@ var CartManager = class extends EventTarget {
1968
2231
  const line = c?.items.find((i) => i.id === addedLineId);
1969
2232
  if (c && line) {
1970
2233
  line.quantity -= qty;
1971
- removeOriginQuantity(line, qty);
2234
+ removeFromOrigin(line, opts.origin, qty);
1972
2235
  if (line.quantity <= 0) c.items = c.items.filter((i) => i !== line);
1973
2236
  if (c.items.length === 0) this.cartsBySponsor.delete(opts.sponsorId);
1974
2237
  this.persist();
@@ -1977,6 +2240,7 @@ var CartManager = class extends EventTarget {
1977
2240
  this.emitError("AddItem rejected by the backend \u2014 item removed from the cart", err);
1978
2241
  }
1979
2242
  })();
2243
+ settling = mutation;
1980
2244
  this.trackMutation(opts.sponsorId, mutation);
1981
2245
  return addedItem;
1982
2246
  }
@@ -2061,6 +2325,7 @@ var CartManager = class extends EventTarget {
2061
2325
  }
2062
2326
  const sync = { sent: target };
2063
2327
  this.qtySyncs.set(key, sync);
2328
+ let settling;
2064
2329
  const run = (async () => {
2065
2330
  let next = target;
2066
2331
  let removalRetried = false;
@@ -2075,9 +2340,9 @@ var CartManager = class extends EventTarget {
2075
2340
  if (next === sync.sent) next = void 0;
2076
2341
  if (next === void 0) this.qtySyncs.delete(key);
2077
2342
  if (resCart && Array.isArray(resCart.line_items)) {
2078
- const applied = this.applyBackendCart(sponsorId, resCart, seq);
2343
+ const applied = this.applyBackendCart(sponsorId, resCart, seq, settling);
2079
2344
  if (!applied && next === void 0) {
2080
- this.reconcileLineFromResponse(sponsorId, cartItemId, resCart);
2345
+ this.reconcileLineFromResponse(sponsorId, cartItemId, resCart, settling);
2081
2346
  }
2082
2347
  }
2083
2348
  } catch (err) {
@@ -2100,6 +2365,7 @@ var CartManager = class extends EventTarget {
2100
2365
  }
2101
2366
  }
2102
2367
  })();
2368
+ settling = run;
2103
2369
  this.trackMutation(sponsorId, run);
2104
2370
  }
2105
2371
  removeItem(lineItemId, sponsorId) {
@@ -2245,6 +2511,22 @@ function addOriginQuantity(line, origin, qty) {
2245
2511
  }
2246
2512
  line.origins = origins;
2247
2513
  }
2514
+ function removeFromOrigin(line, origin, qty) {
2515
+ if (!(qty > 0) || !Array.isArray(line.origins)) return;
2516
+ let left = qty;
2517
+ if (origin) {
2518
+ const target = normalizeOrigin(origin);
2519
+ const index = line.origins.findIndex((o) => sameOrigin(o, target));
2520
+ if (index >= 0) {
2521
+ const found = line.origins[index];
2522
+ const take = Math.min(found.quantity, left);
2523
+ found.quantity -= take;
2524
+ left -= take;
2525
+ if (found.quantity <= 0) line.origins.splice(index, 1);
2526
+ }
2527
+ }
2528
+ if (left > 0) removeOriginQuantity(line, left);
2529
+ }
2248
2530
  function removeOriginQuantity(line, qty) {
2249
2531
  if (!(qty > 0) || !Array.isArray(line.origins)) return;
2250
2532
  let left = qty;
@@ -2270,8 +2552,11 @@ function lineOrigins(line) {
2270
2552
  const known = (Array.isArray(line.origins) ? line.origins : []).filter((o) => o && o.quantity > 0);
2271
2553
  const total = known.reduce((sum, o) => sum + o.quantity, 0);
2272
2554
  const quantity = line.quantity > 0 ? line.quantity : 0;
2273
- if (total >= quantity) return known.map((o) => ({ ...o }));
2274
- return [...known.map((o) => ({ ...o })), { quantity: quantity - total }];
2555
+ if (total === quantity) return known.map((o) => ({ ...o }));
2556
+ if (total < quantity) return [...known.map((o) => ({ ...o })), { quantity: quantity - total }];
2557
+ const copy = { origins: known.map((o) => ({ ...o })) };
2558
+ removeOriginQuantity(copy, total - quantity);
2559
+ return copy.origins;
2275
2560
  }
2276
2561
 
2277
2562
  // src/core/checkout/payments/apple-pay.ts
@@ -2470,7 +2755,7 @@ async function prepareApplePay(config) {
2470
2755
  }
2471
2756
 
2472
2757
  // src/core/version.ts
2473
- var SDK_VERSION = "0.23.0";
2758
+ var SDK_VERSION = "0.24.0";
2474
2759
 
2475
2760
  // src/core/checkout/payments/stripe-embedded.ts
2476
2761
  var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
@@ -5847,19 +6132,24 @@ function toSmallestUnit(amount, currency) {
5847
6132
  // src/core/content.ts
5848
6133
  var CONTENT_ID_QUERY = "vio_content";
5849
6134
  var CONTENT_TITLE_QUERY = "vio_content_title";
6135
+ var CONTENT_ID_MAX_LENGTH = 128;
6136
+ var CONTENT_TITLE_MAX_LENGTH = 255;
5850
6137
  function resolveContent(init, doc = typeof document !== "undefined" ? document : null, loc = typeof location !== "undefined" ? location : null) {
5851
6138
  try {
5852
6139
  const query = parseQuery(loc?.search);
5853
- const id = text(init?.contentId) ?? text(query.get(CONTENT_ID_QUERY)) ?? text(meta(doc, "name", "vio:content-id")) ?? stripQueryAndHash(text(attr(doc, 'link[rel="canonical"]', "href")) ?? text(meta(doc, "property", "og:url"))) ?? text(loc?.pathname);
6140
+ const id = fits(text(init?.contentId)) ?? fits(text(query.get(CONTENT_ID_QUERY))) ?? fits(text(meta(doc, "name", "vio:content-id"))) ?? fits(stripQueryAndHash(text(attr(doc, 'link[rel="canonical"]', "href")))) ?? fits(stripQueryAndHash(text(meta(doc, "property", "og:url")))) ?? fits(text(loc?.pathname));
5854
6141
  const title = text(init?.contentTitle) ?? text(query.get(CONTENT_TITLE_QUERY)) ?? text(meta(doc, "name", "vio:content-title")) ?? text(meta(doc, "property", "og:title")) ?? text(doc?.title);
5855
6142
  const out = {};
5856
6143
  if (id !== void 0) out.id = id;
5857
- if (title !== void 0) out.title = title.slice(0, 255);
6144
+ if (title !== void 0) out.title = title.slice(0, CONTENT_TITLE_MAX_LENGTH);
5858
6145
  return out;
5859
6146
  } catch {
5860
6147
  return {};
5861
6148
  }
5862
6149
  }
6150
+ function fits(value) {
6151
+ return value !== void 0 && value.length <= CONTENT_ID_MAX_LENGTH ? value : void 0;
6152
+ }
5863
6153
  function text(value) {
5864
6154
  if (value === void 0 || value === null) return void 0;
5865
6155
  const s = String(value).trim();
@@ -5892,144 +6182,71 @@ function stripQueryAndHash(url) {
5892
6182
  return clean.trim() || void 0;
5893
6183
  }
5894
6184
 
5895
- // src/core/slots.ts
5896
- var SLOT_ID_MAX_LENGTH = 100;
5897
- var SLOT_DECLARATION_TTL_MS = 24 * 60 * 60 * 1e3;
5898
- function resolveSlotCampaign(boot, slot, now = Date.now(), platform) {
5899
- if (!boot) return null;
5900
- return findSlotAssignment(boot, slot, now, platform) ?? defaultCampaignEntry(boot);
5901
- }
5902
- function findSlotAssignment(boot, slot, now = Date.now(), platform) {
5903
- const name = typeof slot === "string" ? slot.trim() : "";
5904
- if (!boot || !name) return null;
5905
- try {
5906
- const matches = activeCampaigns(boot).filter(
5907
- (c) => slotsOf(c).some((s) => assignmentMatches(s, name, now, platform))
5908
- );
5909
- if (matches.length === 0) return null;
5910
- const defaultId = toNumber(boot.campaign?.id);
5911
- return matches.find((c) => toNumber(c.id) === defaultId) ?? matches[0] ?? null;
5912
- } catch {
5913
- return null;
5914
- }
5915
- }
5916
- function defaultCampaignEntry(boot) {
5917
- if (!boot?.campaign || typeof boot.campaign !== "object") return null;
5918
- const defaultId = toNumber(boot.campaign.id);
5919
- if (defaultId === void 0) return null;
5920
- try {
5921
- const listed = wellFormedCampaigns(boot).find((c) => toNumber(c.id) === defaultId);
5922
- if (listed) return listed;
5923
- } catch {
5924
- }
5925
- return {
5926
- ...boot.campaign,
5927
- isDefault: true,
5928
- primarySponsor: boot.primarySponsor ?? null,
5929
- secondarySponsors: Array.isArray(boot.secondarySponsors) ? boot.secondarySponsors : [],
5930
- slots: []
5931
- };
5932
- }
5933
- function wellFormedCampaigns(boot) {
5934
- const list = Array.isArray(boot.campaigns) ? boot.campaigns : [];
5935
- return list.filter((c) => !!c && typeof c === "object");
5936
- }
5937
- function activeCampaigns(boot) {
5938
- return wellFormedCampaigns(boot).filter((c) => c.status === "active");
5939
- }
5940
- function slotsOf(campaign) {
5941
- const slots = Array.isArray(campaign.slots) ? campaign.slots : [];
5942
- return slots.filter((s) => !!s && typeof s === "object");
5943
- }
5944
- function assignmentMatches(s, name, now, platform) {
5945
- if (typeof s.name !== "string" || s.name.trim() !== name) return false;
5946
- if (platform && s.platform != null && s.platform !== platform) return false;
5947
- const start = parseTime(s.startDate);
5948
- if (start !== void 0 && start > now) return false;
5949
- const end = parseTime(s.endDate);
5950
- if (end !== void 0 && now >= end) return false;
5951
- return true;
5952
- }
5953
- function parseTime(value) {
5954
- if (value == null || value === "") return void 0;
5955
- const t = Date.parse(String(value));
5956
- return Number.isFinite(t) ? t : void 0;
5957
- }
5958
- function toNumber(value) {
5959
- if (value === void 0 || value === null || value === "") return void 0;
5960
- const n = Number(value);
5961
- return Number.isFinite(n) ? n : void 0;
5962
- }
5963
- function normalizeSlotId(value) {
5964
- if (typeof value !== "string") return "";
5965
- return value.trim().slice(0, SLOT_ID_MAX_LENGTH);
5966
- }
5967
- function normalizeSlotLocations(slots) {
5968
- const out = [];
5969
- const seen = /* @__PURE__ */ new Set();
5970
- if (!Array.isArray(slots)) return out;
5971
- for (const raw of slots) {
5972
- const id = normalizeSlotId(typeof raw === "string" ? raw : raw?.id);
5973
- if (!id || seen.has(id)) continue;
5974
- seen.add(id);
5975
- const displayName = raw && typeof raw === "object" && typeof raw.displayName === "string" ? raw.displayName.trim() : "";
5976
- out.push(displayName ? { id, displayName } : { id });
5977
- }
5978
- return out;
5979
- }
5980
- function autoSlotId(kind, ordinal, scope) {
5981
- const prefix = normalizeScope(scope);
5982
- return `${prefix ? `${prefix}-` : ""}${kind}-${ordinal + 1}`;
5983
- }
5984
- function autoSlotDisplayName(kind, ordinal, scopeName) {
5985
- const label = `${kind.charAt(0).toUpperCase()}${kind.slice(1)} ${ordinal + 1}`;
5986
- const name = typeof scopeName === "string" ? scopeName.trim() : "";
5987
- return name ? `${label} \xB7 ${name}` : label;
5988
- }
5989
- function normalizeScope(scope) {
5990
- if (typeof scope !== "string") return "";
5991
- return scope.trim().replace(/\s+/g, "-");
5992
- }
5993
- function slotDeclarationKeys(locations) {
5994
- return locations.map((l) => l.displayName ? `${l.id}${l.displayName}` : l.id).sort();
5995
- }
5996
- function slotDeclarationStorageKey(platform, apiKey) {
5997
- return `vio.slots.${platform}.${fnv1a(apiKey)}`;
5998
- }
5999
- function fnv1a(input) {
6000
- let hash = 2166136261;
6001
- for (let i = 0; i < input.length; i++) {
6002
- hash ^= input.charCodeAt(i);
6003
- hash = Math.imul(hash, 16777619) >>> 0;
6004
- }
6005
- return hash.toString(16).padStart(8, "0");
6006
- }
6007
- function readSlotDeclaration(storageKey) {
6008
- if (typeof localStorage === "undefined") return null;
6009
- try {
6010
- const raw = localStorage.getItem(storageKey);
6011
- if (!raw) return null;
6012
- const parsed = JSON.parse(raw);
6013
- if (!parsed || !Array.isArray(parsed.keys) || typeof parsed.at !== "number") return null;
6014
- return { keys: parsed.keys.filter((x) => typeof x === "string"), at: parsed.at };
6015
- } catch {
6016
- return null;
6017
- }
6018
- }
6019
- function writeSlotDeclaration(storageKey, memory) {
6020
- if (typeof localStorage === "undefined") return;
6021
- try {
6022
- localStorage.setItem(storageKey, JSON.stringify(memory));
6023
- } catch {
6185
+ // src/core/cart/slices.ts
6186
+ function cartSlicesFrom(lines, resolveCampaign, fallbackCurrency) {
6187
+ if (!Array.isArray(lines) || lines.length === 0) return [];
6188
+ const slices = /* @__PURE__ */ new Map();
6189
+ for (const line of lines) {
6190
+ if (!line || typeof line !== "object") continue;
6191
+ const sponsorId = Number(line.sponsorId);
6192
+ const unitPrice = Number(line.unitPrice) || 0;
6193
+ for (const origin of lineOrigins(line)) {
6194
+ let campaignId = origin.campaignId;
6195
+ if (campaignId === void 0) {
6196
+ try {
6197
+ campaignId = resolveCampaign(origin.slot, sponsorId);
6198
+ } catch {
6199
+ campaignId = void 0;
6200
+ }
6201
+ }
6202
+ const key = campaignId === void 0 ? "default" : String(campaignId);
6203
+ let slice = slices.get(key);
6204
+ if (!slice) {
6205
+ slice = {
6206
+ campaignId,
6207
+ sponsors: /* @__PURE__ */ new Set(),
6208
+ slots: /* @__PURE__ */ new Set(),
6209
+ contents: /* @__PURE__ */ new Set(),
6210
+ items: /* @__PURE__ */ new Map(),
6211
+ value: 0,
6212
+ currency: line.currency || fallbackCurrency
6213
+ };
6214
+ slices.set(key, slice);
6215
+ }
6216
+ if (Number.isFinite(sponsorId)) slice.sponsors.add(sponsorId);
6217
+ if (origin.slot) slice.slots.add(origin.slot);
6218
+ if (origin.contentId) slice.contents.add(origin.contentId);
6219
+ const itemKey = `${line.productId}::${line.variantId ?? ""}`;
6220
+ const item = slice.items.get(itemKey);
6221
+ if (item) item.quantity = (item.quantity ?? 0) + origin.quantity;
6222
+ else {
6223
+ slice.items.set(itemKey, {
6224
+ productId: line.productId,
6225
+ name: line.name,
6226
+ brand: line.brand,
6227
+ variantId: line.variantId,
6228
+ price: unitPrice,
6229
+ quantity: origin.quantity
6230
+ });
6231
+ }
6232
+ slice.value += unitPrice * origin.quantity;
6233
+ }
6024
6234
  }
6235
+ const list = [...slices.values()];
6236
+ return list.map((s, i) => ({
6237
+ campaignId: s.campaignId,
6238
+ sponsorId: s.sponsors.size === 1 ? [...s.sponsors][0] : void 0,
6239
+ slot: s.slots.size === 1 ? [...s.slots][0] : void 0,
6240
+ contentId: s.contents.size === 1 ? [...s.contents][0] : void 0,
6241
+ items: [...s.items.values()],
6242
+ value: Math.round(s.value * 100) / 100,
6243
+ currency: s.currency,
6244
+ slice: i + 1,
6245
+ slices: list.length
6246
+ }));
6025
6247
  }
6026
- function isSlotDeclarationFresh(memory, keys, now = Date.now()) {
6027
- if (!memory) return false;
6028
- if (now - memory.at >= SLOT_DECLARATION_TTL_MS || now < memory.at) return false;
6029
- if (memory.keys.length !== keys.length) return false;
6030
- const remembered = [...memory.keys].sort();
6031
- const wanted = [...keys].sort();
6032
- return remembered.every((key, i) => key === wanted[i]);
6248
+ function hasExplicitCampaign(lines) {
6249
+ return lines.some((line) => lineOrigins(line).some((o) => o.campaignId !== void 0));
6033
6250
  }
6034
6251
 
6035
6252
  // src/core/analytics/analytics-manager.ts
@@ -6229,25 +6446,34 @@ var AnalyticsManager = class extends EventTarget {
6229
6446
  if (!anonId) return null;
6230
6447
  const boot = vioBootstrapCache();
6231
6448
  const ctx = opts.context ?? {};
6232
- const content = ctx.contentId === void 0 || ctx.contentTitle === void 0 ? vioContent() : {};
6449
+ const explicitContentId = fitsId(ctx.contentId);
6450
+ const content = explicitContentId === void 0 || ctx.contentTitle === void 0 ? vioContent() : {};
6233
6451
  const context = compact({
6234
6452
  campaign_id: resolveCampaignId(ctx, boot),
6235
6453
  broadcast_id: ctx.broadcastId,
6236
6454
  campaign_component_id: ctx.campaignComponentId,
6237
6455
  app_placement_id: ctx.appPlacementId,
6238
- // A slot is a location id to the campaigns service (ADR-0027).
6239
- location_id: ctx.locationId ?? ctx.slot,
6456
+ // A slot is a location id to the campaigns service (ADR-0027). An
6457
+ // explicit location id that does not fit gives way to the slot.
6458
+ location_id: fitsId(ctx.locationId) ?? fitsId(ctx.slot),
6240
6459
  component_template_id: ctx.componentTemplateId,
6241
6460
  sponsor_id: ctx.sponsorId,
6242
6461
  activation_id: ctx.activationId,
6243
- content_url: ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0),
6462
+ // Cut to the collector's 2 048 — the query string first, since a long
6463
+ // one is usually the tracking tail, not the page. The other host-given
6464
+ // fields (broadcastId, componentTemplateId, errorCode, variant, …) are
6465
+ // the host's responsibility: the SDK does not touch them.
6466
+ content_url: capUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
6244
6467
  // The host's own content id — its join key back onto its reporting.
6245
- // Explicit on the event, else what the SDK resolved for the page.
6246
- content_id: ctx.contentId ?? content.id,
6468
+ // Explicit on the event when it fits, else what the SDK resolved for
6469
+ // the page (already capped per source), else nothing.
6470
+ content_id: explicitContentId ?? fitsId(content.id),
6247
6471
  // Deletion-proof reporting: sources have no trash bin — the title
6248
6472
  // snapshotted at event time keeps reports legible if content dies.
6249
6473
  error_code: ctx.errorCode,
6250
- content_title: ctx.contentTitle ?? content.title ?? (typeof document !== "undefined" && document.title ? document.title.slice(0, 255) : void 0),
6474
+ content_title: capTitle(
6475
+ ctx.contentTitle ?? content.title ?? (typeof document !== "undefined" && document.title ? document.title : void 0)
6476
+ ),
6251
6477
  variant: ctx.variant
6252
6478
  });
6253
6479
  const com = opts.commerce;
@@ -6395,7 +6621,7 @@ var AnalyticsManager = class extends EventTarget {
6395
6621
  const d = ev.detail ?? {};
6396
6622
  const line = this.findLine(d.productId, d.variantId);
6397
6623
  this.track("add_to_cart", {
6398
- context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, slot: slotOf(d), ...contentOf(d) },
6624
+ context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
6399
6625
  commerce: {
6400
6626
  items: [
6401
6627
  {
@@ -6416,7 +6642,7 @@ var AnalyticsManager = class extends EventTarget {
6416
6642
  const d = ev.detail ?? {};
6417
6643
  if (d.productId === void 0) return;
6418
6644
  this.track("remove_from_cart", {
6419
- context: { sponsorId: d.sponsorId, slot: slotOf(d), ...contentOf(d) },
6645
+ context: { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
6420
6646
  commerce: {
6421
6647
  items: [
6422
6648
  {
@@ -6445,7 +6671,7 @@ var AnalyticsManager = class extends EventTarget {
6445
6671
  if (d.productId === void 0) return;
6446
6672
  const price = Number(d.price);
6447
6673
  this.track("select_item", {
6448
- context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, slot: slotOf(d), ...contentOf(d) },
6674
+ context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
6449
6675
  commerce: {
6450
6676
  items: [
6451
6677
  {
@@ -6464,38 +6690,42 @@ var AnalyticsManager = class extends EventTarget {
6464
6690
  });
6465
6691
  on("vio:checkout-open", (ev) => {
6466
6692
  const d = ev.detail ?? {};
6467
- const slices = this.cartSlices(d.sponsorId);
6693
+ const base = { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
6694
+ const { slices, explicit } = this.cartSlices(d.sponsorId);
6468
6695
  if (slices.length > 1) {
6469
6696
  for (const slice of slices) {
6470
6697
  this.track("begin_checkout", {
6471
- context: this.sliceContext(slice, { sponsorId: d.sponsorId, slot: slotOf(d), ...contentOf(d) }),
6472
- commerce: slice.commerce
6698
+ context: this.sliceContext(slice, base),
6699
+ commerce: sliceCommerce(slice),
6700
+ props: { slice: slice.slice, slices: slice.slices }
6473
6701
  });
6474
6702
  }
6475
6703
  return;
6476
6704
  }
6477
6705
  const snap = this.cartCommerce(d.sponsorId);
6478
6706
  this.track("begin_checkout", {
6479
- context: { sponsorId: d.sponsorId, slot: slotOf(d), ...contentOf(d) },
6707
+ context: this.singleSliceContext(slices[0], explicit, base),
6480
6708
  ...snap ? { commerce: snap } : {}
6481
6709
  });
6482
6710
  });
6483
6711
  on("vio:payment-success", (ev) => {
6484
6712
  const d = ev.detail ?? {};
6485
6713
  const sponsorId = d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0;
6486
- const slices = this.cartSlices(sponsorId);
6714
+ const base = { sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
6715
+ const { slices, explicit } = this.cartSlices(sponsorId);
6487
6716
  if (slices.length > 1) {
6488
6717
  for (const slice of slices) {
6489
6718
  this.track("purchase", {
6490
- context: this.sliceContext(slice, { sponsorId, slot: slotOf(d), ...contentOf(d) }),
6491
- commerce: { ...slice.commerce, ...purchaseFromPaymentSuccess(d), paymentMethod: d.method }
6719
+ context: this.sliceContext(slice, base),
6720
+ commerce: { ...sliceCommerce(slice), ...purchaseFromPaymentSuccess(d), paymentMethod: d.method },
6721
+ props: { slice: slice.slice, slices: slice.slices }
6492
6722
  });
6493
6723
  }
6494
6724
  return;
6495
6725
  }
6496
6726
  const snap = this.cartCommerce(sponsorId);
6497
6727
  this.track("purchase", {
6498
- context: { sponsorId, slot: slotOf(d), ...contentOf(d) },
6728
+ context: this.singleSliceContext(slices[0], explicit, base),
6499
6729
  commerce: {
6500
6730
  // value / currency / items: the cart snapshot, on every path.
6501
6731
  ...snap ?? {},
@@ -6523,67 +6753,50 @@ var AnalyticsManager = class extends EventTarget {
6523
6753
  return items;
6524
6754
  }
6525
6755
  /**
6526
- * The cart split per campaign (ADR-0034): every unit of every line goes to
6527
- * the campaign of its origin — the one the place showed when it was added
6528
- * — and units without one to the campaign the current rule resolves for
6529
- * the line's sponsor. One slice per campaign: its items (quantities from
6530
- * the origins), its value, and the slot / content when they are the same
6531
- * for the whole slice. A cart with one campaign is one slice.
6756
+ * The cart split per campaign — the shared rule of cart/slices.ts over the
6757
+ * current cart (else the pre-clear snapshot), plus whether any unit named
6758
+ * its campaign itself (a cart without origins is reported as before).
6532
6759
  */
6533
6760
  cartSlices(sponsorId) {
6534
6761
  const lines = this.cartLines(sponsorId);
6535
- if (lines.length === 0) return [];
6762
+ if (lines.length === 0) return { slices: [], explicit: false };
6536
6763
  const boot = vioBootstrapCache();
6537
- const slices = /* @__PURE__ */ new Map();
6538
- for (const line of lines) {
6539
- for (const origin of lineOrigins(line)) {
6540
- const campaignId = origin.campaignId ?? resolveCampaignId({ slot: origin.slot, sponsorId: line.sponsorId }, boot);
6541
- const key = campaignId === void 0 ? "default" : String(campaignId);
6542
- let slice = slices.get(key);
6543
- if (!slice) {
6544
- slice = {
6545
- campaignId,
6546
- commerce: { items: [], value: 0, currency: line.currency ?? this.lastCartSnapshot?.currency },
6547
- slots: /* @__PURE__ */ new Set(),
6548
- contents: /* @__PURE__ */ new Set(),
6549
- lines: /* @__PURE__ */ new Map()
6550
- };
6551
- slices.set(key, slice);
6552
- }
6553
- if (origin.slot) slice.slots.add(origin.slot);
6554
- if (origin.contentId) slice.contents.add(origin.contentId);
6555
- const itemKey = `${line.productId}::${line.variantId ?? ""}`;
6556
- const item = slice.lines.get(itemKey);
6557
- if (item) item.quantity = (item.quantity ?? 0) + origin.quantity;
6558
- else {
6559
- const fresh = {
6560
- productId: line.productId,
6561
- name: line.name,
6562
- brand: line.brand,
6563
- variantId: line.variantId,
6564
- price: line.unitPrice,
6565
- quantity: origin.quantity
6566
- };
6567
- slice.lines.set(itemKey, fresh);
6568
- slice.commerce.items.push(fresh);
6569
- }
6570
- slice.commerce.value = (slice.commerce.value ?? 0) + line.unitPrice * origin.quantity;
6571
- }
6572
- }
6573
- return [...slices.values()].map((s) => ({
6574
- campaignId: s.campaignId,
6575
- slot: s.slots.size === 1 ? [...s.slots][0] : void 0,
6576
- contentId: s.contents.size === 1 ? [...s.contents][0] : void 0,
6577
- commerce: s.commerce
6578
- }));
6764
+ const slices = cartSlicesFrom(
6765
+ lines,
6766
+ (slot, lineSponsorId) => resolveCampaignId({ slot, sponsorId: lineSponsorId }, boot),
6767
+ this.lastCartSnapshot?.currency
6768
+ );
6769
+ return { slices, explicit: hasExplicitCampaign(lines) };
6579
6770
  }
6580
- /** The context of a per-campaign event: the slice's campaign, slot and content, over the event's own. */
6771
+ /**
6772
+ * The context of one of several per-campaign events: the slice's campaign,
6773
+ * slot and content — only its own; when the slice spans several slots or
6774
+ * articles the event names none rather than the one the click came from.
6775
+ */
6581
6776
  sliceContext(slice, base) {
6582
6777
  return compact({
6583
6778
  ...base,
6584
- campaignId: slice.campaignId ?? base.campaignId,
6585
- slot: slice.slot ?? base.slot,
6586
- contentId: slice.contentId ?? base.contentId
6779
+ sponsorId: slice.sponsorId ?? base.sponsorId,
6780
+ campaignId: slice.campaignId,
6781
+ slot: slice.slot,
6782
+ contentId: slice.contentId,
6783
+ // The click's title only describes the click's content: when the slice
6784
+ // is another article (or several), the page's own title applies instead.
6785
+ contentTitle: slice.contentId !== void 0 && slice.contentId === base.contentId ? base.contentTitle : void 0
6786
+ });
6787
+ }
6788
+ /**
6789
+ * The context of a single-campaign checkout/purchase: when the cart's origins
6790
+ * say which campaign they came from, the event says it too (plus the slot /
6791
+ * content when unique) — a cart without origins is exactly as before.
6792
+ */
6793
+ singleSliceContext(slice, explicit, base) {
6794
+ if (!slice || !explicit) return base;
6795
+ return compact({
6796
+ ...base,
6797
+ campaignId: base.campaignId ?? slice.campaignId,
6798
+ slot: base.slot ?? slice.slot,
6799
+ contentId: base.contentId ?? slice.contentId
6587
6800
  });
6588
6801
  }
6589
6802
  /** Current cart → commerce payload; falls back to the pre-clear snapshot. */
@@ -6686,27 +6899,50 @@ function vioCart() {
6686
6899
  return null;
6687
6900
  }
6688
6901
  }
6689
- function resolveCampaignId(ctx, boot) {
6902
+ function resolveCampaignId(ctx, boot, opts = {}) {
6690
6903
  if (ctx.campaignId !== void 0) return ctx.campaignId;
6691
6904
  const defaultId = boot?.campaign?.id;
6905
+ const config = Configuration.isInitialized ? Configuration.get() : void 0;
6906
+ const sponsorId = toNumber(ctx.sponsorId);
6907
+ const candidates = [
6908
+ ...Array.isArray(opts.pageCampaignId) ? opts.pageCampaignId : [opts.pageCampaignId],
6909
+ config?.campaignId
6910
+ ];
6911
+ const page = activeCampaignEntry(boot, candidates, sponsorId);
6692
6912
  if (ctx.slot) {
6693
- const platform = Configuration.isInitialized ? Configuration.get().platform : void 0;
6694
- const assigned = toNumber2(findSlotAssignment(boot, ctx.slot, Date.now(), platform)?.id);
6695
- if (assigned !== void 0) return assigned;
6913
+ const assignment = findSlotAssignmentDetail(boot, ctx.slot, Date.now(), config?.platform);
6914
+ if (assignment && assignmentApplies(assignment, activeCampaignEntry(boot, candidates), sponsorId)) {
6915
+ const assigned = toNumber(assignment.campaign.id);
6916
+ if (assigned !== void 0) return assigned;
6917
+ }
6696
6918
  }
6697
- const sponsorId = toNumber2(ctx.sponsorId);
6919
+ const pageId = toNumber(page?.id);
6920
+ if (pageId !== void 0) return pageId;
6698
6921
  if (sponsorId === void 0) return defaultId;
6699
- try {
6700
- const campaigns = Array.isArray(boot?.campaigns) ? boot.campaigns : [];
6701
- const matches = campaigns.filter(
6702
- (c) => !!c && typeof c === "object" && c.status === "active" && campaignSponsorIds(c).includes(sponsorId)
6703
- );
6704
- const chosen = matches.find((c) => c.id === defaultId) ?? matches[0];
6705
- const chosenId = toNumber2(chosen?.id);
6706
- if (chosenId !== void 0) return chosenId;
6707
- } catch {
6708
- }
6709
- return defaultId;
6922
+ return toNumber(sponsorCampaignEntry(boot, sponsorId)?.id) ?? defaultId;
6923
+ }
6924
+ function campaignOf(detail) {
6925
+ return toNumber(detail.campaignId);
6926
+ }
6927
+ function fitsId(value) {
6928
+ if (value === void 0 || value === null) return void 0;
6929
+ const s = String(value);
6930
+ return s && s.length <= CONTENT_ID_MAX_LENGTH ? value : void 0;
6931
+ }
6932
+ function capTitle(value) {
6933
+ if (!value) return void 0;
6934
+ return value.length > CONTENT_TITLE_MAX_LENGTH ? value.slice(0, CONTENT_TITLE_MAX_LENGTH) : value;
6935
+ }
6936
+ var CONTENT_URL_MAX_LENGTH = 2048;
6937
+ function capUrl(value) {
6938
+ if (!value) return void 0;
6939
+ if (value.length <= CONTENT_URL_MAX_LENGTH) return value;
6940
+ const cut = value.search(/[?#]/);
6941
+ const withoutQuery = cut >= 0 ? value.slice(0, cut) : value;
6942
+ return withoutQuery.length <= CONTENT_URL_MAX_LENGTH ? withoutQuery : withoutQuery.slice(0, CONTENT_URL_MAX_LENGTH);
6943
+ }
6944
+ function sliceCommerce(slice) {
6945
+ return { items: slice.items, value: slice.value, currency: slice.currency };
6710
6946
  }
6711
6947
  function slotOf(detail) {
6712
6948
  return typeof detail.slot === "string" && detail.slot.trim() ? detail.slot.trim() : void 0;
@@ -6718,17 +6954,6 @@ function contentOf(detail) {
6718
6954
  if (typeof detail.contentTitle === "string" && detail.contentTitle.trim()) out.contentTitle = detail.contentTitle.trim();
6719
6955
  return out;
6720
6956
  }
6721
- function campaignSponsorIds(campaign) {
6722
- const ids = [];
6723
- const primary = toNumber2(campaign.primarySponsor?.id);
6724
- if (primary !== void 0) ids.push(primary);
6725
- const secondaries = Array.isArray(campaign.secondarySponsors) ? campaign.secondarySponsors : [];
6726
- for (const s of secondaries) {
6727
- const id = toNumber2(s?.id);
6728
- if (id !== void 0) ids.push(id);
6729
- }
6730
- return ids;
6731
- }
6732
6957
  function purchaseFromPaymentSuccess(d) {
6733
6958
  const r = d.result ?? {};
6734
6959
  const orderId = firstId(d.orderId, r.confirmation?.orderId, d.checkoutId);
@@ -6742,11 +6967,6 @@ function firstId(...candidates) {
6742
6967
  }
6743
6968
  return void 0;
6744
6969
  }
6745
- function toNumber2(value) {
6746
- if (value === void 0 || value === null || value === "") return void 0;
6747
- const n = Number(value);
6748
- return Number.isFinite(n) ? n : void 0;
6749
- }
6750
6970
  function pushGa4(event) {
6751
6971
  const commerceNames = /* @__PURE__ */ new Set([
6752
6972
  "view_item_list",
@@ -6797,14 +7017,20 @@ var VioFacade = class {
6797
7017
  * (another key or environment) is still in flight after a re-init; its answer
6798
7018
  * belongs to that configuration and must not land in this one. */
6799
7019
  this.generation = 0;
6800
- /** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each + its display name. */
7020
+ /** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each, its display name and its own campaign. */
6801
7021
  this.slotRegistry = /* @__PURE__ */ new Map();
7022
+ /** Slots already warned about (`dashboard`: the dashboard's assignment stands; `replaced`: this page moved it). */
7023
+ this.warnedDashboardSlots = /* @__PURE__ */ new Set();
7024
+ this.warnedReplacedSlots = /* @__PURE__ */ new Set();
6802
7025
  /** Pending debounced declaration of the registry. */
6803
7026
  this.slotDeclareTimer = null;
6804
7027
  /** A name appeared (or the key changed) since the registry was last declared. */
6805
7028
  this.slotsDirty = false;
6806
- /** `Vio.content()` resolved once per page; forgotten on every init(). */
6807
- this.contentCache = null;
7029
+ /** Manifests POSTed by this page load (abuse / rollout guard, see declareSlots). */
7030
+ this.declarationsThisLoad = 0;
7031
+ /** Each cap warns once per page load. */
7032
+ this.warnedLocationCap = false;
7033
+ this.warnedDeclarationCap = false;
6808
7034
  // Client-side managers — created lazily on first access. They don't
6809
7035
  // need Vio.init() to work (state is purely local).
6810
7036
  this.cartManager = null;
@@ -6825,26 +7051,28 @@ var VioFacade = class {
6825
7051
  this.slotDeclareTimer = null;
6826
7052
  }
6827
7053
  this.slotsDirty = this.slotRegistry.size > 0;
6828
- this.contentCache = null;
7054
+ this.declarationsThisLoad = 0;
7055
+ this.warnedLocationCap = false;
7056
+ this.warnedDeclarationCap = false;
7057
+ this.warnedDashboardSlots.clear();
7058
+ this.warnedReplacedSlots.clear();
6829
7059
  }
6830
7060
  /**
6831
7061
  * The content (article, page) this SDK instance is rendered in — what every
6832
7062
  * analytics event carries as `content_id` / `content_title` when the event
6833
- * has no explicit one. Resolved once per page (init option → URL query →
6834
- * `vio:content-*` metas → canonical/og:url → pathname; see
6835
- * `resolveContent`), forgotten on every `init()`. Never throws; `{}` without
6836
- * a document.
7063
+ * has no explicit one: init option → URL query → `vio:content-*` metas →
7064
+ * canonical/og:url → pathname (see `resolveContent`). Resolved LIVE on each
7065
+ * call — a few querySelectors — so an SPA route change, a new canonical or
7066
+ * a new title are seen without any re-init. Never throws; `{}` without a
7067
+ * document.
6837
7068
  */
6838
7069
  content() {
6839
- if (this.contentCache) return this.contentCache;
6840
7070
  let init;
6841
7071
  if (Configuration.isInitialized) {
6842
7072
  const cfg = Configuration.get();
6843
7073
  init = { contentId: cfg.contentId, contentTitle: cfg.contentTitle };
6844
7074
  }
6845
- const resolved = resolveContent(init);
6846
- if (typeof document !== "undefined") this.contentCache = resolved;
6847
- return resolved;
7075
+ return resolveContent(init);
6848
7076
  }
6849
7077
  get isInitialized() {
6850
7078
  return Configuration.isInitialized;
@@ -6952,34 +7180,72 @@ var VioFacade = class {
6952
7180
  * trimmed, cut at 100 chars, deduped; empties are dropped.
6953
7181
  */
6954
7182
  async declareSlots(slots, opts = {}) {
7183
+ let storageKey;
7184
+ let keys = [];
7185
+ let now = Date.now();
6955
7186
  try {
6956
- const locations = normalizeSlotLocations(slots);
6957
- if (locations.length === 0) return null;
7187
+ const all = normalizeSlotLocations(slots);
7188
+ if (all.length === 0) return null;
6958
7189
  if (!Configuration.isInitialized) {
6959
7190
  warn("declareSlots() before Vio.init() \u2014 nothing declared");
6960
7191
  return null;
6961
7192
  }
6962
7193
  const cfg = Configuration.get();
6963
- const storageKey = slotDeclarationStorageKey(cfg.platform, cfg.apiKey);
6964
- const keys = slotDeclarationKeys(locations);
6965
- const now = Date.now();
6966
- if (!opts.force && isSlotDeclarationFresh(readSlotDeclaration(storageKey), keys, now)) {
7194
+ storageKey = slotDeclarationStorageKey(cfg.platform, cfg.apiKey);
7195
+ now = Date.now();
7196
+ const pageCampaignId = cfg.campaignId;
7197
+ for (const l of all) {
7198
+ if (l.campaignId !== void 0 && l.campaignId === pageCampaignId) delete l.campaignId;
7199
+ }
7200
+ const memory = readSlotDeclaration(storageKey);
7201
+ const keyOf = (l) => slotDeclarationKey(l, pageCampaignId);
7202
+ const pending2 = new Set(pendingSlotKeys(memory, all.map(keyOf), now));
7203
+ let locations = opts.force ? all : all.filter((l) => pending2.has(keyOf(l)));
7204
+ if (locations.length === 0) return null;
7205
+ if (locations.length > MAX_LOCATIONS_PER_DECLARATION) {
7206
+ if (!this.warnedLocationCap) {
7207
+ this.warnedLocationCap = true;
7208
+ warn(
7209
+ `declareSlots(): ${locations.length} slots on one page \u2014 only the first ${MAX_LOCATIONS_PER_DECLARATION} are declared`
7210
+ );
7211
+ }
7212
+ locations = locations.slice(0, MAX_LOCATIONS_PER_DECLARATION);
7213
+ }
7214
+ keys = locations.map(keyOf);
7215
+ if (!opts.force && this.declarationsThisLoad >= MAX_DECLARATIONS_PER_LOAD) {
7216
+ if (!this.warnedDeclarationCap) {
7217
+ this.warnedDeclarationCap = true;
7218
+ warn(
7219
+ `declareSlots(): already ${MAX_DECLARATIONS_PER_LOAD} manifests this page load \u2014 further ones are skipped (force: true to override)`
7220
+ );
7221
+ }
6967
7222
  return null;
6968
7223
  }
7224
+ this.declarationsThisLoad++;
6969
7225
  const result = await this.api.declareManifest({
6970
7226
  platform: cfg.platform,
6971
7227
  mode: "merge",
6972
- locations
7228
+ locations,
7229
+ ...pageCampaignId !== void 0 ? { campaignId: pageCampaignId } : {}
6973
7230
  });
7231
+ this.reportAssignments(result, locations, pageCampaignId, cfg.debug);
6974
7232
  if (result?.mode === "merge") {
6975
- writeSlotDeclaration(storageKey, { keys, at: now });
7233
+ const unhandled = this.campaignNotTakenUp(result, locations, pageCampaignId);
7234
+ const settled = keys.filter((k) => !unhandled.has(k));
7235
+ let next = rememberSlotKeys(memory, settled, now);
7236
+ if (unhandled.size > 0) next = rememberSlotKeys(next, [...unhandled], failedAt(now));
7237
+ writeSlotDeclaration(storageKey, next);
6976
7238
  } else {
7239
+ writeSlotDeclaration(storageKey, rememberSlotKeys(memory, keys, failedAt(now)));
6977
7240
  warn(
6978
7241
  "slots declared but the server ignored mode=merge (old server); not caching, will retry next load"
6979
7242
  );
6980
7243
  }
6981
7244
  return result;
6982
7245
  } catch (err) {
7246
+ if (storageKey && keys.length > 0) {
7247
+ writeSlotDeclaration(storageKey, rememberSlotKeys(readSlotDeclaration(storageKey), keys, failedAt(now)));
7248
+ }
6983
7249
  warn("declareSlots() failed \u2014", err);
6984
7250
  return null;
6985
7251
  }
@@ -6987,21 +7253,24 @@ var VioFacade = class {
6987
7253
  /**
6988
7254
  * Register a slot present on the page (the `<vio-*>` elements call it with
6989
7255
  * their effective id — the host's `vio-slot` or the auto-generated one —
6990
- * and the optional display name). The registry is declared ONCE, debounced,
6991
- * after the first successful bootstrap — and again whenever a new id or a
6992
- * new display name appears. A `displayName` given later for an id already
6993
- * registered replaces the one kept; `undefined` leaves it alone. The
6994
- * returned function unregisters; removal never re-declares.
7256
+ * the optional display name, and the element's own campaign when it has
7257
+ * one: `vio-campaign` or an ancestor's `data-vio-campaign`). The registry is
7258
+ * declared ONCE, debounced, after the first successful bootstrap — and again
7259
+ * whenever a new id, display name or campaign appears. A `displayName` /
7260
+ * `campaignId` given later for an id already registered replaces the one
7261
+ * kept; `undefined` leaves it alone. The returned function unregisters;
7262
+ * removal never re-declares.
6995
7263
  */
6996
- registerSlot(name, displayName) {
7264
+ registerSlot(name, displayName, campaignId) {
6997
7265
  const id = normalizeSlotId(name);
6998
7266
  if (!id) return () => {
6999
7267
  };
7000
7268
  const label = typeof displayName === "string" && displayName.trim() ? displayName.trim() : void 0;
7269
+ const campaign = toCampaignId(campaignId);
7001
7270
  const entry = this.slotRegistry.get(id);
7002
7271
  let changed = false;
7003
7272
  if (!entry) {
7004
- this.slotRegistry.set(id, { count: 1, displayName: label });
7273
+ this.slotRegistry.set(id, { count: 1, displayName: label, campaignId: campaign });
7005
7274
  changed = true;
7006
7275
  } else {
7007
7276
  entry.count++;
@@ -7009,6 +7278,10 @@ var VioFacade = class {
7009
7278
  entry.displayName = label;
7010
7279
  changed = true;
7011
7280
  }
7281
+ if (campaign !== void 0 && campaign !== entry.campaignId) {
7282
+ entry.campaignId = campaign;
7283
+ changed = true;
7284
+ }
7012
7285
  }
7013
7286
  if (changed) {
7014
7287
  this.slotsDirty = true;
@@ -7029,9 +7302,30 @@ var VioFacade = class {
7029
7302
  * open campaign for the init platform, else the surface's default. With no
7030
7303
  * slot, the default. `null` before the bootstrap. See `resolveSlotCampaign`.
7031
7304
  */
7032
- campaignForSlot(slot) {
7033
- const platform = Configuration.isInitialized ? Configuration.get().platform : void 0;
7034
- return resolveSlotCampaign(this.cachedBootstrap, slot ?? null, Date.now(), platform);
7305
+ campaignForSlot(slot, hint) {
7306
+ const config = Configuration.isInitialized ? Configuration.get() : void 0;
7307
+ return resolveSlotCampaign(
7308
+ this.cachedBootstrap,
7309
+ slot ?? null,
7310
+ Date.now(),
7311
+ config?.platform,
7312
+ this.pageCampaignCandidates(hint?.campaignId),
7313
+ toNumber(hint?.sponsorId)
7314
+ );
7315
+ }
7316
+ /** The page-campaign candidates in precedence order: the element's (own, ancestor), then `Vio.init`'s. */
7317
+ pageCampaignCandidates(hint) {
7318
+ const config = Configuration.isInitialized ? Configuration.get() : void 0;
7319
+ return campaignCandidates([...Array.isArray(hint) ? hint : [hint], config?.campaignId]);
7320
+ }
7321
+ /**
7322
+ * The campaign the PAGE belongs to (`Vio.init({ campaignId })`, ADR-0034
7323
+ * amendment 3), as the last bootstrap lists it — only when it is there and
7324
+ * active; `null` otherwise (no page campaign, paused, unknown, no bootstrap).
7325
+ */
7326
+ pageCampaign() {
7327
+ const config = Configuration.isInitialized ? Configuration.get() : void 0;
7328
+ return activeCampaignEntry(this.cachedBootstrap, config?.campaignId);
7035
7329
  }
7036
7330
  /**
7037
7331
  * THE attribution helper — the SDK elements, Vev and Replit all use this
@@ -7043,26 +7337,33 @@ var VioFacade = class {
7043
7337
  *
7044
7338
  * `hint.sponsorId` — when the caller already knows the sponsor (a card with
7045
7339
  * `sponsor-id`) it is kept as `sponsorId`, and the campaign follows the same
7046
- * cascade analytics uses (slot assignment → the sponsor's active campaign →
7047
- * default), so what the cart remembers matches what the events say.
7340
+ * cascade analytics uses (slot assignment → page campaign → the sponsor's
7341
+ * active campaign → default), so what the cart remembers matches what the
7342
+ * events say — the page campaign applies there only when that sponsor takes
7343
+ * part in it. `hint.campaignId` — the element's own page campaign
7344
+ * candidates (`vio-campaign`, an ancestor's `data-vio-campaign`), ahead of
7345
+ * `Vio.init`'s; an unusable one falls through to the next; `0` = unset.
7048
7346
  */
7049
7347
  attribution(slot, hint) {
7050
7348
  const out = {};
7051
7349
  try {
7052
- const name = typeof slot === "string" ? slot.trim() : "";
7350
+ const name = normalizeSlotId(slot);
7053
7351
  if (name) out.slot = name;
7054
7352
  const boot = this.cachedBootstrap;
7055
- const hinted = toNumber3(hint?.sponsorId);
7353
+ const hinted = toNumber(hint?.sponsorId);
7354
+ const pageCampaignId = this.pageCampaignCandidates(hint?.campaignId);
7056
7355
  if (boot) {
7057
7356
  if (hinted !== void 0) {
7058
- const campaignId = resolveCampaignId({ slot: name || void 0, sponsorId: hinted }, boot);
7357
+ const campaignId = resolveCampaignId({ slot: name || void 0, sponsorId: hinted }, boot, {
7358
+ pageCampaignId
7359
+ });
7059
7360
  if (campaignId !== void 0) out.campaignId = campaignId;
7060
7361
  out.sponsorId = hinted;
7061
7362
  } else {
7062
- const campaign = this.campaignForSlot(name || null);
7063
- const campaignId = toNumber3(campaign?.id);
7363
+ const campaign = this.campaignForSlot(name || null, { campaignId: pageCampaignId });
7364
+ const campaignId = toNumber(campaign?.id);
7064
7365
  if (campaignId !== void 0) out.campaignId = campaignId;
7065
- const sponsorId = toNumber3(campaign?.primarySponsor?.id);
7366
+ const sponsorId = toNumber(campaign?.primarySponsor?.id);
7066
7367
  if (sponsorId !== void 0) out.sponsorId = sponsorId;
7067
7368
  }
7068
7369
  } else if (hinted !== void 0) {
@@ -7075,15 +7376,91 @@ var VioFacade = class {
7075
7376
  }
7076
7377
  return out;
7077
7378
  }
7379
+ /**
7380
+ * The cart split per campaign — the SAME rule `begin_checkout` / `purchase`
7381
+ * use, for hosts with their own analytics sink (Vev). Every unit of every
7382
+ * line goes to the campaign of its origin; units without one to the campaign
7383
+ * the current rule resolves for the line's sponsor (slot assignment → the
7384
+ * sponsor's active campaign → default); `slot` / `contentId` / `sponsorId`
7385
+ * only when they are the same for the whole slice; `value` with two
7386
+ * decimals; `slice` / `slices` number the parts. Pure over the current cart
7387
+ * (or over `opts.items`); `[]` when there is nothing.
7388
+ */
7389
+ cartSlices(sponsorId, opts = {}) {
7390
+ try {
7391
+ let lines = [];
7392
+ if (Array.isArray(opts.items)) {
7393
+ lines = opts.items.filter((i) => sponsorId === void 0 || Number(i?.sponsorId) === Number(sponsorId));
7394
+ } else {
7395
+ this.cart.getAllCarts().forEach((state, id) => {
7396
+ if (sponsorId === void 0 || id === Number(sponsorId)) lines.push(...state.items);
7397
+ });
7398
+ }
7399
+ const boot = this.cachedBootstrap;
7400
+ return cartSlicesFrom(
7401
+ lines,
7402
+ (slot, lineSponsorId) => resolveCampaignId({ slot, sponsorId: lineSponsorId }, boot)
7403
+ );
7404
+ } catch {
7405
+ return [];
7406
+ }
7407
+ }
7408
+ /**
7409
+ * What the micro did with the page's campaign per slot (`assignment`):
7410
+ * `console.debug` when `Vio.init({ debug: true })`; a warning once per
7411
+ * slot when the dashboard's own assignment stands (only for a slot this
7412
+ * page actually sent a campaign for) and once per slot when this page moved
7413
+ * it from an earlier page assignment (`replaced`).
7414
+ */
7415
+ reportAssignments(result, sent, pageCampaignId, debug) {
7416
+ const answered = Array.isArray(result?.locations) ? result.locations : [];
7417
+ const campaignFor = (id) => sent.find((l) => l.id === id)?.campaignId ?? pageCampaignId;
7418
+ for (const l of answered) {
7419
+ if (!l || typeof l !== "object" || !l.assignment) continue;
7420
+ if (debug && typeof console !== "undefined") console.debug("[Vio] slot", l.locationId, "assignment:", l.assignment);
7421
+ const sentCampaign = campaignFor(l.locationId);
7422
+ if (sentCampaign === void 0) continue;
7423
+ if (l.assignment === "dashboard" && !this.warnedDashboardSlots.has(l.locationId)) {
7424
+ this.warnedDashboardSlots.add(l.locationId);
7425
+ warn(`slot ${l.locationId} is assigned from the dashboard; the page's campaign is ignored there`);
7426
+ }
7427
+ if (l.assignment === "replaced" && !this.warnedReplacedSlots.has(l.locationId)) {
7428
+ this.warnedReplacedSlots.add(l.locationId);
7429
+ warn(`slot ${l.locationId} moved to campaign ${sentCampaign} by this page (it replaced an earlier page assignment)`);
7430
+ }
7431
+ }
7432
+ }
7433
+ /**
7434
+ * The memory keys of the sent locations whose campaign the micro did not
7435
+ * take up: every location when a campaign was sent and no answer carries
7436
+ * `assignment` (an older micro), or the ones answered `none` for a campaign
7437
+ * the bootstrap lists as active. They are remembered with the 1 h TTL.
7438
+ */
7439
+ campaignNotTakenUp(result, sent, pageCampaignId) {
7440
+ const out = /* @__PURE__ */ new Set();
7441
+ const withCampaign = sent.filter((l) => (l.campaignId ?? pageCampaignId) !== void 0);
7442
+ if (withCampaign.length === 0) return out;
7443
+ const answered = Array.isArray(result?.locations) ? result.locations : [];
7444
+ const anyAssignment = answered.some((l) => l && typeof l === "object" && !!l.assignment);
7445
+ for (const l of withCampaign) {
7446
+ const answer = answered.find((a) => a && a.locationId === l.id);
7447
+ const campaignId = l.campaignId ?? pageCampaignId;
7448
+ const notTaken = !anyAssignment || answer?.assignment === "none" && activeCampaignEntry(this.cachedBootstrap, campaignId) !== null;
7449
+ if (notTaken) out.add(slotDeclarationKey(l, pageCampaignId));
7450
+ }
7451
+ return out;
7452
+ }
7078
7453
  scheduleSlotDeclaration() {
7079
7454
  if (this.slotDeclareTimer) clearTimeout(this.slotDeclareTimer);
7080
7455
  this.slotDeclareTimer = setTimeout(() => {
7081
7456
  this.slotDeclareTimer = null;
7082
7457
  this.slotsDirty = false;
7083
7458
  void this.declareSlots(
7084
- [...this.slotRegistry.entries()].map(
7085
- ([id, { displayName }]) => displayName ? { id, displayName } : id
7086
- )
7459
+ [...this.slotRegistry.entries()].map(([id, { displayName, campaignId }]) => ({
7460
+ id,
7461
+ ...displayName ? { displayName } : {},
7462
+ ...campaignId !== void 0 ? { campaignId } : {}
7463
+ }))
7087
7464
  );
7088
7465
  }, SLOT_DECLARATION_DEBOUNCE_MS);
7089
7466
  }
@@ -7107,10 +7484,8 @@ var VioFacade = class {
7107
7484
  function warn(...args) {
7108
7485
  if (typeof console !== "undefined") console.warn("[Vio]", ...args);
7109
7486
  }
7110
- function toNumber3(value) {
7111
- if (value === void 0 || value === null || value === "") return void 0;
7112
- const n = Number(value);
7113
- return Number.isFinite(n) ? n : void 0;
7487
+ function failedAt(now) {
7488
+ return now - (SLOT_DECLARATION_TTL_MS - SLOT_DECLARATION_FAILURE_TTL_MS);
7114
7489
  }
7115
7490
  var _vioGlobal = globalThis;
7116
7491
  var Vio = _vioGlobal.__VIO_FACADE__ ?? (_vioGlobal.__VIO_FACADE__ = new VioFacade());
@@ -7164,6 +7539,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
7164
7539
  };
7165
7540
  }
7166
7541
 
7167
- export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, CREATE_CART_MUTATION, CREATE_CHECKOUT_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, KUSTOM_RETURN_QUERY_KEYS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, kustomReturnOutcome, lineOrigins, nexiReturnPaymentId, normalizeOrigin, normalizeScope, primaryImageUrl, purchaseFromPaymentSuccess, readNexiPending, resolveCampaignId, resolveContent, resolveSlotCampaign, sameOrigin, showApplePaySheet, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
7168
- //# sourceMappingURL=chunk-AMDI7KH3.js.map
7169
- //# sourceMappingURL=chunk-AMDI7KH3.js.map
7542
+ export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, CREATE_CART_MUTATION, CREATE_CHECKOUT_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, KUSTOM_RETURN_QUERY_KEYS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, campaignCandidates, cartSlicesFrom, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, kustomReturnOutcome, lineOrigins, nexiReturnPaymentId, normalizeOrigin, normalizeScope, normalizeSlotId, primaryImageUrl, purchaseFromPaymentSuccess, readNexiPending, resolveCampaignId, resolveContent, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
7543
+ //# sourceMappingURL=chunk-AXJKB2CW.js.map
7544
+ //# sourceMappingURL=chunk-AXJKB2CW.js.map