@vio-live/web-sdk 0.21.0 → 0.23.1
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 +177 -0
- package/dist/chunk-45YKS5UG.js +3 -0
- package/dist/{chunk-PZUOMOLF.js.map → chunk-45YKS5UG.js.map} +1 -1
- package/dist/{chunk-WTVGVDZX.js → chunk-BRJMT7P4.js} +1053 -80
- package/dist/chunk-BRJMT7P4.js.map +1 -0
- package/dist/{chunk-MRJR6DG5.js → chunk-UVDOKFGD.js} +403 -34
- package/dist/chunk-UVDOKFGD.js.map +1 -0
- package/dist/core/index.d.ts +431 -16
- 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/react/index.js.map +1 -1
- package/dist/{tokens-Dkbfcd1L.d.ts → tokens-DDfZuHF6.d.ts} +108 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-MRJR6DG5.js.map +0 -1
- package/dist/chunk-PZUOMOLF.js +0 -3
- package/dist/chunk-WTVGVDZX.js.map +0 -1
|
@@ -46,6 +46,9 @@ var ConfigurationSingleton = class {
|
|
|
46
46
|
this.resolved = {
|
|
47
47
|
apiKey: input.apiKey,
|
|
48
48
|
environment: env,
|
|
49
|
+
platform: input.platform ?? "web",
|
|
50
|
+
contentId: input.contentId,
|
|
51
|
+
contentTitle: input.contentTitle,
|
|
49
52
|
apiBase: input.apiBase ?? defaults.apiBase,
|
|
50
53
|
graphQLBase: input.graphQLBase ?? defaults.graphQLBase,
|
|
51
54
|
eventsBase: input.eventsBase ?? defaults.eventsBase,
|
|
@@ -58,6 +61,7 @@ var ConfigurationSingleton = class {
|
|
|
58
61
|
const masked = input.apiKey.length > 4 ? "*".repeat(Math.max(0, input.apiKey.length - 4)) + input.apiKey.slice(-4) : "****";
|
|
59
62
|
console.info("[Vio] Initialized", {
|
|
60
63
|
environment: env,
|
|
64
|
+
platform: this.resolved.platform,
|
|
61
65
|
apiBase: this.resolved.apiBase,
|
|
62
66
|
apiKey: masked,
|
|
63
67
|
stripeConfigured: !!input.stripePublishableKey,
|
|
@@ -118,9 +122,21 @@ var VioApi = class {
|
|
|
118
122
|
/**
|
|
119
123
|
* Bootstrap — fetches client app config (sponsors with commerce keys,
|
|
120
124
|
* default market, campaign id). Called once at SDK init.
|
|
125
|
+
*
|
|
126
|
+
* With `platform`, `campaigns[].slots[]` only lists the assignments of that
|
|
127
|
+
* platform (plus the ones with no platform) — see ADR-0027.
|
|
121
128
|
*/
|
|
122
|
-
async bootstrap() {
|
|
123
|
-
|
|
129
|
+
async bootstrap(platform) {
|
|
130
|
+
const query = platform ? `?platform=${encodeURIComponent(platform)}` : "";
|
|
131
|
+
return this.get(`/v2/mobile/config${query}`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Declare the slots of this page to the campaigns service
|
|
135
|
+
* (`POST /v2/mobile/components/manifest`, ADR-0034). The web SDK always
|
|
136
|
+
* sends `mode: 'merge'`; see `Vio.declareSlots()` for the dedupe on top.
|
|
137
|
+
*/
|
|
138
|
+
async declareManifest(body) {
|
|
139
|
+
return this.post("/v2/mobile/components/manifest", body);
|
|
124
140
|
}
|
|
125
141
|
/**
|
|
126
142
|
* Get placement components for a campaign. Used by the carousel auto-fetch
|
|
@@ -146,6 +162,22 @@ var VioApi = class {
|
|
|
146
162
|
}
|
|
147
163
|
return await res.json();
|
|
148
164
|
}
|
|
165
|
+
async post(path, body) {
|
|
166
|
+
const url = `${this.apiBase}${path}`;
|
|
167
|
+
const res = await fetch(url, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"X-API-Key": this.apiKey,
|
|
171
|
+
Accept: "application/json",
|
|
172
|
+
"Content-Type": "application/json"
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
throw new VioApiError(res.status, await safeText(res), `POST ${path}`);
|
|
178
|
+
}
|
|
179
|
+
return await res.json();
|
|
180
|
+
}
|
|
149
181
|
};
|
|
150
182
|
var VioApiError = class extends Error {
|
|
151
183
|
constructor(status, body, endpoint) {
|
|
@@ -1385,23 +1417,40 @@ function getCustomerSessionId() {
|
|
|
1385
1417
|
}
|
|
1386
1418
|
return id;
|
|
1387
1419
|
}
|
|
1420
|
+
var CART_GRAPHQL_TIMEOUT_MS = 15e3;
|
|
1388
1421
|
async function executeCartGraphQL(query, variables, options = {}) {
|
|
1389
1422
|
const resolvedOptions = typeof options === "number" ? await getCartGraphQLOptions(options) : !options.apiKey && !options.commerceKey ? { ...await getCartGraphQLOptions(), ...options } : options;
|
|
1390
1423
|
const { endpoint = "https://graph-ql-dev.vio.live", apiKey, commerceKey } = resolvedOptions;
|
|
1391
1424
|
const headers = { "Content-Type": "application/json" };
|
|
1392
1425
|
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
1393
1426
|
if (commerceKey || apiKey) headers["Authorization"] = commerceKey || apiKey;
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1427
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1428
|
+
let timer;
|
|
1429
|
+
const timedOut = new Promise((_, reject) => {
|
|
1430
|
+
timer = setTimeout(() => {
|
|
1431
|
+
controller?.abort();
|
|
1432
|
+
reject(new Error(`GraphQL request timed out after ${CART_GRAPHQL_TIMEOUT_MS}ms`));
|
|
1433
|
+
}, CART_GRAPHQL_TIMEOUT_MS);
|
|
1398
1434
|
});
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1435
|
+
try {
|
|
1436
|
+
const res = await Promise.race([
|
|
1437
|
+
fetch(endpoint, {
|
|
1438
|
+
method: "POST",
|
|
1439
|
+
headers,
|
|
1440
|
+
body: JSON.stringify({ query, variables }),
|
|
1441
|
+
signal: controller?.signal
|
|
1442
|
+
}),
|
|
1443
|
+
timedOut
|
|
1444
|
+
]);
|
|
1445
|
+
const json = await Promise.race([res.json().catch(() => ({})), timedOut]);
|
|
1446
|
+
if (!res.ok || json.errors) {
|
|
1447
|
+
const errMsg = json.errors?.[0]?.message || `GraphQL request failed (HTTP ${res.status})`;
|
|
1448
|
+
throw new Error(errMsg);
|
|
1449
|
+
}
|
|
1450
|
+
return json;
|
|
1451
|
+
} finally {
|
|
1452
|
+
clearTimeout(timer);
|
|
1403
1453
|
}
|
|
1404
|
-
return json;
|
|
1405
1454
|
}
|
|
1406
1455
|
async function createCart(customerSessionId, currency, shippingCountry, options) {
|
|
1407
1456
|
const json = await executeCartGraphQL(
|
|
@@ -1581,6 +1630,14 @@ var CartManager = class extends EventTarget {
|
|
|
1581
1630
|
* landed (Alan, 2026-08-18: shippings queried before AddItem settled,
|
|
1582
1631
|
* came back empty). */
|
|
1583
1632
|
this.inFlightMutations = /* @__PURE__ */ new Map();
|
|
1633
|
+
/** Per-line quantity sync with the backend: at most ONE UpdateItem /
|
|
1634
|
+
* DeleteItem in flight per line. Targets that arrive while one is in flight
|
|
1635
|
+
* are coalesced into `pending` (only the latest survives) and sent when the
|
|
1636
|
+
* current request settles. Keyed by `${sponsorId}:${cartItemId}`.
|
|
1637
|
+
* (Alan, 2026-10-15: a burst of +/− clicks fired one UpdateItem per click
|
|
1638
|
+
* in parallel; each response re-applied the whole backend cart, so an
|
|
1639
|
+
* older one landing after a newer optimistic change rolled the line back.) */
|
|
1640
|
+
this.qtySyncs = /* @__PURE__ */ new Map();
|
|
1584
1641
|
/** Quantity intents for lines whose backend id hasn't arrived yet — replayed
|
|
1585
1642
|
* when the AddItem response reconciles (keyed by product::variant). */
|
|
1586
1643
|
this.pendingQtyIntents = /* @__PURE__ */ new Map();
|
|
@@ -1591,21 +1648,89 @@ var CartManager = class extends EventTarget {
|
|
|
1591
1648
|
this.mutationSeq.set(sponsorId, n);
|
|
1592
1649
|
return n;
|
|
1593
1650
|
}
|
|
1594
|
-
/** Resolves once
|
|
1595
|
-
* sponsor has settled (success or failure)
|
|
1651
|
+
/** Resolves once every in-flight AddItem/UpdateItem/DeleteItem for this
|
|
1652
|
+
* sponsor has settled (success or failure), including ones started while
|
|
1653
|
+
* waiting (a quantity intent replayed by an AddItem reconcile). No-op if
|
|
1654
|
+
* nothing is pending. */
|
|
1596
1655
|
async waitForCartMutations(sponsorId) {
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
await
|
|
1601
|
-
} catch {
|
|
1656
|
+
for (; ; ) {
|
|
1657
|
+
const set = this.inFlightMutations.get(sponsorId);
|
|
1658
|
+
if (!set || set.size === 0) return;
|
|
1659
|
+
await Promise.allSettled([...set]);
|
|
1602
1660
|
}
|
|
1603
1661
|
}
|
|
1604
|
-
/**
|
|
1605
|
-
|
|
1606
|
-
|
|
1662
|
+
/**
|
|
1663
|
+
* True while an AddItem/UpdateItem/DeleteItem of this sponsor OTHER than
|
|
1664
|
+
* `except` has not settled. A mutation applying its own answer passes
|
|
1665
|
+
* itself: its response is current for what IT sent — only the others make
|
|
1666
|
+
* it stale.
|
|
1667
|
+
*/
|
|
1668
|
+
hasMutationsInFlight(sponsorId, except) {
|
|
1669
|
+
const set = this.inFlightMutations.get(sponsorId);
|
|
1670
|
+
if (!set || set.size === 0) return false;
|
|
1671
|
+
for (const m of set) if (m !== except) return true;
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
/** Register a backend mutation so `waitForCartMutations` can await it. */
|
|
1675
|
+
trackMutation(sponsorId, mutation) {
|
|
1676
|
+
let set = this.inFlightMutations.get(sponsorId);
|
|
1677
|
+
if (!set) {
|
|
1678
|
+
set = /* @__PURE__ */ new Set();
|
|
1679
|
+
this.inFlightMutations.set(sponsorId, set);
|
|
1680
|
+
}
|
|
1681
|
+
const tracked = set;
|
|
1682
|
+
tracked.add(mutation);
|
|
1683
|
+
const untrack = () => {
|
|
1684
|
+
tracked.delete(mutation);
|
|
1685
|
+
if (tracked.size === 0 && this.inFlightMutations.get(sponsorId) === tracked) {
|
|
1686
|
+
this.inFlightMutations.delete(sponsorId);
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
mutation.then(untrack, untrack);
|
|
1690
|
+
}
|
|
1691
|
+
qtySyncKey(sponsorId, cartItemId) {
|
|
1692
|
+
return `${sponsorId}:${cartItemId}`;
|
|
1693
|
+
}
|
|
1694
|
+
/** The quantity a line is heading to (pending beats in flight), or
|
|
1695
|
+
* undefined when no quantity sync is active for it. */
|
|
1696
|
+
intendedQuantity(sponsorId, cartItemId) {
|
|
1697
|
+
const sync = this.qtySyncs.get(this.qtySyncKey(sponsorId, cartItemId));
|
|
1698
|
+
return sync ? sync.pending ?? sync.sent : void 0;
|
|
1699
|
+
}
|
|
1700
|
+
/** Apply a backend response only if no newer response was applied already.
|
|
1701
|
+
* Returns false when it was discarded as stale. */
|
|
1702
|
+
applyBackendCart(sponsorId, backendCart, seq, settling) {
|
|
1703
|
+
if (seq < (this.appliedSeq.get(sponsorId) ?? 0)) return false;
|
|
1607
1704
|
this.appliedSeq.set(sponsorId, seq);
|
|
1608
|
-
this.updateFromBackendCart(sponsorId, backendCart);
|
|
1705
|
+
this.updateFromBackendCart(sponsorId, backendCart, { settling });
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
/** The final answer of a line's sync lost the per-sponsor seq race (another
|
|
1709
|
+
* line answered later): the cart as a whole is stale, but for THIS line it
|
|
1710
|
+
* is the latest word. Copy its quantity (and price) so a server-side clamp
|
|
1711
|
+
* — stock — doesn't leave the optimistic value behind for good, with the
|
|
1712
|
+
* checkout / Apple Pay total built on it. */
|
|
1713
|
+
reconcileLineFromResponse(sponsorId, cartItemId, resCart, settling) {
|
|
1714
|
+
const cart = this.cartsBySponsor.get(sponsorId);
|
|
1715
|
+
const line = cart?.items.find((i) => i.cartItemId === cartItemId || i.id === cartItemId);
|
|
1716
|
+
const server = resCart.line_items?.find((li) => li.id === cartItemId);
|
|
1717
|
+
if (!cart || !line || !server) return;
|
|
1718
|
+
const price = server.price?.amount_incl_taxes ?? server.price?.amount;
|
|
1719
|
+
let changed = false;
|
|
1720
|
+
if (server.quantity !== line.quantity) {
|
|
1721
|
+
line.quantity = server.quantity;
|
|
1722
|
+
if (!this.hasMutationsInFlight(sponsorId, settling)) {
|
|
1723
|
+
line.origins = reconciledOrigins(line.origins, server.quantity);
|
|
1724
|
+
}
|
|
1725
|
+
changed = true;
|
|
1726
|
+
}
|
|
1727
|
+
if (price != null && price !== line.unitPrice) {
|
|
1728
|
+
line.unitPrice = price;
|
|
1729
|
+
changed = true;
|
|
1730
|
+
}
|
|
1731
|
+
if (!changed) return;
|
|
1732
|
+
this.persist();
|
|
1733
|
+
this.emit();
|
|
1609
1734
|
}
|
|
1610
1735
|
/** Surface a cart sync problem to hosts/UI (and the console). */
|
|
1611
1736
|
emitError(message, err) {
|
|
@@ -1672,8 +1797,12 @@ var CartManager = class extends EventTarget {
|
|
|
1672
1797
|
this.cartIdPromises.set(sponsorId, creation);
|
|
1673
1798
|
return creation;
|
|
1674
1799
|
}
|
|
1675
|
-
/**
|
|
1676
|
-
|
|
1800
|
+
/**
|
|
1801
|
+
* Overwrite local cart state with the backend cart response (source of
|
|
1802
|
+
* truth). `opts.settling` is the mutation whose answer this is: it does not
|
|
1803
|
+
* count as "in flight" for the origins (see `hasMutationsInFlight`).
|
|
1804
|
+
*/
|
|
1805
|
+
updateFromBackendCart(sponsorId, backendCart, opts = {}) {
|
|
1677
1806
|
const cart = this.cartsBySponsor.get(sponsorId);
|
|
1678
1807
|
if (!cart) return;
|
|
1679
1808
|
if (backendCart.cart_id) cart.cartId = backendCart.cart_id;
|
|
@@ -1684,6 +1813,7 @@ var CartManager = class extends EventTarget {
|
|
|
1684
1813
|
if (backendCart.available_shipping_countries) {
|
|
1685
1814
|
cart.availableShippingCountries = backendCart.available_shipping_countries;
|
|
1686
1815
|
}
|
|
1816
|
+
const removedBeforeSync = [];
|
|
1687
1817
|
if (Array.isArray(backendCart.line_items)) {
|
|
1688
1818
|
const existingItems = cart.items || [];
|
|
1689
1819
|
if (backendCart.line_items.length === 0 && existingItems.length > 0) {
|
|
@@ -1691,7 +1821,16 @@ var CartManager = class extends EventTarget {
|
|
|
1691
1821
|
this.emit();
|
|
1692
1822
|
return;
|
|
1693
1823
|
}
|
|
1694
|
-
cart.items = backendCart.line_items.
|
|
1824
|
+
cart.items = backendCart.line_items.flatMap((li) => {
|
|
1825
|
+
const intended = this.intendedQuantity(sponsorId, li.id);
|
|
1826
|
+
if (intended !== void 0 && intended <= 0) return [];
|
|
1827
|
+
const earlyKey = this.intentKey(li.product_id, li.variant_id);
|
|
1828
|
+
const earlyIntent = this.pendingQtyIntents.get(earlyKey);
|
|
1829
|
+
if (earlyIntent !== void 0 && earlyIntent <= 0) {
|
|
1830
|
+
this.pendingQtyIntents.delete(earlyKey);
|
|
1831
|
+
removedBeforeSync.push(li.id);
|
|
1832
|
+
return [];
|
|
1833
|
+
}
|
|
1695
1834
|
const urlFromBackend = (typeof li.image === "string" ? li.image : li.image?.url) || li.imageUrl || li.image_url || "";
|
|
1696
1835
|
const matchedLocal = existingItems.find((existing) => {
|
|
1697
1836
|
const p1 = normId(existing.productId);
|
|
@@ -1707,7 +1846,7 @@ var CartManager = class extends EventTarget {
|
|
|
1707
1846
|
return false;
|
|
1708
1847
|
});
|
|
1709
1848
|
const imageUrl = urlFromBackend || matchedLocal?.imageUrl || "";
|
|
1710
|
-
|
|
1849
|
+
const line = {
|
|
1711
1850
|
id: li.id,
|
|
1712
1851
|
cartItemId: li.id,
|
|
1713
1852
|
productId: li.product_id,
|
|
@@ -1718,14 +1857,25 @@ var CartManager = class extends EventTarget {
|
|
|
1718
1857
|
unitPrice: li.price?.amount_incl_taxes ?? li.price?.amount ?? matchedLocal?.unitPrice ?? 0,
|
|
1719
1858
|
currency: li.price?.currency_code || backendCart.currency || matchedLocal?.currency || "NOK",
|
|
1720
1859
|
imageUrl,
|
|
1721
|
-
quantity: li.quantity,
|
|
1860
|
+
quantity: intended ?? li.quantity,
|
|
1861
|
+
// The server knows nothing about origins: keep the local ones. They
|
|
1862
|
+
// are squared with its quantity (a stock clamp) only when no OTHER
|
|
1863
|
+
// mutation is in flight for this sponsor — an AddItem answering
|
|
1864
|
+
// while another is pending reports a quantity that is already old
|
|
1865
|
+
// (see `lineOrigins`).
|
|
1866
|
+
origins: matchedLocal ? this.hasMutationsInFlight(sponsorId, opts.settling) ? matchedLocal.origins : reconciledOrigins(matchedLocal.origins, intended ?? li.quantity) : void 0,
|
|
1722
1867
|
shipping: li.shipping,
|
|
1723
1868
|
availableShippings: li.available_shippings
|
|
1724
1869
|
};
|
|
1870
|
+
return [line];
|
|
1725
1871
|
});
|
|
1726
1872
|
}
|
|
1727
1873
|
this.persist();
|
|
1728
1874
|
this.emit();
|
|
1875
|
+
const syncedCartId = cart.cartId;
|
|
1876
|
+
if (syncedCartId) {
|
|
1877
|
+
for (const id of removedBeforeSync) this.syncQuantity(sponsorId, syncedCartId, id, 0);
|
|
1878
|
+
}
|
|
1729
1879
|
if (this.pendingQtyIntents.size > 0) {
|
|
1730
1880
|
const currentCart = this.cartsBySponsor.get(sponsorId);
|
|
1731
1881
|
for (const [key, qty] of [...this.pendingQtyIntents]) {
|
|
@@ -1761,7 +1911,8 @@ var CartManager = class extends EventTarget {
|
|
|
1761
1911
|
currency: price.currency_code,
|
|
1762
1912
|
imageUrl,
|
|
1763
1913
|
quantity: opts.quantity,
|
|
1764
|
-
variantId: opts.variantId
|
|
1914
|
+
variantId: opts.variantId,
|
|
1915
|
+
origin: opts.origin
|
|
1765
1916
|
});
|
|
1766
1917
|
}
|
|
1767
1918
|
/** Add a manually-specified line item (no Product DTO). */
|
|
@@ -1794,6 +1945,7 @@ var CartManager = class extends EventTarget {
|
|
|
1794
1945
|
});
|
|
1795
1946
|
if (existing) {
|
|
1796
1947
|
existing.quantity += qty;
|
|
1948
|
+
addOriginQuantity(existing, opts.origin, qty);
|
|
1797
1949
|
} else {
|
|
1798
1950
|
const item = {
|
|
1799
1951
|
id: lineId(),
|
|
@@ -1805,7 +1957,8 @@ var CartManager = class extends EventTarget {
|
|
|
1805
1957
|
unitPrice: opts.unitPrice,
|
|
1806
1958
|
currency: opts.currency,
|
|
1807
1959
|
imageUrl: opts.imageUrl,
|
|
1808
|
-
quantity: qty
|
|
1960
|
+
quantity: qty,
|
|
1961
|
+
origins: [{ ...normalizeOrigin(opts.origin), quantity: qty }]
|
|
1809
1962
|
};
|
|
1810
1963
|
cart.items.push(item);
|
|
1811
1964
|
}
|
|
@@ -1814,6 +1967,7 @@ var CartManager = class extends EventTarget {
|
|
|
1814
1967
|
const addedItem = existing ?? cart.items[cart.items.length - 1];
|
|
1815
1968
|
const addedLineId = addedItem.id;
|
|
1816
1969
|
const seq = this.nextSeq(opts.sponsorId);
|
|
1970
|
+
let settling;
|
|
1817
1971
|
const mutation = (async () => {
|
|
1818
1972
|
try {
|
|
1819
1973
|
const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
|
|
@@ -1828,7 +1982,7 @@ var CartManager = class extends EventTarget {
|
|
|
1828
1982
|
];
|
|
1829
1983
|
const resCart = await addItem(cartId, lineItemsInput, gqlOpts);
|
|
1830
1984
|
if (resCart && Array.isArray(resCart.line_items)) {
|
|
1831
|
-
this.applyBackendCart(opts.sponsorId, resCart, seq);
|
|
1985
|
+
this.applyBackendCart(opts.sponsorId, resCart, seq, settling);
|
|
1832
1986
|
}
|
|
1833
1987
|
}
|
|
1834
1988
|
} catch (err) {
|
|
@@ -1836,6 +1990,7 @@ var CartManager = class extends EventTarget {
|
|
|
1836
1990
|
const line = c?.items.find((i) => i.id === addedLineId);
|
|
1837
1991
|
if (c && line) {
|
|
1838
1992
|
line.quantity -= qty;
|
|
1993
|
+
removeFromOrigin(line, opts.origin, qty);
|
|
1839
1994
|
if (line.quantity <= 0) c.items = c.items.filter((i) => i !== line);
|
|
1840
1995
|
if (c.items.length === 0) this.cartsBySponsor.delete(opts.sponsorId);
|
|
1841
1996
|
this.persist();
|
|
@@ -1844,24 +1999,40 @@ var CartManager = class extends EventTarget {
|
|
|
1844
1999
|
this.emitError("AddItem rejected by the backend \u2014 item removed from the cart", err);
|
|
1845
2000
|
}
|
|
1846
2001
|
})();
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
if (this.inFlightMutations.get(opts.sponsorId) === mutation) {
|
|
1850
|
-
this.inFlightMutations.delete(opts.sponsorId);
|
|
1851
|
-
}
|
|
1852
|
-
});
|
|
2002
|
+
settling = mutation;
|
|
2003
|
+
this.trackMutation(opts.sponsorId, mutation);
|
|
1853
2004
|
return addedItem;
|
|
1854
2005
|
}
|
|
1855
2006
|
intentKey(productId, variantId) {
|
|
1856
2007
|
return `${normId(productId)}::${normId(variantId)}`;
|
|
1857
2008
|
}
|
|
1858
|
-
|
|
2009
|
+
findLine(sponsorId, lineItemId) {
|
|
2010
|
+
return this.cartsBySponsor.get(sponsorId)?.items.find((i) => i.id === lineItemId || i.cartItemId === lineItemId);
|
|
2011
|
+
}
|
|
2012
|
+
/** Change a line's quantity by `delta`, computed on the manager's CURRENT
|
|
2013
|
+
* quantity — not on whatever a UI rendered a moment ago. A burst of +/−
|
|
2014
|
+
* clicks before a re-render therefore asks for 2, 3, 4… and not for the
|
|
2015
|
+
* same target several times. Reaching 0 removes the line. */
|
|
2016
|
+
adjustQuantity(lineItemId, sponsorId, delta, opts = {}) {
|
|
2017
|
+
const item = this.findLine(sponsorId, lineItemId);
|
|
2018
|
+
if (!item) return;
|
|
2019
|
+
this.updateQuantity(lineItemId, sponsorId, item.quantity + delta, opts);
|
|
2020
|
+
}
|
|
2021
|
+
/** Set a line's quantity (0 or less removes it). Optimistic: the local
|
|
2022
|
+
* state and `change` update at once; the backend is synced through
|
|
2023
|
+
* `syncQuantity`, one request in flight per line, latest target wins. */
|
|
2024
|
+
updateQuantity(lineItemId, sponsorId, quantity, opts = {}) {
|
|
1859
2025
|
const cart = this.cartsBySponsor.get(sponsorId);
|
|
1860
2026
|
if (!cart) return;
|
|
1861
2027
|
const item = cart.items.find((i) => i.id === lineItemId || i.cartItemId === lineItemId);
|
|
1862
2028
|
if (!item) return;
|
|
2029
|
+
const target = quantity > 0 ? quantity : 0;
|
|
2030
|
+
if (target === item.quantity) return;
|
|
1863
2031
|
const targetItemId = item.cartItemId;
|
|
1864
|
-
|
|
2032
|
+
const removedOrigin = latestOrigin(item);
|
|
2033
|
+
if (target > item.quantity) addOriginQuantity(item, opts.origin, target - item.quantity);
|
|
2034
|
+
else removeOriginQuantity(item, item.quantity - target);
|
|
2035
|
+
if (target <= 0) {
|
|
1865
2036
|
cart.items = cart.items.filter((i) => i !== item);
|
|
1866
2037
|
if (typeof document !== "undefined") {
|
|
1867
2038
|
document.dispatchEvent(
|
|
@@ -1875,42 +2046,86 @@ var CartManager = class extends EventTarget {
|
|
|
1875
2046
|
price: item.unitPrice,
|
|
1876
2047
|
quantity: item.quantity,
|
|
1877
2048
|
currency: item.currency,
|
|
1878
|
-
sponsorId
|
|
2049
|
+
sponsorId,
|
|
2050
|
+
// Attribution of the removed units: their most recent origin.
|
|
2051
|
+
slot: removedOrigin?.slot,
|
|
2052
|
+
campaignId: removedOrigin?.campaignId,
|
|
2053
|
+
contentId: removedOrigin?.contentId
|
|
1879
2054
|
}
|
|
1880
2055
|
})
|
|
1881
2056
|
);
|
|
1882
2057
|
}
|
|
1883
2058
|
} else {
|
|
1884
|
-
item.quantity =
|
|
2059
|
+
item.quantity = target;
|
|
1885
2060
|
}
|
|
1886
2061
|
if (cart.items.length === 0) this.cartsBySponsor.delete(sponsorId);
|
|
1887
2062
|
this.persist();
|
|
1888
2063
|
this.emit();
|
|
1889
2064
|
if (!cart.cartId) return;
|
|
1890
2065
|
if (!targetItemId) {
|
|
1891
|
-
this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId),
|
|
2066
|
+
this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
|
|
1892
2067
|
return;
|
|
1893
2068
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2069
|
+
this.syncQuantity(sponsorId, cart.cartId, targetItemId, target);
|
|
2070
|
+
}
|
|
2071
|
+
/** Send a line's target quantity to the backend — ONE request in flight per
|
|
2072
|
+
* line. A target that arrives while one is in flight replaces any earlier
|
|
2073
|
+
* waiting one and goes out when the current request settles, so the server
|
|
2074
|
+
* ends on the last intent and never sees two updates racing each other.
|
|
2075
|
+
* While the sync is active the line keeps its optimistic quantity through
|
|
2076
|
+
* any backend response (see `updateFromBackendCart`); a rejection drops
|
|
2077
|
+
* what was queued and reconciles the cart from the server. */
|
|
2078
|
+
syncQuantity(sponsorId, backendCartId, cartItemId, target) {
|
|
2079
|
+
const key = this.qtySyncKey(sponsorId, cartItemId);
|
|
2080
|
+
const active = this.qtySyncs.get(key);
|
|
2081
|
+
if (active) {
|
|
2082
|
+
active.pending = target;
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const sync = { sent: target };
|
|
2086
|
+
this.qtySyncs.set(key, sync);
|
|
2087
|
+
let settling;
|
|
2088
|
+
const run = (async () => {
|
|
2089
|
+
let next = target;
|
|
2090
|
+
let removalRetried = false;
|
|
2091
|
+
while (next !== void 0) {
|
|
2092
|
+
sync.sent = next;
|
|
2093
|
+
sync.pending = void 0;
|
|
2094
|
+
const seq = this.nextSeq(sponsorId);
|
|
2095
|
+
try {
|
|
2096
|
+
const gqlOpts = await getCartGraphQLOptions(sponsorId);
|
|
2097
|
+
const resCart = next <= 0 ? await deleteItem(backendCartId, cartItemId, gqlOpts) : await updateItem(backendCartId, cartItemId, null, next, gqlOpts);
|
|
2098
|
+
next = sync.pending;
|
|
2099
|
+
if (next === sync.sent) next = void 0;
|
|
2100
|
+
if (next === void 0) this.qtySyncs.delete(key);
|
|
2101
|
+
if (resCart && Array.isArray(resCart.line_items)) {
|
|
2102
|
+
const applied = this.applyBackendCart(sponsorId, resCart, seq, settling);
|
|
2103
|
+
if (!applied && next === void 0) {
|
|
2104
|
+
this.reconcileLineFromResponse(sponsorId, cartItemId, resCart, settling);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
const retryRemoval = !removalRetried && sync.pending !== void 0 && sync.pending <= 0;
|
|
2109
|
+
next = void 0;
|
|
2110
|
+
if (retryRemoval) {
|
|
2111
|
+
removalRetried = true;
|
|
2112
|
+
sync.sent = 0;
|
|
2113
|
+
sync.pending = void 0;
|
|
2114
|
+
next = 0;
|
|
2115
|
+
} else {
|
|
2116
|
+
this.qtySyncs.delete(key);
|
|
2117
|
+
}
|
|
2118
|
+
this.emitError("Update/DeleteItem failed \u2014 cart re-synced from backend", err);
|
|
2119
|
+
try {
|
|
2120
|
+
await this.fetchCartFromBackend(sponsorId);
|
|
2121
|
+
} catch (fetchErr) {
|
|
2122
|
+
this.emitError("cart re-sync after a rejected Update/DeleteItem failed", fetchErr);
|
|
2123
|
+
}
|
|
1902
2124
|
}
|
|
1903
|
-
} catch (err) {
|
|
1904
|
-
this.emitError("Update/DeleteItem failed \u2014 cart re-synced from backend", err);
|
|
1905
|
-
void this.fetchCartFromBackend(sponsorId);
|
|
1906
2125
|
}
|
|
1907
2126
|
})();
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
if (this.inFlightMutations.get(sponsorId) === mutation) {
|
|
1911
|
-
this.inFlightMutations.delete(sponsorId);
|
|
1912
|
-
}
|
|
1913
|
-
});
|
|
2127
|
+
settling = run;
|
|
2128
|
+
this.trackMutation(sponsorId, run);
|
|
1914
2129
|
}
|
|
1915
2130
|
removeItem(lineItemId, sponsorId) {
|
|
1916
2131
|
this.updateQuantity(lineItemId, sponsorId, 0);
|
|
@@ -2021,6 +2236,87 @@ var CartManager = class extends EventTarget {
|
|
|
2021
2236
|
this.dispatchEvent(new CustomEvent("change", { detail }));
|
|
2022
2237
|
}
|
|
2023
2238
|
};
|
|
2239
|
+
function normalizeOrigin(origin) {
|
|
2240
|
+
const out = {};
|
|
2241
|
+
if (!origin || typeof origin !== "object") return out;
|
|
2242
|
+
const campaignId = Number(origin.campaignId);
|
|
2243
|
+
if (origin.campaignId !== void 0 && origin.campaignId !== null && Number.isFinite(campaignId)) {
|
|
2244
|
+
out.campaignId = campaignId;
|
|
2245
|
+
}
|
|
2246
|
+
if (typeof origin.slot === "string" && origin.slot.trim()) out.slot = origin.slot.trim();
|
|
2247
|
+
if (origin.contentId !== void 0 && origin.contentId !== null && String(origin.contentId).trim()) {
|
|
2248
|
+
out.contentId = String(origin.contentId).trim();
|
|
2249
|
+
}
|
|
2250
|
+
return out;
|
|
2251
|
+
}
|
|
2252
|
+
function sameOrigin(a, b) {
|
|
2253
|
+
return a.campaignId === b.campaignId && a.slot === b.slot && a.contentId === b.contentId;
|
|
2254
|
+
}
|
|
2255
|
+
function latestOrigin(line) {
|
|
2256
|
+
const origins = Array.isArray(line.origins) ? line.origins : [];
|
|
2257
|
+
return origins[origins.length - 1];
|
|
2258
|
+
}
|
|
2259
|
+
function addOriginQuantity(line, origin, qty) {
|
|
2260
|
+
if (!(qty > 0)) return;
|
|
2261
|
+
const origins = Array.isArray(line.origins) ? line.origins : [];
|
|
2262
|
+
const target = origin ? normalizeOrigin(origin) : latestOrigin({ origins });
|
|
2263
|
+
const index = target ? origins.findIndex((o) => sameOrigin(o, target)) : -1;
|
|
2264
|
+
if (index >= 0) {
|
|
2265
|
+
const [found] = origins.splice(index, 1);
|
|
2266
|
+
found.quantity += qty;
|
|
2267
|
+
origins.push(found);
|
|
2268
|
+
} else {
|
|
2269
|
+
origins.push({ ...target ?? {}, quantity: qty });
|
|
2270
|
+
}
|
|
2271
|
+
line.origins = origins;
|
|
2272
|
+
}
|
|
2273
|
+
function removeFromOrigin(line, origin, qty) {
|
|
2274
|
+
if (!(qty > 0) || !Array.isArray(line.origins)) return;
|
|
2275
|
+
let left = qty;
|
|
2276
|
+
if (origin) {
|
|
2277
|
+
const target = normalizeOrigin(origin);
|
|
2278
|
+
const index = line.origins.findIndex((o) => sameOrigin(o, target));
|
|
2279
|
+
if (index >= 0) {
|
|
2280
|
+
const found = line.origins[index];
|
|
2281
|
+
const take = Math.min(found.quantity, left);
|
|
2282
|
+
found.quantity -= take;
|
|
2283
|
+
left -= take;
|
|
2284
|
+
if (found.quantity <= 0) line.origins.splice(index, 1);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
if (left > 0) removeOriginQuantity(line, left);
|
|
2288
|
+
}
|
|
2289
|
+
function removeOriginQuantity(line, qty) {
|
|
2290
|
+
if (!(qty > 0) || !Array.isArray(line.origins)) return;
|
|
2291
|
+
let left = qty;
|
|
2292
|
+
const origins = line.origins;
|
|
2293
|
+
while (left > 0 && origins.length > 0) {
|
|
2294
|
+
const last = origins[origins.length - 1];
|
|
2295
|
+
const take = Math.min(last.quantity, left);
|
|
2296
|
+
last.quantity -= take;
|
|
2297
|
+
left -= take;
|
|
2298
|
+
if (last.quantity <= 0) origins.pop();
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function reconciledOrigins(origins, quantity) {
|
|
2302
|
+
if (!Array.isArray(origins)) return void 0;
|
|
2303
|
+
const copy = origins.map((o) => ({ ...o }));
|
|
2304
|
+
const total = copy.reduce((sum, o) => sum + (o.quantity > 0 ? o.quantity : 0), 0);
|
|
2305
|
+
const line = { origins: copy };
|
|
2306
|
+
if (quantity > total) addOriginQuantity(line, void 0, quantity - total);
|
|
2307
|
+
else if (quantity < total) removeOriginQuantity(line, total - quantity);
|
|
2308
|
+
return line.origins;
|
|
2309
|
+
}
|
|
2310
|
+
function lineOrigins(line) {
|
|
2311
|
+
const known = (Array.isArray(line.origins) ? line.origins : []).filter((o) => o && o.quantity > 0);
|
|
2312
|
+
const total = known.reduce((sum, o) => sum + o.quantity, 0);
|
|
2313
|
+
const quantity = line.quantity > 0 ? line.quantity : 0;
|
|
2314
|
+
if (total === quantity) return known.map((o) => ({ ...o }));
|
|
2315
|
+
if (total < quantity) return [...known.map((o) => ({ ...o })), { quantity: quantity - total }];
|
|
2316
|
+
const copy = { origins: known.map((o) => ({ ...o })) };
|
|
2317
|
+
removeOriginQuantity(copy, total - quantity);
|
|
2318
|
+
return copy.origins;
|
|
2319
|
+
}
|
|
2024
2320
|
|
|
2025
2321
|
// src/core/checkout/payments/apple-pay.ts
|
|
2026
2322
|
var STRIPE_JS_URL = "https://js.stripe.com/v3";
|
|
@@ -2218,7 +2514,7 @@ async function prepareApplePay(config) {
|
|
|
2218
2514
|
}
|
|
2219
2515
|
|
|
2220
2516
|
// src/core/version.ts
|
|
2221
|
-
var SDK_VERSION = "0.
|
|
2517
|
+
var SDK_VERSION = "0.23.1";
|
|
2222
2518
|
|
|
2223
2519
|
// src/core/checkout/payments/stripe-embedded.ts
|
|
2224
2520
|
var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
|
|
@@ -2726,8 +3022,8 @@ function renderKustomSnippet(container, htmlSnippet) {
|
|
|
2726
3022
|
const scripts = Array.from(container.querySelectorAll("script"));
|
|
2727
3023
|
for (const oldScript of scripts) {
|
|
2728
3024
|
const newScript = document.createElement("script");
|
|
2729
|
-
for (const
|
|
2730
|
-
newScript.setAttribute(
|
|
3025
|
+
for (const attr2 of Array.from(oldScript.attributes)) {
|
|
3026
|
+
newScript.setAttribute(attr2.name, attr2.value);
|
|
2731
3027
|
}
|
|
2732
3028
|
newScript.text = oldScript.text;
|
|
2733
3029
|
oldScript.parentNode?.replaceChild(newScript, oldScript);
|
|
@@ -3785,6 +4081,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3785
4081
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3786
4082
|
/** Create the backend checkout for the sponsor's cart (auto-accepts terms). */
|
|
3787
4083
|
async createCheckout(sponsorId) {
|
|
4084
|
+
await this.cartManager.waitForCartMutations(sponsorId);
|
|
3788
4085
|
const cart = this.cartManager.getCart(sponsorId);
|
|
3789
4086
|
let cartId = cart?.cartId;
|
|
3790
4087
|
if (!cartId) {
|
|
@@ -3977,6 +4274,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3977
4274
|
async prepareRedirectPayment(sponsorId, formData, extraUpdateVars = {}) {
|
|
3978
4275
|
const spId = sponsorId ?? this.state?.sponsorId;
|
|
3979
4276
|
if (!spId) throw new Error("[CheckoutManager] no sponsor for payment");
|
|
4277
|
+
await this.cartManager.waitForCartMutations(spId);
|
|
3980
4278
|
const cart = this.cartManager.getCart(spId);
|
|
3981
4279
|
let cartId = cart?.cartId;
|
|
3982
4280
|
if (!cartId) {
|
|
@@ -4091,6 +4389,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4091
4389
|
* plain payment once the shopper chose one in our form.
|
|
4092
4390
|
*/
|
|
4093
4391
|
async startVippsPayment(sponsorId, formData, options = {}) {
|
|
4392
|
+
const waitFor = sponsorId ?? this.state?.sponsorId;
|
|
4393
|
+
if (waitFor) await this.cartManager.waitForCartMutations(waitFor);
|
|
4094
4394
|
const { spId, checkoutId, emailVal, opts } = await this.prepareRedirectPayment(
|
|
4095
4395
|
sponsorId,
|
|
4096
4396
|
formData,
|
|
@@ -5588,6 +5888,285 @@ function toSmallestUnit(amount, currency) {
|
|
|
5588
5888
|
return Math.round(amount * 100);
|
|
5589
5889
|
}
|
|
5590
5890
|
|
|
5891
|
+
// src/core/content.ts
|
|
5892
|
+
var CONTENT_ID_QUERY = "vio_content";
|
|
5893
|
+
var CONTENT_TITLE_QUERY = "vio_content_title";
|
|
5894
|
+
var CONTENT_ID_MAX_LENGTH = 128;
|
|
5895
|
+
var CONTENT_TITLE_MAX_LENGTH = 255;
|
|
5896
|
+
function resolveContent(init, doc = typeof document !== "undefined" ? document : null, loc = typeof location !== "undefined" ? location : null) {
|
|
5897
|
+
try {
|
|
5898
|
+
const query = parseQuery(loc?.search);
|
|
5899
|
+
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));
|
|
5900
|
+
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);
|
|
5901
|
+
const out = {};
|
|
5902
|
+
if (id !== void 0) out.id = id;
|
|
5903
|
+
if (title !== void 0) out.title = title.slice(0, CONTENT_TITLE_MAX_LENGTH);
|
|
5904
|
+
return out;
|
|
5905
|
+
} catch {
|
|
5906
|
+
return {};
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
function fits(value) {
|
|
5910
|
+
return value !== void 0 && value.length <= CONTENT_ID_MAX_LENGTH ? value : void 0;
|
|
5911
|
+
}
|
|
5912
|
+
function text(value) {
|
|
5913
|
+
if (value === void 0 || value === null) return void 0;
|
|
5914
|
+
const s = String(value).trim();
|
|
5915
|
+
return s ? s : void 0;
|
|
5916
|
+
}
|
|
5917
|
+
function parseQuery(search) {
|
|
5918
|
+
try {
|
|
5919
|
+
return new URLSearchParams(typeof search === "string" ? search : "");
|
|
5920
|
+
} catch {
|
|
5921
|
+
return new URLSearchParams();
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
function meta(doc, key, value) {
|
|
5925
|
+
return attr(doc, `meta[${key}="${value}"]`, "content");
|
|
5926
|
+
}
|
|
5927
|
+
function attr(doc, selector, name) {
|
|
5928
|
+
try {
|
|
5929
|
+
if (!doc || typeof doc.querySelector !== "function") return void 0;
|
|
5930
|
+
const el = doc.querySelector(selector);
|
|
5931
|
+
const v = el?.getAttribute?.(name);
|
|
5932
|
+
return typeof v === "string" ? v : void 0;
|
|
5933
|
+
} catch {
|
|
5934
|
+
return void 0;
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
function stripQueryAndHash(url) {
|
|
5938
|
+
if (!url) return void 0;
|
|
5939
|
+
const cut = url.search(/[?#]/);
|
|
5940
|
+
const clean = cut >= 0 ? url.slice(0, cut) : url;
|
|
5941
|
+
return clean.trim() || void 0;
|
|
5942
|
+
}
|
|
5943
|
+
|
|
5944
|
+
// src/core/slots.ts
|
|
5945
|
+
var SLOT_ID_MAX_LENGTH = 100;
|
|
5946
|
+
var SLOT_DECLARATION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5947
|
+
var SLOT_DECLARATION_FAILURE_TTL_MS = 60 * 60 * 1e3;
|
|
5948
|
+
var MAX_LOCATIONS_PER_DECLARATION = 100;
|
|
5949
|
+
var MAX_DECLARATIONS_PER_LOAD = 3;
|
|
5950
|
+
function resolveSlotCampaign(boot, slot, now = Date.now(), platform) {
|
|
5951
|
+
if (!boot) return null;
|
|
5952
|
+
return findSlotAssignment(boot, slot, now, platform) ?? defaultCampaignEntry(boot);
|
|
5953
|
+
}
|
|
5954
|
+
function findSlotAssignment(boot, slot, now = Date.now(), platform) {
|
|
5955
|
+
const name = typeof slot === "string" ? slot.trim() : "";
|
|
5956
|
+
if (!boot || !name) return null;
|
|
5957
|
+
try {
|
|
5958
|
+
const matches = activeCampaigns(boot).filter(
|
|
5959
|
+
(c) => slotsOf(c).some((s) => assignmentMatches(s, name, now, platform))
|
|
5960
|
+
);
|
|
5961
|
+
if (matches.length === 0) return null;
|
|
5962
|
+
const defaultId = toNumber(boot.campaign?.id);
|
|
5963
|
+
return matches.find((c) => toNumber(c.id) === defaultId) ?? matches[0] ?? null;
|
|
5964
|
+
} catch {
|
|
5965
|
+
return null;
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
function defaultCampaignEntry(boot) {
|
|
5969
|
+
if (!boot?.campaign || typeof boot.campaign !== "object") return null;
|
|
5970
|
+
const defaultId = toNumber(boot.campaign.id);
|
|
5971
|
+
if (defaultId === void 0) return null;
|
|
5972
|
+
try {
|
|
5973
|
+
const listed = wellFormedCampaigns(boot).find((c) => toNumber(c.id) === defaultId);
|
|
5974
|
+
if (listed) return listed;
|
|
5975
|
+
} catch {
|
|
5976
|
+
}
|
|
5977
|
+
return {
|
|
5978
|
+
...boot.campaign,
|
|
5979
|
+
isDefault: true,
|
|
5980
|
+
primarySponsor: boot.primarySponsor ?? null,
|
|
5981
|
+
secondarySponsors: Array.isArray(boot.secondarySponsors) ? boot.secondarySponsors : [],
|
|
5982
|
+
slots: []
|
|
5983
|
+
};
|
|
5984
|
+
}
|
|
5985
|
+
function wellFormedCampaigns(boot) {
|
|
5986
|
+
const list = Array.isArray(boot.campaigns) ? boot.campaigns : [];
|
|
5987
|
+
return list.filter((c) => !!c && typeof c === "object");
|
|
5988
|
+
}
|
|
5989
|
+
function activeCampaigns(boot) {
|
|
5990
|
+
return wellFormedCampaigns(boot).filter((c) => c.status === "active");
|
|
5991
|
+
}
|
|
5992
|
+
function slotsOf(campaign) {
|
|
5993
|
+
const slots = Array.isArray(campaign.slots) ? campaign.slots : [];
|
|
5994
|
+
return slots.filter((s) => !!s && typeof s === "object");
|
|
5995
|
+
}
|
|
5996
|
+
function assignmentMatches(s, name, now, platform) {
|
|
5997
|
+
if (typeof s.name !== "string" || s.name.trim() !== name) return false;
|
|
5998
|
+
if (platform && s.platform != null && s.platform !== platform) return false;
|
|
5999
|
+
const start = parseTime(s.startDate);
|
|
6000
|
+
if (start !== void 0 && start > now) return false;
|
|
6001
|
+
const end = parseTime(s.endDate);
|
|
6002
|
+
if (end !== void 0 && now >= end) return false;
|
|
6003
|
+
return true;
|
|
6004
|
+
}
|
|
6005
|
+
function parseTime(value) {
|
|
6006
|
+
if (value == null || value === "") return void 0;
|
|
6007
|
+
const t = Date.parse(String(value));
|
|
6008
|
+
return Number.isFinite(t) ? t : void 0;
|
|
6009
|
+
}
|
|
6010
|
+
function toNumber(value) {
|
|
6011
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
6012
|
+
const n = Number(value);
|
|
6013
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6014
|
+
}
|
|
6015
|
+
function normalizeSlotId(value) {
|
|
6016
|
+
if (typeof value !== "string") return "";
|
|
6017
|
+
return value.trim().slice(0, SLOT_ID_MAX_LENGTH).trim();
|
|
6018
|
+
}
|
|
6019
|
+
function normalizeSlotLocations(slots) {
|
|
6020
|
+
const out = [];
|
|
6021
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6022
|
+
if (!Array.isArray(slots)) return out;
|
|
6023
|
+
for (const raw of slots) {
|
|
6024
|
+
const id = normalizeSlotId(typeof raw === "string" ? raw : raw?.id);
|
|
6025
|
+
if (!id || seen.has(id)) continue;
|
|
6026
|
+
seen.add(id);
|
|
6027
|
+
const displayName = raw && typeof raw === "object" && typeof raw.displayName === "string" ? raw.displayName.trim() : "";
|
|
6028
|
+
out.push(displayName ? { id, displayName } : { id });
|
|
6029
|
+
}
|
|
6030
|
+
return out;
|
|
6031
|
+
}
|
|
6032
|
+
function autoSlotId(kind, ordinal, scope) {
|
|
6033
|
+
const tail = `${kind}-${ordinal + 1}`;
|
|
6034
|
+
let prefix = normalizeScope(scope);
|
|
6035
|
+
const room = SLOT_ID_MAX_LENGTH - tail.length - 1;
|
|
6036
|
+
if (prefix.length > room) prefix = prefix.slice(0, Math.max(0, room)).replace(/-+$/, "");
|
|
6037
|
+
return `${prefix ? `${prefix}-` : ""}${tail}`;
|
|
6038
|
+
}
|
|
6039
|
+
function autoSlotDisplayName(kind, ordinal, scopeName) {
|
|
6040
|
+
const label = `${kind.charAt(0).toUpperCase()}${kind.slice(1)} ${ordinal + 1}`;
|
|
6041
|
+
const name = typeof scopeName === "string" ? scopeName.trim() : "";
|
|
6042
|
+
return name ? `${label} \xB7 ${name}` : label;
|
|
6043
|
+
}
|
|
6044
|
+
function normalizeScope(scope) {
|
|
6045
|
+
if (typeof scope !== "string") return "";
|
|
6046
|
+
return scope.trim().replace(/\s+/g, "-");
|
|
6047
|
+
}
|
|
6048
|
+
var SLOT_DECLARATION_MEMORY_CAP = 500;
|
|
6049
|
+
function slotDeclarationKey(location2) {
|
|
6050
|
+
return location2.displayName ? `${location2.id}${location2.displayName}` : location2.id;
|
|
6051
|
+
}
|
|
6052
|
+
function pendingSlotKeys(memory, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
|
|
6053
|
+
return keys.filter((key) => {
|
|
6054
|
+
const at = memory?.[key];
|
|
6055
|
+
if (typeof at !== "number" || !Number.isFinite(at)) return true;
|
|
6056
|
+
return now - at >= ttl || now < at;
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
function rememberSlotKeys(memory, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
|
|
6060
|
+
const next = { ...memory ?? {} };
|
|
6061
|
+
for (const key of keys) next[key] = at;
|
|
6062
|
+
const entries = Object.entries(next);
|
|
6063
|
+
if (entries.length <= cap) return next;
|
|
6064
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
6065
|
+
return Object.fromEntries(entries.slice(0, cap));
|
|
6066
|
+
}
|
|
6067
|
+
function slotDeclarationStorageKey(platform, apiKey) {
|
|
6068
|
+
return `vio.slots.${platform}.${fnv1a(apiKey)}`;
|
|
6069
|
+
}
|
|
6070
|
+
function fnv1a(input) {
|
|
6071
|
+
let hash = 2166136261;
|
|
6072
|
+
for (let i = 0; i < input.length; i++) {
|
|
6073
|
+
hash ^= input.charCodeAt(i);
|
|
6074
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
6075
|
+
}
|
|
6076
|
+
return hash.toString(16).padStart(8, "0");
|
|
6077
|
+
}
|
|
6078
|
+
function readSlotDeclaration(storageKey) {
|
|
6079
|
+
try {
|
|
6080
|
+
if (typeof localStorage === "undefined") return {};
|
|
6081
|
+
const raw = localStorage.getItem(storageKey);
|
|
6082
|
+
if (!raw) return {};
|
|
6083
|
+
const parsed = JSON.parse(raw);
|
|
6084
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
6085
|
+
if (Array.isArray(parsed.keys)) return {};
|
|
6086
|
+
const out = {};
|
|
6087
|
+
for (const [key, at] of Object.entries(parsed)) {
|
|
6088
|
+
if (typeof at === "number" && Number.isFinite(at)) out[key] = at;
|
|
6089
|
+
}
|
|
6090
|
+
return out;
|
|
6091
|
+
} catch {
|
|
6092
|
+
return {};
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
function writeSlotDeclaration(storageKey, memory) {
|
|
6096
|
+
try {
|
|
6097
|
+
if (typeof localStorage === "undefined") return;
|
|
6098
|
+
localStorage.setItem(storageKey, JSON.stringify(memory));
|
|
6099
|
+
} catch {
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
|
|
6103
|
+
// src/core/cart/slices.ts
|
|
6104
|
+
function cartSlicesFrom(lines, resolveCampaign, fallbackCurrency) {
|
|
6105
|
+
if (!Array.isArray(lines) || lines.length === 0) return [];
|
|
6106
|
+
const slices = /* @__PURE__ */ new Map();
|
|
6107
|
+
for (const line of lines) {
|
|
6108
|
+
if (!line || typeof line !== "object") continue;
|
|
6109
|
+
const sponsorId = Number(line.sponsorId);
|
|
6110
|
+
const unitPrice = Number(line.unitPrice) || 0;
|
|
6111
|
+
for (const origin of lineOrigins(line)) {
|
|
6112
|
+
let campaignId = origin.campaignId;
|
|
6113
|
+
if (campaignId === void 0) {
|
|
6114
|
+
try {
|
|
6115
|
+
campaignId = resolveCampaign(origin.slot, sponsorId);
|
|
6116
|
+
} catch {
|
|
6117
|
+
campaignId = void 0;
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
const key = campaignId === void 0 ? "default" : String(campaignId);
|
|
6121
|
+
let slice = slices.get(key);
|
|
6122
|
+
if (!slice) {
|
|
6123
|
+
slice = {
|
|
6124
|
+
campaignId,
|
|
6125
|
+
sponsors: /* @__PURE__ */ new Set(),
|
|
6126
|
+
slots: /* @__PURE__ */ new Set(),
|
|
6127
|
+
contents: /* @__PURE__ */ new Set(),
|
|
6128
|
+
items: /* @__PURE__ */ new Map(),
|
|
6129
|
+
value: 0,
|
|
6130
|
+
currency: line.currency || fallbackCurrency
|
|
6131
|
+
};
|
|
6132
|
+
slices.set(key, slice);
|
|
6133
|
+
}
|
|
6134
|
+
if (Number.isFinite(sponsorId)) slice.sponsors.add(sponsorId);
|
|
6135
|
+
if (origin.slot) slice.slots.add(origin.slot);
|
|
6136
|
+
if (origin.contentId) slice.contents.add(origin.contentId);
|
|
6137
|
+
const itemKey = `${line.productId}::${line.variantId ?? ""}`;
|
|
6138
|
+
const item = slice.items.get(itemKey);
|
|
6139
|
+
if (item) item.quantity = (item.quantity ?? 0) + origin.quantity;
|
|
6140
|
+
else {
|
|
6141
|
+
slice.items.set(itemKey, {
|
|
6142
|
+
productId: line.productId,
|
|
6143
|
+
name: line.name,
|
|
6144
|
+
brand: line.brand,
|
|
6145
|
+
variantId: line.variantId,
|
|
6146
|
+
price: unitPrice,
|
|
6147
|
+
quantity: origin.quantity
|
|
6148
|
+
});
|
|
6149
|
+
}
|
|
6150
|
+
slice.value += unitPrice * origin.quantity;
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
const list = [...slices.values()];
|
|
6154
|
+
return list.map((s, i) => ({
|
|
6155
|
+
campaignId: s.campaignId,
|
|
6156
|
+
sponsorId: s.sponsors.size === 1 ? [...s.sponsors][0] : void 0,
|
|
6157
|
+
slot: s.slots.size === 1 ? [...s.slots][0] : void 0,
|
|
6158
|
+
contentId: s.contents.size === 1 ? [...s.contents][0] : void 0,
|
|
6159
|
+
items: [...s.items.values()],
|
|
6160
|
+
value: Math.round(s.value * 100) / 100,
|
|
6161
|
+
currency: s.currency,
|
|
6162
|
+
slice: i + 1,
|
|
6163
|
+
slices: list.length
|
|
6164
|
+
}));
|
|
6165
|
+
}
|
|
6166
|
+
function hasExplicitCampaign(lines) {
|
|
6167
|
+
return lines.some((line) => lineOrigins(line).some((o) => o.campaignId !== void 0));
|
|
6168
|
+
}
|
|
6169
|
+
|
|
5591
6170
|
// src/core/analytics/analytics-manager.ts
|
|
5592
6171
|
var SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
5593
6172
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
@@ -5785,22 +6364,34 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
5785
6364
|
if (!anonId) return null;
|
|
5786
6365
|
const boot = vioBootstrapCache();
|
|
5787
6366
|
const ctx = opts.context ?? {};
|
|
6367
|
+
const explicitContentId = fitsId(ctx.contentId);
|
|
6368
|
+
const content = explicitContentId === void 0 || ctx.contentTitle === void 0 ? vioContent() : {};
|
|
5788
6369
|
const context = compact({
|
|
5789
6370
|
campaign_id: resolveCampaignId(ctx, boot),
|
|
5790
6371
|
broadcast_id: ctx.broadcastId,
|
|
5791
6372
|
campaign_component_id: ctx.campaignComponentId,
|
|
5792
6373
|
app_placement_id: ctx.appPlacementId,
|
|
5793
|
-
|
|
6374
|
+
// A slot is a location id to the campaigns service (ADR-0027). An
|
|
6375
|
+
// explicit location id that does not fit gives way to the slot.
|
|
6376
|
+
location_id: fitsId(ctx.locationId) ?? fitsId(ctx.slot),
|
|
5794
6377
|
component_template_id: ctx.componentTemplateId,
|
|
5795
6378
|
sponsor_id: ctx.sponsorId,
|
|
5796
6379
|
activation_id: ctx.activationId,
|
|
5797
|
-
|
|
6380
|
+
// Cut to the collector's 2 048 — the query string first, since a long
|
|
6381
|
+
// one is usually the tracking tail, not the page. The other host-given
|
|
6382
|
+
// fields (broadcastId, componentTemplateId, errorCode, variant, …) are
|
|
6383
|
+
// the host's responsibility: the SDK does not touch them.
|
|
6384
|
+
content_url: capUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
|
|
5798
6385
|
// The host's own content id — its join key back onto its reporting.
|
|
5799
|
-
|
|
6386
|
+
// Explicit on the event when it fits, else what the SDK resolved for
|
|
6387
|
+
// the page (already capped per source), else nothing.
|
|
6388
|
+
content_id: explicitContentId ?? fitsId(content.id),
|
|
5800
6389
|
// Deletion-proof reporting: sources have no trash bin — the title
|
|
5801
6390
|
// snapshotted at event time keeps reports legible if content dies.
|
|
5802
6391
|
error_code: ctx.errorCode,
|
|
5803
|
-
content_title:
|
|
6392
|
+
content_title: capTitle(
|
|
6393
|
+
ctx.contentTitle ?? content.title ?? (typeof document !== "undefined" && document.title ? document.title : void 0)
|
|
6394
|
+
),
|
|
5804
6395
|
variant: ctx.variant
|
|
5805
6396
|
});
|
|
5806
6397
|
const com = opts.commerce;
|
|
@@ -5948,7 +6539,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
5948
6539
|
const d = ev.detail ?? {};
|
|
5949
6540
|
const line = this.findLine(d.productId, d.variantId);
|
|
5950
6541
|
this.track("add_to_cart", {
|
|
5951
|
-
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0 },
|
|
6542
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
|
|
5952
6543
|
commerce: {
|
|
5953
6544
|
items: [
|
|
5954
6545
|
{
|
|
@@ -5969,7 +6560,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
5969
6560
|
const d = ev.detail ?? {};
|
|
5970
6561
|
if (d.productId === void 0) return;
|
|
5971
6562
|
this.track("remove_from_cart", {
|
|
5972
|
-
context: { sponsorId: d.sponsorId },
|
|
6563
|
+
context: { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
|
|
5973
6564
|
commerce: {
|
|
5974
6565
|
items: [
|
|
5975
6566
|
{
|
|
@@ -5998,7 +6589,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
5998
6589
|
if (d.productId === void 0) return;
|
|
5999
6590
|
const price = Number(d.price);
|
|
6000
6591
|
this.track("select_item", {
|
|
6001
|
-
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0 },
|
|
6592
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
|
|
6002
6593
|
commerce: {
|
|
6003
6594
|
items: [
|
|
6004
6595
|
{
|
|
@@ -6017,18 +6608,42 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6017
6608
|
});
|
|
6018
6609
|
on("vio:checkout-open", (ev) => {
|
|
6019
6610
|
const d = ev.detail ?? {};
|
|
6611
|
+
const base = { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
|
|
6612
|
+
const { slices, explicit } = this.cartSlices(d.sponsorId);
|
|
6613
|
+
if (slices.length > 1) {
|
|
6614
|
+
for (const slice of slices) {
|
|
6615
|
+
this.track("begin_checkout", {
|
|
6616
|
+
context: this.sliceContext(slice, base),
|
|
6617
|
+
commerce: sliceCommerce(slice),
|
|
6618
|
+
props: { slice: slice.slice, slices: slice.slices }
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6621
|
+
return;
|
|
6622
|
+
}
|
|
6020
6623
|
const snap = this.cartCommerce(d.sponsorId);
|
|
6021
6624
|
this.track("begin_checkout", {
|
|
6022
|
-
context:
|
|
6625
|
+
context: this.singleSliceContext(slices[0], explicit, base),
|
|
6023
6626
|
...snap ? { commerce: snap } : {}
|
|
6024
6627
|
});
|
|
6025
6628
|
});
|
|
6026
6629
|
on("vio:payment-success", (ev) => {
|
|
6027
6630
|
const d = ev.detail ?? {};
|
|
6028
6631
|
const sponsorId = d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0;
|
|
6632
|
+
const base = { sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
|
|
6633
|
+
const { slices, explicit } = this.cartSlices(sponsorId);
|
|
6634
|
+
if (slices.length > 1) {
|
|
6635
|
+
for (const slice of slices) {
|
|
6636
|
+
this.track("purchase", {
|
|
6637
|
+
context: this.sliceContext(slice, base),
|
|
6638
|
+
commerce: { ...sliceCommerce(slice), ...purchaseFromPaymentSuccess(d), paymentMethod: d.method },
|
|
6639
|
+
props: { slice: slice.slice, slices: slice.slices }
|
|
6640
|
+
});
|
|
6641
|
+
}
|
|
6642
|
+
return;
|
|
6643
|
+
}
|
|
6029
6644
|
const snap = this.cartCommerce(sponsorId);
|
|
6030
6645
|
this.track("purchase", {
|
|
6031
|
-
context:
|
|
6646
|
+
context: this.singleSliceContext(slices[0], explicit, base),
|
|
6032
6647
|
commerce: {
|
|
6033
6648
|
// value / currency / items: the cart snapshot, on every path.
|
|
6034
6649
|
...snap ?? {},
|
|
@@ -6039,6 +6654,69 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6039
6654
|
});
|
|
6040
6655
|
});
|
|
6041
6656
|
}
|
|
6657
|
+
/** The lines of the cart (current, else the pre-clear snapshot), optionally one sponsor's. */
|
|
6658
|
+
cartLines(sponsorId) {
|
|
6659
|
+
let items = [];
|
|
6660
|
+
const cart = vioCart();
|
|
6661
|
+
if (cart) {
|
|
6662
|
+
cart.getAllCarts().forEach((state, id) => {
|
|
6663
|
+
if (sponsorId === void 0 || id === sponsorId) items.push(...state.items);
|
|
6664
|
+
});
|
|
6665
|
+
}
|
|
6666
|
+
if (items.length === 0 && this.lastCartSnapshot) {
|
|
6667
|
+
items = this.lastCartSnapshot.items.filter(
|
|
6668
|
+
(i) => sponsorId === void 0 || i.sponsorId === sponsorId
|
|
6669
|
+
);
|
|
6670
|
+
}
|
|
6671
|
+
return items;
|
|
6672
|
+
}
|
|
6673
|
+
/**
|
|
6674
|
+
* The cart split per campaign — the shared rule of cart/slices.ts over the
|
|
6675
|
+
* current cart (else the pre-clear snapshot), plus whether any unit named
|
|
6676
|
+
* its campaign itself (a cart without origins is reported as before).
|
|
6677
|
+
*/
|
|
6678
|
+
cartSlices(sponsorId) {
|
|
6679
|
+
const lines = this.cartLines(sponsorId);
|
|
6680
|
+
if (lines.length === 0) return { slices: [], explicit: false };
|
|
6681
|
+
const boot = vioBootstrapCache();
|
|
6682
|
+
const slices = cartSlicesFrom(
|
|
6683
|
+
lines,
|
|
6684
|
+
(slot, lineSponsorId) => resolveCampaignId({ slot, sponsorId: lineSponsorId }, boot),
|
|
6685
|
+
this.lastCartSnapshot?.currency
|
|
6686
|
+
);
|
|
6687
|
+
return { slices, explicit: hasExplicitCampaign(lines) };
|
|
6688
|
+
}
|
|
6689
|
+
/**
|
|
6690
|
+
* The context of one of several per-campaign events: the slice's campaign,
|
|
6691
|
+
* slot and content — only its own; when the slice spans several slots or
|
|
6692
|
+
* articles the event names none rather than the one the click came from.
|
|
6693
|
+
*/
|
|
6694
|
+
sliceContext(slice, base) {
|
|
6695
|
+
return compact({
|
|
6696
|
+
...base,
|
|
6697
|
+
sponsorId: slice.sponsorId ?? base.sponsorId,
|
|
6698
|
+
campaignId: slice.campaignId,
|
|
6699
|
+
slot: slice.slot,
|
|
6700
|
+
contentId: slice.contentId,
|
|
6701
|
+
// The click's title only describes the click's content: when the slice
|
|
6702
|
+
// is another article (or several), the page's own title applies instead.
|
|
6703
|
+
contentTitle: slice.contentId !== void 0 && slice.contentId === base.contentId ? base.contentTitle : void 0
|
|
6704
|
+
});
|
|
6705
|
+
}
|
|
6706
|
+
/**
|
|
6707
|
+
* The context of a single-campaign checkout/purchase: when the cart's origins
|
|
6708
|
+
* say which campaign they came from, the event says it too (plus the slot /
|
|
6709
|
+
* content when unique) — a cart without origins is exactly as before.
|
|
6710
|
+
*/
|
|
6711
|
+
singleSliceContext(slice, explicit, base) {
|
|
6712
|
+
if (!slice || !explicit) return base;
|
|
6713
|
+
return compact({
|
|
6714
|
+
...base,
|
|
6715
|
+
campaignId: base.campaignId ?? slice.campaignId,
|
|
6716
|
+
slot: base.slot ?? slice.slot,
|
|
6717
|
+
contentId: base.contentId ?? slice.contentId
|
|
6718
|
+
});
|
|
6719
|
+
}
|
|
6042
6720
|
/** Current cart → commerce payload; falls back to the pre-clear snapshot. */
|
|
6043
6721
|
cartCommerce(sponsorId) {
|
|
6044
6722
|
let items = [];
|
|
@@ -6124,6 +6802,14 @@ function vioFacade() {
|
|
|
6124
6802
|
function vioBootstrapCache() {
|
|
6125
6803
|
return vioFacade()?.bootstrapCache ?? null;
|
|
6126
6804
|
}
|
|
6805
|
+
function vioContent() {
|
|
6806
|
+
try {
|
|
6807
|
+
const facade = vioFacade();
|
|
6808
|
+
return typeof facade?.content === "function" ? facade.content() : resolveContent();
|
|
6809
|
+
} catch {
|
|
6810
|
+
return {};
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6127
6813
|
function vioCart() {
|
|
6128
6814
|
try {
|
|
6129
6815
|
return vioFacade()?.cart ?? null;
|
|
@@ -6134,6 +6820,11 @@ function vioCart() {
|
|
|
6134
6820
|
function resolveCampaignId(ctx, boot) {
|
|
6135
6821
|
if (ctx.campaignId !== void 0) return ctx.campaignId;
|
|
6136
6822
|
const defaultId = boot?.campaign?.id;
|
|
6823
|
+
if (ctx.slot) {
|
|
6824
|
+
const platform = Configuration.isInitialized ? Configuration.get().platform : void 0;
|
|
6825
|
+
const assigned = toNumber(findSlotAssignment(boot, ctx.slot, Date.now(), platform)?.id);
|
|
6826
|
+
if (assigned !== void 0) return assigned;
|
|
6827
|
+
}
|
|
6137
6828
|
const sponsorId = toNumber(ctx.sponsorId);
|
|
6138
6829
|
if (sponsorId === void 0) return defaultId;
|
|
6139
6830
|
try {
|
|
@@ -6148,6 +6839,39 @@ function resolveCampaignId(ctx, boot) {
|
|
|
6148
6839
|
}
|
|
6149
6840
|
return defaultId;
|
|
6150
6841
|
}
|
|
6842
|
+
function campaignOf(detail) {
|
|
6843
|
+
return toNumber(detail.campaignId);
|
|
6844
|
+
}
|
|
6845
|
+
function fitsId(value) {
|
|
6846
|
+
if (value === void 0 || value === null) return void 0;
|
|
6847
|
+
const s = String(value);
|
|
6848
|
+
return s && s.length <= CONTENT_ID_MAX_LENGTH ? value : void 0;
|
|
6849
|
+
}
|
|
6850
|
+
function capTitle(value) {
|
|
6851
|
+
if (!value) return void 0;
|
|
6852
|
+
return value.length > CONTENT_TITLE_MAX_LENGTH ? value.slice(0, CONTENT_TITLE_MAX_LENGTH) : value;
|
|
6853
|
+
}
|
|
6854
|
+
var CONTENT_URL_MAX_LENGTH = 2048;
|
|
6855
|
+
function capUrl(value) {
|
|
6856
|
+
if (!value) return void 0;
|
|
6857
|
+
if (value.length <= CONTENT_URL_MAX_LENGTH) return value;
|
|
6858
|
+
const cut = value.search(/[?#]/);
|
|
6859
|
+
const withoutQuery = cut >= 0 ? value.slice(0, cut) : value;
|
|
6860
|
+
return withoutQuery.length <= CONTENT_URL_MAX_LENGTH ? withoutQuery : withoutQuery.slice(0, CONTENT_URL_MAX_LENGTH);
|
|
6861
|
+
}
|
|
6862
|
+
function sliceCommerce(slice) {
|
|
6863
|
+
return { items: slice.items, value: slice.value, currency: slice.currency };
|
|
6864
|
+
}
|
|
6865
|
+
function slotOf(detail) {
|
|
6866
|
+
return typeof detail.slot === "string" && detail.slot.trim() ? detail.slot.trim() : void 0;
|
|
6867
|
+
}
|
|
6868
|
+
function contentOf(detail) {
|
|
6869
|
+
const out = {};
|
|
6870
|
+
if (typeof detail.contentId === "number" && Number.isFinite(detail.contentId)) out.contentId = detail.contentId;
|
|
6871
|
+
else if (typeof detail.contentId === "string" && detail.contentId.trim()) out.contentId = detail.contentId.trim();
|
|
6872
|
+
if (typeof detail.contentTitle === "string" && detail.contentTitle.trim()) out.contentTitle = detail.contentTitle.trim();
|
|
6873
|
+
return out;
|
|
6874
|
+
}
|
|
6151
6875
|
function campaignSponsorIds(campaign) {
|
|
6152
6876
|
const ids = [];
|
|
6153
6877
|
const primary = toNumber(campaign.primarySponsor?.id);
|
|
@@ -6172,11 +6896,6 @@ function firstId(...candidates) {
|
|
|
6172
6896
|
}
|
|
6173
6897
|
return void 0;
|
|
6174
6898
|
}
|
|
6175
|
-
function toNumber(value) {
|
|
6176
|
-
if (value === void 0 || value === null || value === "") return void 0;
|
|
6177
|
-
const n = Number(value);
|
|
6178
|
-
return Number.isFinite(n) ? n : void 0;
|
|
6179
|
-
}
|
|
6180
6899
|
function pushGa4(event) {
|
|
6181
6900
|
const commerceNames = /* @__PURE__ */ new Set([
|
|
6182
6901
|
"view_item_list",
|
|
@@ -6213,6 +6932,7 @@ function pushGa4(event) {
|
|
|
6213
6932
|
}
|
|
6214
6933
|
|
|
6215
6934
|
// src/core/client.ts
|
|
6935
|
+
var SLOT_DECLARATION_DEBOUNCE_MS = 1e3;
|
|
6216
6936
|
var VioFacade = class {
|
|
6217
6937
|
constructor() {
|
|
6218
6938
|
this.apiInstance = null;
|
|
@@ -6226,6 +6946,17 @@ var VioFacade = class {
|
|
|
6226
6946
|
* (another key or environment) is still in flight after a re-init; its answer
|
|
6227
6947
|
* belongs to that configuration and must not land in this one. */
|
|
6228
6948
|
this.generation = 0;
|
|
6949
|
+
/** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each + its display name. */
|
|
6950
|
+
this.slotRegistry = /* @__PURE__ */ new Map();
|
|
6951
|
+
/** Pending debounced declaration of the registry. */
|
|
6952
|
+
this.slotDeclareTimer = null;
|
|
6953
|
+
/** A name appeared (or the key changed) since the registry was last declared. */
|
|
6954
|
+
this.slotsDirty = false;
|
|
6955
|
+
/** Manifests POSTed by this page load (abuse / rollout guard, see declareSlots). */
|
|
6956
|
+
this.declarationsThisLoad = 0;
|
|
6957
|
+
/** Each cap warns once per page load. */
|
|
6958
|
+
this.warnedLocationCap = false;
|
|
6959
|
+
this.warnedDeclarationCap = false;
|
|
6229
6960
|
// Client-side managers — created lazily on first access. They don't
|
|
6230
6961
|
// need Vio.init() to work (state is purely local).
|
|
6231
6962
|
this.cartManager = null;
|
|
@@ -6241,6 +6972,31 @@ var VioFacade = class {
|
|
|
6241
6972
|
this.bootstrapFailedAt = 0;
|
|
6242
6973
|
this.generation++;
|
|
6243
6974
|
this.checkoutManager?.clearPaymentMethodsCache();
|
|
6975
|
+
if (this.slotDeclareTimer) {
|
|
6976
|
+
clearTimeout(this.slotDeclareTimer);
|
|
6977
|
+
this.slotDeclareTimer = null;
|
|
6978
|
+
}
|
|
6979
|
+
this.slotsDirty = this.slotRegistry.size > 0;
|
|
6980
|
+
this.declarationsThisLoad = 0;
|
|
6981
|
+
this.warnedLocationCap = false;
|
|
6982
|
+
this.warnedDeclarationCap = false;
|
|
6983
|
+
}
|
|
6984
|
+
/**
|
|
6985
|
+
* The content (article, page) this SDK instance is rendered in — what every
|
|
6986
|
+
* analytics event carries as `content_id` / `content_title` when the event
|
|
6987
|
+
* has no explicit one: init option → URL query → `vio:content-*` metas →
|
|
6988
|
+
* canonical/og:url → pathname (see `resolveContent`). Resolved LIVE on each
|
|
6989
|
+
* call — a few querySelectors — so an SPA route change, a new canonical or
|
|
6990
|
+
* a new title are seen without any re-init. Never throws; `{}` without a
|
|
6991
|
+
* document.
|
|
6992
|
+
*/
|
|
6993
|
+
content() {
|
|
6994
|
+
let init;
|
|
6995
|
+
if (Configuration.isInitialized) {
|
|
6996
|
+
const cfg = Configuration.get();
|
|
6997
|
+
init = { contentId: cfg.contentId, contentTitle: cfg.contentTitle };
|
|
6998
|
+
}
|
|
6999
|
+
return resolveContent(init);
|
|
6244
7000
|
}
|
|
6245
7001
|
get isInitialized() {
|
|
6246
7002
|
return Configuration.isInitialized;
|
|
@@ -6292,11 +7048,12 @@ var VioFacade = class {
|
|
|
6292
7048
|
throw new Error("[Vio] bootstrap recently failed \u2014 backing off");
|
|
6293
7049
|
}
|
|
6294
7050
|
const generation = this.generation;
|
|
6295
|
-
const promise = this.api.bootstrap().then(
|
|
7051
|
+
const promise = this.api.bootstrap(Configuration.get().platform).then(
|
|
6296
7052
|
(res) => {
|
|
6297
7053
|
if (generation !== this.generation) return this.bootstrap();
|
|
6298
7054
|
this.cachedBootstrap = res;
|
|
6299
7055
|
this.bootstrapFailedAt = 0;
|
|
7056
|
+
if (this.slotsDirty) this.scheduleSlotDeclaration();
|
|
6300
7057
|
return res;
|
|
6301
7058
|
},
|
|
6302
7059
|
(err) => {
|
|
@@ -6337,14 +7094,230 @@ var VioFacade = class {
|
|
|
6337
7094
|
const cfg = Configuration.get();
|
|
6338
7095
|
return createCommerceClient({ endpoint: cfg.graphQLBase, apiKey: commerceApiKey });
|
|
6339
7096
|
}
|
|
7097
|
+
// MARK: - Slots (ADR-0027 / ADR-0034)
|
|
7098
|
+
/**
|
|
7099
|
+
* Declare this page's slots to the campaigns service
|
|
7100
|
+
* (`POST /v2/mobile/components/manifest`, `mode: 'merge'`, the init
|
|
7101
|
+
* platform). Best-effort: it NEVER throws — a failure is logged and resolves
|
|
7102
|
+
* `null`. The same set is not re-posted from this browser for 24 h unless
|
|
7103
|
+
* `force` (then `null` also means "skipped, already declared"). Ids are
|
|
7104
|
+
* trimmed, cut at 100 chars, deduped; empties are dropped.
|
|
7105
|
+
*/
|
|
7106
|
+
async declareSlots(slots, opts = {}) {
|
|
7107
|
+
let storageKey;
|
|
7108
|
+
let keys = [];
|
|
7109
|
+
let now = Date.now();
|
|
7110
|
+
try {
|
|
7111
|
+
const all = normalizeSlotLocations(slots);
|
|
7112
|
+
if (all.length === 0) return null;
|
|
7113
|
+
if (!Configuration.isInitialized) {
|
|
7114
|
+
warn("declareSlots() before Vio.init() \u2014 nothing declared");
|
|
7115
|
+
return null;
|
|
7116
|
+
}
|
|
7117
|
+
const cfg = Configuration.get();
|
|
7118
|
+
storageKey = slotDeclarationStorageKey(cfg.platform, cfg.apiKey);
|
|
7119
|
+
now = Date.now();
|
|
7120
|
+
const memory = readSlotDeclaration(storageKey);
|
|
7121
|
+
const pending2 = new Set(pendingSlotKeys(memory, all.map(slotDeclarationKey), now));
|
|
7122
|
+
let locations = opts.force ? all : all.filter((l) => pending2.has(slotDeclarationKey(l)));
|
|
7123
|
+
if (locations.length === 0) return null;
|
|
7124
|
+
if (locations.length > MAX_LOCATIONS_PER_DECLARATION) {
|
|
7125
|
+
if (!this.warnedLocationCap) {
|
|
7126
|
+
this.warnedLocationCap = true;
|
|
7127
|
+
warn(
|
|
7128
|
+
`declareSlots(): ${locations.length} slots on one page \u2014 only the first ${MAX_LOCATIONS_PER_DECLARATION} are declared`
|
|
7129
|
+
);
|
|
7130
|
+
}
|
|
7131
|
+
locations = locations.slice(0, MAX_LOCATIONS_PER_DECLARATION);
|
|
7132
|
+
}
|
|
7133
|
+
keys = locations.map(slotDeclarationKey);
|
|
7134
|
+
if (!opts.force && this.declarationsThisLoad >= MAX_DECLARATIONS_PER_LOAD) {
|
|
7135
|
+
if (!this.warnedDeclarationCap) {
|
|
7136
|
+
this.warnedDeclarationCap = true;
|
|
7137
|
+
warn(
|
|
7138
|
+
`declareSlots(): already ${MAX_DECLARATIONS_PER_LOAD} manifests this page load \u2014 further ones are skipped (force: true to override)`
|
|
7139
|
+
);
|
|
7140
|
+
}
|
|
7141
|
+
return null;
|
|
7142
|
+
}
|
|
7143
|
+
this.declarationsThisLoad++;
|
|
7144
|
+
const result = await this.api.declareManifest({
|
|
7145
|
+
platform: cfg.platform,
|
|
7146
|
+
mode: "merge",
|
|
7147
|
+
locations
|
|
7148
|
+
});
|
|
7149
|
+
if (result?.mode === "merge") {
|
|
7150
|
+
writeSlotDeclaration(storageKey, rememberSlotKeys(memory, keys, now));
|
|
7151
|
+
} else {
|
|
7152
|
+
writeSlotDeclaration(storageKey, rememberSlotKeys(memory, keys, failedAt(now)));
|
|
7153
|
+
warn(
|
|
7154
|
+
"slots declared but the server ignored mode=merge (old server); not caching, will retry next load"
|
|
7155
|
+
);
|
|
7156
|
+
}
|
|
7157
|
+
return result;
|
|
7158
|
+
} catch (err) {
|
|
7159
|
+
if (storageKey && keys.length > 0) {
|
|
7160
|
+
writeSlotDeclaration(storageKey, rememberSlotKeys(readSlotDeclaration(storageKey), keys, failedAt(now)));
|
|
7161
|
+
}
|
|
7162
|
+
warn("declareSlots() failed \u2014", err);
|
|
7163
|
+
return null;
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
/**
|
|
7167
|
+
* Register a slot present on the page (the `<vio-*>` elements call it with
|
|
7168
|
+
* their effective id — the host's `vio-slot` or the auto-generated one —
|
|
7169
|
+
* and the optional display name). The registry is declared ONCE, debounced,
|
|
7170
|
+
* after the first successful bootstrap — and again whenever a new id or a
|
|
7171
|
+
* new display name appears. A `displayName` given later for an id already
|
|
7172
|
+
* registered replaces the one kept; `undefined` leaves it alone. The
|
|
7173
|
+
* returned function unregisters; removal never re-declares.
|
|
7174
|
+
*/
|
|
7175
|
+
registerSlot(name, displayName) {
|
|
7176
|
+
const id = normalizeSlotId(name);
|
|
7177
|
+
if (!id) return () => {
|
|
7178
|
+
};
|
|
7179
|
+
const label = typeof displayName === "string" && displayName.trim() ? displayName.trim() : void 0;
|
|
7180
|
+
const entry = this.slotRegistry.get(id);
|
|
7181
|
+
let changed = false;
|
|
7182
|
+
if (!entry) {
|
|
7183
|
+
this.slotRegistry.set(id, { count: 1, displayName: label });
|
|
7184
|
+
changed = true;
|
|
7185
|
+
} else {
|
|
7186
|
+
entry.count++;
|
|
7187
|
+
if (label !== void 0 && label !== entry.displayName) {
|
|
7188
|
+
entry.displayName = label;
|
|
7189
|
+
changed = true;
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
if (changed) {
|
|
7193
|
+
this.slotsDirty = true;
|
|
7194
|
+
if (this.cachedBootstrap) this.scheduleSlotDeclaration();
|
|
7195
|
+
}
|
|
7196
|
+
let released = false;
|
|
7197
|
+
return () => {
|
|
7198
|
+
if (released) return;
|
|
7199
|
+
released = true;
|
|
7200
|
+
const current = this.slotRegistry.get(id);
|
|
7201
|
+
if (!current) return;
|
|
7202
|
+
if (current.count <= 1) this.slotRegistry.delete(id);
|
|
7203
|
+
else current.count--;
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
7206
|
+
/**
|
|
7207
|
+
* The campaign a slot shows now, from the last bootstrap: its assigned and
|
|
7208
|
+
* open campaign for the init platform, else the surface's default. With no
|
|
7209
|
+
* slot, the default. `null` before the bootstrap. See `resolveSlotCampaign`.
|
|
7210
|
+
*/
|
|
7211
|
+
campaignForSlot(slot) {
|
|
7212
|
+
const platform = Configuration.isInitialized ? Configuration.get().platform : void 0;
|
|
7213
|
+
return resolveSlotCampaign(this.cachedBootstrap, slot ?? null, Date.now(), platform);
|
|
7214
|
+
}
|
|
7215
|
+
/**
|
|
7216
|
+
* THE attribution helper — the SDK elements, Vev and Replit all use this
|
|
7217
|
+
* one. For a place (`slot`): the campaign it shows right now
|
|
7218
|
+
* (`campaignForSlot`: its assignment, else the surface's default) as
|
|
7219
|
+
* `campaignId` + that campaign's primary sponsor as `sponsorId`, plus the
|
|
7220
|
+
* page's content (`content()`). Pure, never throws; `{}` before the
|
|
7221
|
+
* bootstrap, except the content.
|
|
7222
|
+
*
|
|
7223
|
+
* `hint.sponsorId` — when the caller already knows the sponsor (a card with
|
|
7224
|
+
* `sponsor-id`) it is kept as `sponsorId`, and the campaign follows the same
|
|
7225
|
+
* cascade analytics uses (slot assignment → the sponsor's active campaign →
|
|
7226
|
+
* default), so what the cart remembers matches what the events say.
|
|
7227
|
+
*/
|
|
7228
|
+
attribution(slot, hint) {
|
|
7229
|
+
const out = {};
|
|
7230
|
+
try {
|
|
7231
|
+
const name = normalizeSlotId(slot);
|
|
7232
|
+
if (name) out.slot = name;
|
|
7233
|
+
const boot = this.cachedBootstrap;
|
|
7234
|
+
const hinted = toNumber(hint?.sponsorId);
|
|
7235
|
+
if (boot) {
|
|
7236
|
+
if (hinted !== void 0) {
|
|
7237
|
+
const campaignId = resolveCampaignId({ slot: name || void 0, sponsorId: hinted }, boot);
|
|
7238
|
+
if (campaignId !== void 0) out.campaignId = campaignId;
|
|
7239
|
+
out.sponsorId = hinted;
|
|
7240
|
+
} else {
|
|
7241
|
+
const campaign = this.campaignForSlot(name || null);
|
|
7242
|
+
const campaignId = toNumber(campaign?.id);
|
|
7243
|
+
if (campaignId !== void 0) out.campaignId = campaignId;
|
|
7244
|
+
const sponsorId = toNumber(campaign?.primarySponsor?.id);
|
|
7245
|
+
if (sponsorId !== void 0) out.sponsorId = sponsorId;
|
|
7246
|
+
}
|
|
7247
|
+
} else if (hinted !== void 0) {
|
|
7248
|
+
out.sponsorId = hinted;
|
|
7249
|
+
}
|
|
7250
|
+
const content = this.content();
|
|
7251
|
+
if (content.id) out.contentId = content.id;
|
|
7252
|
+
if (content.title) out.contentTitle = content.title;
|
|
7253
|
+
} catch {
|
|
7254
|
+
}
|
|
7255
|
+
return out;
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7258
|
+
* The cart split per campaign — the SAME rule `begin_checkout` / `purchase`
|
|
7259
|
+
* use, for hosts with their own analytics sink (Vev). Every unit of every
|
|
7260
|
+
* line goes to the campaign of its origin; units without one to the campaign
|
|
7261
|
+
* the current rule resolves for the line's sponsor (slot assignment → the
|
|
7262
|
+
* sponsor's active campaign → default); `slot` / `contentId` / `sponsorId`
|
|
7263
|
+
* only when they are the same for the whole slice; `value` with two
|
|
7264
|
+
* decimals; `slice` / `slices` number the parts. Pure over the current cart
|
|
7265
|
+
* (or over `opts.items`); `[]` when there is nothing.
|
|
7266
|
+
*/
|
|
7267
|
+
cartSlices(sponsorId, opts = {}) {
|
|
7268
|
+
try {
|
|
7269
|
+
let lines = [];
|
|
7270
|
+
if (Array.isArray(opts.items)) {
|
|
7271
|
+
lines = opts.items.filter((i) => sponsorId === void 0 || Number(i?.sponsorId) === Number(sponsorId));
|
|
7272
|
+
} else {
|
|
7273
|
+
this.cart.getAllCarts().forEach((state, id) => {
|
|
7274
|
+
if (sponsorId === void 0 || id === Number(sponsorId)) lines.push(...state.items);
|
|
7275
|
+
});
|
|
7276
|
+
}
|
|
7277
|
+
const boot = this.cachedBootstrap;
|
|
7278
|
+
return cartSlicesFrom(
|
|
7279
|
+
lines,
|
|
7280
|
+
(slot, lineSponsorId) => resolveCampaignId({ slot, sponsorId: lineSponsorId }, boot)
|
|
7281
|
+
);
|
|
7282
|
+
} catch {
|
|
7283
|
+
return [];
|
|
7284
|
+
}
|
|
7285
|
+
}
|
|
7286
|
+
scheduleSlotDeclaration() {
|
|
7287
|
+
if (this.slotDeclareTimer) clearTimeout(this.slotDeclareTimer);
|
|
7288
|
+
this.slotDeclareTimer = setTimeout(() => {
|
|
7289
|
+
this.slotDeclareTimer = null;
|
|
7290
|
+
this.slotsDirty = false;
|
|
7291
|
+
void this.declareSlots(
|
|
7292
|
+
[...this.slotRegistry.entries()].map(
|
|
7293
|
+
([id, { displayName }]) => displayName ? { id, displayName } : id
|
|
7294
|
+
)
|
|
7295
|
+
);
|
|
7296
|
+
}, SLOT_DECLARATION_DEBOUNCE_MS);
|
|
7297
|
+
}
|
|
6340
7298
|
findSponsor(sponsorId) {
|
|
6341
7299
|
const boot = this.cachedBootstrap;
|
|
6342
7300
|
if (!boot) return void 0;
|
|
6343
7301
|
const id = Number(sponsorId);
|
|
6344
7302
|
if (Number(boot.primarySponsor?.id) === id) return boot.primarySponsor;
|
|
6345
|
-
|
|
7303
|
+
const secondary = boot.secondarySponsors?.find((s) => Number(s.id) === id);
|
|
7304
|
+
if (secondary) return secondary;
|
|
7305
|
+
const campaigns = Array.isArray(boot.campaigns) ? boot.campaigns : [];
|
|
7306
|
+
for (const c of campaigns) {
|
|
7307
|
+
if (!c || typeof c !== "object") continue;
|
|
7308
|
+
if (Number(c.primarySponsor?.id) === id) return c.primarySponsor ?? void 0;
|
|
7309
|
+
const s = Array.isArray(c.secondarySponsors) ? c.secondarySponsors.find((x) => Number(x?.id) === id) : void 0;
|
|
7310
|
+
if (s) return s;
|
|
7311
|
+
}
|
|
7312
|
+
return void 0;
|
|
6346
7313
|
}
|
|
6347
7314
|
};
|
|
7315
|
+
function warn(...args) {
|
|
7316
|
+
if (typeof console !== "undefined") console.warn("[Vio]", ...args);
|
|
7317
|
+
}
|
|
7318
|
+
function failedAt(now) {
|
|
7319
|
+
return now - (SLOT_DECLARATION_TTL_MS - SLOT_DECLARATION_FAILURE_TTL_MS);
|
|
7320
|
+
}
|
|
6348
7321
|
var _vioGlobal = globalThis;
|
|
6349
7322
|
var Vio = _vioGlobal.__VIO_FACADE__ ?? (_vioGlobal.__VIO_FACADE__ = new VioFacade());
|
|
6350
7323
|
|
|
@@ -6397,6 +7370,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
|
|
|
6397
7370
|
};
|
|
6398
7371
|
}
|
|
6399
7372
|
|
|
6400
|
-
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, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, kustomReturnOutcome, nexiReturnPaymentId, primaryImageUrl, purchaseFromPaymentSuccess, readNexiPending, resolveCampaignId, showApplePaySheet, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
|
|
6401
|
-
//# sourceMappingURL=chunk-
|
|
6402
|
-
//# sourceMappingURL=chunk-
|
|
7373
|
+
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, 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, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
|
|
7374
|
+
//# sourceMappingURL=chunk-BRJMT7P4.js.map
|
|
7375
|
+
//# sourceMappingURL=chunk-BRJMT7P4.js.map
|