@vio-live/web-sdk 0.28.0 → 0.32.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/CHANGELOG.md +243 -2
- package/README.md +175 -13
- package/dist/{chunk-Q2NALS7P.js → chunk-C5Q6W4B2.js} +1453 -199
- package/dist/chunk-C5Q6W4B2.js.map +1 -0
- package/dist/{chunk-AFJ5M53F.js → chunk-IR4XYTZL.js} +707 -92
- package/dist/chunk-IR4XYTZL.js.map +1 -0
- package/dist/chunk-QGJUBVH3.js +3 -0
- package/dist/{chunk-67QESEV2.js.map → chunk-QGJUBVH3.js.map} +1 -1
- package/dist/core/index.d.ts +226 -6
- 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-y0KzN7bP.d.ts → tokens-D1DCVzOf.d.ts} +331 -13
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-67QESEV2.js +0 -3
- package/dist/chunk-AFJ5M53F.js.map +0 -1
- package/dist/chunk-Q2NALS7P.js.map +0 -1
|
@@ -207,9 +207,9 @@ function slotDeclarationKey(location2, pageCampaignId) {
|
|
|
207
207
|
const kind = normalizeSlotKind(location2.kind);
|
|
208
208
|
return location2.id + (location2.displayName ? `${location2.displayName}` : "") + (campaignId !== void 0 ? `${campaignId}` : "") + (kind !== void 0 ? `${kind}` : "");
|
|
209
209
|
}
|
|
210
|
-
function pendingSlotKeys(
|
|
210
|
+
function pendingSlotKeys(memory2, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
|
|
211
211
|
return keys.filter((key) => {
|
|
212
|
-
const at =
|
|
212
|
+
const at = memory2?.[key];
|
|
213
213
|
if (typeof at !== "number" || !Number.isFinite(at)) return true;
|
|
214
214
|
return now - at >= ttl || now < at;
|
|
215
215
|
});
|
|
@@ -218,8 +218,8 @@ function slotKeyId(key) {
|
|
|
218
218
|
const cut4 = key.search(/[\u001f\u001e\u001d]/);
|
|
219
219
|
return cut4 >= 0 ? key.slice(0, cut4) : key;
|
|
220
220
|
}
|
|
221
|
-
function rememberSlotKeys(
|
|
222
|
-
const next = { ...
|
|
221
|
+
function rememberSlotKeys(memory2, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
|
|
222
|
+
const next = { ...memory2 ?? {} };
|
|
223
223
|
const ids = new Set(keys.map(slotKeyId));
|
|
224
224
|
for (const existing of Object.keys(next)) {
|
|
225
225
|
if (ids.has(slotKeyId(existing)) && !keys.includes(existing)) delete next[existing];
|
|
@@ -258,10 +258,10 @@ function readSlotDeclaration(storageKey) {
|
|
|
258
258
|
return {};
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
function writeSlotDeclaration(storageKey,
|
|
261
|
+
function writeSlotDeclaration(storageKey, memory2) {
|
|
262
262
|
try {
|
|
263
263
|
if (typeof localStorage === "undefined") return;
|
|
264
|
-
localStorage.setItem(storageKey, JSON.stringify(
|
|
264
|
+
localStorage.setItem(storageKey, JSON.stringify(memory2));
|
|
265
265
|
} catch {
|
|
266
266
|
}
|
|
267
267
|
}
|
|
@@ -466,6 +466,51 @@ function createVioApi(options) {
|
|
|
466
466
|
return new VioApi(options);
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
// src/core/safe-storage.ts
|
|
470
|
+
var memory = {
|
|
471
|
+
local: /* @__PURE__ */ new Map(),
|
|
472
|
+
session: /* @__PURE__ */ new Map()
|
|
473
|
+
};
|
|
474
|
+
function area(kind) {
|
|
475
|
+
try {
|
|
476
|
+
const s = globalThis[kind === "local" ? "localStorage" : "sessionStorage"];
|
|
477
|
+
return s && typeof s.getItem === "function" ? s : null;
|
|
478
|
+
} catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function storageGet(kind, key) {
|
|
483
|
+
const kept = memory[kind];
|
|
484
|
+
if (kept.has(key)) return kept.get(key) ?? null;
|
|
485
|
+
const s = area(kind);
|
|
486
|
+
if (!s) return null;
|
|
487
|
+
try {
|
|
488
|
+
return s.getItem(key);
|
|
489
|
+
} catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function storageSet(kind, key, value, opts = {}) {
|
|
494
|
+
try {
|
|
495
|
+
const s = area(kind);
|
|
496
|
+
if (s) {
|
|
497
|
+
s.setItem(key, value);
|
|
498
|
+
memory[kind].delete(key);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
if (opts.memory !== false) memory[kind].set(key, value);
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
function storageRemove(kind, key) {
|
|
507
|
+
memory[kind].delete(key);
|
|
508
|
+
try {
|
|
509
|
+
area(kind)?.removeItem(key);
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
469
514
|
// src/core/types.ts
|
|
470
515
|
function displayPrice(product) {
|
|
471
516
|
const p = product.price;
|
|
@@ -475,37 +520,24 @@ function displayPrice(product) {
|
|
|
475
520
|
function getGlobalCountryCode() {
|
|
476
521
|
const g = globalThis;
|
|
477
522
|
if (g.__VIO_COUNTRY__) return g.__VIO_COUNTRY__;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const v = localStorage.getItem("vio.country");
|
|
481
|
-
if (v) return v;
|
|
482
|
-
} catch {
|
|
483
|
-
}
|
|
484
|
-
}
|
|
523
|
+
const stored = storageGet("local", "vio.country");
|
|
524
|
+
if (stored) return stored;
|
|
485
525
|
return "NO";
|
|
486
526
|
}
|
|
487
527
|
function getGlobalLocale() {
|
|
488
528
|
const g = globalThis;
|
|
489
529
|
if (g.__VIO_LOCALE__) return g.__VIO_LOCALE__;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const v = localStorage.getItem("vio.locale");
|
|
493
|
-
if (v) return v;
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
}
|
|
530
|
+
const stored = storageGet("local", "vio.locale");
|
|
531
|
+
if (stored) return stored;
|
|
497
532
|
return "nb-NO";
|
|
498
533
|
}
|
|
499
534
|
function getGlobalSymbol(currency) {
|
|
500
535
|
const activeCurrency = currency || getGlobalCurrency();
|
|
501
536
|
const g = globalThis;
|
|
502
537
|
if (!currency && g.__VIO_SYMBOL__) return g.__VIO_SYMBOL__;
|
|
503
|
-
if (!currency
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (v) return v;
|
|
507
|
-
} catch {
|
|
508
|
-
}
|
|
538
|
+
if (!currency) {
|
|
539
|
+
const v = storageGet("local", "vio.symbol");
|
|
540
|
+
if (v) return v;
|
|
509
541
|
}
|
|
510
542
|
const curr = String(activeCurrency).toUpperCase();
|
|
511
543
|
if (curr === "NOK" || curr === "DKK") return "Kr";
|
|
@@ -519,13 +551,8 @@ function getGlobalSymbol(currency) {
|
|
|
519
551
|
function getGlobalCurrency() {
|
|
520
552
|
const g = globalThis;
|
|
521
553
|
if (g.__VIO_CURRENCY__) return g.__VIO_CURRENCY__;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const v = localStorage.getItem("vio.currency");
|
|
525
|
-
if (v) return v;
|
|
526
|
-
} catch {
|
|
527
|
-
}
|
|
528
|
-
}
|
|
554
|
+
const stored = storageGet("local", "vio.currency");
|
|
555
|
+
if (stored) return stored;
|
|
529
556
|
return "NOK";
|
|
530
557
|
}
|
|
531
558
|
var validatedLocales = /* @__PURE__ */ new Map();
|
|
@@ -665,6 +692,80 @@ var GET_PRODUCT_SELLER = (
|
|
|
665
692
|
}
|
|
666
693
|
`
|
|
667
694
|
);
|
|
695
|
+
var GET_PRODUCT_SHIPPING = (
|
|
696
|
+
/* GraphQL */
|
|
697
|
+
`
|
|
698
|
+
query GetProductShipping(
|
|
699
|
+
$productIds: [Int!]
|
|
700
|
+
$useCache: Boolean!
|
|
701
|
+
$currency: String
|
|
702
|
+
$shippingCountryCode: String
|
|
703
|
+
) {
|
|
704
|
+
Channel {
|
|
705
|
+
GetProductsByIds(
|
|
706
|
+
product_ids: $productIds
|
|
707
|
+
useCache: $useCache
|
|
708
|
+
currency: $currency
|
|
709
|
+
shipping_country_code: $shippingCountryCode
|
|
710
|
+
) {
|
|
711
|
+
id
|
|
712
|
+
product_shipping {
|
|
713
|
+
id
|
|
714
|
+
name
|
|
715
|
+
description
|
|
716
|
+
default
|
|
717
|
+
shipping_country {
|
|
718
|
+
country
|
|
719
|
+
price {
|
|
720
|
+
amount
|
|
721
|
+
amount_incl_taxes
|
|
722
|
+
currency_code
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
`
|
|
730
|
+
);
|
|
731
|
+
var GET_PRODUCT_SHIPPING_WITH_LOGO = (
|
|
732
|
+
/* GraphQL */
|
|
733
|
+
`
|
|
734
|
+
query GetProductShippingWithLogo(
|
|
735
|
+
$productIds: [Int!]
|
|
736
|
+
$useCache: Boolean!
|
|
737
|
+
$currency: String
|
|
738
|
+
$shippingCountryCode: String
|
|
739
|
+
) {
|
|
740
|
+
Channel {
|
|
741
|
+
GetProductsByIds(
|
|
742
|
+
product_ids: $productIds
|
|
743
|
+
useCache: $useCache
|
|
744
|
+
currency: $currency
|
|
745
|
+
shipping_country_code: $shippingCountryCode
|
|
746
|
+
) {
|
|
747
|
+
id
|
|
748
|
+
product_shipping {
|
|
749
|
+
id
|
|
750
|
+
name
|
|
751
|
+
description
|
|
752
|
+
logo_url
|
|
753
|
+
default
|
|
754
|
+
shipping_country {
|
|
755
|
+
country
|
|
756
|
+
price {
|
|
757
|
+
amount
|
|
758
|
+
amount_incl_taxes
|
|
759
|
+
currency_code
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
`
|
|
767
|
+
);
|
|
768
|
+
var gatewayHasShippingLogo;
|
|
668
769
|
var ChannelProduct = class {
|
|
669
770
|
constructor(client) {
|
|
670
771
|
this.client = client;
|
|
@@ -704,7 +805,64 @@ var ChannelProduct = class {
|
|
|
704
805
|
return null;
|
|
705
806
|
}
|
|
706
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* The product's shipping rates for the page's market (or `country`),
|
|
810
|
+
* cheapest first; `[]` when it has none there, `null` when they could not
|
|
811
|
+
* be read. Never throws. Never from the cache, for the reason given at
|
|
812
|
+
* getSellerCompany.
|
|
813
|
+
*/
|
|
814
|
+
async getShippingRates(productId, options = {}) {
|
|
815
|
+
const country = options.country || getGlobalCountryCode();
|
|
816
|
+
const variables = {
|
|
817
|
+
productIds: [productId],
|
|
818
|
+
useCache: false,
|
|
819
|
+
currency: options.currency ?? getGlobalCurrency(),
|
|
820
|
+
shippingCountryCode: country
|
|
821
|
+
};
|
|
822
|
+
try {
|
|
823
|
+
let data;
|
|
824
|
+
if (gatewayHasShippingLogo === false) {
|
|
825
|
+
data = await this.client.request(GET_PRODUCT_SHIPPING, variables);
|
|
826
|
+
} else {
|
|
827
|
+
try {
|
|
828
|
+
data = await this.client.request(GET_PRODUCT_SHIPPING_WITH_LOGO, variables);
|
|
829
|
+
gatewayHasShippingLogo = true;
|
|
830
|
+
} catch (err) {
|
|
831
|
+
if (!/logo_url/.test(String(err?.message ?? ""))) throw err;
|
|
832
|
+
gatewayHasShippingLogo = false;
|
|
833
|
+
data = await this.client.request(GET_PRODUCT_SHIPPING, variables);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const product = data.Channel?.GetProductsByIds?.[0];
|
|
837
|
+
if (!product) return null;
|
|
838
|
+
return shippingRatesFor(product.product_shipping, country);
|
|
839
|
+
} catch {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
707
843
|
};
|
|
844
|
+
function shippingRatesFor(productShipping, country) {
|
|
845
|
+
if (!Array.isArray(productShipping)) return [];
|
|
846
|
+
const want = String(country || "").trim().toUpperCase();
|
|
847
|
+
const out = [];
|
|
848
|
+
for (const s of productShipping) {
|
|
849
|
+
const countries = Array.isArray(s?.shipping_country) ? s.shipping_country : [];
|
|
850
|
+
const row = countries.find((c) => String(c?.country ?? "").trim().toUpperCase() === want);
|
|
851
|
+
const price = row?.price;
|
|
852
|
+
const amount = typeof price?.amount_incl_taxes === "number" ? price.amount_incl_taxes : price?.amount;
|
|
853
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) continue;
|
|
854
|
+
out.push({
|
|
855
|
+
id: String(s.id ?? ""),
|
|
856
|
+
name: String(s.name ?? ""),
|
|
857
|
+
price: amount,
|
|
858
|
+
currency: String(price?.currency_code ?? ""),
|
|
859
|
+
default: s.default === true,
|
|
860
|
+
description: typeof s.description === "string" ? s.description.trim() : "",
|
|
861
|
+
logoUrl: httpsUrlOrEmpty(s.logo_url ?? s.logoUrl)
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return out.sort((a, b) => a.price - b.price);
|
|
865
|
+
}
|
|
708
866
|
var Channel = class {
|
|
709
867
|
constructor(client) {
|
|
710
868
|
this.product = new ChannelProduct(client);
|
|
@@ -723,6 +881,11 @@ var CommerceClient = class {
|
|
|
723
881
|
function createCommerceClient(options) {
|
|
724
882
|
return new CommerceClient(options);
|
|
725
883
|
}
|
|
884
|
+
function httpsUrlOrEmpty(value) {
|
|
885
|
+
if (typeof value !== "string") return "";
|
|
886
|
+
const url = value.trim();
|
|
887
|
+
return /^https:\/\/[^\s]+$/i.test(url) ? url : "";
|
|
888
|
+
}
|
|
726
889
|
|
|
727
890
|
// src/core/content.ts
|
|
728
891
|
var CONTENT_ID_QUERY = "vio_content";
|
|
@@ -1890,11 +2053,10 @@ async function getCartGraphQLOptions(sponsorId) {
|
|
|
1890
2053
|
}
|
|
1891
2054
|
var CUSTOMER_SESSION_KEY = "vio.customer_session_id.v1";
|
|
1892
2055
|
function getCustomerSessionId() {
|
|
1893
|
-
|
|
1894
|
-
let id = localStorage.getItem(CUSTOMER_SESSION_KEY);
|
|
2056
|
+
let id = storageGet("local", CUSTOMER_SESSION_KEY);
|
|
1895
2057
|
if (!id) {
|
|
1896
2058
|
id = "cs_" + Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
|
|
1897
|
-
|
|
2059
|
+
storageSet("local", CUSTOMER_SESSION_KEY, id);
|
|
1898
2060
|
}
|
|
1899
2061
|
return id;
|
|
1900
2062
|
}
|
|
@@ -2214,7 +2376,36 @@ var CartManager = class extends EventTarget {
|
|
|
2214
2376
|
/** Quantity intents for lines whose backend id hasn't arrived yet — replayed
|
|
2215
2377
|
* when the AddItem response reconciles (keyed by product::variant). */
|
|
2216
2378
|
this.pendingQtyIntents = /* @__PURE__ */ new Map();
|
|
2379
|
+
/**
|
|
2380
|
+
* Another tab of the site wrote the carts: adopt what it wrote, sponsor by
|
|
2381
|
+
* sponsor — its lines and its backend cart id, or nothing when it cleared
|
|
2382
|
+
* the cart (a payment) — except for a sponsor this tab is mid-write for:
|
|
2383
|
+
* its own answer lands in a moment and is persisted then, over whatever
|
|
2384
|
+
* the other tab had. `key` is null when the other tab cleared the whole
|
|
2385
|
+
* storage. Says `change` (the drawer) and `vio:cart-updated` per sponsor
|
|
2386
|
+
* that changed (`source: 'storage'`); nothing when nothing did.
|
|
2387
|
+
*/
|
|
2388
|
+
this.onStorage = (e) => {
|
|
2389
|
+
if (e.key !== null && e.key !== STORAGE_KEY) return;
|
|
2390
|
+
const stored = this.loadFromStorage();
|
|
2391
|
+
const changed = [];
|
|
2392
|
+
for (const sponsorId of /* @__PURE__ */ new Set([...this.cartsBySponsor.keys(), ...stored.keys()])) {
|
|
2393
|
+
if (this.hasMutationsInFlight(sponsorId) || this.cartIdPromises.has(sponsorId)) continue;
|
|
2394
|
+
const mine = this.cartsBySponsor.get(sponsorId);
|
|
2395
|
+
const theirs = stored.get(sponsorId);
|
|
2396
|
+
if (JSON.stringify(mine ?? null) === JSON.stringify(theirs ?? null)) continue;
|
|
2397
|
+
if (theirs) this.cartsBySponsor.set(sponsorId, theirs);
|
|
2398
|
+
else this.cartsBySponsor.delete(sponsorId);
|
|
2399
|
+
changed.push(sponsorId);
|
|
2400
|
+
}
|
|
2401
|
+
if (changed.length === 0) return;
|
|
2402
|
+
this.emit();
|
|
2403
|
+
for (const sponsorId of changed) this.emitCartUpdated(sponsorId, "storage");
|
|
2404
|
+
};
|
|
2217
2405
|
this.cartsBySponsor = this.loadFromStorage();
|
|
2406
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
2407
|
+
window.addEventListener("storage", this.onStorage);
|
|
2408
|
+
}
|
|
2218
2409
|
}
|
|
2219
2410
|
nextSeq(sponsorId) {
|
|
2220
2411
|
const n = (this.mutationSeq.get(sponsorId) ?? 0) + 1;
|
|
@@ -2374,6 +2565,9 @@ var CartManager = class extends EventTarget {
|
|
|
2374
2565
|
* Overwrite local cart state with the backend cart response (source of
|
|
2375
2566
|
* truth). `opts.settling` is the mutation whose answer this is: it does not
|
|
2376
2567
|
* count as "in flight" for the origins (see `hasMutationsInFlight`).
|
|
2568
|
+
* `opts.authoritative` is a GetCart taken with nothing of this tab's in
|
|
2569
|
+
* flight (`refreshFromServer`): an empty answer then means the cart IS
|
|
2570
|
+
* empty, and the local lines go — not an add still on its way.
|
|
2377
2571
|
*/
|
|
2378
2572
|
updateFromBackendCart(sponsorId, backendCart, opts = {}) {
|
|
2379
2573
|
const cart = this.cartsBySponsor.get(sponsorId);
|
|
@@ -2389,7 +2583,7 @@ var CartManager = class extends EventTarget {
|
|
|
2389
2583
|
const removedBeforeSync = [];
|
|
2390
2584
|
if (Array.isArray(backendCart.line_items)) {
|
|
2391
2585
|
const existingItems = cart.items || [];
|
|
2392
|
-
if (backendCart.line_items.length === 0 && existingItems.length > 0) {
|
|
2586
|
+
if (backendCart.line_items.length === 0 && existingItems.length > 0 && !opts.authoritative) {
|
|
2393
2587
|
this.persist();
|
|
2394
2588
|
this.emit();
|
|
2395
2589
|
return;
|
|
@@ -2743,6 +2937,60 @@ var CartManager = class extends EventTarget {
|
|
|
2743
2937
|
if (resCart) this.updateFromBackendCart(sponsorId, resCart);
|
|
2744
2938
|
return resCart;
|
|
2745
2939
|
}
|
|
2940
|
+
/**
|
|
2941
|
+
* The cart as the server has it, for the moment a checkout is about to be
|
|
2942
|
+
* built on it: what the shopper is about to pay must be what the page shows.
|
|
2943
|
+
* Waits for this tab's own mutations (their answers are the server's too),
|
|
2944
|
+
* then applies GetCart as it is — a line another tab added, one it removed,
|
|
2945
|
+
* an empty cart — and tells the page: `change` for the drawer, and
|
|
2946
|
+
* `vio:cart-updated` on `document` (`source: 'server'`) for a host that
|
|
2947
|
+
* draws its own. A mutation that starts while the server is answering makes
|
|
2948
|
+
* that answer old: it is asked once more. Nothing to ask without a backend
|
|
2949
|
+
* cart. A GetCart that fails leaves the local state as it was and does not
|
|
2950
|
+
* throw — the checkout then goes on with the lines the last mutation
|
|
2951
|
+
* answered, as it always did. Resolves with the sponsor's cart.
|
|
2952
|
+
*/
|
|
2953
|
+
async refreshFromServer(sponsorId) {
|
|
2954
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2955
|
+
await this.waitForCartMutations(sponsorId);
|
|
2956
|
+
const cart = this.cartsBySponsor.get(sponsorId);
|
|
2957
|
+
if (!cart?.cartId) return cart;
|
|
2958
|
+
let resCart;
|
|
2959
|
+
try {
|
|
2960
|
+
const gqlOpts = await getCartGraphQLOptions(sponsorId);
|
|
2961
|
+
resCart = await getCart(cart.cartId, gqlOpts);
|
|
2962
|
+
} catch (err) {
|
|
2963
|
+
if (typeof console !== "undefined") {
|
|
2964
|
+
console.warn("[CartManager] GetCart before the checkout failed \u2014 the local cart stands", err);
|
|
2965
|
+
}
|
|
2966
|
+
return cart;
|
|
2967
|
+
}
|
|
2968
|
+
if (this.hasMutationsInFlight(sponsorId)) continue;
|
|
2969
|
+
const backendCart = resCart;
|
|
2970
|
+
if (backendCart && Array.isArray(backendCart.line_items)) {
|
|
2971
|
+
this.updateFromBackendCart(sponsorId, backendCart, { authoritative: true });
|
|
2972
|
+
this.emitCartUpdated(sponsorId, "server");
|
|
2973
|
+
}
|
|
2974
|
+
return this.cartsBySponsor.get(sponsorId);
|
|
2975
|
+
}
|
|
2976
|
+
return this.cartsBySponsor.get(sponsorId);
|
|
2977
|
+
}
|
|
2978
|
+
/** `vio:cart-updated` on `document` — see `CartUpdatedDetail`. */
|
|
2979
|
+
emitCartUpdated(sponsorId, source) {
|
|
2980
|
+
if (typeof document === "undefined") return;
|
|
2981
|
+
const cart = this.cartsBySponsor.get(sponsorId);
|
|
2982
|
+
const items = (cart?.items ?? []).map((item) => ({ ...item }));
|
|
2983
|
+
const detail = {
|
|
2984
|
+
sponsorId,
|
|
2985
|
+
cartId: cart?.cartId,
|
|
2986
|
+
items,
|
|
2987
|
+
itemCount: items.reduce((count, item) => count + item.quantity, 0),
|
|
2988
|
+
subtotal: items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0),
|
|
2989
|
+
currency: cart?.currency || getGlobalCurrency(),
|
|
2990
|
+
source
|
|
2991
|
+
};
|
|
2992
|
+
document.dispatchEvent(new CustomEvent("vio:cart-updated", { bubbles: true, detail }));
|
|
2993
|
+
}
|
|
2746
2994
|
/** Line items grouped by supplier (shipping is per-supplier server-side). */
|
|
2747
2995
|
async fetchLineItemsBySupplier(sponsorId) {
|
|
2748
2996
|
const cart = this.cartsBySponsor.get(sponsorId);
|
|
@@ -2795,19 +3043,23 @@ var CartManager = class extends EventTarget {
|
|
|
2795
3043
|
return formatPrice(this.totalAcrossSponsors, this.primaryCurrency);
|
|
2796
3044
|
}
|
|
2797
3045
|
// MARK: - Persistence
|
|
3046
|
+
/**
|
|
3047
|
+
* Keep the carts for the next page and the other tabs. Where storage
|
|
3048
|
+
* refuses (in-app browsers, private modes) nothing throws (safe-storage.ts):
|
|
3049
|
+
* the carts stay in this manager's own map, the add to cart and the
|
|
3050
|
+
* checkout go on, and the cart just does not survive a reload there.
|
|
3051
|
+
*/
|
|
2798
3052
|
persist() {
|
|
2799
|
-
if (typeof localStorage === "undefined") return;
|
|
2800
3053
|
try {
|
|
2801
3054
|
const obj = {};
|
|
2802
3055
|
for (const [k, v] of this.cartsBySponsor) obj[String(k)] = v;
|
|
2803
|
-
|
|
3056
|
+
storageSet("local", STORAGE_KEY, JSON.stringify(obj), { memory: false });
|
|
2804
3057
|
} catch {
|
|
2805
3058
|
}
|
|
2806
3059
|
}
|
|
2807
3060
|
loadFromStorage() {
|
|
2808
|
-
if (typeof localStorage === "undefined") return /* @__PURE__ */ new Map();
|
|
2809
3061
|
try {
|
|
2810
|
-
const raw =
|
|
3062
|
+
const raw = storageGet("local", STORAGE_KEY);
|
|
2811
3063
|
if (!raw) return /* @__PURE__ */ new Map();
|
|
2812
3064
|
const obj = JSON.parse(raw);
|
|
2813
3065
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -2969,13 +3221,15 @@ function sponsorIdentityFrom(boot, sponsorId) {
|
|
|
2969
3221
|
if (!name && !logoUrl) return null;
|
|
2970
3222
|
const termsUrl = pageUrlOrNull(sponsor.termsUrl);
|
|
2971
3223
|
const refundPolicyUrl = pageUrlOrNull(sponsor.refundPolicyUrl);
|
|
3224
|
+
const highlights = highlightsOf(sponsor.highlights);
|
|
2972
3225
|
return {
|
|
2973
3226
|
id,
|
|
2974
3227
|
name,
|
|
2975
3228
|
logoUrl,
|
|
2976
3229
|
avatarUrl: urlOrNull(sponsor.avatarUrl),
|
|
2977
3230
|
...termsUrl ? { termsUrl } : {},
|
|
2978
|
-
...refundPolicyUrl ? { refundPolicyUrl } : {}
|
|
3231
|
+
...refundPolicyUrl ? { refundPolicyUrl } : {},
|
|
3232
|
+
...highlights.length ? { highlights } : {}
|
|
2979
3233
|
};
|
|
2980
3234
|
} catch {
|
|
2981
3235
|
return null;
|
|
@@ -3070,6 +3324,20 @@ function rememberedSponsor(sponsorId) {
|
|
|
3070
3324
|
return null;
|
|
3071
3325
|
}
|
|
3072
3326
|
}
|
|
3327
|
+
function highlightsOf(value) {
|
|
3328
|
+
if (!Array.isArray(value)) return [];
|
|
3329
|
+
const out = [];
|
|
3330
|
+
for (const item of value) {
|
|
3331
|
+
if (!item || typeof item !== "object") continue;
|
|
3332
|
+
const { icon, title, text: text3 } = item;
|
|
3333
|
+
const t = typeof title === "string" ? title.trim() : "";
|
|
3334
|
+
if (!t) continue;
|
|
3335
|
+
const body = typeof text3 === "string" ? text3.trim() : "";
|
|
3336
|
+
out.push({ icon: typeof icon === "string" ? icon.trim() : "", title: t, ...body ? { text: body } : {} });
|
|
3337
|
+
if (out.length === 3) break;
|
|
3338
|
+
}
|
|
3339
|
+
return out;
|
|
3340
|
+
}
|
|
3073
3341
|
|
|
3074
3342
|
// src/core/checkout/payments/apple-pay.ts
|
|
3075
3343
|
var STRIPE_JS_URL = "https://js.stripe.com/v3";
|
|
@@ -3267,7 +3535,7 @@ async function prepareApplePay(config) {
|
|
|
3267
3535
|
}
|
|
3268
3536
|
|
|
3269
3537
|
// src/core/version.ts
|
|
3270
|
-
var SDK_VERSION = "0.
|
|
3538
|
+
var SDK_VERSION = "0.32.0";
|
|
3271
3539
|
|
|
3272
3540
|
// src/core/checkout/payments/stripe-embedded.ts
|
|
3273
3541
|
var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
|
|
@@ -3433,11 +3701,8 @@ async function mountKlarnaExpressButton(opts) {
|
|
|
3433
3701
|
const returnUrl = opts.returnUrl ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : "");
|
|
3434
3702
|
let settled = false;
|
|
3435
3703
|
button.on("click", () => {
|
|
3436
|
-
if (opts.pendingKey && opts.pendingContext
|
|
3437
|
-
|
|
3438
|
-
sessionStorage.setItem(opts.pendingKey, JSON.stringify(opts.pendingContext));
|
|
3439
|
-
} catch {
|
|
3440
|
-
}
|
|
3704
|
+
if (opts.pendingKey && opts.pendingContext) {
|
|
3705
|
+
storageSet("session", opts.pendingKey, JSON.stringify(opts.pendingContext));
|
|
3441
3706
|
}
|
|
3442
3707
|
const promise = sdk.Payment.initiate(() => ({
|
|
3443
3708
|
currency: opts.paymentRequest.currency,
|
|
@@ -4515,6 +4780,74 @@ function adyenReturnOutcome(payment) {
|
|
|
4515
4780
|
}
|
|
4516
4781
|
}
|
|
4517
4782
|
|
|
4783
|
+
// src/core/checkout/phone.ts
|
|
4784
|
+
var CALLING_CODES = {
|
|
4785
|
+
NO: "47",
|
|
4786
|
+
SE: "46",
|
|
4787
|
+
DK: "45",
|
|
4788
|
+
FI: "358",
|
|
4789
|
+
IS: "354",
|
|
4790
|
+
DE: "49",
|
|
4791
|
+
GB: "44",
|
|
4792
|
+
UK: "44",
|
|
4793
|
+
IE: "353",
|
|
4794
|
+
NL: "31",
|
|
4795
|
+
BE: "32",
|
|
4796
|
+
FR: "33",
|
|
4797
|
+
ES: "34",
|
|
4798
|
+
PT: "351",
|
|
4799
|
+
IT: "39",
|
|
4800
|
+
AT: "43",
|
|
4801
|
+
CH: "41",
|
|
4802
|
+
PL: "48",
|
|
4803
|
+
EE: "372",
|
|
4804
|
+
LV: "371",
|
|
4805
|
+
LT: "370",
|
|
4806
|
+
CZ: "420",
|
|
4807
|
+
SK: "421",
|
|
4808
|
+
HU: "36",
|
|
4809
|
+
US: "1",
|
|
4810
|
+
CA: "1"
|
|
4811
|
+
};
|
|
4812
|
+
var KNOWN_CODES = [...new Set(Object.values(CALLING_CODES))].sort((a, b) => b.length - a.length);
|
|
4813
|
+
var NATIONAL_LENGTH = {
|
|
4814
|
+
"47": [8, 8],
|
|
4815
|
+
"45": [8, 8],
|
|
4816
|
+
"46": [7, 9],
|
|
4817
|
+
"358": [6, 10],
|
|
4818
|
+
"49": [10, 11],
|
|
4819
|
+
"44": [10, 10],
|
|
4820
|
+
"1": [10, 10]
|
|
4821
|
+
};
|
|
4822
|
+
function callingCodeFor(country) {
|
|
4823
|
+
return CALLING_CODES[String(country ?? "").trim().toUpperCase()] ?? null;
|
|
4824
|
+
}
|
|
4825
|
+
function parsePhone(raw, country) {
|
|
4826
|
+
const typed = String(raw ?? "").trim().replace(/[\s\-().]/g, "");
|
|
4827
|
+
if (!typed) return null;
|
|
4828
|
+
let code = null;
|
|
4829
|
+
let national;
|
|
4830
|
+
if (/^(\+|00)/.test(typed)) {
|
|
4831
|
+
const digits = typed.replace(/^(\+|00)/, "");
|
|
4832
|
+
if (!/^\d+$/.test(digits)) return null;
|
|
4833
|
+
code = KNOWN_CODES.find((c) => digits.startsWith(c)) ?? null;
|
|
4834
|
+
if (!code) return null;
|
|
4835
|
+
national = digits.slice(code.length);
|
|
4836
|
+
} else {
|
|
4837
|
+
if (!/^\d+$/.test(typed)) return null;
|
|
4838
|
+
code = callingCodeFor(country);
|
|
4839
|
+
if (!code) return null;
|
|
4840
|
+
national = typed;
|
|
4841
|
+
}
|
|
4842
|
+
if (national.startsWith("0") && code !== "47" && code !== "45") national = national.slice(1);
|
|
4843
|
+
const [min, max] = NATIONAL_LENGTH[code] ?? [6, 12];
|
|
4844
|
+
if (national.length < min || national.length > max) return null;
|
|
4845
|
+
return { phone: national, phone_code: code };
|
|
4846
|
+
}
|
|
4847
|
+
function isValidPhone(raw, country) {
|
|
4848
|
+
return parsePhone(raw, country) !== null;
|
|
4849
|
+
}
|
|
4850
|
+
|
|
4518
4851
|
// src/core/checkout/checkout-manager.ts
|
|
4519
4852
|
function describeError(err) {
|
|
4520
4853
|
if (err instanceof Error) return err.message;
|
|
@@ -4532,6 +4865,7 @@ function describeError(err) {
|
|
|
4532
4865
|
}
|
|
4533
4866
|
return String(err);
|
|
4534
4867
|
}
|
|
4868
|
+
var KLARNA_CONFIRMED_KEY = "vio.klarna.confirmed.v1";
|
|
4535
4869
|
var KLARNA_PENDING_KEY = "vio.klarna.pending.v1";
|
|
4536
4870
|
var KLARNA_SHIPPING_OPTIONS = [
|
|
4537
4871
|
{
|
|
@@ -4695,15 +5029,20 @@ function buildCheckoutAddress(address) {
|
|
|
4695
5029
|
const zip = address.postalCode || address.postal_code || address.zip || null;
|
|
4696
5030
|
const country_code = address.countryCode || address.country_code || address.country || getGlobalCountryCode();
|
|
4697
5031
|
if (!firstName && !lastName && !address1 && !city && !zip) return null;
|
|
5032
|
+
const phone = parsePhone(address.phone, country_code);
|
|
4698
5033
|
return {
|
|
4699
5034
|
first_name: firstName,
|
|
4700
5035
|
last_name: lastName,
|
|
4701
5036
|
address1,
|
|
4702
5037
|
city,
|
|
4703
5038
|
zip,
|
|
4704
|
-
country_code
|
|
5039
|
+
country_code,
|
|
5040
|
+
...phone ? { phone: phone.phone, phone_code: phone.phone_code } : {}
|
|
4705
5041
|
};
|
|
4706
5042
|
}
|
|
5043
|
+
function isCompleteCheckoutAddress(a) {
|
|
5044
|
+
return !!a && ["first_name", "last_name", "address1", "city", "zip"].every((k) => String(a[k] ?? "").trim().length > 0);
|
|
5045
|
+
}
|
|
4707
5046
|
function adyenReturnUrl(params) {
|
|
4708
5047
|
let page = null;
|
|
4709
5048
|
if (typeof window !== "undefined" && window.location?.href) {
|
|
@@ -4729,6 +5068,11 @@ function adyenReturnUrl(params) {
|
|
|
4729
5068
|
bare.search = "";
|
|
4730
5069
|
return build(bare);
|
|
4731
5070
|
}
|
|
5071
|
+
function paymentMethodName(method) {
|
|
5072
|
+
if (typeof method !== "string") return void 0;
|
|
5073
|
+
const name = method.trim().toLowerCase().replace(/\s+/g, "-");
|
|
5074
|
+
return name || void 0;
|
|
5075
|
+
}
|
|
4732
5076
|
function resolveHttpsReturnUrl(overrideUrl, params = null) {
|
|
4733
5077
|
let url = overrideUrl ?? void 0;
|
|
4734
5078
|
if (!url && typeof window !== "undefined" && window.location) {
|
|
@@ -4763,6 +5107,18 @@ function resolveHttpsReturnUrl(overrideUrl, params = null) {
|
|
|
4763
5107
|
async function executeKlarnaGraphQL(query, variables, sponsorId) {
|
|
4764
5108
|
return executeCartGraphQL(query, variables, await getCartGraphQLOptions(sponsorId));
|
|
4765
5109
|
}
|
|
5110
|
+
function usesStripe(methods) {
|
|
5111
|
+
if (!Array.isArray(methods)) return false;
|
|
5112
|
+
return methods.some((m) => {
|
|
5113
|
+
if (String(m?.name ?? "").toLowerCase() !== "stripe") return false;
|
|
5114
|
+
const config = Array.isArray(m?.config) ? m.config : [];
|
|
5115
|
+
const key = config.find((c) => c?.name === "publishableKey")?.value;
|
|
5116
|
+
return typeof key === "string" && key.startsWith("pk_");
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
function cartTotal(items) {
|
|
5120
|
+
return items.reduce((sum, i) => sum + (Number(i.unitPrice) || 0) * (Number(i.quantity) || 0), 0);
|
|
5121
|
+
}
|
|
4766
5122
|
var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
4767
5123
|
constructor(cartManager, deps = {}) {
|
|
4768
5124
|
super();
|
|
@@ -4771,6 +5127,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4771
5127
|
this.currentStateValue = null;
|
|
4772
5128
|
/** The last checkout + identity written to the memory — the state is set on every keystroke of the form. */
|
|
4773
5129
|
this.lastRememberedSponsor = "";
|
|
5130
|
+
/** Backend checkouts this page load announced with `vio:checkout-created`: one event each. */
|
|
5131
|
+
this.announcedCheckouts = /* @__PURE__ */ new Set();
|
|
4774
5132
|
/** Counts `open()` calls — see CheckoutState.session. */
|
|
4775
5133
|
this.sessionSeq = 0;
|
|
4776
5134
|
this.applePayAvailableCache = null;
|
|
@@ -4809,10 +5167,17 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4809
5167
|
this.paymentMethodsCache = /* @__PURE__ */ new Map();
|
|
4810
5168
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4811
5169
|
this.paymentMethodsPromises = /* @__PURE__ */ new Map();
|
|
5170
|
+
/** What the open checkout's cart looked like when it was last emitted. */
|
|
5171
|
+
this.lastCartSignature = null;
|
|
4812
5172
|
/** In-flight `getOrderReference` per checkout id (see there). */
|
|
4813
5173
|
this.referenceInFlight = /* @__PURE__ */ new Map();
|
|
4814
5174
|
this.kustomController = null;
|
|
4815
|
-
|
|
5175
|
+
if (Configuration.isInitialized && Configuration.get()?.stripePublishableKey) {
|
|
5176
|
+
preloadStripeJs();
|
|
5177
|
+
}
|
|
5178
|
+
if (typeof cartManager.addEventListener === "function") {
|
|
5179
|
+
cartManager.addEventListener("change", () => this.onCartChange());
|
|
5180
|
+
}
|
|
4816
5181
|
}
|
|
4817
5182
|
/**
|
|
4818
5183
|
* Every write of the state goes through here: a state that names its
|
|
@@ -4826,11 +5191,29 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4826
5191
|
this.currentStateValue = next;
|
|
4827
5192
|
if (next?.checkoutId && next.sponsorId) this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId);
|
|
4828
5193
|
}
|
|
5194
|
+
/** The open checkout's lines as a signature, to tell a real change from an echo. */
|
|
5195
|
+
cartSignature(sponsorId) {
|
|
5196
|
+
return (this.cartManager.getCart(sponsorId)?.items ?? []).map((i) => `${i.productId}:${i.variantId ?? ""}:${i.quantity}:${i.unitPrice}`).join("|");
|
|
5197
|
+
}
|
|
5198
|
+
/** The open checkout follows its cart: subtotal recomputed and `change` said when the lines differ. */
|
|
5199
|
+
onCartChange() {
|
|
5200
|
+
const state = this.state;
|
|
5201
|
+
if (!state) return;
|
|
5202
|
+
const subtotal = this.cartManager.subtotalForSponsor(state.sponsorId);
|
|
5203
|
+
const signature = this.cartSignature(state.sponsorId);
|
|
5204
|
+
if (subtotal === state.subtotal && signature === this.lastCartSignature) return;
|
|
5205
|
+
this.lastCartSignature = signature;
|
|
5206
|
+
this.state = { ...state, subtotal };
|
|
5207
|
+
this.emit();
|
|
5208
|
+
}
|
|
4829
5209
|
open(sponsorId) {
|
|
4830
5210
|
const cart = this.cartManager.getCart(sponsorId);
|
|
4831
5211
|
if (!cart || cart.items.length === 0) {
|
|
4832
5212
|
throw new Error(`[VioCheckout] sponsor ${sponsorId} has no cart items`);
|
|
4833
5213
|
}
|
|
5214
|
+
if (!(cartTotal(cart.items) > 0)) {
|
|
5215
|
+
throw new Error(`[VioCheckout] sponsor ${sponsorId} has a cart that totals 0`);
|
|
5216
|
+
}
|
|
4834
5217
|
this.state = {
|
|
4835
5218
|
sponsorId,
|
|
4836
5219
|
session: ++this.sessionSeq,
|
|
@@ -4839,6 +5222,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4839
5222
|
// the host may have switched currency since the cart was created.
|
|
4840
5223
|
currency: getGlobalCurrency()
|
|
4841
5224
|
};
|
|
5225
|
+
this.lastCartSignature = this.cartSignature(sponsorId);
|
|
4842
5226
|
this.emit();
|
|
4843
5227
|
return this.state;
|
|
4844
5228
|
}
|
|
@@ -4863,9 +5247,15 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4863
5247
|
return void 0;
|
|
4864
5248
|
}
|
|
4865
5249
|
}
|
|
4866
|
-
/**
|
|
4867
|
-
|
|
5250
|
+
/**
|
|
5251
|
+
* Create the backend checkout for the sponsor's cart (auto-accepts terms);
|
|
5252
|
+
* born attributed when the facade knows the attribution. `paymentMethod` is
|
|
5253
|
+
* the method it is created to pay with, when the caller knows it (else the
|
|
5254
|
+
* state's): it goes on `vio:checkout-created`.
|
|
5255
|
+
*/
|
|
5256
|
+
async createCheckout(sponsorId, paymentMethod) {
|
|
4868
5257
|
await this.cartManager.waitForCartMutations(sponsorId);
|
|
5258
|
+
await this.cartManager.refreshFromServer(sponsorId);
|
|
4869
5259
|
const cart = this.cartManager.getCart(sponsorId);
|
|
4870
5260
|
let cartId = cart?.cartId;
|
|
4871
5261
|
if (!cartId) {
|
|
@@ -4885,6 +5275,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4885
5275
|
checkout
|
|
4886
5276
|
};
|
|
4887
5277
|
this.emit();
|
|
5278
|
+
this.announceCheckout(checkout.id, sponsorId, paymentMethod ?? this.state?.paymentMethod, {
|
|
5279
|
+
campaignId: attribution?.campaign_id
|
|
5280
|
+
});
|
|
4888
5281
|
try {
|
|
4889
5282
|
const updated = await updateCheckout(
|
|
4890
5283
|
{
|
|
@@ -4907,6 +5300,50 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4907
5300
|
}
|
|
4908
5301
|
return checkout;
|
|
4909
5302
|
}
|
|
5303
|
+
/**
|
|
5304
|
+
* `vio:checkout-created` on `window` — `{ sponsorId, checkoutId,
|
|
5305
|
+
* paymentMethod, campaignId?, redirect }` — the moment a backend checkout
|
|
5306
|
+
* exists for a payment, before anything is asked of the provider. Analytics
|
|
5307
|
+
* answers it with `add_payment_info` (ADR-0039: the one link from the
|
|
5308
|
+
* order Commerce confirms back to this browser's session); `redirect: true`
|
|
5309
|
+
* when the page is about to leave for the provider. Once per checkout per
|
|
5310
|
+
* page load; never throws.
|
|
5311
|
+
*/
|
|
5312
|
+
announceCheckout(checkoutId, sponsorId, paymentMethod, opts = {}) {
|
|
5313
|
+
if (!checkoutId || this.announcedCheckouts.has(checkoutId)) return;
|
|
5314
|
+
this.announcedCheckouts.add(checkoutId);
|
|
5315
|
+
if (typeof window === "undefined" || typeof window.dispatchEvent !== "function") return;
|
|
5316
|
+
const method = paymentMethodName(paymentMethod);
|
|
5317
|
+
try {
|
|
5318
|
+
window.dispatchEvent(
|
|
5319
|
+
new CustomEvent("vio:checkout-created", {
|
|
5320
|
+
detail: {
|
|
5321
|
+
sponsorId,
|
|
5322
|
+
checkoutId,
|
|
5323
|
+
...method ? { paymentMethod: method } : {},
|
|
5324
|
+
...typeof opts.campaignId === "number" ? { campaignId: opts.campaignId } : {},
|
|
5325
|
+
redirect: opts.redirect === true
|
|
5326
|
+
}
|
|
5327
|
+
})
|
|
5328
|
+
);
|
|
5329
|
+
} catch {
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5333
|
+
* Nothing is created or paid for a cart that is empty or totals 0 — as
|
|
5334
|
+
* the SERVER has it (the callers read it from the server first). Throws,
|
|
5335
|
+
* so a payment button ends in its error path, never at a provider asking
|
|
5336
|
+
* for 0 kr.
|
|
5337
|
+
*/
|
|
5338
|
+
assertPayableCart(sponsorId) {
|
|
5339
|
+
const cart = this.cartManager.getCart(sponsorId);
|
|
5340
|
+
if (!cart || !Array.isArray(cart.items) || cart.items.length === 0) {
|
|
5341
|
+
throw new Error(`[CheckoutManager] the cart of sponsor ${sponsorId} is empty \u2014 nothing to pay`);
|
|
5342
|
+
}
|
|
5343
|
+
if (!(cartTotal(cart.items) > 0)) {
|
|
5344
|
+
throw new Error(`[CheckoutManager] the cart of sponsor ${sponsorId} totals 0 \u2014 nothing to pay`);
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
4910
5347
|
/** Update the backend checkout (terms are re-asserted unless overridden; the attribution rides along unless the caller gives its own). */
|
|
4911
5348
|
async updateCheckout(variables, sponsorId) {
|
|
4912
5349
|
const spId = sponsorId ?? this.state?.sponsorId;
|
|
@@ -5037,6 +5474,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5037
5474
|
const opts = await getCartGraphQLOptions(spId);
|
|
5038
5475
|
const res = await getAvailablePaymentMethods(opts);
|
|
5039
5476
|
if (res) this.paymentMethodsCache.set(spId, res);
|
|
5477
|
+
if (usesStripe(res)) preloadStripeJs();
|
|
5040
5478
|
return res;
|
|
5041
5479
|
} finally {
|
|
5042
5480
|
this.paymentMethodsPromises.delete(spId);
|
|
@@ -5136,10 +5574,21 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5136
5574
|
* backend cart + checkout exist, push email/address onto the checkout, and
|
|
5137
5575
|
* return everything the payment mutation needs.
|
|
5138
5576
|
*/
|
|
5139
|
-
async prepareRedirectPayment(sponsorId, formData, extraUpdateVars = {}) {
|
|
5577
|
+
async prepareRedirectPayment(sponsorId, formData, extraUpdateVars = {}, leaving = false, vippsWithAddress = false) {
|
|
5140
5578
|
const spId = sponsorId ?? this.state?.sponsorId;
|
|
5141
5579
|
if (!spId) throw new Error("[CheckoutManager] no sponsor for payment");
|
|
5142
5580
|
await this.cartManager.waitForCartMutations(spId);
|
|
5581
|
+
await this.cartManager.refreshFromServer(spId);
|
|
5582
|
+
this.assertPayableCart(spId);
|
|
5583
|
+
const isVipps = extraUpdateVars?.paymentMethod === "Vipps";
|
|
5584
|
+
const addr = typeof formData === "object" && formData !== null ? formData : this.state?.address;
|
|
5585
|
+
const built = buildCheckoutAddress(addr);
|
|
5586
|
+
if (isVipps && vippsWithAddress && !(isCompleteCheckoutAddress(built) && built?.phone)) {
|
|
5587
|
+
throw new Error(
|
|
5588
|
+
"[CheckoutManager] the standard Vipps payment needs the complete delivery address (name, street, postcode, city and a mobile number) \u2014 no payment started"
|
|
5589
|
+
);
|
|
5590
|
+
}
|
|
5591
|
+
const addressObj = !isVipps ? built : vippsWithAddress ? built : null;
|
|
5143
5592
|
const cart = this.cartManager.getCart(spId);
|
|
5144
5593
|
let cartId = cart?.cartId;
|
|
5145
5594
|
if (!cartId) {
|
|
@@ -5154,13 +5603,14 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5154
5603
|
if (!checkoutId) {
|
|
5155
5604
|
const checkoutRes = attribution ? await createCheckout(cartId, opts, { attribution }) : await createCheckout(cartId, opts);
|
|
5156
5605
|
checkoutId = checkoutRes?.id;
|
|
5606
|
+
this.announceCheckout(checkoutId, spId, String(extraUpdateVars.paymentMethod ?? "") || this.state?.paymentMethod, {
|
|
5607
|
+
redirect: leaving,
|
|
5608
|
+
campaignId: attribution?.campaign_id
|
|
5609
|
+
});
|
|
5157
5610
|
}
|
|
5158
5611
|
if (!checkoutId) {
|
|
5159
5612
|
throw new Error("[CheckoutManager] no backend checkout \u2014 cannot start the payment");
|
|
5160
5613
|
}
|
|
5161
|
-
const isVipps = extraUpdateVars?.paymentMethod === "Vipps";
|
|
5162
|
-
const addr = typeof formData === "object" && formData !== null ? formData : this.state?.address;
|
|
5163
|
-
const addressObj = !isVipps ? buildCheckoutAddress(addr) : null;
|
|
5164
5614
|
const emailVal = (typeof formData === "string" ? formData : formData?.email) || this.state?.address?.email || "";
|
|
5165
5615
|
const hasEmail = !!emailVal && emailVal.includes("@");
|
|
5166
5616
|
if (!hasEmail && !isVipps) {
|
|
@@ -5222,7 +5672,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5222
5672
|
const { spId, checkoutId, emailVal, opts } = await this.prepareRedirectPayment(
|
|
5223
5673
|
sponsorId,
|
|
5224
5674
|
formData,
|
|
5225
|
-
{ paymentMethod: "Stripe" }
|
|
5675
|
+
{ paymentMethod: "Stripe" },
|
|
5676
|
+
true
|
|
5226
5677
|
);
|
|
5227
5678
|
const successUrl = resolveHttpsReturnUrl(null, {
|
|
5228
5679
|
vio_payment: "success",
|
|
@@ -5254,6 +5705,11 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5254
5705
|
* so the caller can open the ordinary checkout instead. Without the flag
|
|
5255
5706
|
* the backend decides: Express while the cart has no delivery yet, a
|
|
5256
5707
|
* plain payment once the shopper chose one in our form.
|
|
5708
|
+
*
|
|
5709
|
+
* `express: false` is the checkout's standard path (2026-10-28): the
|
|
5710
|
+
* form's address — name, street, mobile — goes on the checkout before the
|
|
5711
|
+
* payment is created, and the delivery already chosen on the cart is what
|
|
5712
|
+
* Vipps charges for.
|
|
5257
5713
|
*/
|
|
5258
5714
|
async startVippsPayment(sponsorId, formData, options = {}) {
|
|
5259
5715
|
const waitFor = sponsorId ?? this.state?.sponsorId;
|
|
@@ -5261,7 +5717,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5261
5717
|
const { spId, checkoutId, emailVal, opts } = await this.prepareRedirectPayment(
|
|
5262
5718
|
sponsorId,
|
|
5263
5719
|
formData,
|
|
5264
|
-
{ paymentMethod: "Vipps" }
|
|
5720
|
+
{ paymentMethod: "Vipps" },
|
|
5721
|
+
true,
|
|
5722
|
+
options.express === false
|
|
5265
5723
|
);
|
|
5266
5724
|
const returnUrl = resolveHttpsReturnUrl(null, {
|
|
5267
5725
|
vio_payment: "success",
|
|
@@ -5318,7 +5776,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5318
5776
|
const opts = await getCartGraphQLOptions(spId);
|
|
5319
5777
|
let checkoutId = this.state?.checkoutId;
|
|
5320
5778
|
if (!checkoutId) {
|
|
5321
|
-
const checkoutRes = await this.createCheckout(spId);
|
|
5779
|
+
const checkoutRes = await this.createCheckout(spId, "kustom");
|
|
5322
5780
|
checkoutId = checkoutRes?.id;
|
|
5323
5781
|
if (checkoutId && this.state) {
|
|
5324
5782
|
this.state = { ...this.state, checkoutId };
|
|
@@ -5394,7 +5852,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5394
5852
|
const opts = await getCartGraphQLOptions(spId);
|
|
5395
5853
|
let checkoutId = this.state?.checkoutId;
|
|
5396
5854
|
if (!checkoutId) {
|
|
5397
|
-
const checkoutRes = await this.createCheckout(spId);
|
|
5855
|
+
const checkoutRes = await this.createCheckout(spId, "qliro");
|
|
5398
5856
|
checkoutId = checkoutRes?.id;
|
|
5399
5857
|
if (checkoutId && this.state) {
|
|
5400
5858
|
this.state = { ...this.state, checkoutId };
|
|
@@ -5482,7 +5940,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5482
5940
|
const opts = await getCartGraphQLOptions(spId);
|
|
5483
5941
|
let checkoutId = this.state?.checkoutId;
|
|
5484
5942
|
if (!checkoutId) {
|
|
5485
|
-
const checkoutRes = await this.createCheckout(spId);
|
|
5943
|
+
const checkoutRes = await this.createCheckout(spId, "walley");
|
|
5486
5944
|
checkoutId = checkoutRes?.id;
|
|
5487
5945
|
if (checkoutId && this.state) {
|
|
5488
5946
|
this.state = { ...this.state, checkoutId };
|
|
@@ -5551,7 +6009,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5551
6009
|
if (this.state) this.state = { ...this.state, checkoutId };
|
|
5552
6010
|
}
|
|
5553
6011
|
if (!checkoutId) {
|
|
5554
|
-
const checkoutRes = await this.createCheckout(spId);
|
|
6012
|
+
const checkoutRes = await this.createCheckout(spId, "nexi");
|
|
5555
6013
|
checkoutId = checkoutRes?.id;
|
|
5556
6014
|
if (checkoutId && this.state) {
|
|
5557
6015
|
this.state = { ...this.state, checkoutId };
|
|
@@ -6116,7 +6574,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6116
6574
|
return async (raw) => {
|
|
6117
6575
|
let checkoutId = this.state?.checkoutId;
|
|
6118
6576
|
if (!checkoutId) {
|
|
6119
|
-
const created = await this.createCheckout(spId);
|
|
6577
|
+
const created = await this.createCheckout(spId, "apple-pay");
|
|
6120
6578
|
checkoutId = created?.id ?? this.state?.checkoutId;
|
|
6121
6579
|
}
|
|
6122
6580
|
if (!checkoutId) {
|
|
@@ -6348,7 +6806,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6348
6806
|
}
|
|
6349
6807
|
let checkoutId = ctx.checkoutId ?? this.state.checkoutId;
|
|
6350
6808
|
try {
|
|
6351
|
-
const checkoutRes = await this.createCheckout(sponsorId);
|
|
6809
|
+
const checkoutRes = await this.createCheckout(sponsorId, "klarna");
|
|
6352
6810
|
if (checkoutRes?.id) {
|
|
6353
6811
|
checkoutId = checkoutRes.id;
|
|
6354
6812
|
ctx.checkoutId = checkoutRes.id;
|
|
@@ -6400,6 +6858,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6400
6858
|
if (nativeRes.checkout_id) {
|
|
6401
6859
|
ctx.checkoutId = nativeRes.checkout_id;
|
|
6402
6860
|
this.state = { ...this.state, checkoutId: nativeRes.checkout_id };
|
|
6861
|
+
this.announceCheckout(nativeRes.checkout_id, sponsorId, "klarna");
|
|
6403
6862
|
}
|
|
6404
6863
|
if (nativeRes.session_id) {
|
|
6405
6864
|
ctx.sessionId = nativeRes.session_id;
|
|
@@ -6497,7 +6956,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6497
6956
|
try {
|
|
6498
6957
|
let checkoutId = ctx.checkoutId ?? this.state.checkoutId;
|
|
6499
6958
|
try {
|
|
6500
|
-
const checkoutRes = await this.createCheckout(sponsorId);
|
|
6959
|
+
const checkoutRes = await this.createCheckout(sponsorId, "klarna");
|
|
6501
6960
|
if (checkoutRes?.id) {
|
|
6502
6961
|
checkoutId = checkoutRes.id;
|
|
6503
6962
|
ctx.checkoutId = checkoutRes.id;
|
|
@@ -6543,6 +7002,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6543
7002
|
if (nativeRes.checkout_id) {
|
|
6544
7003
|
ctx.checkoutId = nativeRes.checkout_id;
|
|
6545
7004
|
this.state = { ...this.state, checkoutId: nativeRes.checkout_id };
|
|
7005
|
+
this.announceCheckout(nativeRes.checkout_id, sponsorId, "klarna");
|
|
6546
7006
|
}
|
|
6547
7007
|
if (nativeRes.session_id) {
|
|
6548
7008
|
ctx.sessionId = nativeRes.session_id;
|
|
@@ -6596,14 +7056,13 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6596
7056
|
* the SDK's resumed `complete` event — finish creating the order + confirm.
|
|
6597
7057
|
*/
|
|
6598
7058
|
async resumeKlarnaReturn() {
|
|
6599
|
-
|
|
6600
|
-
const raw = sessionStorage.getItem(KLARNA_PENDING_KEY);
|
|
7059
|
+
const raw = storageGet("session", KLARNA_PENDING_KEY);
|
|
6601
7060
|
if (!raw) return;
|
|
6602
7061
|
let pending2;
|
|
6603
7062
|
try {
|
|
6604
7063
|
pending2 = JSON.parse(raw);
|
|
6605
7064
|
} catch {
|
|
6606
|
-
|
|
7065
|
+
storageRemove("session", KLARNA_PENDING_KEY);
|
|
6607
7066
|
return;
|
|
6608
7067
|
}
|
|
6609
7068
|
const fromUrl = readKlarnaTokenFromUrl();
|
|
@@ -6661,7 +7120,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6661
7120
|
async completeKlarnaOrder(authorizationToken, ctx, result) {
|
|
6662
7121
|
if (this.klarnaOrderInFlight) return;
|
|
6663
7122
|
this.klarnaOrderInFlight = true;
|
|
6664
|
-
if (
|
|
7123
|
+
if (storageGet("session", KLARNA_CONFIRMED_KEY) === authorizationToken) {
|
|
6665
7124
|
this.klarnaOrderInFlight = false;
|
|
6666
7125
|
return;
|
|
6667
7126
|
}
|
|
@@ -6689,10 +7148,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6689
7148
|
}
|
|
6690
7149
|
const orderData = confirmRes.order || confirmRes;
|
|
6691
7150
|
const buyerRef = normalizeBuyerRef(confirmRes[BUYER_REF_FIELD]);
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
sessionStorage.removeItem(KLARNA_PENDING_KEY);
|
|
6695
|
-
}
|
|
7151
|
+
storageSet("session", KLARNA_CONFIRMED_KEY, authorizationToken);
|
|
7152
|
+
storageRemove("session", KLARNA_PENDING_KEY);
|
|
6696
7153
|
const state = this.state ?? { sponsorId: ctx.sponsorId, subtotal: ctx.orderAmount, currency: ctx.currency };
|
|
6697
7154
|
this.dispatchEvent(
|
|
6698
7155
|
new CustomEvent("payment-complete", {
|
|
@@ -6934,6 +7391,54 @@ function safeCall(fn) {
|
|
|
6934
7391
|
}
|
|
6935
7392
|
}
|
|
6936
7393
|
|
|
7394
|
+
// src/core/analytics/internal-traffic.ts
|
|
7395
|
+
var INTERNAL_KEY = "vio.internal.v1";
|
|
7396
|
+
var INTERNAL_PARAM = "vio_internal";
|
|
7397
|
+
function defaultInternalEnvironment() {
|
|
7398
|
+
return {
|
|
7399
|
+
location: typeof location !== "undefined" ? location : null,
|
|
7400
|
+
storage: localStorageOrNull()
|
|
7401
|
+
};
|
|
7402
|
+
}
|
|
7403
|
+
function resolveInternal(env = defaultInternalEnvironment()) {
|
|
7404
|
+
const param = internalParam(env.location?.search);
|
|
7405
|
+
if (param !== void 0) {
|
|
7406
|
+
writeInternal(param, env.storage);
|
|
7407
|
+
return param;
|
|
7408
|
+
}
|
|
7409
|
+
return readInternal(env.storage);
|
|
7410
|
+
}
|
|
7411
|
+
function readInternal(storage2 = defaultInternalEnvironment().storage) {
|
|
7412
|
+
try {
|
|
7413
|
+
return storage2?.getItem(INTERNAL_KEY) === "1";
|
|
7414
|
+
} catch {
|
|
7415
|
+
return false;
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7418
|
+
function writeInternal(internal, storage2 = defaultInternalEnvironment().storage) {
|
|
7419
|
+
try {
|
|
7420
|
+
if (internal) storage2?.setItem(INTERNAL_KEY, "1");
|
|
7421
|
+
else storage2?.removeItem?.(INTERNAL_KEY);
|
|
7422
|
+
} catch {
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7425
|
+
function internalParam(search) {
|
|
7426
|
+
try {
|
|
7427
|
+
const value = new URLSearchParams(typeof search === "string" ? search : "").get(INTERNAL_PARAM)?.trim();
|
|
7428
|
+
if (value === "1") return true;
|
|
7429
|
+
if (value === "0") return false;
|
|
7430
|
+
} catch {
|
|
7431
|
+
}
|
|
7432
|
+
return void 0;
|
|
7433
|
+
}
|
|
7434
|
+
function localStorageOrNull() {
|
|
7435
|
+
try {
|
|
7436
|
+
return typeof window !== "undefined" && window.localStorage ? window.localStorage : null;
|
|
7437
|
+
} catch {
|
|
7438
|
+
return null;
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
|
|
6937
7442
|
// src/core/cart/slices.ts
|
|
6938
7443
|
function cartSlicesFrom(lines, resolveCampaign, fallbackCurrency) {
|
|
6939
7444
|
if (!Array.isArray(lines) || lines.length === 0) return [];
|
|
@@ -7010,6 +7515,7 @@ var FLUSH_AT_COUNT = 20;
|
|
|
7010
7515
|
var MAX_QUEUE = 500;
|
|
7011
7516
|
var RETRY_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
7012
7517
|
var ANON_KEY = "vio.anon.v1";
|
|
7518
|
+
var MAX_ITEMS = 100;
|
|
7013
7519
|
var SESSION_KEY = "vio.session.v1";
|
|
7014
7520
|
var IMPRESSION_THRESHOLD = 0.5;
|
|
7015
7521
|
var IMPRESSION_DWELL_MS = 1e3;
|
|
@@ -7017,7 +7523,14 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7017
7523
|
constructor() {
|
|
7018
7524
|
super(...arguments);
|
|
7019
7525
|
this.started = false;
|
|
7020
|
-
this.options = {
|
|
7526
|
+
this.options = {
|
|
7527
|
+
host: "custom",
|
|
7528
|
+
collector: true,
|
|
7529
|
+
dataLayer: false,
|
|
7530
|
+
debug: false,
|
|
7531
|
+
autoTrack: true,
|
|
7532
|
+
privateContent: false
|
|
7533
|
+
};
|
|
7021
7534
|
this.queue = [];
|
|
7022
7535
|
this.flushTimer = null;
|
|
7023
7536
|
this.retryAttempt = 0;
|
|
@@ -7038,8 +7551,18 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7038
7551
|
this.client = null;
|
|
7039
7552
|
/** The landing it was read from — persisted later when consent arrives after the first event. */
|
|
7040
7553
|
this.landing = null;
|
|
7554
|
+
/**
|
|
7555
|
+
* Internal traffic (ADR-0041): `client.internal: true` on every event while
|
|
7556
|
+
* set. Read once (`?vio_internal=1|0`, else `localStorage`) at `Vio.init()`
|
|
7557
|
+
* or on the first event; `setInternal()` changes it. `null` = not read yet.
|
|
7558
|
+
*/
|
|
7559
|
+
this.internal = null;
|
|
7560
|
+
/** The console line for testers goes out once per page load. */
|
|
7561
|
+
this.internalAnnounced = false;
|
|
7041
7562
|
/** Checkouts (`c:<id>`) and orders (`o:<number>`) this page load reported a `purchase` for: one each. */
|
|
7042
7563
|
this.purchased = /* @__PURE__ */ new Set();
|
|
7564
|
+
/** Checkouts this page load reported an `add_payment_info` for: one each. */
|
|
7565
|
+
this.paymentInfoReported = /* @__PURE__ */ new Set();
|
|
7043
7566
|
/** Purchases still waiting for Vio's order number: each sends at once, with what it has, when called. */
|
|
7044
7567
|
this.pendingPurchases = /* @__PURE__ */ new Set();
|
|
7045
7568
|
/** Between `pagehide` and `pageshow` (bfcache): nothing waits, it all goes out now. */
|
|
@@ -7085,7 +7608,8 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7085
7608
|
collector: options.collector ?? this.options.collector,
|
|
7086
7609
|
dataLayer: options.dataLayer ?? this.options.dataLayer,
|
|
7087
7610
|
debug: options.debug ?? this.options.debug,
|
|
7088
|
-
autoTrack: options.autoTrack ?? this.options.autoTrack
|
|
7611
|
+
autoTrack: options.autoTrack ?? this.options.autoTrack,
|
|
7612
|
+
privateContent: options.privateContent ?? this.options.privateContent
|
|
7089
7613
|
};
|
|
7090
7614
|
if (options.sessionId) this.externalSessionId = options.sessionId;
|
|
7091
7615
|
if (this.started || !hasDom()) return;
|
|
@@ -7107,7 +7631,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7107
7631
|
this.teardown.push(() => window.removeEventListener("pageshow", onPageShow));
|
|
7108
7632
|
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
7109
7633
|
const onVisibility = () => {
|
|
7110
|
-
if (document.visibilityState !== "hidden"
|
|
7634
|
+
if (document.visibilityState !== "hidden") return;
|
|
7111
7635
|
this.settlePendingPurchases();
|
|
7112
7636
|
this.flushWithBeacon();
|
|
7113
7637
|
};
|
|
@@ -7128,6 +7652,43 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7128
7652
|
identify(externalUserId) {
|
|
7129
7653
|
this.externalUserId = externalUserId ?? void 0;
|
|
7130
7654
|
}
|
|
7655
|
+
/**
|
|
7656
|
+
* Mark (or unmark) this browser as Vio's own traffic (ADR-0041): while
|
|
7657
|
+
* marked, every event carries `client.internal: true` and the collector's
|
|
7658
|
+
* reads leave it out. Remembered in `localStorage` (`vio.internal.v1`) —
|
|
7659
|
+
* the same mark `?vio_internal=1` / `?vio_internal=0` sets on any page.
|
|
7660
|
+
*/
|
|
7661
|
+
setInternal(internal) {
|
|
7662
|
+
this.internal = internal === true;
|
|
7663
|
+
writeInternal(this.internal);
|
|
7664
|
+
this.announceInternal();
|
|
7665
|
+
}
|
|
7666
|
+
/** Whether this browser is marked as internal traffic (reads the mark the first time). */
|
|
7667
|
+
get isInternal() {
|
|
7668
|
+
return this.syncInternal();
|
|
7669
|
+
}
|
|
7670
|
+
/**
|
|
7671
|
+
* Read the mark: the page's `?vio_internal=1|0` (remembered), else what the
|
|
7672
|
+
* browser remembers. Once per page load — `Vio.init()` calls it so a page
|
|
7673
|
+
* with the parameter marks the browser even if it never starts analytics;
|
|
7674
|
+
* `force` reads again.
|
|
7675
|
+
*/
|
|
7676
|
+
syncInternal(force = false) {
|
|
7677
|
+
if (this.internal === null || force) {
|
|
7678
|
+
this.internal = hasDom() ? resolveInternal() : readInternal(null);
|
|
7679
|
+
this.announceInternal();
|
|
7680
|
+
}
|
|
7681
|
+
return this.internal;
|
|
7682
|
+
}
|
|
7683
|
+
/** One console line when marked, so a tester can see the mark took. */
|
|
7684
|
+
announceInternal() {
|
|
7685
|
+
if (!this.internal || this.internalAnnounced) return;
|
|
7686
|
+
this.internalAnnounced = true;
|
|
7687
|
+
try {
|
|
7688
|
+
console.info("[Vio.analytics] this browser is marked as internal traffic (vio_internal=1): its events are not counted. Open any page with ?vio_internal=0 to undo.");
|
|
7689
|
+
} catch {
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7131
7692
|
/** Queue one event. Works pre-init and pre-start (SSR: no-op). */
|
|
7132
7693
|
track(name, opts = {}) {
|
|
7133
7694
|
if (!hasDom()) return;
|
|
@@ -7172,7 +7733,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7172
7733
|
this.ensureSession();
|
|
7173
7734
|
if (this.impressedComponents.has(key)) return;
|
|
7174
7735
|
this.impressedComponents.add(key);
|
|
7175
|
-
this.track(name, { context });
|
|
7736
|
+
this.track(name, { context, commerce: impressionCommerce(opts.commerce) });
|
|
7176
7737
|
opts.onImpression?.();
|
|
7177
7738
|
observer.disconnect();
|
|
7178
7739
|
}, IMPRESSION_DWELL_MS);
|
|
@@ -7253,26 +7814,28 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7253
7814
|
// one is usually the tracking tail, not the page. The other host-given
|
|
7254
7815
|
// fields (broadcastId, componentTemplateId, errorCode, variant, …) are
|
|
7255
7816
|
// the host's responsibility: the SDK does not touch them.
|
|
7256
|
-
|
|
7817
|
+
// Con `privateContent` ninguno de los tres sale del dispositivo.
|
|
7818
|
+
content_url: this.options.privateContent ? void 0 : capContentUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
|
|
7257
7819
|
// The host's own content id — its join key back onto its reporting.
|
|
7258
7820
|
// Explicit on the event when it fits, else what the SDK resolved for
|
|
7259
7821
|
// the page (already capped per source), else nothing.
|
|
7260
|
-
content_id: explicitContentId ?? fitsId(content.id),
|
|
7822
|
+
content_id: this.options.privateContent ? void 0 : explicitContentId ?? fitsId(content.id),
|
|
7261
7823
|
// Deletion-proof reporting: sources have no trash bin — the title
|
|
7262
7824
|
// snapshotted at event time keeps reports legible if content dies.
|
|
7263
7825
|
error_code: ctx.errorCode,
|
|
7264
|
-
content_title: capTitle(
|
|
7826
|
+
content_title: this.options.privateContent ? void 0 : capTitle(
|
|
7265
7827
|
ctx.contentTitle ?? content.title ?? (typeof document !== "undefined" && document.title ? document.title : void 0)
|
|
7266
7828
|
),
|
|
7267
7829
|
variant: ctx.variant
|
|
7268
7830
|
});
|
|
7269
7831
|
const com = opts.commerce;
|
|
7270
7832
|
const commerce = com ? compact({
|
|
7271
|
-
|
|
7833
|
+
// The collector rejects an event with more lines than its cap, whole.
|
|
7834
|
+
items: com.items?.slice(0, MAX_ITEMS).map(
|
|
7272
7835
|
(i) => compact({
|
|
7273
7836
|
product_id: String(i.productId),
|
|
7274
|
-
name: i.name,
|
|
7275
|
-
brand: i.brand,
|
|
7837
|
+
name: this.options.privateContent ? void 0 : i.name,
|
|
7838
|
+
brand: this.options.privateContent ? void 0 : i.brand,
|
|
7276
7839
|
variant_id: i.variantId !== void 0 ? String(i.variantId) : void 0,
|
|
7277
7840
|
price: i.price,
|
|
7278
7841
|
quantity: i.quantity
|
|
@@ -7289,7 +7852,8 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7289
7852
|
// ids does not carry a stable identifier of a person either.
|
|
7290
7853
|
buyer_id: this.canPersist ? normalizeBuyerRef(com.buyerId) : void 0
|
|
7291
7854
|
}) : void 0;
|
|
7292
|
-
const
|
|
7855
|
+
const base = this.clientBlock();
|
|
7856
|
+
const client = this.syncInternal() ? { ...base ?? {}, internal: true } : base;
|
|
7293
7857
|
return {
|
|
7294
7858
|
event_id: uuid(),
|
|
7295
7859
|
name,
|
|
@@ -7520,6 +8084,34 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7520
8084
|
...snap || buyerId !== void 0 ? { commerce: { ...snap ?? {}, ...buyer } } : {}
|
|
7521
8085
|
});
|
|
7522
8086
|
});
|
|
8087
|
+
on("vio:checkout-created", (ev) => {
|
|
8088
|
+
const d = ev.detail ?? {};
|
|
8089
|
+
const checkoutId = firstId(d.checkoutId);
|
|
8090
|
+
if (!checkoutId || this.paymentInfoReported.has(checkoutId)) return;
|
|
8091
|
+
this.paymentInfoReported.add(checkoutId);
|
|
8092
|
+
const sponsorId = toNumber(d.sponsorId);
|
|
8093
|
+
const base = { sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
|
|
8094
|
+
const payment = {
|
|
8095
|
+
checkoutId,
|
|
8096
|
+
...typeof d.paymentMethod === "string" && d.paymentMethod ? { paymentMethod: d.paymentMethod } : {}
|
|
8097
|
+
};
|
|
8098
|
+
const { slices, explicit } = this.cartSlices(sponsorId);
|
|
8099
|
+
if (slices.length > 1) {
|
|
8100
|
+
for (const slice of slices) {
|
|
8101
|
+
this.track("add_payment_info", {
|
|
8102
|
+
context: this.sliceContext(slice, base),
|
|
8103
|
+
commerce: { ...sliceCommerce(slice), ...payment },
|
|
8104
|
+
props: { slice: slice.slice, slices: slice.slices }
|
|
8105
|
+
});
|
|
8106
|
+
}
|
|
8107
|
+
} else {
|
|
8108
|
+
this.track("add_payment_info", {
|
|
8109
|
+
context: this.singleSliceContext(slices[0], explicit, base),
|
|
8110
|
+
commerce: { ...this.cartCommerce(sponsorId) ?? {}, ...payment }
|
|
8111
|
+
});
|
|
8112
|
+
}
|
|
8113
|
+
if (d.redirect === true) this.flushWithBeacon();
|
|
8114
|
+
});
|
|
7523
8115
|
on("vio:payment-success", (ev) => {
|
|
7524
8116
|
const d = ev.detail ?? {};
|
|
7525
8117
|
const ids = purchaseIdsOf(d);
|
|
@@ -7600,6 +8192,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7600
8192
|
}
|
|
7601
8193
|
this.record(event);
|
|
7602
8194
|
}
|
|
8195
|
+
this.sendNow();
|
|
7603
8196
|
};
|
|
7604
8197
|
const ask = ids.orderIdKind === "checkout" ? this.orderReferenceAsker(ids.checkoutId, sponsorId) : null;
|
|
7605
8198
|
if (!ask) {
|
|
@@ -7608,7 +8201,6 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7608
8201
|
}
|
|
7609
8202
|
if (this.isPageHidden()) {
|
|
7610
8203
|
send();
|
|
7611
|
-
this.flushWithBeacon();
|
|
7612
8204
|
return;
|
|
7613
8205
|
}
|
|
7614
8206
|
let done = false;
|
|
@@ -7622,6 +8214,14 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
7622
8214
|
this.pendingPurchases.add(settleNow);
|
|
7623
8215
|
void waitForOrderNumber(ask, () => done).then(settle, () => settle());
|
|
7624
8216
|
}
|
|
8217
|
+
/**
|
|
8218
|
+
* Sends the queue now, not at the next tick: by beacon when the page is
|
|
8219
|
+
* hidden or going away (a request may not survive it), else by fetch.
|
|
8220
|
+
*/
|
|
8221
|
+
sendNow() {
|
|
8222
|
+
if (this.isPageHidden()) this.flushWithBeacon();
|
|
8223
|
+
else void this.flush();
|
|
8224
|
+
}
|
|
7625
8225
|
/** Sends every purchase still waiting for its order number, at once, with what it has. */
|
|
7626
8226
|
settlePendingPurchases() {
|
|
7627
8227
|
for (const settle of [...this.pendingPurchases]) settle();
|
|
@@ -7782,6 +8382,15 @@ function safeStorageSet(key, value) {
|
|
|
7782
8382
|
} catch {
|
|
7783
8383
|
}
|
|
7784
8384
|
}
|
|
8385
|
+
function impressionCommerce(read) {
|
|
8386
|
+
if (!read) return void 0;
|
|
8387
|
+
try {
|
|
8388
|
+
const commerce = read();
|
|
8389
|
+
return commerce?.items?.length ? commerce : void 0;
|
|
8390
|
+
} catch {
|
|
8391
|
+
return void 0;
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
7785
8394
|
function compact(obj) {
|
|
7786
8395
|
const out = {};
|
|
7787
8396
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -7957,6 +8566,7 @@ function pushGa4(event) {
|
|
|
7957
8566
|
"add_to_cart",
|
|
7958
8567
|
"view_cart",
|
|
7959
8568
|
"begin_checkout",
|
|
8569
|
+
"add_payment_info",
|
|
7960
8570
|
"purchase"
|
|
7961
8571
|
]);
|
|
7962
8572
|
if (!commerceNames.has(event.name)) return;
|
|
@@ -7970,6 +8580,7 @@ function pushGa4(event) {
|
|
|
7970
8580
|
currency: commerce.currency,
|
|
7971
8581
|
value: commerce.value,
|
|
7972
8582
|
transaction_id: commerce.order_id,
|
|
8583
|
+
payment_type: event.name === "add_payment_info" ? commerce.payment_method : void 0,
|
|
7973
8584
|
items: commerce.items?.map(
|
|
7974
8585
|
(i) => compact({
|
|
7975
8586
|
item_id: i.product_id,
|
|
@@ -8113,6 +8724,10 @@ var VioFacade = class {
|
|
|
8113
8724
|
this.surfaceMode = null;
|
|
8114
8725
|
this.surfaceRequest = null;
|
|
8115
8726
|
this.preloadPrivacyMode();
|
|
8727
|
+
try {
|
|
8728
|
+
this.analytics.syncInternal(true);
|
|
8729
|
+
} catch {
|
|
8730
|
+
}
|
|
8116
8731
|
this.checkoutManager?.clearPaymentMethodsCache();
|
|
8117
8732
|
if (this.slotDeclareTimer) {
|
|
8118
8733
|
clearTimeout(this.slotDeclareTimer);
|
|
@@ -8304,9 +8919,9 @@ var VioFacade = class {
|
|
|
8304
8919
|
for (const l of all) {
|
|
8305
8920
|
if (l.campaignId !== void 0 && l.campaignId === pageCampaignId) delete l.campaignId;
|
|
8306
8921
|
}
|
|
8307
|
-
const
|
|
8922
|
+
const memory2 = readSlotDeclaration(storageKey);
|
|
8308
8923
|
const keyOf = (l) => slotDeclarationKey(l, pageCampaignId);
|
|
8309
|
-
const pending2 = new Set(pendingSlotKeys(
|
|
8924
|
+
const pending2 = new Set(pendingSlotKeys(memory2, all.map(keyOf), now));
|
|
8310
8925
|
let locations = opts.force ? all : all.filter((l) => pending2.has(keyOf(l)));
|
|
8311
8926
|
if (locations.length === 0) return null;
|
|
8312
8927
|
if (locations.length > MAX_LOCATIONS_PER_DECLARATION) {
|
|
@@ -8339,11 +8954,11 @@ var VioFacade = class {
|
|
|
8339
8954
|
if (result?.mode === "merge") {
|
|
8340
8955
|
const unhandled = this.campaignNotTakenUp(result, locations, pageCampaignId);
|
|
8341
8956
|
const settled = keys.filter((k) => !unhandled.has(k));
|
|
8342
|
-
let next = rememberSlotKeys(
|
|
8957
|
+
let next = rememberSlotKeys(memory2, settled, now);
|
|
8343
8958
|
if (unhandled.size > 0) next = rememberSlotKeys(next, [...unhandled], failedAt(now));
|
|
8344
8959
|
writeSlotDeclaration(storageKey, next);
|
|
8345
8960
|
} else {
|
|
8346
|
-
writeSlotDeclaration(storageKey, rememberSlotKeys(
|
|
8961
|
+
writeSlotDeclaration(storageKey, rememberSlotKeys(memory2, keys, failedAt(now)));
|
|
8347
8962
|
warn(
|
|
8348
8963
|
"slots declared but the server ignored mode=merge (old server); not caching, will retry next load"
|
|
8349
8964
|
);
|
|
@@ -8793,6 +9408,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
|
|
|
8793
9408
|
};
|
|
8794
9409
|
}
|
|
8795
9410
|
|
|
8796
|
-
export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CHECKOUT_SPONSOR_STORAGE_KEY, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, checkoutSponsorOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature };
|
|
8797
|
-
//# sourceMappingURL=chunk-
|
|
8798
|
-
//# sourceMappingURL=chunk-
|
|
9411
|
+
export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CHECKOUT_SPONSOR_STORAGE_KEY, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, INTERNAL_KEY, INTERNAL_PARAM, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, checkoutSponsorOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCountryCode, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, isValidPhone, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readInternal, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveInternal, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storageGet, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature, writeInternal };
|
|
9412
|
+
//# sourceMappingURL=chunk-IR4XYTZL.js.map
|
|
9413
|
+
//# sourceMappingURL=chunk-IR4XYTZL.js.map
|