@vio-live/web-sdk 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -13
- package/dist/{chunk-AXJKB2CW.js → chunk-5KWICUME.js} +958 -139
- package/dist/chunk-5KWICUME.js.map +1 -0
- package/dist/{chunk-N6U65DWG.js → chunk-A3LN5JEM.js} +147 -41
- package/dist/chunk-A3LN5JEM.js.map +1 -0
- package/dist/chunk-NCP76AXW.js +3 -0
- package/dist/{chunk-45YKS5UG.js.map → chunk-NCP76AXW.js.map} +1 -1
- package/dist/core/index.d.ts +1692 -1107
- 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-C3qiGvLV.d.ts → tokens-Bn7fK9ta.d.ts} +42 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +2 -1
- package/dist/chunk-45YKS5UG.js +0 -3
- package/dist/chunk-AXJKB2CW.js.map +0 -1
- package/dist/chunk-N6U65DWG.js.map +0 -1
|
@@ -17,6 +17,19 @@ var SLOT_DECLARATION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
|
17
17
|
var SLOT_DECLARATION_FAILURE_TTL_MS = 60 * 60 * 1e3;
|
|
18
18
|
var MAX_LOCATIONS_PER_DECLARATION = 100;
|
|
19
19
|
var MAX_DECLARATIONS_PER_LOAD = 3;
|
|
20
|
+
var SLOT_KIND_MAX_LENGTH = 32;
|
|
21
|
+
function componentKindOf(templateId, slot) {
|
|
22
|
+
const hint = typeof templateId === "string" ? templateId.trim().toLowerCase() : "";
|
|
23
|
+
if (hint) return hint.replace(/^(product|vev)-/, "").slice(0, SLOT_KIND_MAX_LENGTH) || void 0;
|
|
24
|
+
const id = typeof slot === "string" ? slot.toLowerCase() : "";
|
|
25
|
+
const m = id.match(/(?:^|-)(card|carousel|grid|detail|banner|list|hero|stepper)(?:-|$)/);
|
|
26
|
+
return m?.[1];
|
|
27
|
+
}
|
|
28
|
+
function normalizeSlotKind(value) {
|
|
29
|
+
if (typeof value !== "string") return void 0;
|
|
30
|
+
const kind = value.trim().toLowerCase().slice(0, SLOT_KIND_MAX_LENGTH).trim();
|
|
31
|
+
return kind || void 0;
|
|
32
|
+
}
|
|
20
33
|
function resolveSlotCampaign(boot, slot, now = Date.now(), platform, pageCampaignId, sponsorId) {
|
|
21
34
|
if (!boot) return null;
|
|
22
35
|
const page = activeCampaignEntry(boot, pageCampaignId);
|
|
@@ -163,9 +176,11 @@ function normalizeSlotLocations(slots) {
|
|
|
163
176
|
seen.add(id);
|
|
164
177
|
const displayName = raw && typeof raw === "object" && typeof raw.displayName === "string" ? raw.displayName.trim() : "";
|
|
165
178
|
const campaignId = raw && typeof raw === "object" ? toCampaignId(raw.campaignId) : void 0;
|
|
179
|
+
const kind = raw && typeof raw === "object" ? normalizeSlotKind(raw.kind) : void 0;
|
|
166
180
|
const location2 = { id };
|
|
167
181
|
if (displayName) location2.displayName = displayName;
|
|
168
182
|
if (campaignId !== void 0) location2.campaignId = campaignId;
|
|
183
|
+
if (kind) location2.kind = kind;
|
|
169
184
|
out.push(location2);
|
|
170
185
|
}
|
|
171
186
|
return out;
|
|
@@ -189,7 +204,8 @@ function normalizeScope(scope) {
|
|
|
189
204
|
var SLOT_DECLARATION_MEMORY_CAP = 500;
|
|
190
205
|
function slotDeclarationKey(location2, pageCampaignId) {
|
|
191
206
|
const campaignId = toCampaignId(location2.campaignId) ?? toCampaignId(pageCampaignId);
|
|
192
|
-
|
|
207
|
+
const kind = normalizeSlotKind(location2.kind);
|
|
208
|
+
return location2.id + (location2.displayName ? `${location2.displayName}` : "") + (campaignId !== void 0 ? `${campaignId}` : "") + (kind !== void 0 ? `${kind}` : "");
|
|
193
209
|
}
|
|
194
210
|
function pendingSlotKeys(memory, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
|
|
195
211
|
return keys.filter((key) => {
|
|
@@ -199,8 +215,8 @@ function pendingSlotKeys(memory, keys, now = Date.now(), ttl = SLOT_DECLARATION_
|
|
|
199
215
|
});
|
|
200
216
|
}
|
|
201
217
|
function slotKeyId(key) {
|
|
202
|
-
const
|
|
203
|
-
return
|
|
218
|
+
const cut4 = key.search(/[\u001f\u001e\u001d]/);
|
|
219
|
+
return cut4 >= 0 ? key.slice(0, cut4) : key;
|
|
204
220
|
}
|
|
205
221
|
function rememberSlotKeys(memory, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
|
|
206
222
|
const next = { ...memory ?? {} };
|
|
@@ -290,6 +306,8 @@ var ConfigurationSingleton = class {
|
|
|
290
306
|
contentTitle: input.contentTitle,
|
|
291
307
|
campaignId: toCampaignId(input.campaignId),
|
|
292
308
|
debug: input.debug === true,
|
|
309
|
+
// Only the two words the backend knows; anything else is "not said".
|
|
310
|
+
privacyMode: input.privacyMode === "minimal" || input.privacyMode === "standard" ? input.privacyMode : void 0,
|
|
293
311
|
apiBase: input.apiBase ?? defaults.apiBase,
|
|
294
312
|
graphQLBase: input.graphQLBase ?? defaults.graphQLBase,
|
|
295
313
|
eventsBase: input.eventsBase ?? defaults.eventsBase,
|
|
@@ -379,6 +397,14 @@ var VioApi = class {
|
|
|
379
397
|
async declareManifest(body) {
|
|
380
398
|
return this.post("/v2/mobile/components/manifest", body);
|
|
381
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* The surface of the key (`GET /v2/mobile/surface`): its id, name, status
|
|
402
|
+
* and `privacyMode` — what decides whether the order attribution may carry
|
|
403
|
+
* the entry context (ADR-0035). A 404 means a service that predates it.
|
|
404
|
+
*/
|
|
405
|
+
async surface() {
|
|
406
|
+
return this.get("/v2/mobile/surface");
|
|
407
|
+
}
|
|
382
408
|
/**
|
|
383
409
|
* Get placement components for a campaign. Used by the carousel auto-fetch
|
|
384
410
|
* mode (`<vio-product-carousel campaign-id="36" location="news-favorites">`).
|
|
@@ -698,6 +724,214 @@ function createCommerceClient(options) {
|
|
|
698
724
|
return new CommerceClient(options);
|
|
699
725
|
}
|
|
700
726
|
|
|
727
|
+
// src/core/content.ts
|
|
728
|
+
var CONTENT_ID_QUERY = "vio_content";
|
|
729
|
+
var CONTENT_TITLE_QUERY = "vio_content_title";
|
|
730
|
+
var CONTENT_ID_MAX_LENGTH = 128;
|
|
731
|
+
var CONTENT_TITLE_MAX_LENGTH = 255;
|
|
732
|
+
var CONTENT_URL_MAX_LENGTH = 2048;
|
|
733
|
+
function originAndPath(raw, max = CONTENT_URL_MAX_LENGTH) {
|
|
734
|
+
const value = text(raw);
|
|
735
|
+
if (!value) return void 0;
|
|
736
|
+
try {
|
|
737
|
+
const url = new URL(value);
|
|
738
|
+
return cutTo(`${url.origin}${url.pathname}`, max);
|
|
739
|
+
} catch {
|
|
740
|
+
const end = value.search(/[?#]/);
|
|
741
|
+
return cutTo(end >= 0 ? value.slice(0, end) : value, max);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function cutTo(value, max) {
|
|
745
|
+
const out = value.length > max ? value.slice(0, max) : value;
|
|
746
|
+
return out || void 0;
|
|
747
|
+
}
|
|
748
|
+
function capContentUrl(value) {
|
|
749
|
+
const url = text(value);
|
|
750
|
+
if (!url) return void 0;
|
|
751
|
+
if (url.length <= CONTENT_URL_MAX_LENGTH) return url;
|
|
752
|
+
const cut4 = url.search(/[?#]/);
|
|
753
|
+
const withoutQuery = cut4 >= 0 ? url.slice(0, cut4) : url;
|
|
754
|
+
return withoutQuery.length <= CONTENT_URL_MAX_LENGTH ? withoutQuery : withoutQuery.slice(0, CONTENT_URL_MAX_LENGTH);
|
|
755
|
+
}
|
|
756
|
+
function resolveContent(init, doc = typeof document !== "undefined" ? document : null, loc = typeof location !== "undefined" ? location : null) {
|
|
757
|
+
const out = {};
|
|
758
|
+
try {
|
|
759
|
+
const url = originAndPath(loc?.href);
|
|
760
|
+
if (url !== void 0) out.url = url;
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
const query = parseQuery(loc?.search);
|
|
765
|
+
const id = fits(text(init?.contentId)) ?? fits(text(query.get(CONTENT_ID_QUERY))) ?? fits(text(meta(doc, "name", "vio:content-id"))) ?? fits(stripQueryAndHash(text(attr(doc, 'link[rel="canonical"]', "href")))) ?? fits(stripQueryAndHash(text(meta(doc, "property", "og:url")))) ?? fits(text(loc?.pathname));
|
|
766
|
+
const title = text(init?.contentTitle) ?? text(query.get(CONTENT_TITLE_QUERY)) ?? text(meta(doc, "name", "vio:content-title")) ?? text(meta(doc, "property", "og:title")) ?? text(doc?.title);
|
|
767
|
+
if (id !== void 0) out.id = id;
|
|
768
|
+
if (title !== void 0) out.title = title.slice(0, CONTENT_TITLE_MAX_LENGTH);
|
|
769
|
+
return out;
|
|
770
|
+
} catch {
|
|
771
|
+
return out;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function fits(value) {
|
|
775
|
+
return value !== void 0 && value.length <= CONTENT_ID_MAX_LENGTH ? value : void 0;
|
|
776
|
+
}
|
|
777
|
+
function text(value) {
|
|
778
|
+
if (value === void 0 || value === null) return void 0;
|
|
779
|
+
const s = String(value).trim();
|
|
780
|
+
return s ? s : void 0;
|
|
781
|
+
}
|
|
782
|
+
function parseQuery(search) {
|
|
783
|
+
try {
|
|
784
|
+
return new URLSearchParams(typeof search === "string" ? search : "");
|
|
785
|
+
} catch {
|
|
786
|
+
return new URLSearchParams();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function meta(doc, key, value) {
|
|
790
|
+
return attr(doc, `meta[${key}="${value}"]`, "content");
|
|
791
|
+
}
|
|
792
|
+
function attr(doc, selector, name) {
|
|
793
|
+
try {
|
|
794
|
+
if (!doc || typeof doc.querySelector !== "function") return void 0;
|
|
795
|
+
const el = doc.querySelector(selector);
|
|
796
|
+
const v = el?.getAttribute?.(name);
|
|
797
|
+
return typeof v === "string" ? v : void 0;
|
|
798
|
+
} catch {
|
|
799
|
+
return void 0;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function stripQueryAndHash(url) {
|
|
803
|
+
if (!url) return void 0;
|
|
804
|
+
const cut4 = url.search(/[?#]/);
|
|
805
|
+
const clean = cut4 >= 0 ? url.slice(0, cut4) : url;
|
|
806
|
+
return clean.trim() || void 0;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/core/cart/origin-wire.ts
|
|
810
|
+
var GRAPHQL_INT_MAX = 2147483647;
|
|
811
|
+
var LINE_ORIGIN_CAPS = Object.freeze({
|
|
812
|
+
locationId: SLOT_ID_MAX_LENGTH,
|
|
813
|
+
componentKind: SLOT_KIND_MAX_LENGTH,
|
|
814
|
+
contentUrl: 2048,
|
|
815
|
+
contentTitle: CONTENT_TITLE_MAX_LENGTH
|
|
816
|
+
});
|
|
817
|
+
function lineOriginWire(origin, options = {}) {
|
|
818
|
+
if (!origin || typeof origin !== "object") return void 0;
|
|
819
|
+
const page = options.page ?? {};
|
|
820
|
+
const standard = options.privacyMode === "standard";
|
|
821
|
+
const out = {};
|
|
822
|
+
const campaignId = Number(origin.campaignId);
|
|
823
|
+
if (Number.isInteger(campaignId) && campaignId > 0 && campaignId <= GRAPHQL_INT_MAX) out.campaign_id = campaignId;
|
|
824
|
+
const locationId = cut(origin.slot, LINE_ORIGIN_CAPS.locationId);
|
|
825
|
+
if (locationId && standard) out.location_id = locationId;
|
|
826
|
+
const kind = cut(origin.componentKind, LINE_ORIGIN_CAPS.componentKind) ?? componentKindOf(void 0, origin.slot);
|
|
827
|
+
if (kind) out.component_kind = kind;
|
|
828
|
+
if (!out.campaign_id && !out.location_id && !out.component_kind) return void 0;
|
|
829
|
+
if (!standard) return out;
|
|
830
|
+
const url = originAndPath(origin.contentUrl, LINE_ORIGIN_CAPS.contentUrl) ?? originAndPath(page.contentUrl, LINE_ORIGIN_CAPS.contentUrl);
|
|
831
|
+
if (url) out.content_url = url;
|
|
832
|
+
const title = cut(origin.contentTitle, LINE_ORIGIN_CAPS.contentTitle) ?? cut(page.contentTitle, LINE_ORIGIN_CAPS.contentTitle);
|
|
833
|
+
if (title) out.content_title = title;
|
|
834
|
+
return out;
|
|
835
|
+
}
|
|
836
|
+
function currentOriginPage() {
|
|
837
|
+
const out = {};
|
|
838
|
+
try {
|
|
839
|
+
const facade = globalThis.__VIO_FACADE__;
|
|
840
|
+
const content = typeof facade?.content === "function" ? facade.content() : resolveContent();
|
|
841
|
+
const url = originAndPath(content?.url, LINE_ORIGIN_CAPS.contentUrl);
|
|
842
|
+
if (url) out.contentUrl = url;
|
|
843
|
+
const title = cut(content?.title, LINE_ORIGIN_CAPS.contentTitle);
|
|
844
|
+
if (title) out.contentTitle = title;
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
function cut(value, max) {
|
|
850
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
851
|
+
const text3 = String(value).trim();
|
|
852
|
+
if (!text3) return void 0;
|
|
853
|
+
return text3.length > max ? text3.slice(0, max).trim() || void 0 : text3;
|
|
854
|
+
}
|
|
855
|
+
async function currentPrivacyMode() {
|
|
856
|
+
try {
|
|
857
|
+
const facade = globalThis.__VIO_FACADE__;
|
|
858
|
+
if (typeof facade?.privacyMode !== "function") return "unknown";
|
|
859
|
+
const mode = await facade.privacyMode();
|
|
860
|
+
return mode === "standard" || mode === "minimal" ? mode : "unknown";
|
|
861
|
+
} catch {
|
|
862
|
+
return "unknown";
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/core/buyer-ref.ts
|
|
867
|
+
var BUYER_REF_MAX_LENGTH = 128;
|
|
868
|
+
var BUYER_REF_PATTERN = /^[A-Za-z0-9._~-]+$/;
|
|
869
|
+
function normalizeBuyerRef(value) {
|
|
870
|
+
if (typeof value !== "string") return void 0;
|
|
871
|
+
const ref = value.trim();
|
|
872
|
+
if (!ref || ref.length > BUYER_REF_MAX_LENGTH || !BUYER_REF_PATTERN.test(ref)) return void 0;
|
|
873
|
+
return ref;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/core/api/gateway-features.ts
|
|
877
|
+
var FEATURE_TOKENS = {
|
|
878
|
+
buyer_ref: ["buyer_ref"],
|
|
879
|
+
line_origin: ["origin", "CartLineOriginInput"],
|
|
880
|
+
checkout_attribution: ["attribution", "CheckoutAttributionInput", "CheckoutAttributionEntryInput"]
|
|
881
|
+
};
|
|
882
|
+
var UNKNOWN_PATTERN = /cannot query field|unknown field|unknown argument|unknown type|is not defined by type|not defined by type|field "[^"]+" is not defined/i;
|
|
883
|
+
var refused = /* @__PURE__ */ new Set();
|
|
884
|
+
var warned = /* @__PURE__ */ new Set();
|
|
885
|
+
function gatewayHas(feature) {
|
|
886
|
+
return !refused.has(feature);
|
|
887
|
+
}
|
|
888
|
+
function resetGatewayFeatures(feature) {
|
|
889
|
+
if (feature) {
|
|
890
|
+
refused.delete(feature);
|
|
891
|
+
warned.delete(feature);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
refused.clear();
|
|
895
|
+
warned.clear();
|
|
896
|
+
}
|
|
897
|
+
function messagesOf(err) {
|
|
898
|
+
if (!err || typeof err !== "object") return [];
|
|
899
|
+
const { message, graphql } = err;
|
|
900
|
+
return [message, ...(graphql?.errors ?? []).map((e) => e?.message)].filter(
|
|
901
|
+
(m) => typeof m === "string"
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
function ranSomething(err) {
|
|
905
|
+
if (!err || typeof err !== "object") return false;
|
|
906
|
+
const graphql = err.graphql;
|
|
907
|
+
if (graphql?.hasData === true) return true;
|
|
908
|
+
return (graphql?.errors ?? []).some((e) => Array.isArray(e?.path) && e.path.length > 0);
|
|
909
|
+
}
|
|
910
|
+
function isUnknownTokenError(err, token) {
|
|
911
|
+
if (ranSomething(err)) return false;
|
|
912
|
+
return messagesOf(err).some((m) => m.includes(`"${token}"`) && UNKNOWN_PATTERN.test(m));
|
|
913
|
+
}
|
|
914
|
+
function isUnknownFeatureError(err, feature) {
|
|
915
|
+
return FEATURE_TOKENS[feature].some((token) => isUnknownTokenError(err, token));
|
|
916
|
+
}
|
|
917
|
+
async function withGatewayFeature(feature, request) {
|
|
918
|
+
if (!gatewayHas(feature)) return request(false);
|
|
919
|
+
try {
|
|
920
|
+
return await request(true);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
if (!isUnknownFeatureError(err, feature)) throw err;
|
|
923
|
+
refused.add(feature);
|
|
924
|
+
if (!warned.has(feature) && typeof console !== "undefined") {
|
|
925
|
+
warned.add(feature);
|
|
926
|
+
console.warn(
|
|
927
|
+
`[Vio] the commerce gateway does not know "${FEATURE_TOKENS[feature][0]}" (${feature}) yet \u2014 sent without it on this page load:`,
|
|
928
|
+
messagesOf(err)[0] ?? ""
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
return request(false);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
701
935
|
// src/core/api/cart-queries.ts
|
|
702
936
|
var CREATE_CART_MUTATION = `
|
|
703
937
|
mutation CreateCart($customerSessionId: String!, $currency: String!, $shippingCountry: String) {
|
|
@@ -915,10 +1149,10 @@ mutation DeleteItem($cartId: String!, $cartItemId: String!) {
|
|
|
915
1149
|
}
|
|
916
1150
|
}
|
|
917
1151
|
`;
|
|
918
|
-
var
|
|
919
|
-
mutation UpdateItem($cartId: String!, $cartItemId: String!, $shippingId: String, $qty: Int) {
|
|
1152
|
+
var updateItemMutation = (withExtra) => `
|
|
1153
|
+
mutation UpdateItem($cartId: String!, $cartItemId: String!, $shippingId: String, $qty: Int${withExtra ? ", $origin: CartLineOriginInput" : ""}) {
|
|
920
1154
|
Cart {
|
|
921
|
-
UpdateItem(cart_id: $cartId, cart_item_id: $cartItemId, shipping_id: $shippingId, qty: $qty) {
|
|
1155
|
+
UpdateItem(cart_id: $cartId, cart_item_id: $cartItemId, shipping_id: $shippingId, qty: $qty${withExtra ? ", origin: $origin" : ""}) {
|
|
922
1156
|
cart_id
|
|
923
1157
|
customer_session_id
|
|
924
1158
|
shipping_country
|
|
@@ -987,6 +1221,8 @@ mutation UpdateItem($cartId: String!, $cartItemId: String!, $shippingId: String,
|
|
|
987
1221
|
}
|
|
988
1222
|
}
|
|
989
1223
|
`;
|
|
1224
|
+
var UPDATE_ITEM_MUTATION = updateItemMutation(false);
|
|
1225
|
+
var UPDATE_ITEM_WITH_ORIGIN_MUTATION = updateItemMutation(true);
|
|
990
1226
|
var GET_CART_QUERY = `
|
|
991
1227
|
query GetCart($cartId: String!) {
|
|
992
1228
|
Cart {
|
|
@@ -1200,10 +1436,10 @@ mutation UpdateShippingsBySupplier($cartId: String!, $data: [DataUpdateShippings
|
|
|
1200
1436
|
}
|
|
1201
1437
|
}
|
|
1202
1438
|
`;
|
|
1203
|
-
var
|
|
1204
|
-
mutation CreateCheckout($cartId: String
|
|
1439
|
+
var createCheckoutMutation = (withExtra) => `
|
|
1440
|
+
mutation CreateCheckout($cartId: String!${withExtra ? ", $attribution: CheckoutAttributionInput" : ""}) {
|
|
1205
1441
|
Checkout {
|
|
1206
|
-
CreateCheckout(cart_id: $cartId) {
|
|
1442
|
+
CreateCheckout(cart_id: $cartId${withExtra ? ", attribution: $attribution" : ""}) {
|
|
1207
1443
|
buyer_accepts_purchase_conditions
|
|
1208
1444
|
buyer_accepts_terms_conditions
|
|
1209
1445
|
created_at
|
|
@@ -1332,10 +1568,12 @@ mutation CreateCheckout($cartId: String!) {
|
|
|
1332
1568
|
}
|
|
1333
1569
|
}
|
|
1334
1570
|
`;
|
|
1335
|
-
var
|
|
1336
|
-
|
|
1571
|
+
var CREATE_CHECKOUT_MUTATION = createCheckoutMutation(false);
|
|
1572
|
+
var CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION = createCheckoutMutation(true);
|
|
1573
|
+
var updateCheckoutMutation = (withExtra) => `
|
|
1574
|
+
mutation UpdateCheckout($checkoutId: String!, $status: String, $successUrl: String, $email: String, $cancelUrl: String, $paymentMethod: String, $shippingAddress: AddressArgs, $billingAddress: AddressArgs, $buyerAcceptsPurchaseConditions: Boolean, $buyerAcceptsTermsConditions: Boolean${withExtra ? ", $attribution: CheckoutAttributionInput" : ""}) {
|
|
1337
1575
|
Checkout {
|
|
1338
|
-
UpdateCheckout(checkout_id: $checkoutId, status: $status, success_url: $successUrl, email: $email, cancel_url: $cancelUrl, payment_method: $paymentMethod, shipping_address: $shippingAddress, billing_address: $billingAddress, buyer_accepts_purchase_conditions: $buyerAcceptsPurchaseConditions, buyer_accepts_terms_conditions: $buyerAcceptsTermsConditions) {
|
|
1576
|
+
UpdateCheckout(checkout_id: $checkoutId, status: $status, success_url: $successUrl, email: $email, cancel_url: $cancelUrl, payment_method: $paymentMethod, shipping_address: $shippingAddress, billing_address: $billingAddress, buyer_accepts_purchase_conditions: $buyerAcceptsPurchaseConditions, buyer_accepts_terms_conditions: $buyerAcceptsTermsConditions${withExtra ? ", attribution: $attribution" : ""}) {
|
|
1339
1577
|
buyer_accepts_purchase_conditions
|
|
1340
1578
|
buyer_accepts_terms_conditions
|
|
1341
1579
|
created_at
|
|
@@ -1464,6 +1702,8 @@ mutation UpdateCheckout($checkoutId: String!, $status: String, $successUrl: Stri
|
|
|
1464
1702
|
}
|
|
1465
1703
|
}
|
|
1466
1704
|
`;
|
|
1705
|
+
var UPDATE_CHECKOUT_MUTATION = updateCheckoutMutation(false);
|
|
1706
|
+
var UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION = updateCheckoutMutation(true);
|
|
1467
1707
|
var GET_CHECKOUT_QUERY = `
|
|
1468
1708
|
query GetCheckout($checkoutId: String!) {
|
|
1469
1709
|
Checkout {
|
|
@@ -1686,7 +1926,9 @@ async function executeCartGraphQL(query, variables, options = {}) {
|
|
|
1686
1926
|
const json = await Promise.race([res.json().catch(() => ({})), timedOut]);
|
|
1687
1927
|
if (!res.ok || json.errors) {
|
|
1688
1928
|
const errMsg = json.errors?.[0]?.message || `GraphQL request failed (HTTP ${res.status})`;
|
|
1689
|
-
throw new Error(errMsg)
|
|
1929
|
+
throw Object.assign(new Error(errMsg), {
|
|
1930
|
+
graphql: { hasData: json.data !== void 0 && json.data !== null, errors: json.errors ?? [] }
|
|
1931
|
+
});
|
|
1690
1932
|
}
|
|
1691
1933
|
return json;
|
|
1692
1934
|
} finally {
|
|
@@ -1706,19 +1948,37 @@ async function createCart(customerSessionId, currency, shippingCountry, options)
|
|
|
1706
1948
|
return json?.data?.Cart?.CreateCart;
|
|
1707
1949
|
}
|
|
1708
1950
|
async function addItem(cartId, lineItems, options) {
|
|
1709
|
-
const
|
|
1951
|
+
const attributed = lineItems.some((li) => !!li?.origin);
|
|
1952
|
+
const json = attributed ? await withGatewayFeature(
|
|
1953
|
+
"line_origin",
|
|
1954
|
+
(supported) => executeCartGraphQL(
|
|
1955
|
+
ADD_ITEM_MUTATION,
|
|
1956
|
+
{ cartId, lineItems: supported ? lineItems : lineItems.map(withoutOrigin) },
|
|
1957
|
+
options
|
|
1958
|
+
)
|
|
1959
|
+
) : await executeCartGraphQL(ADD_ITEM_MUTATION, { cartId, lineItems }, options);
|
|
1710
1960
|
return json?.data?.Cart?.AddItem;
|
|
1711
1961
|
}
|
|
1962
|
+
function withoutOrigin(lineItem) {
|
|
1963
|
+
if (!lineItem || typeof lineItem !== "object") return lineItem;
|
|
1964
|
+
const { origin: _origin, ...rest } = lineItem;
|
|
1965
|
+
return rest;
|
|
1966
|
+
}
|
|
1712
1967
|
async function deleteItem(cartId, cartItemId, options) {
|
|
1713
1968
|
const json = await executeCartGraphQL(DELETE_ITEM_MUTATION, { cartId, cartItemId }, options);
|
|
1714
1969
|
return json?.data?.Cart?.DeleteItem;
|
|
1715
1970
|
}
|
|
1716
|
-
async function updateItem(cartId, cartItemId, shippingId, qty, options) {
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1971
|
+
async function updateItem(cartId, cartItemId, shippingId, qty, options, extra = {}) {
|
|
1972
|
+
const variables = { cartId, cartItemId, shippingId: shippingId || null, qty: qty != null ? qty : null };
|
|
1973
|
+
const origin = extra.origin;
|
|
1974
|
+
const json = origin ? await withGatewayFeature(
|
|
1975
|
+
"line_origin",
|
|
1976
|
+
(supported) => executeCartGraphQL(
|
|
1977
|
+
supported ? UPDATE_ITEM_WITH_ORIGIN_MUTATION : UPDATE_ITEM_MUTATION,
|
|
1978
|
+
supported ? { ...variables, origin } : variables,
|
|
1979
|
+
options
|
|
1980
|
+
)
|
|
1981
|
+
) : await executeCartGraphQL(UPDATE_ITEM_MUTATION, variables, options);
|
|
1722
1982
|
return json?.data?.Cart?.UpdateItem;
|
|
1723
1983
|
}
|
|
1724
1984
|
async function getCart(cartId, options) {
|
|
@@ -1737,32 +1997,68 @@ async function updateShippingsBySupplier(cartId, data, options) {
|
|
|
1737
1997
|
);
|
|
1738
1998
|
return json?.data?.Cart?.UpdateShippingsBySupplier;
|
|
1739
1999
|
}
|
|
1740
|
-
async function createCheckout(cartId, options) {
|
|
1741
|
-
const
|
|
2000
|
+
async function createCheckout(cartId, options, extra = {}) {
|
|
2001
|
+
const attribution = extra.attribution;
|
|
2002
|
+
const json = attribution ? await withGatewayFeature(
|
|
2003
|
+
"checkout_attribution",
|
|
2004
|
+
(supported) => executeCartGraphQL(
|
|
2005
|
+
supported ? CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION : CREATE_CHECKOUT_MUTATION,
|
|
2006
|
+
supported ? { cartId, attribution } : { cartId },
|
|
2007
|
+
options
|
|
2008
|
+
)
|
|
2009
|
+
) : await executeCartGraphQL(CREATE_CHECKOUT_MUTATION, { cartId }, options);
|
|
1742
2010
|
return json?.data?.Checkout?.CreateCheckout;
|
|
1743
2011
|
}
|
|
1744
2012
|
async function updateCheckout(variables, options) {
|
|
1745
|
-
const
|
|
2013
|
+
const { attribution, ...legacy } = variables;
|
|
2014
|
+
const json = attribution ? await withGatewayFeature(
|
|
2015
|
+
"checkout_attribution",
|
|
2016
|
+
(supported) => executeCartGraphQL(
|
|
2017
|
+
supported ? UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION : UPDATE_CHECKOUT_MUTATION,
|
|
2018
|
+
supported ? variables : legacy,
|
|
2019
|
+
options
|
|
2020
|
+
)
|
|
2021
|
+
) : await executeCartGraphQL(UPDATE_CHECKOUT_MUTATION, legacy, options);
|
|
1746
2022
|
return json?.data?.Checkout?.UpdateCheckout;
|
|
1747
2023
|
}
|
|
1748
2024
|
async function getCheckout(checkoutId, options) {
|
|
1749
2025
|
const json = await executeCartGraphQL(GET_CHECKOUT_QUERY, { checkoutId }, options);
|
|
1750
2026
|
return json?.data?.Checkout?.GetCheckout;
|
|
1751
2027
|
}
|
|
1752
|
-
var
|
|
2028
|
+
var BUYER_REF_FIELD = "buyer_ref";
|
|
2029
|
+
function gatewayHasBuyerRef() {
|
|
2030
|
+
return gatewayHas("buyer_ref");
|
|
2031
|
+
}
|
|
2032
|
+
function withBuyerRef(request) {
|
|
2033
|
+
return withGatewayFeature("buyer_ref", request);
|
|
2034
|
+
}
|
|
2035
|
+
var orderNumberQuery = (withRef) => `
|
|
1753
2036
|
query GetCheckoutOrderNumber($checkoutId: String!) {
|
|
1754
2037
|
Checkout {
|
|
1755
2038
|
GetCheckout(checkout_id: $checkoutId) {
|
|
1756
2039
|
id
|
|
1757
|
-
order_id
|
|
2040
|
+
order_id${withRef ? `
|
|
2041
|
+
${BUYER_REF_FIELD}` : ""}
|
|
1758
2042
|
}
|
|
1759
2043
|
}
|
|
1760
2044
|
}
|
|
1761
2045
|
`;
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2046
|
+
var GET_CHECKOUT_ORDER_NUMBER_QUERY = orderNumberQuery(true);
|
|
2047
|
+
var GET_CHECKOUT_ORDER_NUMBER_QUERY_LEGACY = orderNumberQuery(false);
|
|
2048
|
+
async function getCheckoutReference(checkoutId, options) {
|
|
2049
|
+
const json = await withBuyerRef(
|
|
2050
|
+
(withRef) => executeCartGraphQL(
|
|
2051
|
+
withRef ? GET_CHECKOUT_ORDER_NUMBER_QUERY : GET_CHECKOUT_ORDER_NUMBER_QUERY_LEGACY,
|
|
2052
|
+
{ checkoutId },
|
|
2053
|
+
options
|
|
2054
|
+
)
|
|
2055
|
+
);
|
|
2056
|
+
const checkout = json?.data?.Checkout?.GetCheckout;
|
|
2057
|
+
const n = Number(checkout?.order_id);
|
|
2058
|
+
return {
|
|
2059
|
+
orderNumber: Number.isInteger(n) && n > 0 ? n : null,
|
|
2060
|
+
buyerRef: normalizeBuyerRef(checkout?.[BUYER_REF_FIELD]) ?? null
|
|
2061
|
+
};
|
|
1766
2062
|
}
|
|
1767
2063
|
async function getAvailablePaymentMethods(options) {
|
|
1768
2064
|
const json = await executeCartGraphQL(GET_AVAILABLE_PAYMENT_METHODS_QUERY, {}, options);
|
|
@@ -1796,18 +2092,27 @@ async function createPaymentApplePay(variables, options) {
|
|
|
1796
2092
|
const json = await executeCartGraphQL(CREATE_PAYMENT_APPLE_PAY_MUTATION, variables, options);
|
|
1797
2093
|
return json?.data?.Payment?.CreatePaymentApplePay;
|
|
1798
2094
|
}
|
|
1799
|
-
var
|
|
2095
|
+
var confirmApplePayMutation = (withRef) => `
|
|
1800
2096
|
mutation ConfirmPaymentApplePay($checkoutId: String!, $applePayToken: String!, $email: String, $shippingAddress: ApplePayAddressInput) {
|
|
1801
2097
|
Payment {
|
|
1802
2098
|
ConfirmPaymentApplePay(checkout_id: $checkoutId, apple_pay_token: $applePayToken, email: $email, shipping_address: $shippingAddress) {
|
|
1803
2099
|
order_id
|
|
1804
|
-
status
|
|
2100
|
+
status${withRef ? `
|
|
2101
|
+
${BUYER_REF_FIELD}` : ""}
|
|
1805
2102
|
}
|
|
1806
2103
|
}
|
|
1807
2104
|
}
|
|
1808
2105
|
`;
|
|
2106
|
+
var CONFIRM_PAYMENT_APPLE_PAY_MUTATION = confirmApplePayMutation(true);
|
|
2107
|
+
var CONFIRM_PAYMENT_APPLE_PAY_MUTATION_LEGACY = confirmApplePayMutation(false);
|
|
1809
2108
|
async function confirmPaymentApplePay(variables, options) {
|
|
1810
|
-
const json = await
|
|
2109
|
+
const json = await withBuyerRef(
|
|
2110
|
+
(withRef) => executeCartGraphQL(
|
|
2111
|
+
withRef ? CONFIRM_PAYMENT_APPLE_PAY_MUTATION : CONFIRM_PAYMENT_APPLE_PAY_MUTATION_LEGACY,
|
|
2112
|
+
variables,
|
|
2113
|
+
options
|
|
2114
|
+
)
|
|
2115
|
+
);
|
|
1811
2116
|
return json?.data?.Payment?.ConfirmPaymentApplePay;
|
|
1812
2117
|
}
|
|
1813
2118
|
|
|
@@ -1845,6 +2150,33 @@ async function getVippsStatus(variables, options) {
|
|
|
1845
2150
|
|
|
1846
2151
|
// src/core/cart/cart-manager.ts
|
|
1847
2152
|
var STORAGE_KEY = "vio.cart.v1";
|
|
2153
|
+
function finalTarget(sync) {
|
|
2154
|
+
return sync.steps.reduce((quantity, step) => quantity + step.delta, sync.sent);
|
|
2155
|
+
}
|
|
2156
|
+
function queueStep(sync, target, origin) {
|
|
2157
|
+
const delta = target - finalTarget(sync);
|
|
2158
|
+
if (delta === 0) return;
|
|
2159
|
+
if (delta > 0) {
|
|
2160
|
+
const last = sync.steps[sync.steps.length - 1];
|
|
2161
|
+
if (last && last.delta > 0 && sameOrigin(last.origin ?? {}, origin ?? {})) last.delta += delta;
|
|
2162
|
+
else sync.steps.push({ delta, origin });
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
let left = -delta;
|
|
2166
|
+
while (left > 0 && sync.steps.length > 0) {
|
|
2167
|
+
const last = sync.steps[sync.steps.length - 1];
|
|
2168
|
+
if (last.delta <= 0) break;
|
|
2169
|
+
const take = Math.min(last.delta, left);
|
|
2170
|
+
last.delta -= take;
|
|
2171
|
+
left -= take;
|
|
2172
|
+
if (last.delta === 0) sync.steps.pop();
|
|
2173
|
+
}
|
|
2174
|
+
if (left > 0) {
|
|
2175
|
+
const last = sync.steps[sync.steps.length - 1];
|
|
2176
|
+
if (last && last.delta < 0) last.delta -= left;
|
|
2177
|
+
else sync.steps.push({ delta: -left });
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
1848
2180
|
function lineId() {
|
|
1849
2181
|
return "li_" + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
|
1850
2182
|
}
|
|
@@ -1936,7 +2268,7 @@ var CartManager = class extends EventTarget {
|
|
|
1936
2268
|
* undefined when no quantity sync is active for it. */
|
|
1937
2269
|
intendedQuantity(sponsorId, cartItemId) {
|
|
1938
2270
|
const sync = this.qtySyncs.get(this.qtySyncKey(sponsorId, cartItemId));
|
|
1939
|
-
return sync ? sync
|
|
2271
|
+
return sync ? finalTarget(sync) : void 0;
|
|
1940
2272
|
}
|
|
1941
2273
|
/** Apply a backend response only if no newer response was applied already.
|
|
1942
2274
|
* Returns false when it was discarded as stale. */
|
|
@@ -2099,7 +2431,8 @@ var CartManager = class extends EventTarget {
|
|
|
2099
2431
|
currency: li.price?.currency_code || backendCart.currency || matchedLocal?.currency || "NOK",
|
|
2100
2432
|
imageUrl,
|
|
2101
2433
|
quantity: intended ?? li.quantity,
|
|
2102
|
-
// The server
|
|
2434
|
+
// The server keeps its own copy of the origins (per request); the
|
|
2435
|
+
// cart read model does not answer them, so keep the local ones. They
|
|
2103
2436
|
// are squared with its quantity (a stock clamp) only when no OTHER
|
|
2104
2437
|
// mutation is in flight for this sponsor — an AddItem answering
|
|
2105
2438
|
// while another is pending reports a quantity that is already old
|
|
@@ -2214,11 +2547,16 @@ var CartManager = class extends EventTarget {
|
|
|
2214
2547
|
const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
|
|
2215
2548
|
if (cartId) {
|
|
2216
2549
|
const gqlOpts = await getCartGraphQLOptions(opts.sponsorId);
|
|
2550
|
+
const origin = opts.origin ? lineOriginWire(normalizeOrigin(opts.origin), {
|
|
2551
|
+
page: currentOriginPage(),
|
|
2552
|
+
privacyMode: await currentPrivacyMode()
|
|
2553
|
+
}) : void 0;
|
|
2217
2554
|
const lineItemsInput = [
|
|
2218
2555
|
{
|
|
2219
2556
|
product_id: Number(opts.productId || 0),
|
|
2220
2557
|
quantity: qty,
|
|
2221
|
-
variant_id: opts.variantId != null ? Number(opts.variantId) : null
|
|
2558
|
+
variant_id: opts.variantId != null ? Number(opts.variantId) : null,
|
|
2559
|
+
...origin ? { origin } : {}
|
|
2222
2560
|
}
|
|
2223
2561
|
];
|
|
2224
2562
|
const resCart = await addItem(cartId, lineItemsInput, gqlOpts);
|
|
@@ -2271,8 +2609,10 @@ var CartManager = class extends EventTarget {
|
|
|
2271
2609
|
if (target === item.quantity) return;
|
|
2272
2610
|
const targetItemId = item.cartItemId;
|
|
2273
2611
|
const removedOrigin = latestOrigin(item);
|
|
2274
|
-
|
|
2612
|
+
const increase = target > item.quantity;
|
|
2613
|
+
if (increase) addOriginQuantity(item, opts.origin, target - item.quantity);
|
|
2275
2614
|
else removeOriginQuantity(item, item.quantity - target);
|
|
2615
|
+
const addedOrigin = increase ? latestOrigin(item) : void 0;
|
|
2276
2616
|
if (target <= 0) {
|
|
2277
2617
|
cart.items = cart.items.filter((i) => i !== item);
|
|
2278
2618
|
if (typeof document !== "undefined") {
|
|
@@ -2307,37 +2647,47 @@ var CartManager = class extends EventTarget {
|
|
|
2307
2647
|
this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
|
|
2308
2648
|
return;
|
|
2309
2649
|
}
|
|
2310
|
-
this.syncQuantity(sponsorId, cart.cartId, targetItemId, target);
|
|
2650
|
+
this.syncQuantity(sponsorId, cart.cartId, targetItemId, target, addedOrigin);
|
|
2311
2651
|
}
|
|
2312
2652
|
/** Send a line's target quantity to the backend — ONE request in flight per
|
|
2313
|
-
* line.
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
*
|
|
2318
|
-
*
|
|
2319
|
-
|
|
2653
|
+
* line. Targets that arrive while one is in flight queue behind it as
|
|
2654
|
+
* steps (`queueStep`): reduced like the line's origins, sent in order once
|
|
2655
|
+
* the current request settles — one request per queued increase from a
|
|
2656
|
+
* different origin, one for a net decrease — so the server ends on the
|
|
2657
|
+
* last intent, credits every increase to its own origin, and never sees two
|
|
2658
|
+
* updates racing each other. While the sync is active the line keeps its
|
|
2659
|
+
* optimistic quantity through any backend response (see
|
|
2660
|
+
* `updateFromBackendCart`); a rejection drops what was queued and
|
|
2661
|
+
* reconciles the cart from the server. */
|
|
2662
|
+
syncQuantity(sponsorId, backendCartId, cartItemId, target, origin) {
|
|
2320
2663
|
const key = this.qtySyncKey(sponsorId, cartItemId);
|
|
2321
2664
|
const active = this.qtySyncs.get(key);
|
|
2322
2665
|
if (active) {
|
|
2323
|
-
active
|
|
2666
|
+
queueStep(active, target, origin);
|
|
2324
2667
|
return;
|
|
2325
2668
|
}
|
|
2326
|
-
const sync = { sent: target };
|
|
2669
|
+
const sync = { sent: target, steps: [] };
|
|
2327
2670
|
this.qtySyncs.set(key, sync);
|
|
2328
2671
|
let settling;
|
|
2329
2672
|
const run = (async () => {
|
|
2330
2673
|
let next = target;
|
|
2674
|
+
let nextOrigin = origin;
|
|
2331
2675
|
let removalRetried = false;
|
|
2332
2676
|
while (next !== void 0) {
|
|
2333
|
-
sync.sent = next;
|
|
2334
|
-
sync.pending = void 0;
|
|
2335
2677
|
const seq = this.nextSeq(sponsorId);
|
|
2336
2678
|
try {
|
|
2337
2679
|
const gqlOpts = await getCartGraphQLOptions(sponsorId);
|
|
2338
|
-
const
|
|
2339
|
-
|
|
2340
|
-
|
|
2680
|
+
const originWire = next > 0 && nextOrigin ? lineOriginWire(nextOrigin, { page: currentOriginPage(), privacyMode: await currentPrivacyMode() }) : void 0;
|
|
2681
|
+
const resCart = next <= 0 ? await deleteItem(backendCartId, cartItemId, gqlOpts) : originWire ? await updateItem(backendCartId, cartItemId, null, next, gqlOpts, { origin: originWire }) : await updateItem(backendCartId, cartItemId, null, next, gqlOpts);
|
|
2682
|
+
const step = sync.steps.shift();
|
|
2683
|
+
if (step) {
|
|
2684
|
+
next = sync.sent + step.delta;
|
|
2685
|
+
nextOrigin = step.delta > 0 ? step.origin : void 0;
|
|
2686
|
+
sync.sent = next;
|
|
2687
|
+
} else {
|
|
2688
|
+
next = void 0;
|
|
2689
|
+
nextOrigin = void 0;
|
|
2690
|
+
}
|
|
2341
2691
|
if (next === void 0) this.qtySyncs.delete(key);
|
|
2342
2692
|
if (resCart && Array.isArray(resCart.line_items)) {
|
|
2343
2693
|
const applied = this.applyBackendCart(sponsorId, resCart, seq, settling);
|
|
@@ -2346,12 +2696,13 @@ var CartManager = class extends EventTarget {
|
|
|
2346
2696
|
}
|
|
2347
2697
|
}
|
|
2348
2698
|
} catch (err) {
|
|
2349
|
-
const retryRemoval = !removalRetried && sync.
|
|
2699
|
+
const retryRemoval = !removalRetried && sync.steps.length > 0 && finalTarget(sync) <= 0;
|
|
2700
|
+
sync.steps = [];
|
|
2350
2701
|
next = void 0;
|
|
2702
|
+
nextOrigin = void 0;
|
|
2351
2703
|
if (retryRemoval) {
|
|
2352
2704
|
removalRetried = true;
|
|
2353
2705
|
sync.sent = 0;
|
|
2354
|
-
sync.pending = void 0;
|
|
2355
2706
|
next = 0;
|
|
2356
2707
|
} else {
|
|
2357
2708
|
this.qtySyncs.delete(key);
|
|
@@ -2488,10 +2839,17 @@ function normalizeOrigin(origin) {
|
|
|
2488
2839
|
if (origin.contentId !== void 0 && origin.contentId !== null && String(origin.contentId).trim()) {
|
|
2489
2840
|
out.contentId = String(origin.contentId).trim();
|
|
2490
2841
|
}
|
|
2842
|
+
const kind = componentKindOf(origin.componentKind ?? origin.componentTemplateId);
|
|
2843
|
+
if (kind) out.componentKind = kind;
|
|
2844
|
+
const contentUrl = originAndPath(origin.contentUrl);
|
|
2845
|
+
if (contentUrl) out.contentUrl = contentUrl;
|
|
2846
|
+
if (typeof origin.contentTitle === "string" && origin.contentTitle.trim()) {
|
|
2847
|
+
out.contentTitle = origin.contentTitle.trim().slice(0, CONTENT_TITLE_MAX_LENGTH);
|
|
2848
|
+
}
|
|
2491
2849
|
return out;
|
|
2492
2850
|
}
|
|
2493
2851
|
function sameOrigin(a, b) {
|
|
2494
|
-
return a.campaignId === b.campaignId && a.slot === b.slot && a.contentId === b.contentId;
|
|
2852
|
+
return a.campaignId === b.campaignId && a.slot === b.slot && a.contentId === b.contentId && a.componentKind === b.componentKind;
|
|
2495
2853
|
}
|
|
2496
2854
|
function latestOrigin(line) {
|
|
2497
2855
|
const origins = Array.isArray(line.origins) ? line.origins : [];
|
|
@@ -2755,7 +3113,7 @@ async function prepareApplePay(config) {
|
|
|
2755
3113
|
}
|
|
2756
3114
|
|
|
2757
3115
|
// src/core/version.ts
|
|
2758
|
-
var SDK_VERSION = "0.
|
|
3116
|
+
var SDK_VERSION = "0.25.0";
|
|
2759
3117
|
|
|
2760
3118
|
// src/core/checkout/payments/stripe-embedded.ts
|
|
2761
3119
|
var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
|
|
@@ -4063,13 +4421,14 @@ mutation CreatePaymentKlarnaNative($checkoutId: String!, $countryCode: String, $
|
|
|
4063
4421
|
}
|
|
4064
4422
|
}
|
|
4065
4423
|
`;
|
|
4066
|
-
var
|
|
4424
|
+
var confirmPaymentKlarnaNativeMutation = (withRef) => `
|
|
4067
4425
|
mutation ConfirmPaymentKlarnaNative($checkoutId: String!, $authorizationToken: String!, $autoCapture: Boolean, $billingAddress: KlarnaNativeAddressInput, $shippingAddress: KlarnaNativeAddressInput, $customer: KlarnaNativeCustomerInput) {
|
|
4068
4426
|
Payment {
|
|
4069
4427
|
ConfirmPaymentKlarnaNative(checkout_id: $checkoutId, authorization_token: $authorizationToken, auto_capture: $autoCapture, billing_address: $billingAddress, shipping_address: $shippingAddress, customer: $customer) {
|
|
4070
4428
|
order_id
|
|
4071
4429
|
checkout_id
|
|
4072
|
-
fraud_status
|
|
4430
|
+
fraud_status${withRef ? `
|
|
4431
|
+
${BUYER_REF_FIELD}` : ""}
|
|
4073
4432
|
order {
|
|
4074
4433
|
order_id
|
|
4075
4434
|
status
|
|
@@ -4251,9 +4610,10 @@ async function executeKlarnaGraphQL(query, variables, sponsorId) {
|
|
|
4251
4610
|
return executeCartGraphQL(query, variables, await getCartGraphQLOptions(sponsorId));
|
|
4252
4611
|
}
|
|
4253
4612
|
var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
4254
|
-
constructor(cartManager) {
|
|
4613
|
+
constructor(cartManager, deps = {}) {
|
|
4255
4614
|
super();
|
|
4256
4615
|
this.cartManager = cartManager;
|
|
4616
|
+
this.deps = deps;
|
|
4257
4617
|
this.state = null;
|
|
4258
4618
|
/** Counts `open()` calls — see CheckoutState.session. */
|
|
4259
4619
|
this.sessionSeq = 0;
|
|
@@ -4293,6 +4653,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4293
4653
|
this.paymentMethodsCache = /* @__PURE__ */ new Map();
|
|
4294
4654
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4295
4655
|
this.paymentMethodsPromises = /* @__PURE__ */ new Map();
|
|
4656
|
+
/** In-flight `getOrderReference` per checkout id (see there). */
|
|
4657
|
+
this.referenceInFlight = /* @__PURE__ */ new Map();
|
|
4296
4658
|
this.kustomController = null;
|
|
4297
4659
|
preloadStripeJs();
|
|
4298
4660
|
}
|
|
@@ -4320,7 +4682,20 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4320
4682
|
}
|
|
4321
4683
|
// MARK: - Backend checkout (Vio Commerce GraphQL)
|
|
4322
4684
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4323
|
-
/**
|
|
4685
|
+
/**
|
|
4686
|
+
* The attribution of the order this sponsor's cart is about to become
|
|
4687
|
+
* (ADR-0035 phase B), through the facade; `undefined` when there is none
|
|
4688
|
+
* to give or it fails — attribution never costs a checkout.
|
|
4689
|
+
*/
|
|
4690
|
+
async attributionFor(sponsorId) {
|
|
4691
|
+
if (!sponsorId || typeof this.deps.orderAttribution !== "function") return void 0;
|
|
4692
|
+
try {
|
|
4693
|
+
return await this.deps.orderAttribution(sponsorId) ?? void 0;
|
|
4694
|
+
} catch {
|
|
4695
|
+
return void 0;
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
/** Create the backend checkout for the sponsor's cart (auto-accepts terms); born attributed when the facade knows the attribution. */
|
|
4324
4699
|
async createCheckout(sponsorId) {
|
|
4325
4700
|
await this.cartManager.waitForCartMutations(sponsorId);
|
|
4326
4701
|
const cart = this.cartManager.getCart(sponsorId);
|
|
@@ -4332,7 +4707,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4332
4707
|
}
|
|
4333
4708
|
}
|
|
4334
4709
|
const opts = await getCartGraphQLOptions(sponsorId);
|
|
4335
|
-
const
|
|
4710
|
+
const attribution = await this.attributionFor(sponsorId);
|
|
4711
|
+
const checkout = attribution ? await createCheckout(cartId, opts, { attribution }) : await createCheckout(cartId, opts);
|
|
4336
4712
|
if (checkout?.id) {
|
|
4337
4713
|
this.state = {
|
|
4338
4714
|
...this.state ?? { sponsorId, subtotal: 0, currency: getGlobalCurrency() },
|
|
@@ -4363,12 +4739,15 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4363
4739
|
}
|
|
4364
4740
|
return checkout;
|
|
4365
4741
|
}
|
|
4366
|
-
/** Update the backend checkout (terms are re-asserted unless overridden). */
|
|
4742
|
+
/** Update the backend checkout (terms are re-asserted unless overridden; the attribution rides along unless the caller gives its own). */
|
|
4367
4743
|
async updateCheckout(variables, sponsorId) {
|
|
4368
|
-
const
|
|
4744
|
+
const spId = sponsorId ?? this.state?.sponsorId;
|
|
4745
|
+
const opts = await getCartGraphQLOptions(spId);
|
|
4746
|
+
const attribution = "attribution" in variables ? void 0 : await this.attributionFor(spId);
|
|
4369
4747
|
const fullVars = {
|
|
4370
4748
|
buyerAcceptsPurchaseConditions: true,
|
|
4371
4749
|
buyerAcceptsTermsConditions: true,
|
|
4750
|
+
...attribution ? { attribution } : {},
|
|
4372
4751
|
...variables
|
|
4373
4752
|
};
|
|
4374
4753
|
const updated = await updateCheckout(fullVars, opts);
|
|
@@ -4388,13 +4767,30 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4388
4767
|
* receipt, and a receipt does not fail for it.
|
|
4389
4768
|
*/
|
|
4390
4769
|
async getOrderNumber(checkoutId, sponsorId) {
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4770
|
+
return (await this.getOrderReference(checkoutId, sponsorId)).orderNumber;
|
|
4771
|
+
}
|
|
4772
|
+
/**
|
|
4773
|
+
* What a paid checkout says of itself: the order number (as above) and the
|
|
4774
|
+
* buyer reference — shopcart's keyed hash of the buyer's email, what the
|
|
4775
|
+
* `vio:payment-success` detail carries as `buyerRef` and analytics sends as
|
|
4776
|
+
* `buyer_id`. Never throws; `null` for whatever the API does not say.
|
|
4777
|
+
*/
|
|
4778
|
+
async getOrderReference(checkoutId, sponsorId) {
|
|
4779
|
+
if (!checkoutId) return { orderNumber: null, buyerRef: null };
|
|
4780
|
+
const inFlight = this.referenceInFlight.get(checkoutId);
|
|
4781
|
+
if (inFlight) return inFlight;
|
|
4782
|
+
const request = (async () => {
|
|
4783
|
+
try {
|
|
4784
|
+
const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
|
|
4785
|
+
return await getCheckoutReference(checkoutId, opts);
|
|
4786
|
+
} catch {
|
|
4787
|
+
return { orderNumber: null, buyerRef: null };
|
|
4788
|
+
} finally {
|
|
4789
|
+
this.referenceInFlight.delete(checkoutId);
|
|
4790
|
+
}
|
|
4791
|
+
})();
|
|
4792
|
+
this.referenceInFlight.set(checkoutId, request);
|
|
4793
|
+
return request;
|
|
4398
4794
|
}
|
|
4399
4795
|
/**
|
|
4400
4796
|
* Payment methods enabled for the sponsor (backend-configured). Memoised per
|
|
@@ -4525,9 +4921,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4525
4921
|
throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
|
|
4526
4922
|
}
|
|
4527
4923
|
const opts = await getCartGraphQLOptions(spId);
|
|
4924
|
+
const attribution = await this.attributionFor(spId);
|
|
4528
4925
|
let checkoutId = this.state?.checkoutId;
|
|
4529
4926
|
if (!checkoutId) {
|
|
4530
|
-
const checkoutRes = await createCheckout(cartId, opts);
|
|
4927
|
+
const checkoutRes = attribution ? await createCheckout(cartId, opts, { attribution }) : await createCheckout(cartId, opts);
|
|
4531
4928
|
checkoutId = checkoutRes?.id;
|
|
4532
4929
|
}
|
|
4533
4930
|
if (!checkoutId) {
|
|
@@ -4546,6 +4943,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
4546
4943
|
...hasEmail ? { email: emailVal } : {},
|
|
4547
4944
|
buyerAcceptsPurchaseConditions: true,
|
|
4548
4945
|
buyerAcceptsTermsConditions: true,
|
|
4946
|
+
...attribution ? { attribution } : {},
|
|
4549
4947
|
...extraUpdateVars
|
|
4550
4948
|
};
|
|
4551
4949
|
if (addressObj) {
|
|
@@ -5541,7 +5939,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
5541
5939
|
`[VioCheckout] Apple Pay confirmation failed (status: ${confirmRes?.status ?? "none"})`
|
|
5542
5940
|
);
|
|
5543
5941
|
}
|
|
5544
|
-
|
|
5942
|
+
const buyerRef = normalizeBuyerRef(confirmRes?.[BUYER_REF_FIELD]);
|
|
5943
|
+
return { orderId: confirmRes?.order_id, status: confirmRes?.status, ...buyerRef ? { buyerRef } : {} };
|
|
5545
5944
|
};
|
|
5546
5945
|
}
|
|
5547
5946
|
static {
|
|
@@ -6044,16 +6443,19 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6044
6443
|
shippingAddress: buildKlarnaAddress(address),
|
|
6045
6444
|
customer: buildKlarnaCustomer(address)
|
|
6046
6445
|
};
|
|
6047
|
-
const json = await
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6446
|
+
const json = await withBuyerRef(
|
|
6447
|
+
(withRef) => executeKlarnaGraphQL(
|
|
6448
|
+
confirmPaymentKlarnaNativeMutation(withRef),
|
|
6449
|
+
variables,
|
|
6450
|
+
ctx?.sponsorId ?? this.state?.sponsorId
|
|
6451
|
+
)
|
|
6051
6452
|
);
|
|
6052
6453
|
const confirmRes = json?.data?.Payment?.ConfirmPaymentKlarnaNative;
|
|
6053
6454
|
if (!confirmRes) {
|
|
6054
6455
|
throw new Error("Klarna order confirmation failed: missing response data");
|
|
6055
6456
|
}
|
|
6056
6457
|
const orderData = confirmRes.order || confirmRes;
|
|
6458
|
+
const buyerRef = normalizeBuyerRef(confirmRes[BUYER_REF_FIELD]);
|
|
6057
6459
|
if (typeof sessionStorage !== "undefined") {
|
|
6058
6460
|
sessionStorage.setItem("vio.klarna.confirmed.v1", authorizationToken);
|
|
6059
6461
|
sessionStorage.removeItem(KLARNA_PENDING_KEY);
|
|
@@ -6071,7 +6473,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
|
|
|
6071
6473
|
result: {
|
|
6072
6474
|
...result,
|
|
6073
6475
|
order: orderData,
|
|
6074
|
-
chargedTotal: typeof orderData?.order_amount === "number" ? orderData.order_amount / 100 : ctx.orderAmount / 100
|
|
6476
|
+
chargedTotal: typeof orderData?.order_amount === "number" ? orderData.order_amount / 100 : ctx.orderAmount / 100,
|
|
6477
|
+
...buyerRef ? { buyerRef } : {}
|
|
6075
6478
|
}
|
|
6076
6479
|
}
|
|
6077
6480
|
})
|
|
@@ -6129,57 +6532,173 @@ function toSmallestUnit(amount, currency) {
|
|
|
6129
6532
|
return Math.round(amount * 100);
|
|
6130
6533
|
}
|
|
6131
6534
|
|
|
6132
|
-
// src/core/
|
|
6133
|
-
var
|
|
6134
|
-
var
|
|
6135
|
-
var
|
|
6136
|
-
var
|
|
6137
|
-
|
|
6535
|
+
// src/core/analytics/client-context.ts
|
|
6536
|
+
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
6537
|
+
var CLIENT_LANGUAGE_MAX_LENGTH = 16;
|
|
6538
|
+
var CLIENT_REFERRER_MAX_LENGTH = 2048;
|
|
6539
|
+
var CLIENT_UTM_MAX_LENGTH = 128;
|
|
6540
|
+
var CLIENT_TZ_MAX_LENGTH = 64;
|
|
6541
|
+
var CLIENT_SCREEN_MAX = 65535;
|
|
6542
|
+
var CLIENT_LANDING_URL_MAX_LENGTH = 2048;
|
|
6543
|
+
var LANDING_KEY = "vio.landing.v1";
|
|
6544
|
+
function defaultClientEnvironment() {
|
|
6545
|
+
return {
|
|
6546
|
+
navigator: typeof navigator !== "undefined" ? navigator : null,
|
|
6547
|
+
document: typeof document !== "undefined" ? document : null,
|
|
6548
|
+
location: typeof location !== "undefined" ? location : null,
|
|
6549
|
+
screen: typeof screen !== "undefined" ? screen : null,
|
|
6550
|
+
storage: sessionStorageOrNull(),
|
|
6551
|
+
timeZone: () => Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
6552
|
+
};
|
|
6553
|
+
}
|
|
6554
|
+
function resolveLanding(env = defaultClientEnvironment(), persist = true) {
|
|
6138
6555
|
try {
|
|
6139
|
-
const
|
|
6140
|
-
const
|
|
6141
|
-
const
|
|
6142
|
-
const
|
|
6143
|
-
if (
|
|
6144
|
-
|
|
6145
|
-
|
|
6556
|
+
const utm = utmOf(env.location?.search);
|
|
6557
|
+
const raw = text2(env.document?.referrer);
|
|
6558
|
+
const internal = isSameHost(raw, env.location?.host);
|
|
6559
|
+
const stored = readLanding(env.storage);
|
|
6560
|
+
if ((utm === void 0 || internal) && stored) return stored;
|
|
6561
|
+
const landing = {};
|
|
6562
|
+
const referrer = internal ? void 0 : landingReferrer(raw);
|
|
6563
|
+
if (referrer) landing.referrer = referrer;
|
|
6564
|
+
if (utm) landing.utm = utm;
|
|
6565
|
+
const url = landingUrl(env.location?.href);
|
|
6566
|
+
if (url) landing.url = url;
|
|
6567
|
+
if (persist) writeLanding(env.storage, landing);
|
|
6568
|
+
return landing;
|
|
6146
6569
|
} catch {
|
|
6147
6570
|
return {};
|
|
6148
6571
|
}
|
|
6149
6572
|
}
|
|
6150
|
-
function
|
|
6151
|
-
return
|
|
6573
|
+
function landingReferrer(raw) {
|
|
6574
|
+
return originAndPath(raw, CLIENT_REFERRER_MAX_LENGTH);
|
|
6152
6575
|
}
|
|
6153
|
-
function
|
|
6154
|
-
|
|
6155
|
-
const s = String(value).trim();
|
|
6156
|
-
return s ? s : void 0;
|
|
6576
|
+
function landingUrl(raw) {
|
|
6577
|
+
return originAndPath(raw, CLIENT_LANDING_URL_MAX_LENGTH);
|
|
6157
6578
|
}
|
|
6158
|
-
function
|
|
6579
|
+
function storedLanding(storage2 = defaultClientEnvironment().storage) {
|
|
6580
|
+
return readLanding(storage2);
|
|
6581
|
+
}
|
|
6582
|
+
function isSameHost(referrer, host) {
|
|
6583
|
+
const value = text2(referrer);
|
|
6584
|
+
const own = text2(host);
|
|
6585
|
+
if (!value || !own) return false;
|
|
6159
6586
|
try {
|
|
6160
|
-
return new
|
|
6587
|
+
return new URL(value).host.toLowerCase() === own.toLowerCase();
|
|
6161
6588
|
} catch {
|
|
6162
|
-
return
|
|
6589
|
+
return false;
|
|
6163
6590
|
}
|
|
6164
6591
|
}
|
|
6165
|
-
function
|
|
6166
|
-
|
|
6592
|
+
function persistLanding(landing, storage2 = defaultClientEnvironment().storage) {
|
|
6593
|
+
writeLanding(storage2, landing);
|
|
6167
6594
|
}
|
|
6168
|
-
function
|
|
6595
|
+
function removeLanding(storage2 = defaultClientEnvironment().storage) {
|
|
6169
6596
|
try {
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6597
|
+
storage2?.removeItem?.(LANDING_KEY);
|
|
6598
|
+
} catch {
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
function resolveClientContext(env = defaultClientEnvironment(), landing = resolveLanding(env)) {
|
|
6602
|
+
const out = {};
|
|
6603
|
+
try {
|
|
6604
|
+
const language = cut2(text2(env.navigator?.language), CLIENT_LANGUAGE_MAX_LENGTH);
|
|
6605
|
+
if (language) out.language = language;
|
|
6606
|
+
const referrer = cut2(landing.referrer, CLIENT_REFERRER_MAX_LENGTH);
|
|
6607
|
+
if (referrer) out.referrer = referrer;
|
|
6608
|
+
for (const key of UTM_KEYS) {
|
|
6609
|
+
const value = cut2(landing.utm?.[key], CLIENT_UTM_MAX_LENGTH);
|
|
6610
|
+
if (value) out[key] = value;
|
|
6611
|
+
}
|
|
6612
|
+
const w = dimension(env.screen?.width);
|
|
6613
|
+
const h = dimension(env.screen?.height);
|
|
6614
|
+
if (w !== void 0) out.screen_w = w;
|
|
6615
|
+
if (h !== void 0) out.screen_h = h;
|
|
6616
|
+
const tz = cut2(text2(safeCall(env.timeZone)), CLIENT_TZ_MAX_LENGTH);
|
|
6617
|
+
if (tz) out.tz = tz;
|
|
6618
|
+
} catch {
|
|
6619
|
+
}
|
|
6620
|
+
return out;
|
|
6621
|
+
}
|
|
6622
|
+
function utmOf(search) {
|
|
6623
|
+
let params;
|
|
6624
|
+
try {
|
|
6625
|
+
params = new URLSearchParams(typeof search === "string" ? search : "");
|
|
6174
6626
|
} catch {
|
|
6175
6627
|
return void 0;
|
|
6176
6628
|
}
|
|
6629
|
+
const out = {};
|
|
6630
|
+
let any = false;
|
|
6631
|
+
for (const key of UTM_KEYS) {
|
|
6632
|
+
const value = cut2(text2(params.get(key)), CLIENT_UTM_MAX_LENGTH);
|
|
6633
|
+
if (value) {
|
|
6634
|
+
out[key] = value;
|
|
6635
|
+
any = true;
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
return any ? out : void 0;
|
|
6177
6639
|
}
|
|
6178
|
-
function
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6640
|
+
function readLanding(storage2) {
|
|
6641
|
+
try {
|
|
6642
|
+
const raw = storage2?.getItem(LANDING_KEY);
|
|
6643
|
+
if (!raw) return null;
|
|
6644
|
+
const parsed = JSON.parse(raw);
|
|
6645
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
6646
|
+
const p = parsed;
|
|
6647
|
+
const landing = {};
|
|
6648
|
+
const referrer = cut2(text2(p.referrer), CLIENT_REFERRER_MAX_LENGTH);
|
|
6649
|
+
if (referrer) landing.referrer = referrer;
|
|
6650
|
+
const url = cut2(text2(p.url), CLIENT_LANDING_URL_MAX_LENGTH);
|
|
6651
|
+
if (url) landing.url = url;
|
|
6652
|
+
if (p.utm && typeof p.utm === "object") {
|
|
6653
|
+
const utm = {};
|
|
6654
|
+
let any = false;
|
|
6655
|
+
for (const key of UTM_KEYS) {
|
|
6656
|
+
const value = cut2(text2(p.utm[key]), CLIENT_UTM_MAX_LENGTH);
|
|
6657
|
+
if (value) {
|
|
6658
|
+
utm[key] = value;
|
|
6659
|
+
any = true;
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
if (any) landing.utm = utm;
|
|
6663
|
+
}
|
|
6664
|
+
return landing;
|
|
6665
|
+
} catch {
|
|
6666
|
+
return null;
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
function writeLanding(storage2, landing) {
|
|
6670
|
+
try {
|
|
6671
|
+
storage2?.setItem(LANDING_KEY, JSON.stringify(landing));
|
|
6672
|
+
} catch {
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
function sessionStorageOrNull() {
|
|
6676
|
+
try {
|
|
6677
|
+
return typeof sessionStorage !== "undefined" && sessionStorage ? sessionStorage : null;
|
|
6678
|
+
} catch {
|
|
6679
|
+
return null;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
function text2(value) {
|
|
6683
|
+
if (value === void 0 || value === null) return void 0;
|
|
6684
|
+
const s = String(value).trim();
|
|
6685
|
+
return s ? s : void 0;
|
|
6686
|
+
}
|
|
6687
|
+
function cut2(value, max) {
|
|
6688
|
+
if (value === void 0) return void 0;
|
|
6689
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
6690
|
+
}
|
|
6691
|
+
function dimension(value) {
|
|
6692
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
6693
|
+
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
6694
|
+
return Math.min(Math.floor(n), CLIENT_SCREEN_MAX);
|
|
6695
|
+
}
|
|
6696
|
+
function safeCall(fn) {
|
|
6697
|
+
try {
|
|
6698
|
+
return fn ? fn() : void 0;
|
|
6699
|
+
} catch {
|
|
6700
|
+
return void 0;
|
|
6701
|
+
}
|
|
6183
6702
|
}
|
|
6184
6703
|
|
|
6185
6704
|
// src/core/cart/slices.ts
|
|
@@ -6280,8 +6799,16 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6280
6799
|
/** Last NON-EMPTY cart snapshot — `purchase` uses it because several flows
|
|
6281
6800
|
* (Apple Pay) clear the cart BEFORE `vio:payment-success` fires. */
|
|
6282
6801
|
this.lastCartSnapshot = null;
|
|
6802
|
+
/** The `client` block (language, landing referrer / utm, screen, tz): read once per page load, reused on every event. */
|
|
6803
|
+
this.client = null;
|
|
6804
|
+
/** The landing it was read from — persisted later when consent arrives after the first event. */
|
|
6805
|
+
this.landing = null;
|
|
6283
6806
|
this.teardown = [];
|
|
6284
6807
|
}
|
|
6808
|
+
/** The landing this manager resolved on this page load (the first event's), or null before it; read only — for the order's entry context when nothing is stored yet. */
|
|
6809
|
+
get currentLanding() {
|
|
6810
|
+
return this.landing;
|
|
6811
|
+
}
|
|
6285
6812
|
// MARK: - Public API
|
|
6286
6813
|
/**
|
|
6287
6814
|
* Install listeners + begin flushing. Explicit on purpose (no module
|
|
@@ -6299,10 +6826,12 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6299
6826
|
if (granted) {
|
|
6300
6827
|
if (this.anonIdCache) safeStorageSet(ANON_KEY, this.anonIdCache);
|
|
6301
6828
|
if (this.sessionId) this.persistSession();
|
|
6829
|
+
if (this.landing) persistLanding(this.landing);
|
|
6302
6830
|
return;
|
|
6303
6831
|
}
|
|
6304
6832
|
safeStorageRemove(ANON_KEY);
|
|
6305
6833
|
safeStorageRemove(SESSION_KEY);
|
|
6834
|
+
removeLanding();
|
|
6306
6835
|
}
|
|
6307
6836
|
/** ¿Podemos escribir en el dispositivo? */
|
|
6308
6837
|
get canPersist() {
|
|
@@ -6463,7 +6992,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6463
6992
|
// one is usually the tracking tail, not the page. The other host-given
|
|
6464
6993
|
// fields (broadcastId, componentTemplateId, errorCode, variant, …) are
|
|
6465
6994
|
// the host's responsibility: the SDK does not touch them.
|
|
6466
|
-
content_url:
|
|
6995
|
+
content_url: capContentUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
|
|
6467
6996
|
// The host's own content id — its join key back onto its reporting.
|
|
6468
6997
|
// Explicit on the event when it fits, else what the SDK resolved for
|
|
6469
6998
|
// the page (already capped per source), else nothing.
|
|
@@ -6491,8 +7020,13 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6491
7020
|
value: com.value,
|
|
6492
7021
|
currency: com.currency,
|
|
6493
7022
|
order_id: com.orderId,
|
|
6494
|
-
payment_method: com.paymentMethod
|
|
7023
|
+
payment_method: com.paymentMethod,
|
|
7024
|
+
// Only a reference survives — never an email, whatever a host passes —
|
|
7025
|
+
// and only with consent to persist: a visit measured with ephemeral
|
|
7026
|
+
// ids does not carry a stable identifier of a person either.
|
|
7027
|
+
buyer_id: this.canPersist ? normalizeBuyerRef(com.buyerId) : void 0
|
|
6495
7028
|
}) : void 0;
|
|
7029
|
+
const client = this.clientBlock();
|
|
6496
7030
|
return {
|
|
6497
7031
|
event_id: uuid(),
|
|
6498
7032
|
name,
|
|
@@ -6505,9 +7039,22 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6505
7039
|
...this.externalUserId ? { external_user_id: this.externalUserId } : {},
|
|
6506
7040
|
...Object.keys(context).length ? { context } : {},
|
|
6507
7041
|
...commerce ? { commerce } : {},
|
|
7042
|
+
...client ? { client } : {},
|
|
6508
7043
|
...opts.props ? { props: opts.props } : {}
|
|
6509
7044
|
};
|
|
6510
7045
|
}
|
|
7046
|
+
/**
|
|
7047
|
+
* The `client` block: read once — the landing is remembered for the session
|
|
7048
|
+
* only when we may persist — then the same (frozen) object on every event.
|
|
7049
|
+
* `null` when there is nothing to say (no browser).
|
|
7050
|
+
*/
|
|
7051
|
+
clientBlock() {
|
|
7052
|
+
if (!this.client) {
|
|
7053
|
+
this.landing = resolveLanding(void 0, this.canPersist);
|
|
7054
|
+
this.client = Object.freeze(resolveClientContext(void 0, this.landing));
|
|
7055
|
+
}
|
|
7056
|
+
return Object.keys(this.client).length ? this.client : null;
|
|
7057
|
+
}
|
|
6511
7058
|
/** Guarda la sesión solo si podemos escribir en el dispositivo. */
|
|
6512
7059
|
persistSession(now = Date.now()) {
|
|
6513
7060
|
if (!this.canPersist || !this.sessionId) return;
|
|
@@ -6621,7 +7168,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6621
7168
|
const d = ev.detail ?? {};
|
|
6622
7169
|
const line = this.findLine(d.productId, d.variantId);
|
|
6623
7170
|
this.track("add_to_cart", {
|
|
6624
|
-
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
|
|
7171
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), componentTemplateId: templateOf(d), ...contentOf(d) },
|
|
6625
7172
|
commerce: {
|
|
6626
7173
|
items: [
|
|
6627
7174
|
{
|
|
@@ -6671,7 +7218,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6671
7218
|
if (d.productId === void 0) return;
|
|
6672
7219
|
const price = Number(d.price);
|
|
6673
7220
|
this.track("select_item", {
|
|
6674
|
-
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) },
|
|
7221
|
+
context: { sponsorId: d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0, campaignId: campaignOf(d), slot: slotOf(d), componentTemplateId: templateOf(d), ...contentOf(d) },
|
|
6675
7222
|
commerce: {
|
|
6676
7223
|
items: [
|
|
6677
7224
|
{
|
|
@@ -6690,13 +7237,15 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6690
7237
|
});
|
|
6691
7238
|
on("vio:checkout-open", (ev) => {
|
|
6692
7239
|
const d = ev.detail ?? {};
|
|
6693
|
-
const base = { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
|
|
7240
|
+
const base = { sponsorId: d.sponsorId, campaignId: campaignOf(d), slot: slotOf(d), componentTemplateId: templateOf(d), ...contentOf(d) };
|
|
7241
|
+
const buyerId = buyerRefOf(d);
|
|
7242
|
+
const buyer = buyerId !== void 0 ? { buyerId } : {};
|
|
6694
7243
|
const { slices, explicit } = this.cartSlices(d.sponsorId);
|
|
6695
7244
|
if (slices.length > 1) {
|
|
6696
7245
|
for (const slice of slices) {
|
|
6697
7246
|
this.track("begin_checkout", {
|
|
6698
7247
|
context: this.sliceContext(slice, base),
|
|
6699
|
-
commerce: sliceCommerce(slice),
|
|
7248
|
+
commerce: { ...sliceCommerce(slice), ...buyer },
|
|
6700
7249
|
props: { slice: slice.slice, slices: slice.slices }
|
|
6701
7250
|
});
|
|
6702
7251
|
}
|
|
@@ -6705,7 +7254,7 @@ var AnalyticsManager = class extends EventTarget {
|
|
|
6705
7254
|
const snap = this.cartCommerce(d.sponsorId);
|
|
6706
7255
|
this.track("begin_checkout", {
|
|
6707
7256
|
context: this.singleSliceContext(slices[0], explicit, base),
|
|
6708
|
-
...snap ? { commerce: snap } : {}
|
|
7257
|
+
...snap || buyerId !== void 0 ? { commerce: { ...snap ?? {}, ...buyer } } : {}
|
|
6709
7258
|
});
|
|
6710
7259
|
});
|
|
6711
7260
|
on("vio:payment-success", (ev) => {
|
|
@@ -6933,20 +7482,16 @@ function capTitle(value) {
|
|
|
6933
7482
|
if (!value) return void 0;
|
|
6934
7483
|
return value.length > CONTENT_TITLE_MAX_LENGTH ? value.slice(0, CONTENT_TITLE_MAX_LENGTH) : value;
|
|
6935
7484
|
}
|
|
6936
|
-
var CONTENT_URL_MAX_LENGTH = 2048;
|
|
6937
|
-
function capUrl(value) {
|
|
6938
|
-
if (!value) return void 0;
|
|
6939
|
-
if (value.length <= CONTENT_URL_MAX_LENGTH) return value;
|
|
6940
|
-
const cut = value.search(/[?#]/);
|
|
6941
|
-
const withoutQuery = cut >= 0 ? value.slice(0, cut) : value;
|
|
6942
|
-
return withoutQuery.length <= CONTENT_URL_MAX_LENGTH ? withoutQuery : withoutQuery.slice(0, CONTENT_URL_MAX_LENGTH);
|
|
6943
|
-
}
|
|
6944
7485
|
function sliceCommerce(slice) {
|
|
6945
7486
|
return { items: slice.items, value: slice.value, currency: slice.currency };
|
|
6946
7487
|
}
|
|
6947
7488
|
function slotOf(detail) {
|
|
6948
7489
|
return typeof detail.slot === "string" && detail.slot.trim() ? detail.slot.trim() : void 0;
|
|
6949
7490
|
}
|
|
7491
|
+
function templateOf(detail) {
|
|
7492
|
+
const value = typeof detail.componentTemplateId === "string" ? detail.componentTemplateId.trim() : "";
|
|
7493
|
+
return fitsId(value || void 0);
|
|
7494
|
+
}
|
|
6950
7495
|
function contentOf(detail) {
|
|
6951
7496
|
const out = {};
|
|
6952
7497
|
if (typeof detail.contentId === "number" && Number.isFinite(detail.contentId)) out.contentId = detail.contentId;
|
|
@@ -6954,10 +7499,33 @@ function contentOf(detail) {
|
|
|
6954
7499
|
if (typeof detail.contentTitle === "string" && detail.contentTitle.trim()) out.contentTitle = detail.contentTitle.trim();
|
|
6955
7500
|
return out;
|
|
6956
7501
|
}
|
|
7502
|
+
function buyerRefOf(d) {
|
|
7503
|
+
if (!d) return void 0;
|
|
7504
|
+
const r = d.result ?? {};
|
|
7505
|
+
const candidates = [
|
|
7506
|
+
d.buyerRef,
|
|
7507
|
+
d.buyer_ref,
|
|
7508
|
+
r.buyerRef,
|
|
7509
|
+
r.buyer_ref,
|
|
7510
|
+
r.confirmation?.buyerRef,
|
|
7511
|
+
r.confirmation?.buyer_ref,
|
|
7512
|
+
r.checkout?.buyerRef,
|
|
7513
|
+
r.checkout?.buyer_ref
|
|
7514
|
+
];
|
|
7515
|
+
for (const candidate of candidates) {
|
|
7516
|
+
const ref = normalizeBuyerRef(candidate);
|
|
7517
|
+
if (ref) return ref;
|
|
7518
|
+
}
|
|
7519
|
+
return void 0;
|
|
7520
|
+
}
|
|
6957
7521
|
function purchaseFromPaymentSuccess(d) {
|
|
6958
7522
|
const r = d.result ?? {};
|
|
6959
7523
|
const orderId = firstId(d.orderId, r.confirmation?.orderId, d.checkoutId);
|
|
6960
|
-
|
|
7524
|
+
const buyerId = buyerRefOf(d);
|
|
7525
|
+
return {
|
|
7526
|
+
...orderId !== void 0 ? { orderId } : {},
|
|
7527
|
+
...buyerId !== void 0 ? { buyerId } : {}
|
|
7528
|
+
};
|
|
6961
7529
|
}
|
|
6962
7530
|
function firstId(...candidates) {
|
|
6963
7531
|
for (const c of candidates) {
|
|
@@ -7002,8 +7570,87 @@ function pushGa4(event) {
|
|
|
7002
7570
|
});
|
|
7003
7571
|
}
|
|
7004
7572
|
|
|
7573
|
+
// src/core/checkout/attribution.ts
|
|
7574
|
+
var CHECKOUT_ATTRIBUTION_CAPS = Object.freeze({
|
|
7575
|
+
surfaceKey: 255,
|
|
7576
|
+
contentUrl: 2048,
|
|
7577
|
+
contentTitle: CONTENT_TITLE_MAX_LENGTH,
|
|
7578
|
+
referrer: CLIENT_REFERRER_MAX_LENGTH,
|
|
7579
|
+
utm: CLIENT_UTM_MAX_LENGTH,
|
|
7580
|
+
landingUrl: CLIENT_LANDING_URL_MAX_LENGTH
|
|
7581
|
+
});
|
|
7582
|
+
function checkoutAttributionOf(input) {
|
|
7583
|
+
const out = {};
|
|
7584
|
+
const surfaceKey = cut3(input.surfaceKey, CHECKOUT_ATTRIBUTION_CAPS.surfaceKey);
|
|
7585
|
+
if (surfaceKey) out.surface_key = surfaceKey;
|
|
7586
|
+
const campaignId = positiveInt(input.pageCampaignId) ?? dominantCampaignOf(input.slices);
|
|
7587
|
+
if (campaignId !== void 0) out.campaign_id = campaignId;
|
|
7588
|
+
if (input.privacyMode === "standard") {
|
|
7589
|
+
const url = originAndPath(input.content?.url, CHECKOUT_ATTRIBUTION_CAPS.contentUrl);
|
|
7590
|
+
if (url) out.content_url = url;
|
|
7591
|
+
const title = cut3(input.content?.title, CHECKOUT_ATTRIBUTION_CAPS.contentTitle);
|
|
7592
|
+
if (title) out.content_title = title;
|
|
7593
|
+
const entry = entryContextOf(input.landing);
|
|
7594
|
+
if (entry) out.entry = entry;
|
|
7595
|
+
}
|
|
7596
|
+
return Object.keys(out).length ? out : void 0;
|
|
7597
|
+
}
|
|
7598
|
+
function entryContextOf(landing) {
|
|
7599
|
+
if (!landing || typeof landing !== "object") return void 0;
|
|
7600
|
+
const out = {};
|
|
7601
|
+
const referrer = cut3(landing.referrer, CHECKOUT_ATTRIBUTION_CAPS.referrer);
|
|
7602
|
+
if (referrer) out.referrer = referrer;
|
|
7603
|
+
const source = cut3(landing.utm?.utm_source, CHECKOUT_ATTRIBUTION_CAPS.utm);
|
|
7604
|
+
if (source) out.utm_source = source;
|
|
7605
|
+
const medium = cut3(landing.utm?.utm_medium, CHECKOUT_ATTRIBUTION_CAPS.utm);
|
|
7606
|
+
if (medium) out.utm_medium = medium;
|
|
7607
|
+
const campaign = cut3(landing.utm?.utm_campaign, CHECKOUT_ATTRIBUTION_CAPS.utm);
|
|
7608
|
+
if (campaign) out.utm_campaign = campaign;
|
|
7609
|
+
const landingUrl2 = cut3(landing.url, CHECKOUT_ATTRIBUTION_CAPS.landingUrl);
|
|
7610
|
+
if (landingUrl2) out.landing_url = landingUrl2;
|
|
7611
|
+
return Object.keys(out).length ? out : void 0;
|
|
7612
|
+
}
|
|
7613
|
+
function dominantCampaignOf(slices) {
|
|
7614
|
+
if (!Array.isArray(slices)) return void 0;
|
|
7615
|
+
let best;
|
|
7616
|
+
for (const slice of slices) {
|
|
7617
|
+
const campaignId = positiveInt(slice?.campaignId);
|
|
7618
|
+
if (campaignId === void 0) continue;
|
|
7619
|
+
const items = Array.isArray(slice.items) ? slice.items : [];
|
|
7620
|
+
const units = items.reduce((sum, item) => sum + (Number(item?.quantity) > 0 ? Number(item.quantity) : 0), 0);
|
|
7621
|
+
const value = Number(slice.value) || 0;
|
|
7622
|
+
if (!best || units > best.units || units === best.units && value > best.value) {
|
|
7623
|
+
best = { campaignId, units, value };
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
return best?.campaignId;
|
|
7627
|
+
}
|
|
7628
|
+
function positiveInt(value) {
|
|
7629
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
7630
|
+
const n = Number(value);
|
|
7631
|
+
return Number.isInteger(n) && n > 0 && n <= GRAPHQL_INT_MAX ? n : void 0;
|
|
7632
|
+
}
|
|
7633
|
+
function cut3(value, max) {
|
|
7634
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
7635
|
+
const text3 = String(value).trim();
|
|
7636
|
+
if (!text3) return void 0;
|
|
7637
|
+
return text3.length > max ? text3.slice(0, max).trim() || void 0 : text3;
|
|
7638
|
+
}
|
|
7639
|
+
|
|
7005
7640
|
// src/core/client.ts
|
|
7006
7641
|
var SLOT_DECLARATION_DEBOUNCE_MS = 1e3;
|
|
7642
|
+
var PRIVACY_MODE_TIMEOUT_MS = 1500;
|
|
7643
|
+
var PRIVACY_UNKNOWN_TTL_MS = 6e4;
|
|
7644
|
+
function privacyModeOf(value) {
|
|
7645
|
+
return value === "minimal" || value === "standard" ? value : void 0;
|
|
7646
|
+
}
|
|
7647
|
+
function privacyModeOfSurface(res) {
|
|
7648
|
+
const surface = res?.surface;
|
|
7649
|
+
if (!surface || typeof surface !== "object") return "unknown";
|
|
7650
|
+
const raw = surface.privacyMode;
|
|
7651
|
+
if (raw === void 0 || raw === null) return "standard";
|
|
7652
|
+
return privacyModeOf(raw) ?? "unknown";
|
|
7653
|
+
}
|
|
7007
7654
|
var VioFacade = class {
|
|
7008
7655
|
constructor() {
|
|
7009
7656
|
this.apiInstance = null;
|
|
@@ -7017,7 +7664,7 @@ var VioFacade = class {
|
|
|
7017
7664
|
* (another key or environment) is still in flight after a re-init; its answer
|
|
7018
7665
|
* belongs to that configuration and must not land in this one. */
|
|
7019
7666
|
this.generation = 0;
|
|
7020
|
-
/** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each, its display name
|
|
7667
|
+
/** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each, its display name, its own campaign and its kind. */
|
|
7021
7668
|
this.slotRegistry = /* @__PURE__ */ new Map();
|
|
7022
7669
|
/** Slots already warned about (`dashboard`: the dashboard's assignment stands; `replaced`: this page moved it). */
|
|
7023
7670
|
this.warnedDashboardSlots = /* @__PURE__ */ new Set();
|
|
@@ -7036,6 +7683,10 @@ var VioFacade = class {
|
|
|
7036
7683
|
this.cartManager = null;
|
|
7037
7684
|
this.checkoutManager = null;
|
|
7038
7685
|
this.analyticsManager = null;
|
|
7686
|
+
/** What the surface answered of its privacy mode (`GET /v2/mobile/surface`) and when; `unknown` expires after a minute. */
|
|
7687
|
+
this.surfaceMode = null;
|
|
7688
|
+
/** The request in flight for it, if any. */
|
|
7689
|
+
this.surfaceRequest = null;
|
|
7039
7690
|
}
|
|
7040
7691
|
init(config) {
|
|
7041
7692
|
Configuration.init(config);
|
|
@@ -7045,6 +7696,9 @@ var VioFacade = class {
|
|
|
7045
7696
|
this.cachedBootstrap = null;
|
|
7046
7697
|
this.bootstrapFailedAt = 0;
|
|
7047
7698
|
this.generation++;
|
|
7699
|
+
this.surfaceMode = null;
|
|
7700
|
+
this.surfaceRequest = null;
|
|
7701
|
+
this.preloadPrivacyMode();
|
|
7048
7702
|
this.checkoutManager?.clearPaymentMethodsCache();
|
|
7049
7703
|
if (this.slotDeclareTimer) {
|
|
7050
7704
|
clearTimeout(this.slotDeclareTimer);
|
|
@@ -7086,9 +7740,13 @@ var VioFacade = class {
|
|
|
7086
7740
|
if (!this.cartManager) this.cartManager = new CartManager();
|
|
7087
7741
|
return this.cartManager;
|
|
7088
7742
|
}
|
|
7089
|
-
/** Checkout flow manager. Subscribes to cart state via the CartManager. */
|
|
7743
|
+
/** Checkout flow manager. Subscribes to cart state via the CartManager; asks here for the order's attribution. */
|
|
7090
7744
|
get checkout() {
|
|
7091
|
-
if (!this.checkoutManager)
|
|
7745
|
+
if (!this.checkoutManager) {
|
|
7746
|
+
this.checkoutManager = new CheckoutManager(this.cart, {
|
|
7747
|
+
orderAttribution: (sponsorId) => this.orderAttribution(sponsorId)
|
|
7748
|
+
});
|
|
7749
|
+
}
|
|
7092
7750
|
return this.checkoutManager;
|
|
7093
7751
|
}
|
|
7094
7752
|
/**
|
|
@@ -7130,6 +7788,7 @@ var VioFacade = class {
|
|
|
7130
7788
|
this.cachedBootstrap = res;
|
|
7131
7789
|
this.bootstrapFailedAt = 0;
|
|
7132
7790
|
if (this.slotsDirty) this.scheduleSlotDeclaration();
|
|
7791
|
+
this.preloadPrivacyMode();
|
|
7133
7792
|
return res;
|
|
7134
7793
|
},
|
|
7135
7794
|
(err) => {
|
|
@@ -7256,21 +7915,31 @@ var VioFacade = class {
|
|
|
7256
7915
|
* the optional display name, and the element's own campaign when it has
|
|
7257
7916
|
* one: `vio-campaign` or an ancestor's `data-vio-campaign`). The registry is
|
|
7258
7917
|
* declared ONCE, debounced, after the first successful bootstrap — and again
|
|
7259
|
-
* whenever a new id, display name or
|
|
7918
|
+
* whenever a new id, display name, campaign or kind appears. A `displayName` /
|
|
7260
7919
|
* `campaignId` given later for an id already registered replaces the one
|
|
7261
|
-
* kept; `undefined` leaves it alone. The
|
|
7920
|
+
* kept; `undefined` leaves it alone. The third argument is the campaign id
|
|
7921
|
+
* (as before) or `{ campaignId?, kind? }`, and a fourth `{ kind? }` may
|
|
7922
|
+
* follow a positional campaign — the elements call
|
|
7923
|
+
* `registerSlot(id, name, campaignId, { kind })` so a facade from an older
|
|
7924
|
+
* bundle, which reads only three arguments, still gets the campaign. `kind`
|
|
7925
|
+
* is what kind of element holds the slot (`card` | `carousel` | `detail`);
|
|
7926
|
+
* the FIRST kind an id gets stays (the cards a carousel renders share its
|
|
7927
|
+
* id: the slot is the carousel's). The returned function unregisters;
|
|
7262
7928
|
* removal never re-declares.
|
|
7263
7929
|
*/
|
|
7264
|
-
registerSlot(name, displayName,
|
|
7930
|
+
registerSlot(name, displayName, campaignIdOrOptions, options) {
|
|
7265
7931
|
const id = normalizeSlotId(name);
|
|
7266
7932
|
if (!id) return () => {
|
|
7267
7933
|
};
|
|
7268
7934
|
const label = typeof displayName === "string" && displayName.trim() ? displayName.trim() : void 0;
|
|
7269
|
-
const
|
|
7935
|
+
const positional = campaignIdOrOptions !== null && typeof campaignIdOrOptions === "object" ? campaignIdOrOptions : { campaignId: campaignIdOrOptions };
|
|
7936
|
+
const merged = { ...positional, ...options ?? {} };
|
|
7937
|
+
const campaign = toCampaignId(merged.campaignId);
|
|
7938
|
+
const kind = normalizeSlotKind(merged.kind);
|
|
7270
7939
|
const entry = this.slotRegistry.get(id);
|
|
7271
7940
|
let changed = false;
|
|
7272
7941
|
if (!entry) {
|
|
7273
|
-
this.slotRegistry.set(id, { count: 1, displayName: label, campaignId: campaign });
|
|
7942
|
+
this.slotRegistry.set(id, { count: 1, displayName: label, campaignId: campaign, kind });
|
|
7274
7943
|
changed = true;
|
|
7275
7944
|
} else {
|
|
7276
7945
|
entry.count++;
|
|
@@ -7282,6 +7951,10 @@ var VioFacade = class {
|
|
|
7282
7951
|
entry.campaignId = campaign;
|
|
7283
7952
|
changed = true;
|
|
7284
7953
|
}
|
|
7954
|
+
if (kind !== void 0 && entry.kind === void 0) {
|
|
7955
|
+
entry.kind = kind;
|
|
7956
|
+
changed = true;
|
|
7957
|
+
}
|
|
7285
7958
|
}
|
|
7286
7959
|
if (changed) {
|
|
7287
7960
|
this.slotsDirty = true;
|
|
@@ -7372,10 +8045,155 @@ var VioFacade = class {
|
|
|
7372
8045
|
const content = this.content();
|
|
7373
8046
|
if (content.id) out.contentId = content.id;
|
|
7374
8047
|
if (content.title) out.contentTitle = content.title;
|
|
8048
|
+
if (content.url) out.contentUrl = content.url;
|
|
7375
8049
|
} catch {
|
|
7376
8050
|
}
|
|
7377
8051
|
return out;
|
|
7378
8052
|
}
|
|
8053
|
+
/**
|
|
8054
|
+
* What the surface answered and is still believed: `standard` / `minimal`
|
|
8055
|
+
* for the configuration; `unknown` for a minute after a timeout or a
|
|
8056
|
+
* failure (then it is asked again). `null` = nothing believed.
|
|
8057
|
+
*/
|
|
8058
|
+
believedSurfaceMode() {
|
|
8059
|
+
const answer = this.surfaceMode;
|
|
8060
|
+
if (!answer) return null;
|
|
8061
|
+
if (answer.mode !== "unknown") return answer.mode;
|
|
8062
|
+
return Date.now() - answer.at < PRIVACY_UNKNOWN_TTL_MS ? "unknown" : null;
|
|
8063
|
+
}
|
|
8064
|
+
/**
|
|
8065
|
+
* Ask the surface its privacy mode (`GET /v2/mobile/surface`), once —
|
|
8066
|
+
* started at `init()` and again by `bootstrap()`, never by the payment
|
|
8067
|
+
* path, which only reads what is known (`privacyModeNow`). Resolves what
|
|
8068
|
+
* was learnt within 1.5 s: the mode; `standard` for a service that predates
|
|
8069
|
+
* the endpoint (404) or a surface that names none; `unknown` for a word the
|
|
8070
|
+
* SDK does not know, a timeout or any other failure — believed for a
|
|
8071
|
+
* minute, then asked again. An answer that arrives after the cap still
|
|
8072
|
+
* lands for the next step. Never throws.
|
|
8073
|
+
*/
|
|
8074
|
+
preloadPrivacyMode() {
|
|
8075
|
+
try {
|
|
8076
|
+
if (this.surfaceRequest) return this.surfaceRequest;
|
|
8077
|
+
const believed = this.believedSurfaceMode();
|
|
8078
|
+
if (believed) return Promise.resolve(believed);
|
|
8079
|
+
if (!Configuration.isInitialized) return Promise.resolve("unknown");
|
|
8080
|
+
const now = Date.now();
|
|
8081
|
+
if (Configuration.get().privacyMode === "minimal") {
|
|
8082
|
+
this.surfaceMode = { mode: "minimal", at: now };
|
|
8083
|
+
return Promise.resolve("minimal");
|
|
8084
|
+
}
|
|
8085
|
+
const generation = this.generation;
|
|
8086
|
+
const learnt = (mode) => {
|
|
8087
|
+
if (generation !== this.generation) return;
|
|
8088
|
+
if (mode === "unknown" && this.surfaceMode && this.surfaceMode.mode !== "unknown") return;
|
|
8089
|
+
this.surfaceMode = { mode, at: Date.now() };
|
|
8090
|
+
};
|
|
8091
|
+
const asked = this.api.surface().then(
|
|
8092
|
+
(res) => privacyModeOfSurface(res),
|
|
8093
|
+
(err) => err instanceof VioApiError && err.status === 404 ? "standard" : "unknown"
|
|
8094
|
+
);
|
|
8095
|
+
let timer;
|
|
8096
|
+
const capped = new Promise((resolve) => {
|
|
8097
|
+
timer = setTimeout(() => resolve("unknown"), PRIVACY_MODE_TIMEOUT_MS);
|
|
8098
|
+
});
|
|
8099
|
+
const request = Promise.race([asked, capped]).then((mode) => {
|
|
8100
|
+
clearTimeout(timer);
|
|
8101
|
+
learnt(mode);
|
|
8102
|
+
if (this.surfaceRequest === request) this.surfaceRequest = null;
|
|
8103
|
+
return mode;
|
|
8104
|
+
});
|
|
8105
|
+
void asked.then((mode) => {
|
|
8106
|
+
clearTimeout(timer);
|
|
8107
|
+
learnt(mode);
|
|
8108
|
+
});
|
|
8109
|
+
this.surfaceRequest = request;
|
|
8110
|
+
return request;
|
|
8111
|
+
} catch {
|
|
8112
|
+
return Promise.resolve("unknown");
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
8115
|
+
/**
|
|
8116
|
+
* How much of the visit the order attribution may carry (ADR-0035): the
|
|
8117
|
+
* surface's privacy mode, as known RIGHT NOW, without waiting —
|
|
8118
|
+
* `Vio.init({ privacyMode: 'minimal' })` when the host says so, else what
|
|
8119
|
+
* the bootstrap says of the surface, else what the surface answered to the
|
|
8120
|
+
* ask `init()` started (`preloadPrivacyMode`), else the host's `standard`
|
|
8121
|
+
* once the surface could not tell. `unknown` while nothing is known: then
|
|
8122
|
+
* the page, the slot and the entry stay off what goes to Commerce, because
|
|
8123
|
+
* what can reveal which screen someone was on, and why, is only sent when
|
|
8124
|
+
* the surface is known to allow it. The payment path reads this one.
|
|
8125
|
+
*/
|
|
8126
|
+
privacyModeNow() {
|
|
8127
|
+
try {
|
|
8128
|
+
const config = Configuration.isInitialized ? Configuration.get() : void 0;
|
|
8129
|
+
if (config?.privacyMode === "minimal") return "minimal";
|
|
8130
|
+
const fromBootstrap = privacyModeOf(this.cachedBootstrap?.surface?.privacyMode);
|
|
8131
|
+
if (fromBootstrap) return fromBootstrap;
|
|
8132
|
+
if (!config) return "unknown";
|
|
8133
|
+
const believed = this.believedSurfaceMode();
|
|
8134
|
+
if (believed === "standard" || believed === "minimal") return believed;
|
|
8135
|
+
if (!believed) this.preloadPrivacyMode();
|
|
8136
|
+
return believed === "unknown" ? config.privacyMode ?? "unknown" : "unknown";
|
|
8137
|
+
} catch {
|
|
8138
|
+
return "unknown";
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
/**
|
|
8142
|
+
* `privacyModeNow`, but waiting for the ask in flight when there is one
|
|
8143
|
+
* (at most its 1.5 s cap) — for the cart's lines, which are not the
|
|
8144
|
+
* payment path. Never throws.
|
|
8145
|
+
*/
|
|
8146
|
+
async privacyMode() {
|
|
8147
|
+
try {
|
|
8148
|
+
const now = this.privacyModeNow();
|
|
8149
|
+
if (now !== "unknown") return now;
|
|
8150
|
+
if (!this.surfaceRequest) return "unknown";
|
|
8151
|
+
const asked = await this.surfaceRequest;
|
|
8152
|
+
if (asked !== "unknown") return asked;
|
|
8153
|
+
const config = Configuration.isInitialized ? Configuration.get() : void 0;
|
|
8154
|
+
return config?.privacyMode ?? "unknown";
|
|
8155
|
+
} catch {
|
|
8156
|
+
return "unknown";
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
/**
|
|
8160
|
+
* The attribution of the order a sponsor's cart is about to become
|
|
8161
|
+
* (ADR-0035 phase B) — what `CreateCheckout` / `UpdateCheckout` send as
|
|
8162
|
+
* `attribution` and shopcart writes on the order: the surface (its SDK
|
|
8163
|
+
* key), the page's campaign (`pageCampaign()`; without one, the campaign
|
|
8164
|
+
* that explains most units of the cart under the same rule `purchase` is
|
|
8165
|
+
* split by — `cartSlices`) and, on a surface KNOWN to be `standard`
|
|
8166
|
+
* (`privacyModeNow`), the page (`content()`'s url as origin + path, and
|
|
8167
|
+
* title) and where the session came in: the landing the session REMEMBERS
|
|
8168
|
+
* (`storedLanding`, else the one analytics resolved on this page) — never
|
|
8169
|
+
* the page the shopper pays on taken for a landing. Synchronous: nothing
|
|
8170
|
+
* on the way to a payment waits for it. snake_case, capped to Commerce's
|
|
8171
|
+
* limits. `undefined` when there is nothing to say; never throws.
|
|
8172
|
+
*/
|
|
8173
|
+
orderAttribution(sponsorId) {
|
|
8174
|
+
try {
|
|
8175
|
+
const config = Configuration.isInitialized ? Configuration.get() : void 0;
|
|
8176
|
+
const content = this.content();
|
|
8177
|
+
return checkoutAttributionOf({
|
|
8178
|
+
surfaceKey: config?.apiKey,
|
|
8179
|
+
pageCampaignId: toNumber(this.pageCampaign()?.id),
|
|
8180
|
+
slices: this.cartSlices(sponsorId),
|
|
8181
|
+
content: { url: content.url, title: content.title },
|
|
8182
|
+
landing: this.rememberedLanding(),
|
|
8183
|
+
privacyMode: this.privacyModeNow()
|
|
8184
|
+
});
|
|
8185
|
+
} catch {
|
|
8186
|
+
return void 0;
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
/** The landing the session remembers: what the first page stored, else what analytics resolved on this page load (consent pending); null otherwise. */
|
|
8190
|
+
rememberedLanding() {
|
|
8191
|
+
try {
|
|
8192
|
+
return storedLanding() ?? this.analyticsManager?.currentLanding ?? null;
|
|
8193
|
+
} catch {
|
|
8194
|
+
return null;
|
|
8195
|
+
}
|
|
8196
|
+
}
|
|
7379
8197
|
/**
|
|
7380
8198
|
* The cart split per campaign — the SAME rule `begin_checkout` / `purchase`
|
|
7381
8199
|
* use, for hosts with their own analytics sink (Vev). Every unit of every
|
|
@@ -7456,10 +8274,11 @@ var VioFacade = class {
|
|
|
7456
8274
|
this.slotDeclareTimer = null;
|
|
7457
8275
|
this.slotsDirty = false;
|
|
7458
8276
|
void this.declareSlots(
|
|
7459
|
-
[...this.slotRegistry.entries()].map(([id, { displayName, campaignId }]) => ({
|
|
8277
|
+
[...this.slotRegistry.entries()].map(([id, { displayName, campaignId, kind }]) => ({
|
|
7460
8278
|
id,
|
|
7461
8279
|
...displayName ? { displayName } : {},
|
|
7462
|
-
...campaignId !== void 0 ? { campaignId } : {}
|
|
8280
|
+
...campaignId !== void 0 ? { campaignId } : {},
|
|
8281
|
+
...kind ? { kind } : {}
|
|
7463
8282
|
}))
|
|
7464
8283
|
);
|
|
7465
8284
|
}, SLOT_DECLARATION_DEBOUNCE_MS);
|
|
@@ -7539,6 +8358,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
|
|
|
7539
8358
|
};
|
|
7540
8359
|
}
|
|
7541
8360
|
|
|
7542
|
-
export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, KUSTOM_RETURN_QUERY_KEYS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, campaignCandidates, cartSlicesFrom, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, kustomReturnOutcome, lineOrigins, nexiReturnPaymentId, normalizeOrigin, normalizeScope, normalizeSlotId, primaryImageUrl, purchaseFromPaymentSuccess, readNexiPending, resolveCampaignId, resolveContent, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
|
|
7543
|
-
//# sourceMappingURL=chunk-
|
|
7544
|
-
//# sourceMappingURL=chunk-
|
|
8361
|
+
export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, 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, 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, readNexiPending, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature };
|
|
8362
|
+
//# sourceMappingURL=chunk-5KWICUME.js.map
|
|
8363
|
+
//# sourceMappingURL=chunk-5KWICUME.js.map
|