@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.
- package/README.md +105 -21
- package/dist/chunk-45YKS5UG.js +3 -0
- package/dist/{chunk-25QECQDS.js.map → chunk-45YKS5UG.js.map} +1 -1
- package/dist/{chunk-AMDI7KH3.js → chunk-AXJKB2CW.js} +680 -305
- package/dist/chunk-AXJKB2CW.js.map +1 -0
- package/dist/{chunk-COCHGJ2K.js → chunk-N6U65DWG.js} +165 -67
- package/dist/chunk-N6U65DWG.js.map +1 -0
- package/dist/core/index.d.ts +255 -80
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-DSYvrBsB.d.ts → tokens-C3qiGvLV.d.ts} +34 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-25QECQDS.js +0 -3
- package/dist/chunk-AMDI7KH3.js.map +0 -1
- package/dist/chunk-COCHGJ2K.js.map +0 -1
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
1787
|
-
|
|
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
|
|
1845
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|
|
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/
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
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
|
|
6027
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6246
|
-
|
|
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:
|
|
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
|
|
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,
|
|
6472
|
-
commerce: slice
|
|
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:
|
|
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
|
|
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,
|
|
6491
|
-
commerce: { ...slice
|
|
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:
|
|
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
|
|
6527
|
-
*
|
|
6528
|
-
*
|
|
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 =
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
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
|
|
6694
|
-
|
|
6695
|
-
|
|
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
|
|
6919
|
+
const pageId = toNumber(page?.id);
|
|
6920
|
+
if (pageId !== void 0) return pageId;
|
|
6698
6921
|
if (sponsorId === void 0) return defaultId;
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
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
|
|
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
|
-
/**
|
|
6807
|
-
this.
|
|
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.
|
|
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
|
|
6834
|
-
*
|
|
6835
|
-
*
|
|
6836
|
-
* a
|
|
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
|
-
|
|
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
|
|
6957
|
-
if (
|
|
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
|
-
|
|
6964
|
-
|
|
6965
|
-
const
|
|
6966
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
6991
|
-
*
|
|
6992
|
-
*
|
|
6993
|
-
*
|
|
6994
|
-
*
|
|
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
|
|
7034
|
-
return resolveSlotCampaign(
|
|
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
|
|
7047
|
-
* default), so what the cart remembers matches what the
|
|
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 =
|
|
7350
|
+
const name = normalizeSlotId(slot);
|
|
7053
7351
|
if (name) out.slot = name;
|
|
7054
7352
|
const boot = this.cachedBootstrap;
|
|
7055
|
-
const hinted =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
7111
|
-
|
|
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-
|
|
7169
|
-
//# sourceMappingURL=chunk-
|
|
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
|