@shoppexio/storefront 1.0.103 → 1.0.104
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 +6 -0
- package/dist/{chunk-ESI3HWQF.js → chunk-BW2EO2WP.js} +23 -1
- package/dist/{chunk-ESI3HWQF.js.map → chunk-BW2EO2WP.js.map} +1 -1
- package/dist/customer.cjs +22 -0
- package/dist/customer.cjs.map +1 -1
- package/dist/customer.js +1 -1
- package/dist/index.cjs +1065 -984
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +1043 -987
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
customerWarrantyClaimSchema,
|
|
10
10
|
customerWarrantyListSchema,
|
|
11
11
|
storefrontStorageKey
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BW2EO2WP.js";
|
|
13
13
|
|
|
14
14
|
// ../sdk/src/core/errors.ts
|
|
15
15
|
var CURRENCY_UNAVAILABLE_ERROR_CODE = "errors.storefront.currency_unavailable";
|
|
@@ -836,1101 +836,1141 @@ async function captureAffiliateFromUrl(param = "ref") {
|
|
|
836
836
|
return holdsOtherCode ? held.code : code;
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
// ../
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
839
|
+
// ../contracts/src/storefront-consent-choice.ts
|
|
840
|
+
var COOKIE_CONSENT_VERSION = 1;
|
|
841
|
+
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
842
|
+
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
843
|
+
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
844
|
+
function parseCookieConsentValue(value) {
|
|
845
|
+
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
846
|
+
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ../sdk/src/core/analytics-session.ts
|
|
850
|
+
var ANALYTICS_SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
851
|
+
var ANALYTICS_VISITS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
852
|
+
var ANALYTICS_VISITS_MAX = 20;
|
|
853
|
+
var MAX_ENGAGED_MS_PER_PAGE = 30 * 60 * 1e3;
|
|
854
|
+
var UTM_STORAGE_PREFIX = "shoppex:utm:";
|
|
855
|
+
var analyticsSessionKey = (storeSlug) => `analytics_session_${storeSlug}`;
|
|
856
|
+
var analyticsVisitsKey = (storeSlug) => `analytics_visits_${storeSlug}`;
|
|
857
|
+
function pageLanding() {
|
|
858
|
+
let r = "";
|
|
859
|
+
try {
|
|
860
|
+
r = document.referrer ? new URL(document.referrer).hostname : "";
|
|
861
|
+
} catch {
|
|
845
862
|
}
|
|
846
|
-
|
|
863
|
+
const params = new URLSearchParams(window.location?.search ?? "");
|
|
864
|
+
const u = ["source", "medium", "campaign"].map((key) => params.get(`utm_${key}`)?.trim().toLowerCase() ?? "").join("|");
|
|
865
|
+
return { r: r === window.location?.hostname ? "" : r, u: u === "||" ? "" : u };
|
|
847
866
|
}
|
|
848
|
-
function
|
|
849
|
-
if (
|
|
850
|
-
|
|
867
|
+
function readAnalyticsConsent() {
|
|
868
|
+
if (typeof document === "undefined" || typeof navigator === "undefined") return "unknown";
|
|
869
|
+
const privacy = navigator;
|
|
870
|
+
if (privacy.globalPrivacyControl === true || privacy.doNotTrack === "1") return "declined";
|
|
871
|
+
const prefix = `${COOKIE_CONSENT_COOKIE}=`;
|
|
872
|
+
let raw;
|
|
873
|
+
try {
|
|
874
|
+
raw = document.cookie.split(/;\s*/).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
875
|
+
} catch {
|
|
876
|
+
return "unknown";
|
|
877
|
+
}
|
|
878
|
+
const choice = parseCookieConsentValue(raw ?? null);
|
|
879
|
+
if (!choice) return "unknown";
|
|
880
|
+
return choice.analytics ? "granted" : "declined";
|
|
851
881
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return
|
|
882
|
+
function readVisits(storeSlug, now) {
|
|
883
|
+
const stored = getItem(analyticsVisitsKey(storeSlug));
|
|
884
|
+
if (!Array.isArray(stored)) return [];
|
|
885
|
+
return stored.filter((visit) => typeof visit?.id === "string" && typeof visit.at === "number" && now - visit.at < ANALYTICS_VISITS_WINDOW_MS);
|
|
856
886
|
}
|
|
857
|
-
function
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
variant_id: input.variant_id,
|
|
861
|
-
price_variant_id: input.price_variant_id ?? null,
|
|
862
|
-
addons: normalizeAddons(input.addons),
|
|
863
|
-
custom_fields: normalizeCustomFields(input.custom_fields),
|
|
864
|
-
unit_price: typeof input.price_data?.unit_price === "number" && Number.isFinite(input.price_data.unit_price) ? input.price_data.unit_price : null,
|
|
865
|
-
pay_what_you_want_price: typeof input.pay_what_you_want_price === "number" && Number.isFinite(input.pay_what_you_want_price) ? input.pay_what_you_want_price : null
|
|
866
|
-
};
|
|
867
|
-
return hashString(JSON.stringify(payload));
|
|
887
|
+
function forgetAnalyticsData(storeSlug) {
|
|
888
|
+
removeItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
889
|
+
removeItem(analyticsVisitsKey(storeSlug));
|
|
868
890
|
}
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
|
|
891
|
+
function analyticsSessionId(storeSlug, options, now = Date.now()) {
|
|
892
|
+
removeItem(`presence_connection_${storeSlug}`);
|
|
893
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
894
|
+
forgetAnalyticsData(storeSlug);
|
|
895
|
+
return null;
|
|
872
896
|
}
|
|
873
|
-
|
|
897
|
+
const stored = getItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
898
|
+
const live = stored && typeof stored.id === "string" && typeof stored.at === "number" && now - stored.at < ANALYTICS_SESSION_IDLE_MS ? stored.id : null;
|
|
899
|
+
if (!options.touch) return live;
|
|
900
|
+
const landing = pageLanding();
|
|
901
|
+
const newLanding = landing.r && landing.r !== stored?.r || landing.u && landing.u !== stored?.u;
|
|
902
|
+
if (live && !newLanding) {
|
|
903
|
+
setItem(analyticsSessionKey(storeSlug), { ...stored, id: live, at: now }, "sessionStorage");
|
|
904
|
+
return live;
|
|
905
|
+
}
|
|
906
|
+
if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") return null;
|
|
907
|
+
if (landing.r) {
|
|
908
|
+
try {
|
|
909
|
+
globalThis.localStorage.removeItem(storefrontStorageKey(`${UTM_STORAGE_PREFIX}${storeSlug}`));
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const id = crypto.randomUUID();
|
|
914
|
+
setItem(analyticsSessionKey(storeSlug), { id, at: now, ...landing }, "sessionStorage");
|
|
915
|
+
setItem(
|
|
916
|
+
analyticsVisitsKey(storeSlug),
|
|
917
|
+
[{ id, at: now }, ...readVisits(storeSlug, now)].slice(0, ANALYTICS_VISITS_MAX)
|
|
918
|
+
);
|
|
919
|
+
return id;
|
|
874
920
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const sign = value < 0 ? -1 : 1;
|
|
885
|
-
const shifted = shiftDecimal(Math.abs(value), PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
886
|
-
const rounded = Math.round(shifted);
|
|
887
|
-
if (rounded === 0) return 0;
|
|
888
|
-
return sign * shiftDecimal(rounded, -PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
921
|
+
function checkoutAttribution(storeSlug, now = Date.now()) {
|
|
922
|
+
const consent = readAnalyticsConsent();
|
|
923
|
+
if (consent === "declined") {
|
|
924
|
+
forgetAnalyticsData(storeSlug);
|
|
925
|
+
return { declined: true };
|
|
926
|
+
}
|
|
927
|
+
if (consent === "unknown") return void 0;
|
|
928
|
+
const sessionIds = readVisits(storeSlug, now).map((visit) => visit.id);
|
|
929
|
+
return sessionIds.length > 0 ? { session_ids: sessionIds } : void 0;
|
|
889
930
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
931
|
+
var engagedMs = 0;
|
|
932
|
+
var visibleSince = null;
|
|
933
|
+
var clockInstalled = false;
|
|
934
|
+
var pageViewed = false;
|
|
935
|
+
var pageSession = null;
|
|
936
|
+
function markPageViewed(sessionId) {
|
|
937
|
+
pageViewed = true;
|
|
938
|
+
pageSession = sessionId;
|
|
893
939
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
var STORAGE_KEYS = {
|
|
897
|
-
cart: "cart",
|
|
898
|
-
cartBackup: "cart_backup",
|
|
899
|
-
meta: "cart_meta",
|
|
900
|
-
metaBackup: "cart_backup_meta",
|
|
901
|
-
coupon: "cart_coupon",
|
|
902
|
-
couponBackup: "cart_coupon_backup"
|
|
903
|
-
};
|
|
904
|
-
function getStorageKey(type) {
|
|
905
|
-
return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
|
|
940
|
+
function currentPageSession() {
|
|
941
|
+
return pageSession;
|
|
906
942
|
}
|
|
907
|
-
function
|
|
908
|
-
return
|
|
943
|
+
function isPageViewed() {
|
|
944
|
+
return pageViewed;
|
|
909
945
|
}
|
|
910
|
-
function
|
|
911
|
-
|
|
946
|
+
function settleClock(now) {
|
|
947
|
+
if (visibleSince !== null) {
|
|
948
|
+
engagedMs += now - visibleSince;
|
|
949
|
+
visibleSince = document.visibilityState === "visible" ? now : null;
|
|
950
|
+
}
|
|
912
951
|
}
|
|
913
|
-
|
|
914
|
-
|
|
952
|
+
var consentListenerInstalled = false;
|
|
953
|
+
function onAnalyticsConsent(onYes) {
|
|
954
|
+
if (consentListenerInstalled || typeof window === "undefined") return;
|
|
955
|
+
consentListenerInstalled = true;
|
|
956
|
+
window.addEventListener("shoppex:consent", () => {
|
|
957
|
+
if (readAnalyticsConsent() === "granted") onYes();
|
|
958
|
+
});
|
|
915
959
|
}
|
|
916
|
-
function
|
|
917
|
-
|
|
960
|
+
function installEngagedClock(onHidden) {
|
|
961
|
+
if (clockInstalled || typeof document === "undefined") return;
|
|
962
|
+
clockInstalled = true;
|
|
963
|
+
visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
964
|
+
document.addEventListener("visibilitychange", () => {
|
|
965
|
+
const now = Date.now();
|
|
966
|
+
if (document.visibilityState === "visible") {
|
|
967
|
+
visibleSince ?? (visibleSince = now);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
settleClock(now);
|
|
971
|
+
visibleSince = null;
|
|
972
|
+
onHidden();
|
|
973
|
+
});
|
|
974
|
+
window.addEventListener("pagehide", () => {
|
|
975
|
+
settleClock(Date.now());
|
|
976
|
+
visibleSince = null;
|
|
977
|
+
onHidden();
|
|
978
|
+
});
|
|
979
|
+
window.addEventListener("pageshow", (event) => {
|
|
980
|
+
if (event.persisted && document.visibilityState === "visible") visibleSince = Date.now();
|
|
981
|
+
});
|
|
918
982
|
}
|
|
919
|
-
function
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
983
|
+
function takeEngagedMs(now = Date.now()) {
|
|
984
|
+
settleClock(now);
|
|
985
|
+
const taken = Math.min(Math.max(0, Math.round(engagedMs)), MAX_ENGAGED_MS_PER_PAGE);
|
|
986
|
+
engagedMs = 0;
|
|
987
|
+
return taken;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// ../sdk/src/core/attribution.ts
|
|
991
|
+
var TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
992
|
+
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
993
|
+
var MAX_VALUE_LENGTH = 255;
|
|
994
|
+
function readFromUrl(search) {
|
|
995
|
+
let params;
|
|
996
|
+
try {
|
|
997
|
+
params = new URLSearchParams(search);
|
|
998
|
+
} catch {
|
|
999
|
+
return {};
|
|
924
1000
|
}
|
|
925
|
-
|
|
1001
|
+
const utm = {};
|
|
1002
|
+
for (const key of UTM_KEYS) {
|
|
1003
|
+
const value = params.get(key)?.trim();
|
|
1004
|
+
if (value) {
|
|
1005
|
+
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return utm;
|
|
926
1009
|
}
|
|
927
|
-
function
|
|
928
|
-
return
|
|
1010
|
+
function storageKey(storeSlug) {
|
|
1011
|
+
return `${UTM_STORAGE_PREFIX}${storeSlug}`;
|
|
929
1012
|
}
|
|
930
|
-
function
|
|
931
|
-
|
|
932
|
-
|
|
1013
|
+
function readStored(storeSlug) {
|
|
1014
|
+
try {
|
|
1015
|
+
const raw = window.localStorage.getItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1016
|
+
if (!raw) return {};
|
|
1017
|
+
const parsed = JSON.parse(raw);
|
|
1018
|
+
if (!parsed || typeof parsed.at !== "number" || typeof parsed.utm !== "object") return {};
|
|
1019
|
+
if (Date.now() - parsed.at > TTL_MS) {
|
|
1020
|
+
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1021
|
+
return {};
|
|
1022
|
+
}
|
|
1023
|
+
const utm = {};
|
|
1024
|
+
for (const key of UTM_KEYS) {
|
|
1025
|
+
const value = parsed.utm?.[key];
|
|
1026
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1027
|
+
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return utm;
|
|
1031
|
+
} catch {
|
|
1032
|
+
return {};
|
|
933
1033
|
}
|
|
934
|
-
return Math.floor(value);
|
|
935
1034
|
}
|
|
936
|
-
function
|
|
937
|
-
|
|
938
|
-
|
|
1035
|
+
function writeStored(storeSlug, utm) {
|
|
1036
|
+
try {
|
|
1037
|
+
const payload = { utm, at: Date.now() };
|
|
1038
|
+
window.localStorage.setItem(storefrontStorageKey(storageKey(storeSlug)), JSON.stringify(payload));
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
939
1041
|
}
|
|
940
|
-
function
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
return null;
|
|
1042
|
+
function resolveUtmParameters(storeSlug) {
|
|
1043
|
+
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
1044
|
+
return void 0;
|
|
944
1045
|
}
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
function normalizeCartItems(value) {
|
|
951
|
-
if (!Array.isArray(value)) return [];
|
|
952
|
-
const normalized = [];
|
|
953
|
-
for (const entry of value) {
|
|
954
|
-
if (!entry || typeof entry !== "object") continue;
|
|
955
|
-
const record = entry;
|
|
956
|
-
const productId = typeof record.product_id === "string" ? record.product_id.trim() : "";
|
|
957
|
-
const variantId = typeof record.variant_id === "string" ? record.variant_id.trim() : "";
|
|
958
|
-
const quantity = Number(record.quantity);
|
|
959
|
-
if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
|
|
960
|
-
continue;
|
|
961
|
-
}
|
|
962
|
-
const item = {
|
|
963
|
-
line_id: "",
|
|
964
|
-
product_id: productId,
|
|
965
|
-
variant_id: variantId,
|
|
966
|
-
quantity: Math.floor(quantity)
|
|
967
|
-
};
|
|
968
|
-
if (typeof record.price_variant_id === "string") {
|
|
969
|
-
item.price_variant_id = record.price_variant_id;
|
|
970
|
-
}
|
|
971
|
-
if (record.price_data && typeof record.price_data === "object") {
|
|
972
|
-
const priceData = record.price_data;
|
|
973
|
-
if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
|
|
974
|
-
item.price_data = { unit_price: priceData.unit_price };
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
if (typeof record.pay_what_you_want_price === "number" && Number.isFinite(record.pay_what_you_want_price)) {
|
|
978
|
-
item.pay_what_you_want_price = record.pay_what_you_want_price;
|
|
979
|
-
}
|
|
980
|
-
if (Array.isArray(record.addons)) {
|
|
981
|
-
item.addons = record.addons;
|
|
982
|
-
}
|
|
983
|
-
if (record.custom_fields && typeof record.custom_fields === "object" && !Array.isArray(record.custom_fields)) {
|
|
984
|
-
item.custom_fields = record.custom_fields;
|
|
985
|
-
}
|
|
986
|
-
if (typeof record.title === "string") {
|
|
987
|
-
item.title = record.title;
|
|
988
|
-
}
|
|
989
|
-
if (typeof record.variant_title === "string") {
|
|
990
|
-
item.variant_title = record.variant_title;
|
|
991
|
-
}
|
|
992
|
-
if (typeof record.image_url === "string") {
|
|
993
|
-
item.image_url = record.image_url;
|
|
994
|
-
}
|
|
995
|
-
if (Array.isArray(record.addon_labels)) {
|
|
996
|
-
item.addon_labels = record.addon_labels.filter((label) => typeof label === "string");
|
|
997
|
-
}
|
|
998
|
-
if (typeof record.max_quantity === "number" && Number.isFinite(record.max_quantity)) {
|
|
999
|
-
item.max_quantity = record.max_quantity;
|
|
1000
|
-
}
|
|
1001
|
-
if (typeof record.min_quantity === "number" && Number.isFinite(record.min_quantity)) {
|
|
1002
|
-
item.min_quantity = record.min_quantity;
|
|
1046
|
+
const fromUrl = readFromUrl(window.location.search);
|
|
1047
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
1048
|
+
try {
|
|
1049
|
+
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1050
|
+
} catch {
|
|
1003
1051
|
}
|
|
1004
|
-
|
|
1005
|
-
const storedLineId = typeof record.line_id === "string" ? record.line_id.trim() : "";
|
|
1006
|
-
item.line_id = storedLineId || computeCartLineId(item);
|
|
1007
|
-
normalized.push(ensureCartLineId(item));
|
|
1052
|
+
return Object.keys(fromUrl).length > 0 ? fromUrl : void 0;
|
|
1008
1053
|
}
|
|
1009
|
-
|
|
1054
|
+
if (Object.keys(fromUrl).length > 0) {
|
|
1055
|
+
writeStored(storeSlug, fromUrl);
|
|
1056
|
+
return fromUrl;
|
|
1057
|
+
}
|
|
1058
|
+
const stored = readStored(storeSlug);
|
|
1059
|
+
return Object.keys(stored).length > 0 ? stored : void 0;
|
|
1010
1060
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1061
|
+
|
|
1062
|
+
// ../sdk/src/core/page-path.ts
|
|
1063
|
+
var MAX_PATH_LENGTH = 256;
|
|
1064
|
+
function resolveStorefrontPagePath() {
|
|
1065
|
+
if (typeof window === "undefined") return void 0;
|
|
1066
|
+
let pathname;
|
|
1067
|
+
try {
|
|
1068
|
+
const location = window.location;
|
|
1069
|
+
pathname = typeof location?.pathname === "string" && location.pathname ? location.pathname : new URL(String(location?.href ?? "")).pathname;
|
|
1070
|
+
} catch {
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
if (!pathname || !pathname.startsWith("/")) return void 0;
|
|
1074
|
+
return pathname.slice(0, MAX_PATH_LENGTH);
|
|
1013
1075
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1076
|
+
|
|
1077
|
+
// ../sdk/src/core/page-tracker.ts
|
|
1078
|
+
var PLATFORM_PAGE_TRACKER_FLAG = "__shoppexPageTracker";
|
|
1079
|
+
function isPlatformPageTrackerActive() {
|
|
1080
|
+
if (typeof window === "undefined") return false;
|
|
1081
|
+
try {
|
|
1082
|
+
return Boolean(window[PLATFORM_PAGE_TRACKER_FLAG]);
|
|
1083
|
+
} catch {
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1016
1086
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1087
|
+
|
|
1088
|
+
// ../sdk/src/modules/analytics.ts
|
|
1089
|
+
var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
|
|
1090
|
+
var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
|
|
1091
|
+
var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
|
|
1092
|
+
var CART_CLAIM_TTL_MS = 1e4;
|
|
1093
|
+
function confirmedCartKey(storeSlug) {
|
|
1094
|
+
return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1019
1095
|
}
|
|
1020
|
-
function
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1096
|
+
function inFlightCartKey(storeSlug) {
|
|
1097
|
+
return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1098
|
+
}
|
|
1099
|
+
function cartSequenceKey(storeSlug) {
|
|
1100
|
+
return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
|
|
1101
|
+
}
|
|
1102
|
+
function cartSnapshot(cartValue, itemCount) {
|
|
1103
|
+
return `${cartValue ?? ""}|${itemCount ?? ""}`;
|
|
1104
|
+
}
|
|
1105
|
+
function isCartSequence(value) {
|
|
1106
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1107
|
+
}
|
|
1108
|
+
var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
|
|
1109
|
+
function readCartCounter(storeSlug) {
|
|
1110
|
+
const counter = getItem(cartSequenceKey(storeSlug));
|
|
1111
|
+
if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
|
|
1024
1112
|
return null;
|
|
1025
1113
|
}
|
|
1026
|
-
|
|
1027
|
-
return normalizedCoupon;
|
|
1114
|
+
return counter;
|
|
1028
1115
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1116
|
+
var lastCartOrder = null;
|
|
1117
|
+
function nextCartOrder(storeSlug) {
|
|
1118
|
+
const stored = readCartCounter(storeSlug);
|
|
1119
|
+
const scope = stored?.scope ?? lastCartOrder?.scope ?? createClaimToken();
|
|
1120
|
+
const floor = Math.max(stored?.seq ?? 0, lastCartOrder?.scope === scope ? lastCartOrder.seq : 0);
|
|
1121
|
+
const order2 = { scope, seq: Math.max(Date.now(), floor + 1) };
|
|
1122
|
+
lastCartOrder = order2;
|
|
1123
|
+
setItem(cartSequenceKey(storeSlug), order2);
|
|
1124
|
+
return order2;
|
|
1031
1125
|
}
|
|
1032
|
-
function
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const previous = getCartMetadata();
|
|
1037
|
-
const nextMeta = {
|
|
1038
|
-
created_at: previous?.created_at ?? now,
|
|
1039
|
-
last_modified: now,
|
|
1040
|
-
version: (previous?.version ?? 0) + 1,
|
|
1041
|
-
checksum: computeChecksum(normalizedCart)
|
|
1042
|
-
};
|
|
1043
|
-
writeCartStorage("meta", nextMeta);
|
|
1044
|
-
if (normalizedCart.length === 0) {
|
|
1045
|
-
clearCartCoupon();
|
|
1046
|
-
}
|
|
1126
|
+
function cartSequenceLocks() {
|
|
1127
|
+
if (typeof navigator === "undefined") return null;
|
|
1128
|
+
const locks = navigator.locks;
|
|
1129
|
+
return locks && typeof locks.request === "function" ? locks : null;
|
|
1047
1130
|
}
|
|
1048
|
-
function
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const nextMeta = {
|
|
1054
|
-
created_at: base?.created_at ?? now,
|
|
1055
|
-
last_modified: now,
|
|
1056
|
-
version: base?.version ?? 1,
|
|
1057
|
-
checksum: computeChecksum(normalizedCart)
|
|
1058
|
-
};
|
|
1059
|
-
writeCartStorage("meta", nextMeta);
|
|
1131
|
+
function reserveCartOrder(storeSlug) {
|
|
1132
|
+
const take = () => nextCartOrder(storeSlug);
|
|
1133
|
+
const locks = cartSequenceLocks();
|
|
1134
|
+
if (!locks) return Promise.resolve(take());
|
|
1135
|
+
return locks.request(cartSequenceKey(storeSlug), take).catch(take);
|
|
1060
1136
|
}
|
|
1061
|
-
function
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1137
|
+
function readConfirmedCart(storeSlug) {
|
|
1138
|
+
const confirmed = getItem(confirmedCartKey(storeSlug));
|
|
1139
|
+
if (!confirmed || typeof confirmed.snapshot !== "string" || !isCartSequence(confirmed.seq)) return null;
|
|
1140
|
+
return confirmed;
|
|
1064
1141
|
}
|
|
1065
|
-
function
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
if (matches.length === 0) {
|
|
1070
|
-
throw new CartError(`No cart line found for ${productId}/${variantId}`);
|
|
1142
|
+
function readInFlightCart(storeSlug) {
|
|
1143
|
+
const claim = getItem(inFlightCartKey(storeSlug));
|
|
1144
|
+
if (!claim || typeof claim.snapshot !== "string" || !isCartSequence(claim.seq) || typeof claim.token !== "string" || typeof claim.at !== "number") {
|
|
1145
|
+
return null;
|
|
1071
1146
|
}
|
|
1072
|
-
return
|
|
1147
|
+
return claim;
|
|
1073
1148
|
}
|
|
1074
|
-
function
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1149
|
+
function isCartReportedOrInFlight(storeSlug, snapshot) {
|
|
1150
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
1151
|
+
const claim = readInFlightCart(storeSlug);
|
|
1152
|
+
const liveClaim = claim && Date.now() - claim.at < CART_CLAIM_TTL_MS ? claim : null;
|
|
1153
|
+
const expected = liveClaim && (!confirmed || liveClaim.seq > confirmed.seq) ? liveClaim : confirmed;
|
|
1154
|
+
return expected?.snapshot === snapshot;
|
|
1077
1155
|
}
|
|
1078
|
-
function
|
|
1079
|
-
if (
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
addons: options?.addons,
|
|
1091
|
-
custom_fields: options?.custom_fields,
|
|
1092
|
-
price_variant_id: options?.price_variant_id,
|
|
1093
|
-
price_data: options?.price_data,
|
|
1094
|
-
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1095
|
-
});
|
|
1096
|
-
const existingIndex = cart.findIndex((item) => item.line_id === lineId);
|
|
1097
|
-
const quantityBeforeAdd = existingIndex >= 0 ? cart[existingIndex].quantity : 0;
|
|
1098
|
-
if (existingIndex >= 0) {
|
|
1099
|
-
cart[existingIndex].quantity += normalizedQuantity;
|
|
1100
|
-
if (options?.addons) {
|
|
1101
|
-
cart[existingIndex].addons = options.addons;
|
|
1102
|
-
}
|
|
1103
|
-
if (options?.custom_fields) {
|
|
1104
|
-
cart[existingIndex].custom_fields = options.custom_fields;
|
|
1105
|
-
}
|
|
1106
|
-
if (options?.price_variant_id) {
|
|
1107
|
-
cart[existingIndex].price_variant_id = options.price_variant_id;
|
|
1108
|
-
}
|
|
1109
|
-
if (options?.price_data) {
|
|
1110
|
-
cart[existingIndex].price_data = options.price_data;
|
|
1156
|
+
function createClaimToken() {
|
|
1157
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
1158
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
1159
|
+
}
|
|
1160
|
+
function claimCartReport(storeSlug, seq, cartValue, itemCount) {
|
|
1161
|
+
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
1162
|
+
const token = createClaimToken();
|
|
1163
|
+
setItem(inFlightCartKey(storeSlug), { snapshot, seq, token, at: Date.now() });
|
|
1164
|
+
return (delivered) => {
|
|
1165
|
+
if (delivered) {
|
|
1166
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
1167
|
+
if (!confirmed || seq > confirmed.seq) setItem(confirmedCartKey(storeSlug), { snapshot, seq });
|
|
1111
1168
|
}
|
|
1112
|
-
if (
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
applyDisplaySnapshot(cart[existingIndex], options);
|
|
1116
|
-
clampQuantityToBounds(cart[existingIndex]);
|
|
1117
|
-
} else {
|
|
1118
|
-
const pushed = {
|
|
1119
|
-
line_id: lineId,
|
|
1120
|
-
product_id: productId,
|
|
1121
|
-
variant_id: variantId,
|
|
1122
|
-
quantity: normalizedQuantity,
|
|
1123
|
-
addons: options?.addons,
|
|
1124
|
-
custom_fields: options?.custom_fields,
|
|
1125
|
-
price_variant_id: options?.price_variant_id,
|
|
1126
|
-
price_data: options?.price_data,
|
|
1127
|
-
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1128
|
-
title: options?.title,
|
|
1129
|
-
variant_title: options?.variant_title,
|
|
1130
|
-
image_url: options?.image_url,
|
|
1131
|
-
addon_labels: options?.addon_labels,
|
|
1132
|
-
max_quantity: options?.max_quantity,
|
|
1133
|
-
min_quantity: options?.min_quantity
|
|
1134
|
-
};
|
|
1135
|
-
clampQuantityToBounds(pushed);
|
|
1136
|
-
cart.push(pushed);
|
|
1137
|
-
}
|
|
1138
|
-
const intendedQuantity = (existingIndex >= 0 ? cart[existingIndex] : cart[cart.length - 1]).quantity;
|
|
1139
|
-
writeCart(cart);
|
|
1140
|
-
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1141
|
-
if (intendedQuantity > quantityBeforeAdd && persistedLine?.quantity === intendedQuantity) {
|
|
1142
|
-
const cartAffiliateCode = getCartCouponSource() === "AFFILIATE" ? getCartCoupon() : null;
|
|
1143
|
-
void trackAffiliateEvent("add_to_cart", cartAffiliateCode ? { code: cartAffiliateCode } : void 0);
|
|
1144
|
-
}
|
|
1169
|
+
if (readInFlightCart(storeSlug)?.token === token) removeItem(inFlightCartKey(storeSlug));
|
|
1170
|
+
};
|
|
1145
1171
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1172
|
+
var cartSendTail = Promise.resolve();
|
|
1173
|
+
var latestCartRequest = 0;
|
|
1174
|
+
function enqueueCartSend(send) {
|
|
1175
|
+
const request2 = ++latestCartRequest;
|
|
1176
|
+
const run = () => send(() => request2 === latestCartRequest);
|
|
1177
|
+
cartSendTail = cartSendTail.then(run, run);
|
|
1178
|
+
return cartSendTail;
|
|
1179
|
+
}
|
|
1180
|
+
async function postPing(body) {
|
|
1181
|
+
const config = getConfig();
|
|
1182
|
+
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1183
|
+
let timer;
|
|
1184
|
+
const timedOut = new Promise((resolve) => {
|
|
1185
|
+
timer = setTimeout(() => {
|
|
1186
|
+
controller?.abort();
|
|
1187
|
+
resolve(false);
|
|
1188
|
+
}, CART_CLAIM_TTL_MS);
|
|
1189
|
+
});
|
|
1190
|
+
try {
|
|
1191
|
+
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
|
|
1192
|
+
storeSlug: config.storeSlug
|
|
1193
|
+
});
|
|
1194
|
+
const delivered = fetch(`${config.apiBaseUrl}${endpoint}`, {
|
|
1195
|
+
method: "POST",
|
|
1196
|
+
headers: { "Content-Type": "application/json" },
|
|
1197
|
+
body: JSON.stringify(body),
|
|
1198
|
+
signal: controller?.signal
|
|
1199
|
+
}).then((response) => response.ok);
|
|
1200
|
+
return await Promise.race([delivered, timedOut]);
|
|
1201
|
+
} catch {
|
|
1202
|
+
return false;
|
|
1203
|
+
} finally {
|
|
1204
|
+
clearTimeout(timer);
|
|
1157
1205
|
}
|
|
1158
1206
|
}
|
|
1159
|
-
function
|
|
1160
|
-
if (
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1207
|
+
async function trackPageView(cartValue, itemCount) {
|
|
1208
|
+
if (!isInitialized()) return;
|
|
1209
|
+
if (typeof document === "undefined") return;
|
|
1210
|
+
const config = getConfig();
|
|
1211
|
+
const carriesCart = cartValue !== void 0 || itemCount !== void 0;
|
|
1212
|
+
const reservedOrder = carriesCart ? reserveCartOrder(config.storeSlug) : void 0;
|
|
1213
|
+
if (isPlatformPageTrackerActive()) {
|
|
1214
|
+
if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
|
|
1167
1215
|
return;
|
|
1168
1216
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1217
|
+
const sendView = async () => {
|
|
1218
|
+
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
1219
|
+
const settle = order2 ? claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
1220
|
+
const sessionId = analyticsSessionId(config.storeSlug, { touch: true });
|
|
1221
|
+
const previousSession = currentPageSession();
|
|
1222
|
+
if (previousSession && previousSession !== sessionId) reportPageLeave();
|
|
1223
|
+
markPageViewed(sessionId);
|
|
1224
|
+
if (sessionId) installEngagedClock(reportPageLeave);
|
|
1225
|
+
onAnalyticsConsent(startSessionOnConsent);
|
|
1226
|
+
const delivered = await postPing({
|
|
1227
|
+
referer: document.referrer || void 0,
|
|
1228
|
+
cart_value: cartValue,
|
|
1229
|
+
item_count: itemCount,
|
|
1230
|
+
cart_seq: order2?.seq,
|
|
1231
|
+
cart_seq_scope: order2?.scope,
|
|
1232
|
+
session_id: sessionId ?? void 0,
|
|
1233
|
+
// Time on screen of the page before this one (client-side navigation).
|
|
1234
|
+
engaged_ms: sessionId ? takeEngagedMs() || void 0 : void 0,
|
|
1235
|
+
path: resolveStorefrontPagePath(),
|
|
1236
|
+
utm: resolveUtmParameters(config.storeSlug)
|
|
1237
|
+
});
|
|
1238
|
+
settle?.(delivered);
|
|
1239
|
+
};
|
|
1240
|
+
await (reservedOrder ? enqueueCartSend(sendView) : sendView());
|
|
1187
1241
|
}
|
|
1188
|
-
function
|
|
1189
|
-
if (!
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
const lineId = computeCartLineId({
|
|
1198
|
-
product_id: productId,
|
|
1199
|
-
variant_id: variantId,
|
|
1200
|
-
addons: options?.addons,
|
|
1201
|
-
custom_fields: options?.custom_fields,
|
|
1202
|
-
price_variant_id: options?.price_variant_id,
|
|
1203
|
-
price_data: options?.price_data,
|
|
1204
|
-
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1242
|
+
async function trackCartActivity(cartValue, itemCount) {
|
|
1243
|
+
if (!isInitialized()) return;
|
|
1244
|
+
if (typeof document === "undefined") return;
|
|
1245
|
+
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
1246
|
+
const reservedOrder = reserveCartOrder(getConfig().storeSlug);
|
|
1247
|
+
await enqueueCartSend(async (isLatest) => {
|
|
1248
|
+
const order2 = await reservedOrder;
|
|
1249
|
+
if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
|
|
1250
|
+
await sendCartActivity(order2, cartValue, itemCount);
|
|
1205
1251
|
});
|
|
1206
|
-
for (let index = cart.length - 1; index >= 0; index -= 1) {
|
|
1207
|
-
if (cart[index].product_id === productId && cart[index].variant_id === variantId) {
|
|
1208
|
-
cart.splice(index, 1);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
const pushed = {
|
|
1212
|
-
line_id: lineId,
|
|
1213
|
-
product_id: productId,
|
|
1214
|
-
variant_id: variantId,
|
|
1215
|
-
quantity: normalizedQuantity,
|
|
1216
|
-
addons: options?.addons,
|
|
1217
|
-
custom_fields: options?.custom_fields,
|
|
1218
|
-
price_variant_id: options?.price_variant_id,
|
|
1219
|
-
price_data: options?.price_data,
|
|
1220
|
-
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1221
|
-
title: options?.title,
|
|
1222
|
-
variant_title: options?.variant_title,
|
|
1223
|
-
image_url: options?.image_url,
|
|
1224
|
-
addon_labels: options?.addon_labels,
|
|
1225
|
-
max_quantity: options?.max_quantity,
|
|
1226
|
-
min_quantity: options?.min_quantity
|
|
1227
|
-
};
|
|
1228
|
-
clampQuantityToBounds(pushed);
|
|
1229
|
-
cart.push(pushed);
|
|
1230
|
-
writeCart(cart);
|
|
1231
1252
|
}
|
|
1232
|
-
function
|
|
1233
|
-
const
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
if (
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1253
|
+
async function sendCartActivity(order2, cartValue, itemCount) {
|
|
1254
|
+
const config = getConfig();
|
|
1255
|
+
const settle = claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount);
|
|
1256
|
+
const delivered = await postPing({
|
|
1257
|
+
event_type: "presence",
|
|
1258
|
+
cart_value: cartValue,
|
|
1259
|
+
item_count: itemCount,
|
|
1260
|
+
cart_seq: order2.seq,
|
|
1261
|
+
cart_seq_scope: order2.scope,
|
|
1262
|
+
// A cart marks the analytics session as one that did not bounce.
|
|
1263
|
+
session_id: analyticsSessionId(config.storeSlug, { touch: false }) ?? void 0,
|
|
1264
|
+
path: resolveStorefrontPagePath()
|
|
1265
|
+
});
|
|
1266
|
+
settle(delivered);
|
|
1267
|
+
}
|
|
1268
|
+
async function trackProductView(productId) {
|
|
1269
|
+
if (!isInitialized() || typeof document === "undefined" || !productId) return;
|
|
1270
|
+
await sendVisitEvent({ event_type: "product_view", product_id: productId });
|
|
1271
|
+
}
|
|
1272
|
+
async function trackCartAdd(productId, variantId, quantity) {
|
|
1273
|
+
if (!isInitialized() || typeof document === "undefined" || quantity < 1) return;
|
|
1274
|
+
await sendVisitEvent({ event_type: "cart_add", product_id: productId, variant_id: variantId, quantity });
|
|
1275
|
+
}
|
|
1276
|
+
async function sendVisitEvent(body) {
|
|
1277
|
+
const config = getConfig();
|
|
1278
|
+
if (config.preview) return;
|
|
1279
|
+
await postPing({
|
|
1280
|
+
...body,
|
|
1281
|
+
referer: document.referrer || void 0,
|
|
1282
|
+
session_id: analyticsSessionId(config.storeSlug, { touch: true }) ?? void 0,
|
|
1283
|
+
path: resolveStorefrontPagePath(),
|
|
1284
|
+
utm: resolveUtmParameters(config.storeSlug)
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
function startSessionOnConsent() {
|
|
1288
|
+
const config = getConfig();
|
|
1289
|
+
if (!isPageViewed() || analyticsSessionId(config.storeSlug, { touch: false })) return;
|
|
1290
|
+
const sessionId = analyticsSessionId(config.storeSlug, { touch: true });
|
|
1291
|
+
if (!sessionId) return;
|
|
1292
|
+
markPageViewed(sessionId);
|
|
1293
|
+
installEngagedClock(reportPageLeave);
|
|
1294
|
+
void postPing({
|
|
1295
|
+
event_type: "session_start",
|
|
1296
|
+
referer: document.referrer || void 0,
|
|
1297
|
+
session_id: sessionId,
|
|
1298
|
+
path: resolveStorefrontPagePath(),
|
|
1299
|
+
utm: resolveUtmParameters(config.storeSlug)
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function reportPageLeave() {
|
|
1303
|
+
const config = getConfig();
|
|
1304
|
+
const sessionId = currentPageSession();
|
|
1305
|
+
const engagedMs2 = takeEngagedMs();
|
|
1306
|
+
if (!sessionId || engagedMs2 === 0 || readAnalyticsConsent() !== "granted") return;
|
|
1307
|
+
try {
|
|
1308
|
+
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", { storeSlug: config.storeSlug });
|
|
1309
|
+
void fetch(`${config.apiBaseUrl}${endpoint}`, {
|
|
1310
|
+
method: "POST",
|
|
1311
|
+
headers: { "Content-Type": "application/json" },
|
|
1312
|
+
body: JSON.stringify({ event_type: "page_leave", session_id: sessionId, engaged_ms: engagedMs2 }),
|
|
1313
|
+
keepalive: true
|
|
1314
|
+
}).catch(() => {
|
|
1315
|
+
});
|
|
1316
|
+
} catch {
|
|
1288
1317
|
}
|
|
1289
|
-
if (JSON.stringify(cart) === before) return;
|
|
1290
|
-
writeCart(cart);
|
|
1291
1318
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1319
|
+
|
|
1320
|
+
// ../sdk/src/utils/cart-line-id.ts
|
|
1321
|
+
function hashString(value) {
|
|
1322
|
+
let hash = 2166136261;
|
|
1323
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1324
|
+
hash ^= value.charCodeAt(i);
|
|
1325
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
1296
1326
|
}
|
|
1297
|
-
|
|
1298
|
-
const filtered = cart.filter((item) => item.line_id !== normalizedLineId);
|
|
1299
|
-
writeCart(filtered);
|
|
1327
|
+
return (hash >>> 0).toString(16);
|
|
1300
1328
|
}
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
clearCartCoupon();
|
|
1305
|
-
clearLatestQuoteToken();
|
|
1329
|
+
function normalizeAddons(addons) {
|
|
1330
|
+
if (!addons?.length) return [];
|
|
1331
|
+
return [...addons].map((addon) => ({ id: addon.id, quantity: addon.quantity ?? 1 })).sort((a, b) => a.id.localeCompare(b.id));
|
|
1306
1332
|
}
|
|
1307
|
-
function
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1333
|
+
function normalizeCustomFields(fields) {
|
|
1334
|
+
if (!fields) return {};
|
|
1335
|
+
const entries = Object.entries(fields).filter(([, value]) => typeof value === "string").sort(([a], [b]) => a.localeCompare(b));
|
|
1336
|
+
return Object.fromEntries(entries);
|
|
1337
|
+
}
|
|
1338
|
+
function computeCartLineId(input) {
|
|
1339
|
+
const payload = {
|
|
1340
|
+
product_id: input.product_id,
|
|
1341
|
+
variant_id: input.variant_id,
|
|
1342
|
+
price_variant_id: input.price_variant_id ?? null,
|
|
1343
|
+
addons: normalizeAddons(input.addons),
|
|
1344
|
+
custom_fields: normalizeCustomFields(input.custom_fields),
|
|
1345
|
+
unit_price: typeof input.price_data?.unit_price === "number" && Number.isFinite(input.price_data.unit_price) ? input.price_data.unit_price : null,
|
|
1346
|
+
pay_what_you_want_price: typeof input.pay_what_you_want_price === "number" && Number.isFinite(input.pay_what_you_want_price) ? input.pay_what_you_want_price : null
|
|
1347
|
+
};
|
|
1348
|
+
return hashString(JSON.stringify(payload));
|
|
1349
|
+
}
|
|
1350
|
+
function ensureCartLineId(item) {
|
|
1351
|
+
if (typeof item.line_id === "string" && item.line_id.trim()) {
|
|
1352
|
+
return item;
|
|
1315
1353
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1354
|
+
return { ...item, line_id: computeCartLineId(item) };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// ../contracts/src/catalog-unit-price.ts
|
|
1358
|
+
var CATALOG_UNIT_PRICE_DECIMAL_PLACES = 3;
|
|
1359
|
+
var PAYABLE_AMOUNT_DECIMAL_PLACES = 2;
|
|
1360
|
+
var CATALOG_UNIT_PRICE_FORMAT_OPTIONS = {
|
|
1361
|
+
maximumFractionDigits: CATALOG_UNIT_PRICE_DECIMAL_PLACES
|
|
1362
|
+
};
|
|
1363
|
+
function roundPayableAmount(value) {
|
|
1364
|
+
if (!Number.isFinite(value)) return value;
|
|
1365
|
+
const sign = value < 0 ? -1 : 1;
|
|
1366
|
+
const shifted = shiftDecimal(Math.abs(value), PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1367
|
+
const rounded = Math.round(shifted);
|
|
1368
|
+
if (rounded === 0) return 0;
|
|
1369
|
+
return sign * shiftDecimal(rounded, -PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1370
|
+
}
|
|
1371
|
+
function shiftDecimal(value, places) {
|
|
1372
|
+
const [coefficient, currentExponent = "0"] = String(value).split("e");
|
|
1373
|
+
return Number(`${coefficient}e${Number(currentExponent) + places}`);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// ../sdk/src/modules/cart.ts
|
|
1377
|
+
var STORAGE_KEYS = {
|
|
1378
|
+
cart: "cart",
|
|
1379
|
+
cartBackup: "cart_backup",
|
|
1380
|
+
meta: "cart_meta",
|
|
1381
|
+
metaBackup: "cart_backup_meta",
|
|
1382
|
+
coupon: "cart_coupon",
|
|
1383
|
+
couponBackup: "cart_coupon_backup"
|
|
1384
|
+
};
|
|
1385
|
+
function getStorageKey(type) {
|
|
1386
|
+
return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
|
|
1387
|
+
}
|
|
1388
|
+
function cartStorageArea() {
|
|
1389
|
+
return getConfig().preview ? "sessionStorage" : "localStorage";
|
|
1390
|
+
}
|
|
1391
|
+
function readCartStorage(type) {
|
|
1392
|
+
return getItem(getStorageKey(type), cartStorageArea());
|
|
1393
|
+
}
|
|
1394
|
+
function writeCartStorage(type, value) {
|
|
1395
|
+
setItem(getStorageKey(type), value, cartStorageArea());
|
|
1396
|
+
}
|
|
1397
|
+
function removeCartStorage(type) {
|
|
1398
|
+
removeItem(getStorageKey(type), cartStorageArea());
|
|
1399
|
+
}
|
|
1400
|
+
function hashString2(value) {
|
|
1401
|
+
let hash = 2166136261;
|
|
1402
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1403
|
+
hash ^= value.charCodeAt(i);
|
|
1404
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
1327
1405
|
}
|
|
1406
|
+
return (hash >>> 0).toString(16);
|
|
1328
1407
|
}
|
|
1329
|
-
function
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
}
|
|
1347
|
-
return true;
|
|
1408
|
+
function computeChecksum(cart) {
|
|
1409
|
+
return hashString2(JSON.stringify(cart));
|
|
1410
|
+
}
|
|
1411
|
+
function normalizeQuantity(value) {
|
|
1412
|
+
if (!Number.isFinite(value)) {
|
|
1413
|
+
throw new CartError("quantity must be a finite number");
|
|
1414
|
+
}
|
|
1415
|
+
return Math.floor(value);
|
|
1416
|
+
}
|
|
1417
|
+
function normalizeCouponCode(value) {
|
|
1418
|
+
const normalized = value?.trim().toUpperCase();
|
|
1419
|
+
return normalized ? normalized : null;
|
|
1420
|
+
}
|
|
1421
|
+
function getStoredCartCode() {
|
|
1422
|
+
const raw = readCartStorage("coupon");
|
|
1423
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1424
|
+
return null;
|
|
1348
1425
|
}
|
|
1349
|
-
|
|
1426
|
+
const record = raw;
|
|
1427
|
+
const code = typeof record.code === "string" ? normalizeCouponCode(record.code) : null;
|
|
1428
|
+
const source = record.source === "COUPON" || record.source === "AFFILIATE" ? record.source : null;
|
|
1429
|
+
return code && source ? { code, source } : null;
|
|
1350
1430
|
}
|
|
1351
|
-
function
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
normalizedQuantity = normalizeQuantity(incoming.quantity);
|
|
1362
|
-
} catch {
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
if (normalizedQuantity < 1) {
|
|
1431
|
+
function normalizeCartItems(value) {
|
|
1432
|
+
if (!Array.isArray(value)) return [];
|
|
1433
|
+
const normalized = [];
|
|
1434
|
+
for (const entry of value) {
|
|
1435
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1436
|
+
const record = entry;
|
|
1437
|
+
const productId = typeof record.product_id === "string" ? record.product_id.trim() : "";
|
|
1438
|
+
const variantId = typeof record.variant_id === "string" ? record.variant_id.trim() : "";
|
|
1439
|
+
const quantity = Number(record.quantity);
|
|
1440
|
+
if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
|
|
1366
1441
|
continue;
|
|
1367
1442
|
}
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
line_id: incoming.line_id ?? computeCartLineId(incoming),
|
|
1443
|
+
const item = {
|
|
1444
|
+
line_id: "",
|
|
1371
1445
|
product_id: productId,
|
|
1372
1446
|
variant_id: variantId,
|
|
1373
|
-
quantity:
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
|
|
1378
|
-
clampQuantityToBounds(cart[index]);
|
|
1379
|
-
} else {
|
|
1380
|
-
clampQuantityToBounds(mergedLine);
|
|
1381
|
-
cart.push(mergedLine);
|
|
1447
|
+
quantity: Math.floor(quantity)
|
|
1448
|
+
};
|
|
1449
|
+
if (typeof record.price_variant_id === "string") {
|
|
1450
|
+
item.price_variant_id = record.price_variant_id;
|
|
1382
1451
|
}
|
|
1452
|
+
if (record.price_data && typeof record.price_data === "object") {
|
|
1453
|
+
const priceData = record.price_data;
|
|
1454
|
+
if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
|
|
1455
|
+
item.price_data = { unit_price: priceData.unit_price };
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (typeof record.pay_what_you_want_price === "number" && Number.isFinite(record.pay_what_you_want_price)) {
|
|
1459
|
+
item.pay_what_you_want_price = record.pay_what_you_want_price;
|
|
1460
|
+
}
|
|
1461
|
+
if (Array.isArray(record.addons)) {
|
|
1462
|
+
item.addons = record.addons;
|
|
1463
|
+
}
|
|
1464
|
+
if (record.custom_fields && typeof record.custom_fields === "object" && !Array.isArray(record.custom_fields)) {
|
|
1465
|
+
item.custom_fields = record.custom_fields;
|
|
1466
|
+
}
|
|
1467
|
+
if (typeof record.title === "string") {
|
|
1468
|
+
item.title = record.title;
|
|
1469
|
+
}
|
|
1470
|
+
if (typeof record.variant_title === "string") {
|
|
1471
|
+
item.variant_title = record.variant_title;
|
|
1472
|
+
}
|
|
1473
|
+
if (typeof record.image_url === "string") {
|
|
1474
|
+
item.image_url = record.image_url;
|
|
1475
|
+
}
|
|
1476
|
+
if (Array.isArray(record.addon_labels)) {
|
|
1477
|
+
item.addon_labels = record.addon_labels.filter((label) => typeof label === "string");
|
|
1478
|
+
}
|
|
1479
|
+
if (typeof record.max_quantity === "number" && Number.isFinite(record.max_quantity)) {
|
|
1480
|
+
item.max_quantity = record.max_quantity;
|
|
1481
|
+
}
|
|
1482
|
+
if (typeof record.min_quantity === "number" && Number.isFinite(record.min_quantity)) {
|
|
1483
|
+
item.min_quantity = record.min_quantity;
|
|
1484
|
+
}
|
|
1485
|
+
clampQuantityToBounds(item);
|
|
1486
|
+
const storedLineId = typeof record.line_id === "string" ? record.line_id.trim() : "";
|
|
1487
|
+
item.line_id = storedLineId || computeCartLineId(item);
|
|
1488
|
+
normalized.push(ensureCartLineId(item));
|
|
1383
1489
|
}
|
|
1384
|
-
|
|
1385
|
-
return cart;
|
|
1386
|
-
}
|
|
1387
|
-
function moveBasketItem(fromProductId, fromVariantId, toProductId, toVariantId) {
|
|
1388
|
-
const cart = getCart();
|
|
1389
|
-
const fromIndex = cart.findIndex(
|
|
1390
|
-
(item) => item.product_id === fromProductId && item.variant_id === fromVariantId
|
|
1391
|
-
);
|
|
1392
|
-
if (fromIndex < 0) {
|
|
1393
|
-
throw new CartError("Item not found in cart");
|
|
1394
|
-
}
|
|
1395
|
-
const [fromItem] = cart.splice(fromIndex, 1);
|
|
1396
|
-
const toLineId = computeCartLineId({
|
|
1397
|
-
...fromItem,
|
|
1398
|
-
product_id: toProductId,
|
|
1399
|
-
variant_id: toVariantId
|
|
1400
|
-
});
|
|
1401
|
-
const toIndex = cart.findIndex((item) => item.line_id === toLineId);
|
|
1402
|
-
if (toIndex >= 0) {
|
|
1403
|
-
cart[toIndex].quantity += fromItem.quantity;
|
|
1404
|
-
clampQuantityToBounds(cart[toIndex]);
|
|
1405
|
-
} else {
|
|
1406
|
-
const moved = ensureCartLineId({
|
|
1407
|
-
...fromItem,
|
|
1408
|
-
product_id: toProductId,
|
|
1409
|
-
variant_id: toVariantId,
|
|
1410
|
-
line_id: toLineId
|
|
1411
|
-
});
|
|
1412
|
-
clampQuantityToBounds(moved);
|
|
1413
|
-
cart.push(moved);
|
|
1414
|
-
}
|
|
1415
|
-
writeCart(cart);
|
|
1416
|
-
}
|
|
1417
|
-
function validateCartIntegrity() {
|
|
1418
|
-
const cart = getCart();
|
|
1419
|
-
const metadata = getCartMetadata();
|
|
1420
|
-
const checksum = computeChecksum(cart);
|
|
1421
|
-
if (!metadata) {
|
|
1422
|
-
setCartWithMetadata(cart, null);
|
|
1423
|
-
return true;
|
|
1424
|
-
}
|
|
1425
|
-
return metadata.checksum === checksum;
|
|
1426
|
-
}
|
|
1427
|
-
function getCartStats() {
|
|
1428
|
-
const cart = getCart();
|
|
1429
|
-
const integrityValid = validateCartIntegrity();
|
|
1430
|
-
const metadata = getCartMetadata();
|
|
1431
|
-
const backup = readCartStorage("cartBackup") ?? [];
|
|
1432
|
-
const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
|
|
1433
|
-
const totalPrice = cart.reduce((sum, item) => {
|
|
1434
|
-
const unitPrice = item.price_data?.unit_price ?? 0;
|
|
1435
|
-
return sum + roundPayableAmount(unitPrice * item.quantity);
|
|
1436
|
-
}, 0);
|
|
1437
|
-
return {
|
|
1438
|
-
item_count: cart.length,
|
|
1439
|
-
total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
|
|
1440
|
-
last_modified: metadata?.last_modified ?? 0,
|
|
1441
|
-
version: metadata?.version ?? 0,
|
|
1442
|
-
has_backup: backup.length > 0,
|
|
1443
|
-
integrity_valid: integrityValid,
|
|
1444
|
-
total_price: roundPayableAmount(totalPrice),
|
|
1445
|
-
total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
function getCartPayload(coupon) {
|
|
1449
|
-
const config = getConfig();
|
|
1450
|
-
const normalizedCoupon = normalizeCouponCode(coupon) ?? getCartCoupon() ?? void 0;
|
|
1451
|
-
return {
|
|
1452
|
-
store_slug: config.storeSlug,
|
|
1453
|
-
items: getCart(),
|
|
1454
|
-
coupon: normalizedCoupon
|
|
1455
|
-
};
|
|
1490
|
+
return normalized;
|
|
1456
1491
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
const storedSource = getCartCouponSource();
|
|
1460
|
-
if (storedSource === "AFFILIATE" && storedCode) {
|
|
1461
|
-
return storedCode.trim().toLowerCase();
|
|
1462
|
-
}
|
|
1463
|
-
return getAffiliateCode();
|
|
1492
|
+
function getCartMetadata() {
|
|
1493
|
+
return readCartStorage("meta");
|
|
1464
1494
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
function getLatestQuoteToken() {
|
|
1468
|
-
return latestQuoteToken;
|
|
1495
|
+
function getCartCoupon() {
|
|
1496
|
+
return getStoredCartCode()?.code ?? null;
|
|
1469
1497
|
}
|
|
1470
|
-
function
|
|
1471
|
-
|
|
1472
|
-
quoteSequence += 1;
|
|
1498
|
+
function getCartCouponSource() {
|
|
1499
|
+
return getStoredCartCode()?.source ?? null;
|
|
1473
1500
|
}
|
|
1474
|
-
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
shop_slug: payload.store_slug,
|
|
1480
|
-
cart: payload.items,
|
|
1481
|
-
coupon: payload.coupon,
|
|
1482
|
-
affiliate_code: getAmbientAffiliateCodeForQuote() ?? void 0,
|
|
1483
|
-
currency: normalizedCurrency ?? void 0
|
|
1484
|
-
});
|
|
1485
|
-
if (sequence !== quoteSequence) return response;
|
|
1486
|
-
if (response.success) {
|
|
1487
|
-
latestQuoteToken = typeof response.data?.quote_token === "string" ? response.data.quote_token : null;
|
|
1501
|
+
function setCartCoupon(coupon, source = "COUPON") {
|
|
1502
|
+
const normalizedCoupon = normalizeCouponCode(coupon);
|
|
1503
|
+
if (!normalizedCoupon) {
|
|
1504
|
+
removeCartStorage("coupon");
|
|
1505
|
+
return null;
|
|
1488
1506
|
}
|
|
1489
|
-
|
|
1507
|
+
writeCartStorage("coupon", { code: normalizedCoupon, source });
|
|
1508
|
+
return normalizedCoupon;
|
|
1490
1509
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
var COOKIE_CONSENT_VERSION = 1;
|
|
1494
|
-
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
1495
|
-
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
1496
|
-
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
1497
|
-
function parseCookieConsentValue(value) {
|
|
1498
|
-
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
1499
|
-
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
1510
|
+
function clearCartCoupon() {
|
|
1511
|
+
removeCartStorage("coupon");
|
|
1500
1512
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
raw = document.cookie.split(/;\s*/).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
1517
|
-
} catch {
|
|
1518
|
-
return "unknown";
|
|
1519
|
-
}
|
|
1520
|
-
const choice = parseCookieConsentValue(raw ?? null);
|
|
1521
|
-
if (!choice) return "unknown";
|
|
1522
|
-
return choice.analytics ? "granted" : "declined";
|
|
1513
|
+
function writeCart(cart) {
|
|
1514
|
+
const normalizedCart = normalizeCartItems(cart);
|
|
1515
|
+
writeCartStorage("cart", normalizedCart);
|
|
1516
|
+
const now = Date.now();
|
|
1517
|
+
const previous = getCartMetadata();
|
|
1518
|
+
const nextMeta = {
|
|
1519
|
+
created_at: previous?.created_at ?? now,
|
|
1520
|
+
last_modified: now,
|
|
1521
|
+
version: (previous?.version ?? 0) + 1,
|
|
1522
|
+
checksum: computeChecksum(normalizedCart)
|
|
1523
|
+
};
|
|
1524
|
+
writeCartStorage("meta", nextMeta);
|
|
1525
|
+
if (normalizedCart.length === 0) {
|
|
1526
|
+
clearCartCoupon();
|
|
1527
|
+
}
|
|
1523
1528
|
}
|
|
1524
|
-
function
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1529
|
+
function setCartWithMetadata(cart, metadata) {
|
|
1530
|
+
const normalizedCart = normalizeCartItems(cart);
|
|
1531
|
+
writeCartStorage("cart", normalizedCart);
|
|
1532
|
+
const now = Date.now();
|
|
1533
|
+
const base = metadata ?? getCartMetadata();
|
|
1534
|
+
const nextMeta = {
|
|
1535
|
+
created_at: base?.created_at ?? now,
|
|
1536
|
+
last_modified: now,
|
|
1537
|
+
version: base?.version ?? 1,
|
|
1538
|
+
checksum: computeChecksum(normalizedCart)
|
|
1539
|
+
};
|
|
1540
|
+
writeCartStorage("meta", nextMeta);
|
|
1528
1541
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1531
|
-
|
|
1542
|
+
function getCart() {
|
|
1543
|
+
const raw = readCartStorage("cart");
|
|
1544
|
+
return normalizeCartItems(raw);
|
|
1532
1545
|
}
|
|
1533
|
-
function
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
forgetAnalyticsData(storeSlug);
|
|
1537
|
-
return null;
|
|
1538
|
-
}
|
|
1539
|
-
const stored = getItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1540
|
-
const live = stored && typeof stored.id === "string" && typeof stored.at === "number" && now - stored.at < ANALYTICS_SESSION_IDLE_MS ? stored.id : null;
|
|
1541
|
-
if (!options.touch) return live;
|
|
1542
|
-
if (live) {
|
|
1543
|
-
setItem(analyticsSessionKey(storeSlug), { id: live, at: now }, "sessionStorage");
|
|
1544
|
-
return live;
|
|
1545
|
-
}
|
|
1546
|
-
if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") return null;
|
|
1547
|
-
const id = crypto.randomUUID();
|
|
1548
|
-
setItem(analyticsSessionKey(storeSlug), { id, at: now }, "sessionStorage");
|
|
1549
|
-
setItem(
|
|
1550
|
-
analyticsVisitsKey(storeSlug),
|
|
1551
|
-
[{ id, at: now }, ...readVisits(storeSlug, now)].slice(0, ANALYTICS_VISITS_MAX)
|
|
1546
|
+
function resolveCartLineId(productId, variantId, cart = getCart()) {
|
|
1547
|
+
const matches = cart.filter(
|
|
1548
|
+
(item) => item.product_id === productId && item.variant_id === variantId
|
|
1552
1549
|
);
|
|
1553
|
-
|
|
1554
|
-
}
|
|
1555
|
-
function checkoutAttribution(storeSlug, now = Date.now()) {
|
|
1556
|
-
const consent = readAnalyticsConsent();
|
|
1557
|
-
if (consent === "declined") {
|
|
1558
|
-
forgetAnalyticsData(storeSlug);
|
|
1559
|
-
return { declined: true };
|
|
1550
|
+
if (matches.length === 0) {
|
|
1551
|
+
throw new CartError(`No cart line found for ${productId}/${variantId}`);
|
|
1560
1552
|
}
|
|
1561
|
-
|
|
1562
|
-
const sessionIds = readVisits(storeSlug, now).map((visit) => visit.id);
|
|
1563
|
-
return sessionIds.length > 0 ? { session_ids: sessionIds } : void 0;
|
|
1553
|
+
return matches[0].line_id;
|
|
1564
1554
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
var pageViewed = false;
|
|
1569
|
-
var pageSession = null;
|
|
1570
|
-
function markPageViewed(sessionId) {
|
|
1571
|
-
pageViewed = true;
|
|
1572
|
-
pageSession = sessionId;
|
|
1555
|
+
function getCartItemCount() {
|
|
1556
|
+
const cart = getCart();
|
|
1557
|
+
return cart.reduce((sum, item) => sum + item.quantity, 0);
|
|
1573
1558
|
}
|
|
1574
|
-
function
|
|
1575
|
-
|
|
1559
|
+
function addToCart(productId, variantId, quantity = 1, options) {
|
|
1560
|
+
if (!productId || !variantId) {
|
|
1561
|
+
throw new CartError("product_id and variant_id are required");
|
|
1562
|
+
}
|
|
1563
|
+
const normalizedQuantity = normalizeQuantity(quantity);
|
|
1564
|
+
if (normalizedQuantity < 1) {
|
|
1565
|
+
throw new CartError("quantity must be at least 1");
|
|
1566
|
+
}
|
|
1567
|
+
const cart = getCart();
|
|
1568
|
+
const lineId = computeCartLineId({
|
|
1569
|
+
product_id: productId,
|
|
1570
|
+
variant_id: variantId,
|
|
1571
|
+
addons: options?.addons,
|
|
1572
|
+
custom_fields: options?.custom_fields,
|
|
1573
|
+
price_variant_id: options?.price_variant_id,
|
|
1574
|
+
price_data: options?.price_data,
|
|
1575
|
+
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1576
|
+
});
|
|
1577
|
+
const existingIndex = cart.findIndex((item) => item.line_id === lineId);
|
|
1578
|
+
const quantityBeforeAdd = existingIndex >= 0 ? cart[existingIndex].quantity : 0;
|
|
1579
|
+
if (existingIndex >= 0) {
|
|
1580
|
+
cart[existingIndex].quantity += normalizedQuantity;
|
|
1581
|
+
if (options?.addons) {
|
|
1582
|
+
cart[existingIndex].addons = options.addons;
|
|
1583
|
+
}
|
|
1584
|
+
if (options?.custom_fields) {
|
|
1585
|
+
cart[existingIndex].custom_fields = options.custom_fields;
|
|
1586
|
+
}
|
|
1587
|
+
if (options?.price_variant_id) {
|
|
1588
|
+
cart[existingIndex].price_variant_id = options.price_variant_id;
|
|
1589
|
+
}
|
|
1590
|
+
if (options?.price_data) {
|
|
1591
|
+
cart[existingIndex].price_data = options.price_data;
|
|
1592
|
+
}
|
|
1593
|
+
if (options?.pay_what_you_want_price !== void 0) {
|
|
1594
|
+
cart[existingIndex].pay_what_you_want_price = options.pay_what_you_want_price;
|
|
1595
|
+
}
|
|
1596
|
+
applyDisplaySnapshot(cart[existingIndex], options);
|
|
1597
|
+
clampQuantityToBounds(cart[existingIndex]);
|
|
1598
|
+
} else {
|
|
1599
|
+
const pushed = {
|
|
1600
|
+
line_id: lineId,
|
|
1601
|
+
product_id: productId,
|
|
1602
|
+
variant_id: variantId,
|
|
1603
|
+
quantity: normalizedQuantity,
|
|
1604
|
+
addons: options?.addons,
|
|
1605
|
+
custom_fields: options?.custom_fields,
|
|
1606
|
+
price_variant_id: options?.price_variant_id,
|
|
1607
|
+
price_data: options?.price_data,
|
|
1608
|
+
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1609
|
+
title: options?.title,
|
|
1610
|
+
variant_title: options?.variant_title,
|
|
1611
|
+
image_url: options?.image_url,
|
|
1612
|
+
addon_labels: options?.addon_labels,
|
|
1613
|
+
max_quantity: options?.max_quantity,
|
|
1614
|
+
min_quantity: options?.min_quantity
|
|
1615
|
+
};
|
|
1616
|
+
clampQuantityToBounds(pushed);
|
|
1617
|
+
cart.push(pushed);
|
|
1618
|
+
}
|
|
1619
|
+
const intendedQuantity = (existingIndex >= 0 ? cart[existingIndex] : cart[cart.length - 1]).quantity;
|
|
1620
|
+
writeCart(cart);
|
|
1621
|
+
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1622
|
+
if (intendedQuantity > quantityBeforeAdd && persistedLine?.quantity === intendedQuantity) {
|
|
1623
|
+
const cartAffiliateCode = getCartCouponSource() === "AFFILIATE" ? getCartCoupon() : null;
|
|
1624
|
+
void trackAffiliateEvent("add_to_cart", cartAffiliateCode ? { code: cartAffiliateCode } : void 0);
|
|
1625
|
+
void trackCartAdd(productId, variantId, intendedQuantity - quantityBeforeAdd);
|
|
1626
|
+
}
|
|
1576
1627
|
}
|
|
1577
|
-
function
|
|
1578
|
-
|
|
1628
|
+
function clampQuantityToBounds(item) {
|
|
1629
|
+
const min = item.min_quantity;
|
|
1630
|
+
if (typeof min === "number" && Number.isFinite(min) && item.quantity < min) {
|
|
1631
|
+
item.quantity = Math.floor(min);
|
|
1632
|
+
}
|
|
1633
|
+
const max = item.max_quantity;
|
|
1634
|
+
if (typeof max === "number" && Number.isFinite(max) && item.quantity > max) {
|
|
1635
|
+
item.quantity = Math.floor(max);
|
|
1636
|
+
}
|
|
1637
|
+
if (item.quantity < 1) {
|
|
1638
|
+
item.quantity = 1;
|
|
1639
|
+
}
|
|
1579
1640
|
}
|
|
1580
|
-
function
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
1583
|
-
|
|
1641
|
+
function applyDisplaySnapshot(item, options) {
|
|
1642
|
+
if (options?.replace_display_snapshot) {
|
|
1643
|
+
item.title = options.title;
|
|
1644
|
+
item.variant_title = options.variant_title;
|
|
1645
|
+
item.image_url = options.image_url;
|
|
1646
|
+
item.addon_labels = options.addon_labels;
|
|
1647
|
+
item.max_quantity = options.max_quantity;
|
|
1648
|
+
item.min_quantity = options.min_quantity;
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
if (options?.title !== void 0) {
|
|
1652
|
+
item.title = options.title;
|
|
1653
|
+
}
|
|
1654
|
+
if (options?.variant_title !== void 0) {
|
|
1655
|
+
item.variant_title = options.variant_title;
|
|
1656
|
+
}
|
|
1657
|
+
if (options?.image_url !== void 0) {
|
|
1658
|
+
item.image_url = options.image_url;
|
|
1659
|
+
}
|
|
1660
|
+
if (options?.addon_labels !== void 0) {
|
|
1661
|
+
item.addon_labels = options.addon_labels;
|
|
1662
|
+
}
|
|
1663
|
+
if (options?.max_quantity !== void 0) {
|
|
1664
|
+
item.max_quantity = options.max_quantity;
|
|
1665
|
+
}
|
|
1666
|
+
if (options?.min_quantity !== void 0) {
|
|
1667
|
+
item.min_quantity = options.min_quantity;
|
|
1584
1668
|
}
|
|
1585
1669
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1670
|
+
function setCartItem(productId, variantId, quantity = 1, options) {
|
|
1671
|
+
if (!productId || !variantId) {
|
|
1672
|
+
throw new CartError("product_id and variant_id are required");
|
|
1673
|
+
}
|
|
1674
|
+
const normalizedQuantity = normalizeQuantity(quantity);
|
|
1675
|
+
if (normalizedQuantity < 1) {
|
|
1676
|
+
throw new CartError("quantity must be at least 1");
|
|
1677
|
+
}
|
|
1678
|
+
const cart = getCart();
|
|
1679
|
+
const lineId = computeCartLineId({
|
|
1680
|
+
product_id: productId,
|
|
1681
|
+
variant_id: variantId,
|
|
1682
|
+
addons: options?.addons,
|
|
1683
|
+
custom_fields: options?.custom_fields,
|
|
1684
|
+
price_variant_id: options?.price_variant_id,
|
|
1685
|
+
price_data: options?.price_data,
|
|
1686
|
+
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1592
1687
|
});
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
clockInstalled = true;
|
|
1597
|
-
visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
1598
|
-
document.addEventListener("visibilitychange", () => {
|
|
1599
|
-
const now = Date.now();
|
|
1600
|
-
if (document.visibilityState === "visible") {
|
|
1601
|
-
visibleSince ?? (visibleSince = now);
|
|
1602
|
-
return;
|
|
1688
|
+
for (let index = cart.length - 1; index >= 0; index -= 1) {
|
|
1689
|
+
if (cart[index].product_id === productId && cart[index].variant_id === variantId) {
|
|
1690
|
+
cart.splice(index, 1);
|
|
1603
1691
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1692
|
+
}
|
|
1693
|
+
const pushed = {
|
|
1694
|
+
line_id: lineId,
|
|
1695
|
+
product_id: productId,
|
|
1696
|
+
variant_id: variantId,
|
|
1697
|
+
quantity: normalizedQuantity,
|
|
1698
|
+
addons: options?.addons,
|
|
1699
|
+
custom_fields: options?.custom_fields,
|
|
1700
|
+
price_variant_id: options?.price_variant_id,
|
|
1701
|
+
price_data: options?.price_data,
|
|
1702
|
+
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1703
|
+
title: options?.title,
|
|
1704
|
+
variant_title: options?.variant_title,
|
|
1705
|
+
image_url: options?.image_url,
|
|
1706
|
+
addon_labels: options?.addon_labels,
|
|
1707
|
+
max_quantity: options?.max_quantity,
|
|
1708
|
+
min_quantity: options?.min_quantity
|
|
1709
|
+
};
|
|
1710
|
+
clampQuantityToBounds(pushed);
|
|
1711
|
+
cart.push(pushed);
|
|
1712
|
+
writeCart(cart);
|
|
1713
|
+
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1714
|
+
if (persistedLine) void trackCartAdd(productId, variantId, persistedLine.quantity);
|
|
1622
1715
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
var MAX_VALUE_LENGTH = 255;
|
|
1629
|
-
function readFromUrl(search) {
|
|
1630
|
-
let params;
|
|
1631
|
-
try {
|
|
1632
|
-
params = new URLSearchParams(search);
|
|
1633
|
-
} catch {
|
|
1634
|
-
return {};
|
|
1716
|
+
function updateCartItem(lineId, updates) {
|
|
1717
|
+
const cart = getCart();
|
|
1718
|
+
const normalizedLineId = lineId.trim();
|
|
1719
|
+
if (!normalizedLineId) {
|
|
1720
|
+
throw new CartError("line_id is required");
|
|
1635
1721
|
}
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if (value) {
|
|
1640
|
-
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1641
|
-
}
|
|
1722
|
+
const index = cart.findIndex((item) => item.line_id === normalizedLineId);
|
|
1723
|
+
if (index < 0) {
|
|
1724
|
+
throw new CartError("Item not found in cart");
|
|
1642
1725
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
const raw = window.localStorage.getItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1651
|
-
if (!raw) return {};
|
|
1652
|
-
const parsed = JSON.parse(raw);
|
|
1653
|
-
if (!parsed || typeof parsed.at !== "number" || typeof parsed.utm !== "object") return {};
|
|
1654
|
-
if (Date.now() - parsed.at > TTL_MS) {
|
|
1655
|
-
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1656
|
-
return {};
|
|
1657
|
-
}
|
|
1658
|
-
const utm = {};
|
|
1659
|
-
for (const key of UTM_KEYS) {
|
|
1660
|
-
const value = parsed.utm?.[key];
|
|
1661
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1662
|
-
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1663
|
-
}
|
|
1726
|
+
const before = JSON.stringify(cart);
|
|
1727
|
+
if (updates.quantity !== void 0) {
|
|
1728
|
+
const normalizedQuantity = normalizeQuantity(updates.quantity);
|
|
1729
|
+
if (normalizedQuantity < 1) {
|
|
1730
|
+
cart.splice(index, 1);
|
|
1731
|
+
writeCart(cart);
|
|
1732
|
+
return;
|
|
1664
1733
|
}
|
|
1665
|
-
|
|
1666
|
-
} catch {
|
|
1667
|
-
return {};
|
|
1734
|
+
cart[index].quantity = normalizedQuantity;
|
|
1668
1735
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
try {
|
|
1672
|
-
const payload = { utm, at: Date.now() };
|
|
1673
|
-
window.localStorage.setItem(storefrontStorageKey(storageKey(storeSlug)), JSON.stringify(payload));
|
|
1674
|
-
} catch {
|
|
1736
|
+
if (updates.addons !== void 0) {
|
|
1737
|
+
cart[index].addons = updates.addons;
|
|
1675
1738
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
1679
|
-
return void 0;
|
|
1739
|
+
if (updates.custom_fields !== void 0) {
|
|
1740
|
+
cart[index].custom_fields = updates.custom_fields;
|
|
1680
1741
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
try {
|
|
1684
|
-
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1685
|
-
} catch {
|
|
1686
|
-
}
|
|
1687
|
-
return Object.keys(fromUrl).length > 0 ? fromUrl : void 0;
|
|
1742
|
+
if (updates.price_variant_id !== void 0) {
|
|
1743
|
+
cart[index].price_variant_id = updates.price_variant_id;
|
|
1688
1744
|
}
|
|
1689
|
-
if (
|
|
1690
|
-
|
|
1691
|
-
return fromUrl;
|
|
1745
|
+
if (updates.price_data !== void 0) {
|
|
1746
|
+
cart[index].price_data = updates.price_data;
|
|
1692
1747
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
// ../sdk/src/core/page-path.ts
|
|
1698
|
-
var MAX_PATH_LENGTH = 256;
|
|
1699
|
-
function resolveStorefrontPagePath() {
|
|
1700
|
-
if (typeof window === "undefined") return void 0;
|
|
1701
|
-
let pathname;
|
|
1702
|
-
try {
|
|
1703
|
-
const location = window.location;
|
|
1704
|
-
pathname = typeof location?.pathname === "string" && location.pathname ? location.pathname : new URL(String(location?.href ?? "")).pathname;
|
|
1705
|
-
} catch {
|
|
1706
|
-
return void 0;
|
|
1748
|
+
if (updates.pay_what_you_want_price !== void 0) {
|
|
1749
|
+
cart[index].pay_what_you_want_price = updates.pay_what_you_want_price;
|
|
1707
1750
|
}
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
// ../sdk/src/core/page-tracker.ts
|
|
1713
|
-
var PLATFORM_PAGE_TRACKER_FLAG = "__shoppexPageTracker";
|
|
1714
|
-
function isPlatformPageTrackerActive() {
|
|
1715
|
-
if (typeof window === "undefined") return false;
|
|
1716
|
-
try {
|
|
1717
|
-
return Boolean(window[PLATFORM_PAGE_TRACKER_FLAG]);
|
|
1718
|
-
} catch {
|
|
1719
|
-
return false;
|
|
1751
|
+
if (updates.variant_title !== void 0) {
|
|
1752
|
+
if (updates.variant_title === null) delete cart[index].variant_title;
|
|
1753
|
+
else cart[index].variant_title = updates.variant_title;
|
|
1720
1754
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
|
|
1725
|
-
var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
|
|
1726
|
-
var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
|
|
1727
|
-
var CART_CLAIM_TTL_MS = 1e4;
|
|
1728
|
-
function confirmedCartKey(storeSlug) {
|
|
1729
|
-
return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1730
|
-
}
|
|
1731
|
-
function inFlightCartKey(storeSlug) {
|
|
1732
|
-
return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1733
|
-
}
|
|
1734
|
-
function cartSequenceKey(storeSlug) {
|
|
1735
|
-
return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
|
|
1736
|
-
}
|
|
1737
|
-
function cartSnapshot(cartValue, itemCount) {
|
|
1738
|
-
return `${cartValue ?? ""}|${itemCount ?? ""}`;
|
|
1739
|
-
}
|
|
1740
|
-
function isCartSequence(value) {
|
|
1741
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1742
|
-
}
|
|
1743
|
-
var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
|
|
1744
|
-
function readCartCounter(storeSlug) {
|
|
1745
|
-
const counter = getItem(cartSequenceKey(storeSlug));
|
|
1746
|
-
if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
|
|
1747
|
-
return null;
|
|
1755
|
+
if (updates.max_quantity !== void 0) {
|
|
1756
|
+
if (updates.max_quantity === null) delete cart[index].max_quantity;
|
|
1757
|
+
else cart[index].max_quantity = updates.max_quantity;
|
|
1748
1758
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1766
|
-
function reserveCartOrder(storeSlug) {
|
|
1767
|
-
const take = () => nextCartOrder(storeSlug);
|
|
1768
|
-
const locks = cartSequenceLocks();
|
|
1769
|
-
if (!locks) return Promise.resolve(take());
|
|
1770
|
-
return locks.request(cartSequenceKey(storeSlug), take).catch(take);
|
|
1771
|
-
}
|
|
1772
|
-
function readConfirmedCart(storeSlug) {
|
|
1773
|
-
const confirmed = getItem(confirmedCartKey(storeSlug));
|
|
1774
|
-
if (!confirmed || typeof confirmed.snapshot !== "string" || !isCartSequence(confirmed.seq)) return null;
|
|
1775
|
-
return confirmed;
|
|
1759
|
+
if (updates.min_quantity !== void 0) {
|
|
1760
|
+
if (updates.min_quantity === null) delete cart[index].min_quantity;
|
|
1761
|
+
else cart[index].min_quantity = updates.min_quantity;
|
|
1762
|
+
}
|
|
1763
|
+
clampQuantityToBounds(cart[index]);
|
|
1764
|
+
const nextLineId = computeCartLineId(cart[index]);
|
|
1765
|
+
const duplicateIndex = cart.findIndex((item, itemIndex) => itemIndex !== index && item.line_id === nextLineId);
|
|
1766
|
+
if (duplicateIndex >= 0) {
|
|
1767
|
+
cart[duplicateIndex].quantity += cart[index].quantity;
|
|
1768
|
+
clampQuantityToBounds(cart[duplicateIndex]);
|
|
1769
|
+
cart.splice(index, 1);
|
|
1770
|
+
} else {
|
|
1771
|
+
cart[index].line_id = nextLineId;
|
|
1772
|
+
}
|
|
1773
|
+
if (JSON.stringify(cart) === before) return;
|
|
1774
|
+
writeCart(cart);
|
|
1776
1775
|
}
|
|
1777
|
-
function
|
|
1778
|
-
const
|
|
1779
|
-
if (!
|
|
1780
|
-
|
|
1776
|
+
function removeFromCart(lineId) {
|
|
1777
|
+
const normalizedLineId = lineId.trim();
|
|
1778
|
+
if (!normalizedLineId) {
|
|
1779
|
+
throw new CartError("line_id is required");
|
|
1781
1780
|
}
|
|
1782
|
-
|
|
1781
|
+
const cart = getCart();
|
|
1782
|
+
const filtered = cart.filter((item) => item.line_id !== normalizedLineId);
|
|
1783
|
+
writeCart(filtered);
|
|
1783
1784
|
}
|
|
1784
|
-
function
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
return expected?.snapshot === snapshot;
|
|
1785
|
+
function clearCart() {
|
|
1786
|
+
removeCartStorage("cart");
|
|
1787
|
+
removeCartStorage("meta");
|
|
1788
|
+
clearCartCoupon();
|
|
1789
|
+
clearLatestQuoteToken();
|
|
1790
1790
|
}
|
|
1791
|
-
function
|
|
1792
|
-
|
|
1793
|
-
|
|
1791
|
+
function createCartBackup() {
|
|
1792
|
+
const cart = getCart();
|
|
1793
|
+
writeCartStorage("cartBackup", cart);
|
|
1794
|
+
const cartCode = getStoredCartCode();
|
|
1795
|
+
if (cartCode) {
|
|
1796
|
+
writeCartStorage("couponBackup", cartCode);
|
|
1797
|
+
} else {
|
|
1798
|
+
removeCartStorage("couponBackup");
|
|
1799
|
+
}
|
|
1800
|
+
const metadata = getCartMetadata();
|
|
1801
|
+
if (metadata) {
|
|
1802
|
+
writeCartStorage("metaBackup", metadata);
|
|
1803
|
+
} else {
|
|
1804
|
+
const now = Date.now();
|
|
1805
|
+
writeCartStorage("metaBackup", {
|
|
1806
|
+
created_at: now,
|
|
1807
|
+
last_modified: now,
|
|
1808
|
+
version: 1,
|
|
1809
|
+
checksum: computeChecksum(cart)
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1794
1812
|
}
|
|
1795
|
-
function
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1813
|
+
function restoreCartFromBackup() {
|
|
1814
|
+
const backupRaw = readCartStorage("cartBackup");
|
|
1815
|
+
const backup = normalizeCartItems(backupRaw);
|
|
1816
|
+
const backupMeta = readCartStorage("metaBackup");
|
|
1817
|
+
const backupCoupon = readCartStorage("couponBackup");
|
|
1818
|
+
if (backup && backup.length > 0) {
|
|
1819
|
+
setCartWithMetadata(backup, backupMeta);
|
|
1820
|
+
if (backupCoupon && typeof backupCoupon === "object" && !Array.isArray(backupCoupon)) {
|
|
1821
|
+
const record = backupCoupon;
|
|
1822
|
+
const source = record.source === "COUPON" || record.source === "AFFILIATE" ? record.source : null;
|
|
1823
|
+
if (typeof record.code === "string" && source) {
|
|
1824
|
+
setCartCoupon(record.code, source);
|
|
1825
|
+
} else {
|
|
1826
|
+
clearCartCoupon();
|
|
1827
|
+
}
|
|
1828
|
+
} else {
|
|
1829
|
+
clearCartCoupon();
|
|
1803
1830
|
}
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1831
|
+
return true;
|
|
1832
|
+
}
|
|
1833
|
+
return false;
|
|
1806
1834
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1835
|
+
function mergeBaskets(items) {
|
|
1836
|
+
const cart = getCart();
|
|
1837
|
+
for (const incoming of items) {
|
|
1838
|
+
const productId = typeof incoming.product_id === "string" ? incoming.product_id.trim() : "";
|
|
1839
|
+
const variantId = typeof incoming.variant_id === "string" ? incoming.variant_id.trim() : "";
|
|
1840
|
+
if (!productId || !variantId) {
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
let normalizedQuantity;
|
|
1844
|
+
try {
|
|
1845
|
+
normalizedQuantity = normalizeQuantity(incoming.quantity);
|
|
1846
|
+
} catch {
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (normalizedQuantity < 1) {
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
const mergedLine = ensureCartLineId({
|
|
1853
|
+
...incoming,
|
|
1854
|
+
line_id: incoming.line_id ?? computeCartLineId(incoming),
|
|
1855
|
+
product_id: productId,
|
|
1856
|
+
variant_id: variantId,
|
|
1857
|
+
quantity: normalizedQuantity
|
|
1858
|
+
});
|
|
1859
|
+
const index = cart.findIndex((item) => item.line_id === mergedLine.line_id);
|
|
1860
|
+
if (index >= 0) {
|
|
1861
|
+
cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
|
|
1862
|
+
clampQuantityToBounds(cart[index]);
|
|
1863
|
+
} else {
|
|
1864
|
+
clampQuantityToBounds(mergedLine);
|
|
1865
|
+
cart.push(mergedLine);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
writeCart(cart);
|
|
1869
|
+
return cart;
|
|
1814
1870
|
}
|
|
1815
|
-
|
|
1816
|
-
const
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1871
|
+
function moveBasketItem(fromProductId, fromVariantId, toProductId, toVariantId) {
|
|
1872
|
+
const cart = getCart();
|
|
1873
|
+
const fromIndex = cart.findIndex(
|
|
1874
|
+
(item) => item.product_id === fromProductId && item.variant_id === fromVariantId
|
|
1875
|
+
);
|
|
1876
|
+
if (fromIndex < 0) {
|
|
1877
|
+
throw new CartError("Item not found in cart");
|
|
1878
|
+
}
|
|
1879
|
+
const [fromItem] = cart.splice(fromIndex, 1);
|
|
1880
|
+
const toLineId = computeCartLineId({
|
|
1881
|
+
...fromItem,
|
|
1882
|
+
product_id: toProductId,
|
|
1883
|
+
variant_id: toVariantId
|
|
1824
1884
|
});
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1885
|
+
const toIndex = cart.findIndex((item) => item.line_id === toLineId);
|
|
1886
|
+
if (toIndex >= 0) {
|
|
1887
|
+
cart[toIndex].quantity += fromItem.quantity;
|
|
1888
|
+
clampQuantityToBounds(cart[toIndex]);
|
|
1889
|
+
} else {
|
|
1890
|
+
const moved = ensureCartLineId({
|
|
1891
|
+
...fromItem,
|
|
1892
|
+
product_id: toProductId,
|
|
1893
|
+
variant_id: toVariantId,
|
|
1894
|
+
line_id: toLineId
|
|
1828
1895
|
});
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
headers: { "Content-Type": "application/json" },
|
|
1832
|
-
body: JSON.stringify(body),
|
|
1833
|
-
signal: controller?.signal
|
|
1834
|
-
}).then((response) => response.ok);
|
|
1835
|
-
return await Promise.race([delivered, timedOut]);
|
|
1836
|
-
} catch {
|
|
1837
|
-
return false;
|
|
1838
|
-
} finally {
|
|
1839
|
-
clearTimeout(timer);
|
|
1896
|
+
clampQuantityToBounds(moved);
|
|
1897
|
+
cart.push(moved);
|
|
1840
1898
|
}
|
|
1899
|
+
writeCart(cart);
|
|
1841
1900
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
|
|
1850
|
-
return;
|
|
1901
|
+
function validateCartIntegrity() {
|
|
1902
|
+
const cart = getCart();
|
|
1903
|
+
const metadata = getCartMetadata();
|
|
1904
|
+
const checksum = computeChecksum(cart);
|
|
1905
|
+
if (!metadata) {
|
|
1906
|
+
setCartWithMetadata(cart, null);
|
|
1907
|
+
return true;
|
|
1851
1908
|
}
|
|
1852
|
-
|
|
1853
|
-
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
1854
|
-
const settle = order2 ? claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
1855
|
-
const sessionId = analyticsSessionId(config.storeSlug, { touch: true });
|
|
1856
|
-
const previousSession = currentPageSession();
|
|
1857
|
-
if (previousSession && previousSession !== sessionId) reportPageLeave();
|
|
1858
|
-
markPageViewed(sessionId);
|
|
1859
|
-
if (sessionId) installEngagedClock(reportPageLeave);
|
|
1860
|
-
onAnalyticsConsent(startSessionOnConsent);
|
|
1861
|
-
const delivered = await postPing({
|
|
1862
|
-
referer: document.referrer || void 0,
|
|
1863
|
-
cart_value: cartValue,
|
|
1864
|
-
item_count: itemCount,
|
|
1865
|
-
cart_seq: order2?.seq,
|
|
1866
|
-
cart_seq_scope: order2?.scope,
|
|
1867
|
-
session_id: sessionId ?? void 0,
|
|
1868
|
-
// Time on screen of the page before this one (client-side navigation).
|
|
1869
|
-
engaged_ms: sessionId ? takeEngagedMs() || void 0 : void 0,
|
|
1870
|
-
path: resolveStorefrontPagePath(),
|
|
1871
|
-
utm: resolveUtmParameters(config.storeSlug)
|
|
1872
|
-
});
|
|
1873
|
-
settle?.(delivered);
|
|
1874
|
-
};
|
|
1875
|
-
await (reservedOrder ? enqueueCartSend(sendView) : sendView());
|
|
1909
|
+
return metadata.checksum === checksum;
|
|
1876
1910
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
});
|
|
1911
|
+
function getCartStats() {
|
|
1912
|
+
const cart = getCart();
|
|
1913
|
+
const integrityValid = validateCartIntegrity();
|
|
1914
|
+
const metadata = getCartMetadata();
|
|
1915
|
+
const backup = readCartStorage("cartBackup") ?? [];
|
|
1916
|
+
const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
|
|
1917
|
+
const totalPrice = cart.reduce((sum, item) => {
|
|
1918
|
+
const unitPrice = item.price_data?.unit_price ?? 0;
|
|
1919
|
+
return sum + roundPayableAmount(unitPrice * item.quantity);
|
|
1920
|
+
}, 0);
|
|
1921
|
+
return {
|
|
1922
|
+
item_count: cart.length,
|
|
1923
|
+
total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
|
|
1924
|
+
last_modified: metadata?.last_modified ?? 0,
|
|
1925
|
+
version: metadata?.version ?? 0,
|
|
1926
|
+
has_backup: backup.length > 0,
|
|
1927
|
+
integrity_valid: integrityValid,
|
|
1928
|
+
total_price: roundPayableAmount(totalPrice),
|
|
1929
|
+
total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
|
|
1930
|
+
};
|
|
1887
1931
|
}
|
|
1888
|
-
|
|
1932
|
+
function getCartPayload(coupon) {
|
|
1889
1933
|
const config = getConfig();
|
|
1890
|
-
const
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
cart_seq_scope: order2.scope,
|
|
1897
|
-
// A cart marks the analytics session as one that did not bounce.
|
|
1898
|
-
session_id: analyticsSessionId(config.storeSlug, { touch: false }) ?? void 0,
|
|
1899
|
-
path: resolveStorefrontPagePath()
|
|
1900
|
-
});
|
|
1901
|
-
settle(delivered);
|
|
1934
|
+
const normalizedCoupon = normalizeCouponCode(coupon) ?? getCartCoupon() ?? void 0;
|
|
1935
|
+
return {
|
|
1936
|
+
store_slug: config.storeSlug,
|
|
1937
|
+
items: getCart(),
|
|
1938
|
+
coupon: normalizedCoupon
|
|
1939
|
+
};
|
|
1902
1940
|
}
|
|
1903
|
-
function
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
void postPing({
|
|
1911
|
-
event_type: "session_start",
|
|
1912
|
-
referer: document.referrer || void 0,
|
|
1913
|
-
session_id: sessionId,
|
|
1914
|
-
path: resolveStorefrontPagePath(),
|
|
1915
|
-
utm: resolveUtmParameters(config.storeSlug)
|
|
1916
|
-
});
|
|
1941
|
+
function getAmbientAffiliateCodeForQuote() {
|
|
1942
|
+
const storedCode = getCartCoupon();
|
|
1943
|
+
const storedSource = getCartCouponSource();
|
|
1944
|
+
if (storedSource === "AFFILIATE" && storedCode) {
|
|
1945
|
+
return storedCode.trim().toLowerCase();
|
|
1946
|
+
}
|
|
1947
|
+
return getAffiliateCode();
|
|
1917
1948
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1949
|
+
var latestQuoteToken = null;
|
|
1950
|
+
var quoteSequence = 0;
|
|
1951
|
+
function getLatestQuoteToken() {
|
|
1952
|
+
return latestQuoteToken;
|
|
1953
|
+
}
|
|
1954
|
+
function clearLatestQuoteToken() {
|
|
1955
|
+
latestQuoteToken = null;
|
|
1956
|
+
quoteSequence += 1;
|
|
1957
|
+
}
|
|
1958
|
+
async function quoteCart(coupon, currency) {
|
|
1959
|
+
const sequence = quoteSequence += 1;
|
|
1960
|
+
const payload = getCartPayload(coupon);
|
|
1961
|
+
const normalizedCurrency = normalizeRequestedCurrency(currency) ?? resolveBuyerCurrency(getConfig().currency);
|
|
1962
|
+
const response = await post("/v1/storefront/cart/quote", {
|
|
1963
|
+
shop_slug: payload.store_slug,
|
|
1964
|
+
cart: payload.items,
|
|
1965
|
+
coupon: payload.coupon,
|
|
1966
|
+
affiliate_code: getAmbientAffiliateCodeForQuote() ?? void 0,
|
|
1967
|
+
currency: normalizedCurrency ?? void 0
|
|
1968
|
+
});
|
|
1969
|
+
if (sequence !== quoteSequence) return response;
|
|
1970
|
+
if (response.success) {
|
|
1971
|
+
latestQuoteToken = typeof response.data?.quote_token === "string" ? response.data.quote_token : null;
|
|
1933
1972
|
}
|
|
1973
|
+
return response;
|
|
1934
1974
|
}
|
|
1935
1975
|
|
|
1936
1976
|
// ../sdk/src/storefront-storage-sdk.ts
|
|
@@ -1976,7 +2016,8 @@ var storefrontStorageSdk = {
|
|
|
1976
2016
|
trackAffiliateEvent,
|
|
1977
2017
|
// Analytics
|
|
1978
2018
|
trackPageView,
|
|
1979
|
-
trackCartActivity
|
|
2019
|
+
trackCartActivity,
|
|
2020
|
+
trackProductView
|
|
1980
2021
|
};
|
|
1981
2022
|
|
|
1982
2023
|
// ../../node_modules/.bun/openapi-fetch@0.17.0/node_modules/openapi-fetch/dist/index.mjs
|
|
@@ -4574,19 +4615,29 @@ function createTicket(payload) {
|
|
|
4574
4615
|
body: {
|
|
4575
4616
|
...payload.title !== void 0 ? { title: payload.title } : {},
|
|
4576
4617
|
message: payload.message,
|
|
4577
|
-
...payload.invoice_id !== void 0 ? { invoice_id: payload.invoice_id } : {}
|
|
4618
|
+
...payload.invoice_id !== void 0 ? { invoice_id: payload.invoice_id } : {},
|
|
4619
|
+
...payload.attachment_ids?.length ? { attachment_ids: payload.attachment_ids } : {}
|
|
4578
4620
|
}
|
|
4579
4621
|
});
|
|
4580
4622
|
}
|
|
4581
4623
|
function ticket(uniqid) {
|
|
4582
4624
|
return requestCustomer(`/tickets/${encodeURIComponent(uniqid)}`);
|
|
4583
4625
|
}
|
|
4584
|
-
function replyToTicket(uniqid, message) {
|
|
4626
|
+
function replyToTicket(uniqid, message, attachmentIds = []) {
|
|
4585
4627
|
return requestCustomer(`/tickets/${encodeURIComponent(uniqid)}/reply`, {
|
|
4586
4628
|
method: "POST",
|
|
4587
|
-
body: { message }
|
|
4629
|
+
body: attachmentIds.length > 0 ? { message, attachment_ids: attachmentIds } : { message }
|
|
4588
4630
|
});
|
|
4589
4631
|
}
|
|
4632
|
+
function uploadTicketImage(file, ticket2) {
|
|
4633
|
+
const form = new FormData();
|
|
4634
|
+
form.append("file", file);
|
|
4635
|
+
if (ticket2) form.append("ticket", ticket2);
|
|
4636
|
+
return requestCustomer("/ticket-attachments", { method: "POST", formData: form });
|
|
4637
|
+
}
|
|
4638
|
+
function discardTicketImage(id) {
|
|
4639
|
+
return requestCustomer(`/ticket-attachments/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
4640
|
+
}
|
|
4590
4641
|
function updateProfile(patch) {
|
|
4591
4642
|
return requestCustomer("/profile", {
|
|
4592
4643
|
method: "POST",
|
|
@@ -4712,6 +4763,8 @@ var shoppex = {
|
|
|
4712
4763
|
createTicket,
|
|
4713
4764
|
ticket,
|
|
4714
4765
|
replyToTicket,
|
|
4766
|
+
uploadTicketImage,
|
|
4767
|
+
discardTicketImage,
|
|
4715
4768
|
updateProfile,
|
|
4716
4769
|
updateAvatar,
|
|
4717
4770
|
removeAvatar,
|
|
@@ -4769,6 +4822,7 @@ export {
|
|
|
4769
4822
|
createTicket,
|
|
4770
4823
|
dashboard,
|
|
4771
4824
|
src_default as default,
|
|
4825
|
+
discardTicketImage,
|
|
4772
4826
|
emailPreferences,
|
|
4773
4827
|
enrollAsReseller,
|
|
4774
4828
|
ensureCartLineId,
|
|
@@ -4836,11 +4890,13 @@ export {
|
|
|
4836
4890
|
touchStorefrontPresence,
|
|
4837
4891
|
trackCartActivity,
|
|
4838
4892
|
trackPageView,
|
|
4893
|
+
trackProductView,
|
|
4839
4894
|
undoSubscriptionCancel,
|
|
4840
4895
|
updateAvatar,
|
|
4841
4896
|
updateEmailPreferences,
|
|
4842
4897
|
updateNotificationPreferences,
|
|
4843
4898
|
updateProfile,
|
|
4899
|
+
uploadTicketImage,
|
|
4844
4900
|
validateStorefrontCustomFieldValue,
|
|
4845
4901
|
verifyOtp,
|
|
4846
4902
|
warranties
|