@vio-live/web-sdk 0.4.1 → 0.5.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/dist/{chunk-V5ZQG2H5.js → chunk-5CKG3F56.js} +117 -13
- package/dist/chunk-5CKG3F56.js.map +1 -0
- package/dist/{chunk-NP2KCM7X.js → chunk-HNQG56SD.js} +643 -37
- package/dist/chunk-HNQG56SD.js.map +1 -0
- package/dist/chunk-QYQQ5J6E.js +3 -0
- package/dist/{chunk-PBQSHIID.js.map → chunk-QYQQ5J6E.js.map} +1 -1
- package/dist/core/index.d.ts +187 -5
- 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-CvqQYURw.d.ts → tokens-TIH4RkPl.d.ts} +10 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-NP2KCM7X.js.map +0 -1
- package/dist/chunk-PBQSHIID.js +0 -3
- package/dist/chunk-V5ZQG2H5.js.map +0 -1
|
@@ -15,16 +15,19 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
15
15
|
var DEFAULT_URLS = {
|
|
16
16
|
development: {
|
|
17
17
|
apiBase: "https://api-dev.vio.live",
|
|
18
|
-
graphQLBase: "https://graph-ql-dev.vio.live"
|
|
18
|
+
graphQLBase: "https://graph-ql-dev.vio.live",
|
|
19
|
+
eventsBase: "https://events-dev.vio.live"
|
|
19
20
|
},
|
|
20
21
|
// No separate staging GraphQL deployment yet — shares dev's.
|
|
21
22
|
testing: {
|
|
22
23
|
apiBase: "https://api-staging.vio.live",
|
|
23
|
-
graphQLBase: "https://graph-ql-dev.vio.live"
|
|
24
|
+
graphQLBase: "https://graph-ql-dev.vio.live",
|
|
25
|
+
eventsBase: "https://events-staging.vio.live"
|
|
24
26
|
},
|
|
25
27
|
production: {
|
|
26
28
|
apiBase: "https://api.vio.live",
|
|
27
|
-
graphQLBase: "https://graph-ql.vio.live"
|
|
29
|
+
graphQLBase: "https://graph-ql.vio.live",
|
|
30
|
+
eventsBase: "https://events.vio.live"
|
|
28
31
|
}
|
|
29
32
|
};
|
|
30
33
|
var ConfigurationSingleton = class {
|
|
@@ -43,6 +46,7 @@ var ConfigurationSingleton = class {
|
|
|
43
46
|
environment: env,
|
|
44
47
|
apiBase: input.apiBase ?? defaults.apiBase,
|
|
45
48
|
graphQLBase: input.graphQLBase ?? defaults.graphQLBase,
|
|
49
|
+
eventsBase: input.eventsBase ?? defaults.eventsBase,
|
|
46
50
|
stripePublishableKey: input.stripePublishableKey,
|
|
47
51
|
klarnaClientId: input.klarnaClientId,
|
|
48
52
|
// Sandbox by default — live Klarna must be opted into explicitly.
|
|
@@ -1512,6 +1516,13 @@ var CartManager = class extends EventTarget {
|
|
|
1512
1516
|
* slow response can't overwrite the state of a newer one. */
|
|
1513
1517
|
this.mutationSeq = /* @__PURE__ */ new Map();
|
|
1514
1518
|
this.appliedSeq = /* @__PURE__ */ new Map();
|
|
1519
|
+
/** In-flight AddItem/UpdateItem/DeleteItem per sponsor. Callers that need
|
|
1520
|
+
* the backend cart to reflect a just-made change (e.g. fetching shippings
|
|
1521
|
+
* right after an add) should `await waitForCartMutations(sponsorId)` first
|
|
1522
|
+
* — otherwise they can race the mutation and see the cart from before it
|
|
1523
|
+
* landed (Alan, 2026-08-18: shippings queried before AddItem settled,
|
|
1524
|
+
* came back empty). */
|
|
1525
|
+
this.inFlightMutations = /* @__PURE__ */ new Map();
|
|
1515
1526
|
/** Quantity intents for lines whose backend id hasn't arrived yet — replayed
|
|
1516
1527
|
* when the AddItem response reconciles (keyed by product::variant). */
|
|
1517
1528
|
this.pendingQtyIntents = /* @__PURE__ */ new Map();
|
|
@@ -1522,6 +1533,16 @@ var CartManager = class extends EventTarget {
|
|
|
1522
1533
|
this.mutationSeq.set(sponsorId, n);
|
|
1523
1534
|
return n;
|
|
1524
1535
|
}
|
|
1536
|
+
/** Resolves once any in-flight AddItem/UpdateItem/DeleteItem for this
|
|
1537
|
+
* sponsor has settled (success or failure). No-op if nothing is pending. */
|
|
1538
|
+
async waitForCartMutations(sponsorId) {
|
|
1539
|
+
const p = this.inFlightMutations.get(sponsorId);
|
|
1540
|
+
if (!p) return;
|
|
1541
|
+
try {
|
|
1542
|
+
await p;
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1525
1546
|
/** Apply a backend response only if no newer response was applied already. */
|
|
1526
1547
|
applyBackendCart(sponsorId, backendCart, seq) {
|
|
1527
1548
|
if (seq < (this.appliedSeq.get(sponsorId) ?? 0)) return;
|
|
@@ -1735,7 +1756,7 @@ var CartManager = class extends EventTarget {
|
|
|
1735
1756
|
const addedItem = existing ?? cart.items[cart.items.length - 1];
|
|
1736
1757
|
const addedLineId = addedItem.id;
|
|
1737
1758
|
const seq = this.nextSeq(opts.sponsorId);
|
|
1738
|
-
|
|
1759
|
+
const mutation = (async () => {
|
|
1739
1760
|
try {
|
|
1740
1761
|
const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
|
|
1741
1762
|
if (cartId) {
|
|
@@ -1765,6 +1786,12 @@ var CartManager = class extends EventTarget {
|
|
|
1765
1786
|
this.emitError("AddItem rejected by the backend \u2014 item removed from the cart", err);
|
|
1766
1787
|
}
|
|
1767
1788
|
})();
|
|
1789
|
+
this.inFlightMutations.set(opts.sponsorId, mutation);
|
|
1790
|
+
void mutation.finally(() => {
|
|
1791
|
+
if (this.inFlightMutations.get(opts.sponsorId) === mutation) {
|
|
1792
|
+
this.inFlightMutations.delete(opts.sponsorId);
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1768
1795
|
return addedItem;
|
|
1769
1796
|
}
|
|
1770
1797
|
intentKey(productId, variantId) {
|
|
@@ -1791,7 +1818,7 @@ var CartManager = class extends EventTarget {
|
|
|
1791
1818
|
}
|
|
1792
1819
|
const backendCartId = cart.cartId;
|
|
1793
1820
|
const seq = this.nextSeq(sponsorId);
|
|
1794
|
-
|
|
1821
|
+
const mutation = (async () => {
|
|
1795
1822
|
try {
|
|
1796
1823
|
const gqlOpts = await getCartGraphQLOptions(sponsorId);
|
|
1797
1824
|
const resCart = quantity <= 0 ? await deleteItem(backendCartId, targetItemId, gqlOpts) : await updateItem(backendCartId, targetItemId, null, quantity, gqlOpts);
|
|
@@ -1803,6 +1830,12 @@ var CartManager = class extends EventTarget {
|
|
|
1803
1830
|
void this.fetchCartFromBackend(sponsorId);
|
|
1804
1831
|
}
|
|
1805
1832
|
})();
|
|
1833
|
+
this.inFlightMutations.set(sponsorId, mutation);
|
|
1834
|
+
void mutation.finally(() => {
|
|
1835
|
+
if (this.inFlightMutations.get(sponsorId) === mutation) {
|
|
1836
|
+
this.inFlightMutations.delete(sponsorId);
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1806
1839
|
}
|
|
1807
1840
|
removeItem(lineItemId, sponsorId) {
|
|
1808
1841
|
this.updateQuantity(lineItemId, sponsorId, 0);
|
|
@@ -2357,41 +2390,95 @@ function findPayNowCategory(categories) {
|
|
|
2357
2390
|
const byId = (re) => categories.find((c) => c?.identifier && re.test(c.identifier));
|
|
2358
2391
|
const byName = (re) => categories.find((c) => c?.name && re.test(c.name));
|
|
2359
2392
|
const cat = categories.find((c) => c?.identifier === "pay_now") ?? byName(/betal\s*n[åa]|pay\s*now/i) ?? byId(/pay_now|direct_debit|direct_bank_transfer|pay_in_full|klarna_pay_now/i) ?? byName(/direkte|bank|kort|straks|sofort/i) ?? categories.find((c) => c?.identifier && !/pay_later|pay_over_time|slice_it/.test(c.identifier));
|
|
2393
|
+
console.log("findPayNowCategory - Selected: ", cat);
|
|
2360
2394
|
return cat?.identifier ?? null;
|
|
2361
2395
|
}
|
|
2362
2396
|
async function createKlarnaPaymentsWidget(input) {
|
|
2363
2397
|
const Klarna = await loadKpLib();
|
|
2398
|
+
console.log("[DEBUG Klarna] Initializing Klarna Payments");
|
|
2364
2399
|
Klarna.Payments.init({ client_token: input.clientToken });
|
|
2400
|
+
console.log("[DEBUG Klarna] Klarna Payments initialized");
|
|
2401
|
+
Klarna.Payments.on?.("fullscreenOverlayShown", () => {
|
|
2402
|
+
console.log("[DEBUG Klarna] FULLSCREEN OVERLAY SHOWN");
|
|
2403
|
+
});
|
|
2404
|
+
Klarna.Payments.on?.("fullscreenOverlayHidden", () => {
|
|
2405
|
+
console.log("[DEBUG Klarna] FULLSCREEN OVERLAY HIDDEN");
|
|
2406
|
+
});
|
|
2365
2407
|
let selected = input.category ?? findPayNowCategory(input.categories) ?? input.categories[0]?.identifier ?? "pay_now";
|
|
2408
|
+
if (typeof console !== "undefined") {
|
|
2409
|
+
console.log("[VioKlarna] Received payment_method_categories from backend:", input.categories);
|
|
2410
|
+
console.log("[VioKlarna] Selected initial payment_method_category:", selected);
|
|
2411
|
+
}
|
|
2412
|
+
const load = (category) => {
|
|
2413
|
+
if (category) selected = category;
|
|
2414
|
+
if (typeof console !== "undefined") {
|
|
2415
|
+
console.log(
|
|
2416
|
+
"[VioKlarna] Klarna.Payments.load calling with payment_method_category:",
|
|
2417
|
+
selected,
|
|
2418
|
+
"container:",
|
|
2419
|
+
input.container
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
if (input.container && "innerHTML" in input.container) {
|
|
2423
|
+
input.container.innerHTML = "";
|
|
2424
|
+
}
|
|
2425
|
+
return new Promise((resolve, reject) => {
|
|
2426
|
+
try {
|
|
2427
|
+
Klarna.Payments.load(
|
|
2428
|
+
{ container: input.container, payment_method_category: selected },
|
|
2429
|
+
{},
|
|
2430
|
+
(res) => {
|
|
2431
|
+
if (typeof console !== "undefined") {
|
|
2432
|
+
console.log(
|
|
2433
|
+
"[VioKlarna] Klarna.Payments.load response for payment_method_category:",
|
|
2434
|
+
selected,
|
|
2435
|
+
res
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
if (res?.error) reject(res.error);
|
|
2439
|
+
else resolve({ showForm: res?.show_form !== false });
|
|
2440
|
+
}
|
|
2441
|
+
);
|
|
2442
|
+
} catch (err) {
|
|
2443
|
+
reject(err);
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
};
|
|
2366
2447
|
return {
|
|
2367
2448
|
categories: input.categories,
|
|
2368
2449
|
get selected() {
|
|
2369
2450
|
return selected;
|
|
2370
2451
|
},
|
|
2371
|
-
load
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2452
|
+
load,
|
|
2453
|
+
async authorize(category, data) {
|
|
2454
|
+
const cat = category || selected;
|
|
2455
|
+
if (typeof console !== "undefined") {
|
|
2456
|
+
console.log("[DEBUG Klarna] BEFORE AUTHORIZE", {
|
|
2457
|
+
argumentCategory: category,
|
|
2458
|
+
argumentData: data,
|
|
2459
|
+
selected,
|
|
2460
|
+
resolvedCategory: cat
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
if (cat && cat !== selected) {
|
|
2464
|
+
await load(cat);
|
|
2465
|
+
}
|
|
2466
|
+
const finalData = { payment_method_category: cat, ...data ?? {} };
|
|
2467
|
+
if (typeof console !== "undefined") {
|
|
2468
|
+
console.log("[DEBUG Klarna] FINAL AUTHORIZE", { cat, selected, finalData });
|
|
2469
|
+
}
|
|
2389
2470
|
return new Promise((resolve, reject) => {
|
|
2390
2471
|
try {
|
|
2472
|
+
if (typeof console !== "undefined") {
|
|
2473
|
+
console.log("[DEBUG Klarna] ABOUT TO CALL AUTHORIZE");
|
|
2474
|
+
}
|
|
2391
2475
|
Klarna.Payments.authorize(
|
|
2392
|
-
{ payment_method_category:
|
|
2393
|
-
|
|
2476
|
+
{ payment_method_category: cat, auto_finalize: true },
|
|
2477
|
+
finalData,
|
|
2394
2478
|
(res) => {
|
|
2479
|
+
if (typeof console !== "undefined") {
|
|
2480
|
+
console.log("[DEBUG Klarna] RAW AUTHORIZE RESPONSE:", JSON.stringify(res, null, 2));
|
|
2481
|
+
}
|
|
2395
2482
|
if (res?.approved && res.authorization_token) {
|
|
2396
2483
|
resolve(res.authorization_token);
|
|
2397
2484
|
} else if (res?.error) {
|
|
@@ -2403,6 +2490,9 @@ async function createKlarnaPaymentsWidget(input) {
|
|
|
2403
2490
|
}
|
|
2404
2491
|
}
|
|
2405
2492
|
);
|
|
2493
|
+
if (typeof console !== "undefined") {
|
|
2494
|
+
console.log("[DEBUG Klarna] AUTHORIZE CALL RETURNED");
|
|
2495
|
+
}
|
|
2406
2496
|
} catch (err) {
|
|
2407
2497
|
reject(err);
|
|
2408
2498
|
}
|
|
@@ -2770,13 +2860,18 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
2770
2860
|
}
|
|
2771
2861
|
/**
|
|
2772
2862
|
* Real shipping options from the backend cart (per supplier), mapped to the
|
|
2773
|
-
* KlarnaShippingOption shape (minor units). Empty array on failure
|
|
2774
|
-
*
|
|
2863
|
+
* KlarnaShippingOption shape (minor units). Empty array on failure/no
|
|
2864
|
+
* options — Klarna callers use that as-is (no static fallback anymore, see
|
|
2865
|
+
* 2026-08-18: it only ever papered over a race, now fixed via
|
|
2866
|
+
* `waitForCartMutations`). `KLARNA_SHIPPING_OPTIONS` is still used
|
|
2867
|
+
* separately by Apple Pay's synchronous sheet setup, which can't await
|
|
2868
|
+
* this at all.
|
|
2775
2869
|
*/
|
|
2776
2870
|
async fetchAvailableShippings(sponsorId) {
|
|
2777
2871
|
try {
|
|
2778
2872
|
const spId = sponsorId ?? this.state?.sponsorId;
|
|
2779
2873
|
if (!spId) return [];
|
|
2874
|
+
await this.cartManager.waitForCartMutations(spId);
|
|
2780
2875
|
const cart = this.cartManager.getCart(spId);
|
|
2781
2876
|
let cartId = cart?.cartId;
|
|
2782
2877
|
if (!cartId) {
|
|
@@ -3335,8 +3430,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3335
3430
|
async mountKlarnaPayments(container, opts) {
|
|
3336
3431
|
if (!this.state) throw new Error("[VioCheckout] no active checkout \u2014 call open() first");
|
|
3337
3432
|
const sponsorId = this.state.sponsorId;
|
|
3338
|
-
const
|
|
3339
|
-
const shippings = fetchedShippings.length > 0 ? fetchedShippings : KLARNA_SHIPPING_OPTIONS;
|
|
3433
|
+
const shippings = await this.fetchAvailableShippings(sponsorId);
|
|
3340
3434
|
const { ctx } = opts?.withShipping ? this.buildKlarnaInstantContext(opts.shippingId, shippings) : { ctx: this.buildKlarnaPendingContext() };
|
|
3341
3435
|
const selectedOpt = shippings.find((s) => s.id === opts?.shippingId) ?? shippings[0];
|
|
3342
3436
|
if (selectedOpt?.id && selectedOpt?.supplierId) {
|
|
@@ -3396,6 +3490,12 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3396
3490
|
if (!nativeRes || !nativeRes.client_token) {
|
|
3397
3491
|
throw new Error("Klarna session creation failed: missing client_token");
|
|
3398
3492
|
}
|
|
3493
|
+
if (typeof console !== "undefined") {
|
|
3494
|
+
console.log(
|
|
3495
|
+
"[VioCheckout] CreatePaymentKlarnaNative payment_method_categories:",
|
|
3496
|
+
nativeRes.payment_method_categories
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3399
3499
|
if (nativeRes.checkout_id) {
|
|
3400
3500
|
ctx.checkoutId = nativeRes.checkout_id;
|
|
3401
3501
|
this.state = { ...this.state, checkoutId: nativeRes.checkout_id };
|
|
@@ -3421,9 +3521,15 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3421
3521
|
return widget.selected;
|
|
3422
3522
|
},
|
|
3423
3523
|
load: (category) => widget.load(category),
|
|
3424
|
-
authorize: async () => {
|
|
3524
|
+
authorize: async (category) => {
|
|
3425
3525
|
try {
|
|
3426
|
-
const
|
|
3526
|
+
const addr = buildKlarnaAddress(this.state?.address);
|
|
3527
|
+
const customer = buildKlarnaCustomer(this.state?.address);
|
|
3528
|
+
const authData = {
|
|
3529
|
+
...addr ? { billing_address: addr, shipping_address: addr } : {},
|
|
3530
|
+
...customer ? { customer } : {}
|
|
3531
|
+
};
|
|
3532
|
+
const token = await widget.authorize(category, authData);
|
|
3427
3533
|
await this.completeKlarnaOrder(token, ctx, { authorizationToken: token });
|
|
3428
3534
|
} catch (err) {
|
|
3429
3535
|
if (typeof console !== "undefined") {
|
|
@@ -3451,7 +3557,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3451
3557
|
/** Cart snapshot + chosen shipping line/amount, for express purchases. */
|
|
3452
3558
|
buildKlarnaInstantContext(shippingId, availableShippings = []) {
|
|
3453
3559
|
const base = this.buildKlarnaPendingContext();
|
|
3454
|
-
const options = availableShippings
|
|
3560
|
+
const options = availableShippings;
|
|
3455
3561
|
const shipping = (shippingId ? options.find((o) => o.id === shippingId) : void 0) ?? options.find((o) => o.preselected) ?? options[0];
|
|
3456
3562
|
const orderLines = [...base.orderLines];
|
|
3457
3563
|
if (shipping) {
|
|
@@ -3483,8 +3589,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
3483
3589
|
async startKlarnaInstant() {
|
|
3484
3590
|
if (!this.state) throw new Error("[VioCheckout] no active checkout \u2014 call open() first");
|
|
3485
3591
|
const sponsorId = this.state.sponsorId;
|
|
3486
|
-
const
|
|
3487
|
-
const shippings = fetchedShippings.length > 0 ? fetchedShippings : KLARNA_SHIPPING_OPTIONS;
|
|
3592
|
+
const shippings = await this.fetchAvailableShippings(sponsorId);
|
|
3488
3593
|
const { ctx } = this.buildKlarnaInstantContext(void 0, shippings);
|
|
3489
3594
|
let clientToken;
|
|
3490
3595
|
let categories;
|
|
@@ -3744,6 +3849,498 @@ function toSmallestUnit(amount, currency) {
|
|
|
3744
3849
|
return Math.round(amount * 100);
|
|
3745
3850
|
}
|
|
3746
3851
|
|
|
3852
|
+
// src/core/version.ts
|
|
3853
|
+
var SDK_VERSION = "0.5.0";
|
|
3854
|
+
|
|
3855
|
+
// src/core/analytics/analytics-manager.ts
|
|
3856
|
+
var SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
3857
|
+
var FLUSH_INTERVAL_MS = 5e3;
|
|
3858
|
+
var FLUSH_AT_COUNT = 20;
|
|
3859
|
+
var MAX_QUEUE = 500;
|
|
3860
|
+
var RETRY_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
3861
|
+
var ANON_KEY = "vio.anon.v1";
|
|
3862
|
+
var SESSION_KEY = "vio.session.v1";
|
|
3863
|
+
var IMPRESSION_THRESHOLD = 0.5;
|
|
3864
|
+
var IMPRESSION_DWELL_MS = 1e3;
|
|
3865
|
+
var AnalyticsManager = class extends EventTarget {
|
|
3866
|
+
constructor() {
|
|
3867
|
+
super(...arguments);
|
|
3868
|
+
this.started = false;
|
|
3869
|
+
this.options = { host: "custom", collector: true, dataLayer: false, debug: false, autoTrack: true };
|
|
3870
|
+
this.queue = [];
|
|
3871
|
+
this.flushTimer = null;
|
|
3872
|
+
this.retryAttempt = 0;
|
|
3873
|
+
this.retryNotBefore = 0;
|
|
3874
|
+
this.sessionId = null;
|
|
3875
|
+
this.sessionStartedAt = 0;
|
|
3876
|
+
/** Impression once-per-(session, component) guard; reset on session rotation. */
|
|
3877
|
+
this.impressedComponents = /* @__PURE__ */ new Set();
|
|
3878
|
+
/** Last NON-EMPTY cart snapshot — `purchase` uses it because several flows
|
|
3879
|
+
* (Apple Pay) clear the cart BEFORE `vio:payment-success` fires. */
|
|
3880
|
+
this.lastCartSnapshot = null;
|
|
3881
|
+
this.teardown = [];
|
|
3882
|
+
}
|
|
3883
|
+
// MARK: - Public API
|
|
3884
|
+
/**
|
|
3885
|
+
* Install listeners + begin flushing. Explicit on purpose (no module
|
|
3886
|
+
* side-effects). Safe to call twice (second call only updates options).
|
|
3887
|
+
*/
|
|
3888
|
+
start(options = {}) {
|
|
3889
|
+
this.options = {
|
|
3890
|
+
host: options.host ?? this.options.host,
|
|
3891
|
+
collector: options.collector ?? this.options.collector,
|
|
3892
|
+
dataLayer: options.dataLayer ?? this.options.dataLayer,
|
|
3893
|
+
debug: options.debug ?? this.options.debug,
|
|
3894
|
+
autoTrack: options.autoTrack ?? this.options.autoTrack
|
|
3895
|
+
};
|
|
3896
|
+
if (this.started || !hasDom()) return;
|
|
3897
|
+
this.started = true;
|
|
3898
|
+
this.ensureSession();
|
|
3899
|
+
this.flushTimer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);
|
|
3900
|
+
const onPageHide = () => {
|
|
3901
|
+
this.trackSessionEnd();
|
|
3902
|
+
this.flushWithBeacon();
|
|
3903
|
+
};
|
|
3904
|
+
window.addEventListener("pagehide", onPageHide);
|
|
3905
|
+
this.teardown.push(() => window.removeEventListener("pagehide", onPageHide));
|
|
3906
|
+
if (this.options.autoTrack) this.installAutoListeners();
|
|
3907
|
+
}
|
|
3908
|
+
/** Stop timers + listeners (tests / SPA unmount). Queue survives. */
|
|
3909
|
+
stop() {
|
|
3910
|
+
if (this.flushTimer) clearInterval(this.flushTimer);
|
|
3911
|
+
this.flushTimer = null;
|
|
3912
|
+
for (const fn of this.teardown.splice(0)) fn();
|
|
3913
|
+
this.started = false;
|
|
3914
|
+
}
|
|
3915
|
+
/** Attach the partner's user id (opaque) to all subsequent events. */
|
|
3916
|
+
identify(externalUserId) {
|
|
3917
|
+
this.externalUserId = externalUserId ?? void 0;
|
|
3918
|
+
}
|
|
3919
|
+
/** Queue one event. Works pre-init and pre-start (SSR: no-op). */
|
|
3920
|
+
track(name, opts = {}) {
|
|
3921
|
+
if (!hasDom()) return;
|
|
3922
|
+
const event = this.buildEvent(name, opts);
|
|
3923
|
+
if (!event) return;
|
|
3924
|
+
if (this.options.debug) {
|
|
3925
|
+
console.debug("[Vio.analytics]", event.name, event);
|
|
3926
|
+
}
|
|
3927
|
+
try {
|
|
3928
|
+
window.dispatchEvent(new CustomEvent("vio:analytics", { detail: event }));
|
|
3929
|
+
} catch {
|
|
3930
|
+
}
|
|
3931
|
+
if (this.options.dataLayer) pushGa4(event);
|
|
3932
|
+
this.queue.push(event);
|
|
3933
|
+
if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE);
|
|
3934
|
+
if (this.queue.length >= FLUSH_AT_COUNT) void this.flush();
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* Impression helper: observes an element and tracks ONE
|
|
3938
|
+
* `component_impression` per (session, campaignComponentId) when the
|
|
3939
|
+
* contract's rule is met (≥50% visible, ≥1s dwell). Returns an
|
|
3940
|
+
* unobserve function.
|
|
3941
|
+
*/
|
|
3942
|
+
observeImpression(element, context = {}, opts = {}) {
|
|
3943
|
+
if (!hasDom() || typeof IntersectionObserver === "undefined") return () => {
|
|
3944
|
+
};
|
|
3945
|
+
const name = opts.name ?? "component_impression";
|
|
3946
|
+
const dedupe = opts.key ?? String(
|
|
3947
|
+
context.campaignComponentId ?? context.locationId ?? context.componentTemplateId ?? "component"
|
|
3948
|
+
);
|
|
3949
|
+
let dwellTimer = null;
|
|
3950
|
+
const observer = new IntersectionObserver(
|
|
3951
|
+
(entries) => {
|
|
3952
|
+
for (const entry of entries) {
|
|
3953
|
+
if (entry.isIntersecting && entry.intersectionRatio >= IMPRESSION_THRESHOLD) {
|
|
3954
|
+
if (dwellTimer) continue;
|
|
3955
|
+
dwellTimer = setTimeout(() => {
|
|
3956
|
+
const key = `${name}:${dedupe}`;
|
|
3957
|
+
this.ensureSession();
|
|
3958
|
+
if (this.impressedComponents.has(key)) return;
|
|
3959
|
+
this.impressedComponents.add(key);
|
|
3960
|
+
this.track(name, { context });
|
|
3961
|
+
opts.onImpression?.();
|
|
3962
|
+
observer.disconnect();
|
|
3963
|
+
}, IMPRESSION_DWELL_MS);
|
|
3964
|
+
} else if (dwellTimer) {
|
|
3965
|
+
clearTimeout(dwellTimer);
|
|
3966
|
+
dwellTimer = null;
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
},
|
|
3970
|
+
{ threshold: [IMPRESSION_THRESHOLD] }
|
|
3971
|
+
);
|
|
3972
|
+
observer.observe(element);
|
|
3973
|
+
return () => {
|
|
3974
|
+
if (dwellTimer) clearTimeout(dwellTimer);
|
|
3975
|
+
observer.disconnect();
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
/** Force a flush (mostly for tests). */
|
|
3979
|
+
async flush() {
|
|
3980
|
+
if (!this.options.collector) {
|
|
3981
|
+
this.queue = [];
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
if (this.queue.length === 0) return;
|
|
3985
|
+
if (!Configuration.isInitialized) return;
|
|
3986
|
+
if (Date.now() < this.retryNotBefore) return;
|
|
3987
|
+
const batch = this.queue.splice(0, MAX_QUEUE);
|
|
3988
|
+
const body = JSON.stringify({
|
|
3989
|
+
apiKey: Configuration.get().apiKey,
|
|
3990
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3991
|
+
events: batch
|
|
3992
|
+
});
|
|
3993
|
+
try {
|
|
3994
|
+
const res = await fetch(`${eventsBase()}/v1/events`, {
|
|
3995
|
+
method: "POST",
|
|
3996
|
+
headers: { "Content-Type": "application/json" },
|
|
3997
|
+
body,
|
|
3998
|
+
keepalive: batch.length <= 20
|
|
3999
|
+
// keepalive caps payload at ~64KB
|
|
4000
|
+
});
|
|
4001
|
+
if (res.status === 401 || res.status === 400) {
|
|
4002
|
+
console.warn(`[Vio.analytics] collector rejected batch (HTTP ${res.status}) \u2014 dropped`);
|
|
4003
|
+
this.retryAttempt = 0;
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
4007
|
+
this.retryAttempt = 0;
|
|
4008
|
+
} catch {
|
|
4009
|
+
this.queue.unshift(...batch);
|
|
4010
|
+
const backoff = RETRY_BACKOFF_MS[Math.min(this.retryAttempt, RETRY_BACKOFF_MS.length - 1)] ?? 8e3;
|
|
4011
|
+
this.retryAttempt++;
|
|
4012
|
+
this.retryNotBefore = Date.now() + backoff;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
// MARK: - Internals
|
|
4016
|
+
buildEvent(name, opts) {
|
|
4017
|
+
this.ensureSession();
|
|
4018
|
+
if (!this.sessionId) return null;
|
|
4019
|
+
const anonId = this.anonId();
|
|
4020
|
+
if (!anonId) return null;
|
|
4021
|
+
const boot = vioBootstrapCache();
|
|
4022
|
+
const ctx = opts.context ?? {};
|
|
4023
|
+
const context = compact({
|
|
4024
|
+
campaign_id: ctx.campaignId ?? boot?.campaignId,
|
|
4025
|
+
broadcast_id: ctx.broadcastId,
|
|
4026
|
+
campaign_component_id: ctx.campaignComponentId,
|
|
4027
|
+
app_placement_id: ctx.appPlacementId,
|
|
4028
|
+
location_id: ctx.locationId,
|
|
4029
|
+
component_template_id: ctx.componentTemplateId,
|
|
4030
|
+
sponsor_id: ctx.sponsorId,
|
|
4031
|
+
activation_id: ctx.activationId,
|
|
4032
|
+
content_url: ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0),
|
|
4033
|
+
variant: ctx.variant
|
|
4034
|
+
});
|
|
4035
|
+
const com = opts.commerce;
|
|
4036
|
+
const commerce = com ? compact({
|
|
4037
|
+
items: com.items?.map(
|
|
4038
|
+
(i) => compact({
|
|
4039
|
+
product_id: String(i.productId),
|
|
4040
|
+
name: i.name,
|
|
4041
|
+
brand: i.brand,
|
|
4042
|
+
variant_id: i.variantId !== void 0 ? String(i.variantId) : void 0,
|
|
4043
|
+
price: i.price,
|
|
4044
|
+
quantity: i.quantity
|
|
4045
|
+
})
|
|
4046
|
+
),
|
|
4047
|
+
value: com.value,
|
|
4048
|
+
currency: com.currency,
|
|
4049
|
+
order_id: com.orderId,
|
|
4050
|
+
payment_method: com.paymentMethod
|
|
4051
|
+
}) : void 0;
|
|
4052
|
+
return {
|
|
4053
|
+
event_id: uuid(),
|
|
4054
|
+
name,
|
|
4055
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4056
|
+
surface: "web",
|
|
4057
|
+
host: this.options.host,
|
|
4058
|
+
sdk_version: SDK_VERSION,
|
|
4059
|
+
session_id: this.sessionId,
|
|
4060
|
+
anon_id: anonId,
|
|
4061
|
+
...this.externalUserId ? { external_user_id: this.externalUserId } : {},
|
|
4062
|
+
...Object.keys(context).length ? { context } : {},
|
|
4063
|
+
...commerce ? { commerce } : {},
|
|
4064
|
+
...opts.props ? { props: opts.props } : {}
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
anonId() {
|
|
4068
|
+
const stored = safeStorageGet(ANON_KEY);
|
|
4069
|
+
if (stored) return stored;
|
|
4070
|
+
const id = `a-${uuid()}`;
|
|
4071
|
+
safeStorageSet(ANON_KEY, id);
|
|
4072
|
+
return safeStorageGet(ANON_KEY) ?? id;
|
|
4073
|
+
}
|
|
4074
|
+
/** Rolling session: renewed by activity, rotated after 30 min idle. */
|
|
4075
|
+
ensureSession() {
|
|
4076
|
+
const now = Date.now();
|
|
4077
|
+
if (!this.sessionId) {
|
|
4078
|
+
const stored = safeStorageGet(SESSION_KEY);
|
|
4079
|
+
if (stored) {
|
|
4080
|
+
try {
|
|
4081
|
+
const parsed = JSON.parse(stored);
|
|
4082
|
+
if (now - parsed.ts < SESSION_TTL_MS) {
|
|
4083
|
+
this.sessionId = parsed.id;
|
|
4084
|
+
this.sessionStartedAt = parsed.startedAt ?? now;
|
|
4085
|
+
}
|
|
4086
|
+
} catch {
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
} else {
|
|
4090
|
+
const stored = safeStorageGet(SESSION_KEY);
|
|
4091
|
+
if (stored) {
|
|
4092
|
+
try {
|
|
4093
|
+
if (now - JSON.parse(stored).ts >= SESSION_TTL_MS) {
|
|
4094
|
+
this.sessionId = null;
|
|
4095
|
+
}
|
|
4096
|
+
} catch {
|
|
4097
|
+
this.sessionId = null;
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (!this.sessionId) {
|
|
4102
|
+
this.sessionId = `s-${uuid()}`;
|
|
4103
|
+
this.sessionStartedAt = now;
|
|
4104
|
+
this.impressedComponents.clear();
|
|
4105
|
+
safeStorageSet(
|
|
4106
|
+
SESSION_KEY,
|
|
4107
|
+
JSON.stringify({ id: this.sessionId, ts: now, startedAt: now })
|
|
4108
|
+
);
|
|
4109
|
+
this.track("session_start");
|
|
4110
|
+
} else {
|
|
4111
|
+
safeStorageSet(
|
|
4112
|
+
SESSION_KEY,
|
|
4113
|
+
JSON.stringify({ id: this.sessionId, ts: now, startedAt: this.sessionStartedAt })
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
trackSessionEnd() {
|
|
4118
|
+
if (!this.sessionId) return;
|
|
4119
|
+
this.track("session_end", {
|
|
4120
|
+
props: { duration_ms: Date.now() - this.sessionStartedAt }
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
flushWithBeacon() {
|
|
4124
|
+
if (!this.options.collector || this.queue.length === 0) return;
|
|
4125
|
+
if (!Configuration.isInitialized) return;
|
|
4126
|
+
const batch = this.queue.splice(0, MAX_QUEUE);
|
|
4127
|
+
const body = JSON.stringify({
|
|
4128
|
+
apiKey: Configuration.get().apiKey,
|
|
4129
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4130
|
+
events: batch
|
|
4131
|
+
});
|
|
4132
|
+
const ok = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(`${eventsBase()}/v1/events`, new Blob([body], { type: "text/plain" }));
|
|
4133
|
+
if (!ok) this.queue.unshift(...batch);
|
|
4134
|
+
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Auto-instrumentation over the SDK's own DOM events — the host app
|
|
4137
|
+
* writes ZERO tracking code for the commerce funnel.
|
|
4138
|
+
*/
|
|
4139
|
+
installAutoListeners() {
|
|
4140
|
+
const on = (type, handler) => {
|
|
4141
|
+
window.addEventListener(type, handler);
|
|
4142
|
+
this.teardown.push(() => window.removeEventListener(type, handler));
|
|
4143
|
+
};
|
|
4144
|
+
const cart = vioCart();
|
|
4145
|
+
if (cart) {
|
|
4146
|
+
const onChange = (ev) => {
|
|
4147
|
+
const detail = ev.detail;
|
|
4148
|
+
if (!detail) return;
|
|
4149
|
+
const items = [];
|
|
4150
|
+
detail.cartsBySponsor?.forEach((state) => items.push(...state.items));
|
|
4151
|
+
if (items.length > 0) {
|
|
4152
|
+
this.lastCartSnapshot = { items: items.map((i) => ({ ...i })), currency: items[0]?.currency };
|
|
4153
|
+
}
|
|
4154
|
+
};
|
|
4155
|
+
cart.addEventListener("change", onChange);
|
|
4156
|
+
this.teardown.push(() => cart.removeEventListener("change", onChange));
|
|
4157
|
+
}
|
|
4158
|
+
on("vio:added-to-cart", (ev) => {
|
|
4159
|
+
const d = ev.detail ?? {};
|
|
4160
|
+
const line = this.findLine(d.productId, d.variantId);
|
|
4161
|
+
this.track("add_to_cart", {
|
|
4162
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0 },
|
|
4163
|
+
commerce: {
|
|
4164
|
+
items: [
|
|
4165
|
+
{
|
|
4166
|
+
productId: d.productId ?? line?.productId ?? "",
|
|
4167
|
+
name: line?.name,
|
|
4168
|
+
brand: line?.brand,
|
|
4169
|
+
variantId: d.variantId ?? line?.variantId,
|
|
4170
|
+
price: line?.unitPrice,
|
|
4171
|
+
quantity: d.quantity ?? 1
|
|
4172
|
+
}
|
|
4173
|
+
],
|
|
4174
|
+
value: line ? line.unitPrice * (d.quantity ?? 1) : void 0,
|
|
4175
|
+
currency: line?.currency
|
|
4176
|
+
}
|
|
4177
|
+
});
|
|
4178
|
+
});
|
|
4179
|
+
on("vio:product-click", (ev) => {
|
|
4180
|
+
const d = ev.detail ?? {};
|
|
4181
|
+
if (d.productId === void 0) return;
|
|
4182
|
+
const price = Number(d.price);
|
|
4183
|
+
this.track("select_item", {
|
|
4184
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0 },
|
|
4185
|
+
commerce: {
|
|
4186
|
+
items: [
|
|
4187
|
+
{
|
|
4188
|
+
productId: d.productId,
|
|
4189
|
+
name: d.name,
|
|
4190
|
+
brand: d.brand,
|
|
4191
|
+
price: Number.isFinite(price) ? price : void 0
|
|
4192
|
+
}
|
|
4193
|
+
]
|
|
4194
|
+
}
|
|
4195
|
+
});
|
|
4196
|
+
});
|
|
4197
|
+
on("vio:open-cart", () => {
|
|
4198
|
+
const snap = this.cartCommerce();
|
|
4199
|
+
this.track("view_cart", snap ? { commerce: snap } : {});
|
|
4200
|
+
});
|
|
4201
|
+
on("vio:checkout-open", (ev) => {
|
|
4202
|
+
const d = ev.detail ?? {};
|
|
4203
|
+
const snap = this.cartCommerce(d.sponsorId);
|
|
4204
|
+
this.track("begin_checkout", {
|
|
4205
|
+
context: { sponsorId: d.sponsorId },
|
|
4206
|
+
...snap ? { commerce: snap } : {}
|
|
4207
|
+
});
|
|
4208
|
+
});
|
|
4209
|
+
on("vio:payment-success", (ev) => {
|
|
4210
|
+
const d = ev.detail ?? {};
|
|
4211
|
+
const sponsorId = d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0;
|
|
4212
|
+
const snap = this.cartCommerce(sponsorId);
|
|
4213
|
+
this.track("purchase", {
|
|
4214
|
+
context: { sponsorId },
|
|
4215
|
+
commerce: {
|
|
4216
|
+
...snap ?? {},
|
|
4217
|
+
orderId: d.orderId,
|
|
4218
|
+
paymentMethod: d.method
|
|
4219
|
+
}
|
|
4220
|
+
});
|
|
4221
|
+
});
|
|
4222
|
+
}
|
|
4223
|
+
/** Current cart → commerce payload; falls back to the pre-clear snapshot. */
|
|
4224
|
+
cartCommerce(sponsorId) {
|
|
4225
|
+
let items = [];
|
|
4226
|
+
const cart = vioCart();
|
|
4227
|
+
if (cart) {
|
|
4228
|
+
cart.getAllCarts().forEach((state, id) => {
|
|
4229
|
+
if (sponsorId === void 0 || id === sponsorId) items.push(...state.items);
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
if (items.length === 0 && this.lastCartSnapshot) {
|
|
4233
|
+
items = this.lastCartSnapshot.items.filter(
|
|
4234
|
+
(i) => sponsorId === void 0 || i.sponsorId === sponsorId
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
if (items.length === 0) return null;
|
|
4238
|
+
return {
|
|
4239
|
+
items: items.map((i) => ({
|
|
4240
|
+
productId: i.productId,
|
|
4241
|
+
name: i.name,
|
|
4242
|
+
brand: i.brand,
|
|
4243
|
+
variantId: i.variantId,
|
|
4244
|
+
price: i.unitPrice,
|
|
4245
|
+
quantity: i.quantity
|
|
4246
|
+
})),
|
|
4247
|
+
value: items.reduce((sum, i) => sum + i.unitPrice * (i.quantity ?? 1), 0),
|
|
4248
|
+
currency: items[0]?.currency ?? this.lastCartSnapshot?.currency
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
findLine(productId, variantId) {
|
|
4252
|
+
const pool = [];
|
|
4253
|
+
vioCart()?.getAllCarts().forEach((state) => pool.push(...state.items));
|
|
4254
|
+
if (pool.length === 0 && this.lastCartSnapshot) pool.push(...this.lastCartSnapshot.items);
|
|
4255
|
+
return pool.find(
|
|
4256
|
+
(i) => String(i.productId) === String(productId) && (variantId === void 0 || String(i.variantId) === String(variantId))
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
};
|
|
4260
|
+
function hasDom() {
|
|
4261
|
+
return typeof window !== "undefined";
|
|
4262
|
+
}
|
|
4263
|
+
function uuid() {
|
|
4264
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
4265
|
+
return crypto.randomUUID();
|
|
4266
|
+
}
|
|
4267
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
4268
|
+
const r = Math.random() * 16 | 0;
|
|
4269
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
4270
|
+
});
|
|
4271
|
+
}
|
|
4272
|
+
function safeStorageGet(key) {
|
|
4273
|
+
try {
|
|
4274
|
+
return window.localStorage.getItem(key);
|
|
4275
|
+
} catch {
|
|
4276
|
+
return null;
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
function safeStorageSet(key, value) {
|
|
4280
|
+
try {
|
|
4281
|
+
window.localStorage.setItem(key, value);
|
|
4282
|
+
} catch {
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
function compact(obj) {
|
|
4286
|
+
const out = {};
|
|
4287
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
4288
|
+
if (v !== void 0 && v !== null && v !== "") out[k] = v;
|
|
4289
|
+
}
|
|
4290
|
+
return out;
|
|
4291
|
+
}
|
|
4292
|
+
function eventsBase() {
|
|
4293
|
+
return Configuration.get().eventsBase;
|
|
4294
|
+
}
|
|
4295
|
+
function vioFacade() {
|
|
4296
|
+
const g = globalThis;
|
|
4297
|
+
return g.__VIO_FACADE__ ?? null;
|
|
4298
|
+
}
|
|
4299
|
+
function vioBootstrapCache() {
|
|
4300
|
+
return vioFacade()?.bootstrapCache ?? null;
|
|
4301
|
+
}
|
|
4302
|
+
function vioCart() {
|
|
4303
|
+
try {
|
|
4304
|
+
return vioFacade()?.cart ?? null;
|
|
4305
|
+
} catch {
|
|
4306
|
+
return null;
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function pushGa4(event) {
|
|
4310
|
+
const commerceNames = /* @__PURE__ */ new Set([
|
|
4311
|
+
"view_item_list",
|
|
4312
|
+
"select_item",
|
|
4313
|
+
"view_item",
|
|
4314
|
+
"add_to_cart",
|
|
4315
|
+
"view_cart",
|
|
4316
|
+
"begin_checkout",
|
|
4317
|
+
"purchase"
|
|
4318
|
+
]);
|
|
4319
|
+
if (!commerceNames.has(event.name)) return;
|
|
4320
|
+
const w = window;
|
|
4321
|
+
w.dataLayer = w.dataLayer || [];
|
|
4322
|
+
const commerce = event.commerce ?? {};
|
|
4323
|
+
w.dataLayer.push({ ecommerce: null });
|
|
4324
|
+
w.dataLayer.push({
|
|
4325
|
+
event: event.name,
|
|
4326
|
+
ecommerce: compact({
|
|
4327
|
+
currency: commerce.currency,
|
|
4328
|
+
value: commerce.value,
|
|
4329
|
+
transaction_id: commerce.order_id,
|
|
4330
|
+
items: commerce.items?.map(
|
|
4331
|
+
(i) => compact({
|
|
4332
|
+
item_id: i.product_id,
|
|
4333
|
+
item_name: i.name,
|
|
4334
|
+
item_brand: i.brand,
|
|
4335
|
+
item_variant: i.variant_id,
|
|
4336
|
+
price: i.price,
|
|
4337
|
+
quantity: i.quantity
|
|
4338
|
+
})
|
|
4339
|
+
)
|
|
4340
|
+
})
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4343
|
+
|
|
3747
4344
|
// src/core/client.ts
|
|
3748
4345
|
var VioFacade = class {
|
|
3749
4346
|
constructor() {
|
|
@@ -3758,6 +4355,7 @@ var VioFacade = class {
|
|
|
3758
4355
|
// need Vio.init() to work (state is purely local).
|
|
3759
4356
|
this.cartManager = null;
|
|
3760
4357
|
this.checkoutManager = null;
|
|
4358
|
+
this.analyticsManager = null;
|
|
3761
4359
|
}
|
|
3762
4360
|
init(config) {
|
|
3763
4361
|
Configuration.init(config);
|
|
@@ -3785,6 +4383,14 @@ var VioFacade = class {
|
|
|
3785
4383
|
if (!this.checkoutManager) this.checkoutManager = new CheckoutManager(this.cart);
|
|
3786
4384
|
return this.checkoutManager;
|
|
3787
4385
|
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Analytics — track() queues even pre-init; nothing is sent (and no
|
|
4388
|
+
* listeners are installed) until an explicit `Vio.analytics.start()`.
|
|
4389
|
+
*/
|
|
4390
|
+
get analytics() {
|
|
4391
|
+
if (!this.analyticsManager) this.analyticsManager = new AnalyticsManager();
|
|
4392
|
+
return this.analyticsManager;
|
|
4393
|
+
}
|
|
3788
4394
|
// MARK: - Backend-bound
|
|
3789
4395
|
/** Lazy VioApi for REST endpoints on `api-dev.vio.live`. */
|
|
3790
4396
|
get api() {
|
|
@@ -3854,6 +4460,6 @@ var VioFacade = class {
|
|
|
3854
4460
|
var _vioGlobal = globalThis;
|
|
3855
4461
|
var Vio = _vioGlobal.__VIO_FACADE__ ?? (_vioGlobal.__VIO_FACADE__ = new VioFacade());
|
|
3856
4462
|
|
|
3857
|
-
export { ADD_ITEM_MUTATION, 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,
|
|
3858
|
-
//# sourceMappingURL=chunk-
|
|
3859
|
-
//# sourceMappingURL=chunk-
|
|
4463
|
+
export { ADD_ITEM_MUTATION, 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, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, Vio, VioApi, VioApiError, __decorateClass, addItem, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, primaryImageUrl, showApplePaySheet, updateCheckout, updateItem, updateShippingsBySupplier };
|
|
4464
|
+
//# sourceMappingURL=chunk-HNQG56SD.js.map
|
|
4465
|
+
//# sourceMappingURL=chunk-HNQG56SD.js.map
|