@shoppexio/storefront 1.0.103 → 1.0.105
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 +10 -0
- package/dist/{chunk-ESI3HWQF.js → chunk-NTLZJQ6I.js} +58 -3
- package/dist/{chunk-ESI3HWQF.js.map → chunk-NTLZJQ6I.js.map} +1 -1
- package/dist/customer.cjs +56 -42
- package/dist/customer.cjs.map +1 -1
- package/dist/customer.d.cts +5 -13
- package/dist/customer.d.ts +5 -13
- package/dist/customer.js +2 -41
- package/dist/customer.js.map +1 -1
- package/dist/index.cjs +1122 -999
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +1063 -997
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,7 @@ __export(src_exports, {
|
|
|
48
48
|
createTicket: () => createTicket,
|
|
49
49
|
dashboard: () => dashboard,
|
|
50
50
|
default: () => src_default,
|
|
51
|
+
discardTicketImage: () => discardTicketImage,
|
|
51
52
|
emailPreferences: () => emailPreferences,
|
|
52
53
|
enrollAsReseller: () => enrollAsReseller,
|
|
53
54
|
ensureCartLineId: () => ensureCartLineId,
|
|
@@ -79,6 +80,7 @@ __export(src_exports, {
|
|
|
79
80
|
normalizeStorefrontCustomFields: () => normalizeStorefrontCustomFields,
|
|
80
81
|
notificationPreferences: () => notificationPreferences,
|
|
81
82
|
order: () => order,
|
|
83
|
+
orderReview: () => orderReview,
|
|
82
84
|
orders: () => orders,
|
|
83
85
|
pauseSubscription: () => pauseSubscription,
|
|
84
86
|
productMatchesSearchQuery: () => productMatchesSearchQuery,
|
|
@@ -115,11 +117,13 @@ __export(src_exports, {
|
|
|
115
117
|
touchStorefrontPresence: () => touchStorefrontPresence,
|
|
116
118
|
trackCartActivity: () => trackCartActivity,
|
|
117
119
|
trackPageView: () => trackPageView,
|
|
120
|
+
trackProductView: () => trackProductView,
|
|
118
121
|
undoSubscriptionCancel: () => undoSubscriptionCancel,
|
|
119
122
|
updateAvatar: () => updateAvatar,
|
|
120
123
|
updateEmailPreferences: () => updateEmailPreferences,
|
|
121
124
|
updateNotificationPreferences: () => updateNotificationPreferences,
|
|
122
125
|
updateProfile: () => updateProfile,
|
|
126
|
+
uploadTicketImage: () => uploadTicketImage,
|
|
123
127
|
validateStorefrontCustomFieldValue: () => validateStorefrontCustomFieldValue,
|
|
124
128
|
verifyOtp: () => verifyOtp,
|
|
125
129
|
warranties: () => warranties
|
|
@@ -962,1101 +966,1141 @@ async function captureAffiliateFromUrl(param = "ref") {
|
|
|
962
966
|
return holdsOtherCode ? held.code : code;
|
|
963
967
|
}
|
|
964
968
|
|
|
965
|
-
// ../
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
969
|
+
// ../contracts/src/storefront-consent-choice.ts
|
|
970
|
+
var COOKIE_CONSENT_VERSION = 1;
|
|
971
|
+
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
972
|
+
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
973
|
+
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
974
|
+
function parseCookieConsentValue(value) {
|
|
975
|
+
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
976
|
+
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// ../sdk/src/core/analytics-session.ts
|
|
980
|
+
var ANALYTICS_SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
981
|
+
var ANALYTICS_VISITS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
982
|
+
var ANALYTICS_VISITS_MAX = 20;
|
|
983
|
+
var MAX_ENGAGED_MS_PER_PAGE = 30 * 60 * 1e3;
|
|
984
|
+
var UTM_STORAGE_PREFIX = "shoppex:utm:";
|
|
985
|
+
var analyticsSessionKey = (storeSlug) => `analytics_session_${storeSlug}`;
|
|
986
|
+
var analyticsVisitsKey = (storeSlug) => `analytics_visits_${storeSlug}`;
|
|
987
|
+
function pageLanding() {
|
|
988
|
+
let r = "";
|
|
989
|
+
try {
|
|
990
|
+
r = document.referrer ? new URL(document.referrer).hostname : "";
|
|
991
|
+
} catch {
|
|
971
992
|
}
|
|
972
|
-
|
|
993
|
+
const params = new URLSearchParams(window.location?.search ?? "");
|
|
994
|
+
const u = ["source", "medium", "campaign"].map((key) => params.get(`utm_${key}`)?.trim().toLowerCase() ?? "").join("|");
|
|
995
|
+
return { r: r === window.location?.hostname ? "" : r, u: u === "||" ? "" : u };
|
|
973
996
|
}
|
|
974
|
-
function
|
|
975
|
-
if (
|
|
976
|
-
|
|
997
|
+
function readAnalyticsConsent() {
|
|
998
|
+
if (typeof document === "undefined" || typeof navigator === "undefined") return "unknown";
|
|
999
|
+
const privacy = navigator;
|
|
1000
|
+
if (privacy.globalPrivacyControl === true || privacy.doNotTrack === "1") return "declined";
|
|
1001
|
+
const prefix = `${COOKIE_CONSENT_COOKIE}=`;
|
|
1002
|
+
let raw;
|
|
1003
|
+
try {
|
|
1004
|
+
raw = document.cookie.split(/;\s*/).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
1005
|
+
} catch {
|
|
1006
|
+
return "unknown";
|
|
1007
|
+
}
|
|
1008
|
+
const choice = parseCookieConsentValue(raw ?? null);
|
|
1009
|
+
if (!choice) return "unknown";
|
|
1010
|
+
return choice.analytics ? "granted" : "declined";
|
|
977
1011
|
}
|
|
978
|
-
function
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
return
|
|
1012
|
+
function readVisits(storeSlug, now) {
|
|
1013
|
+
const stored = getItem(analyticsVisitsKey(storeSlug));
|
|
1014
|
+
if (!Array.isArray(stored)) return [];
|
|
1015
|
+
return stored.filter((visit2) => typeof visit2?.id === "string" && typeof visit2.at === "number" && now - visit2.at < ANALYTICS_VISITS_WINDOW_MS);
|
|
982
1016
|
}
|
|
983
|
-
function
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
variant_id: input2.variant_id,
|
|
987
|
-
price_variant_id: input2.price_variant_id ?? null,
|
|
988
|
-
addons: normalizeAddons(input2.addons),
|
|
989
|
-
custom_fields: normalizeCustomFields(input2.custom_fields),
|
|
990
|
-
unit_price: typeof input2.price_data?.unit_price === "number" && Number.isFinite(input2.price_data.unit_price) ? input2.price_data.unit_price : null,
|
|
991
|
-
pay_what_you_want_price: typeof input2.pay_what_you_want_price === "number" && Number.isFinite(input2.pay_what_you_want_price) ? input2.pay_what_you_want_price : null
|
|
992
|
-
};
|
|
993
|
-
return hashString(JSON.stringify(payload));
|
|
1017
|
+
function forgetAnalyticsData(storeSlug) {
|
|
1018
|
+
removeItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1019
|
+
removeItem(analyticsVisitsKey(storeSlug));
|
|
994
1020
|
}
|
|
995
|
-
function
|
|
996
|
-
|
|
997
|
-
|
|
1021
|
+
function analyticsSessionId(storeSlug, options, now = Date.now()) {
|
|
1022
|
+
removeItem(`presence_connection_${storeSlug}`);
|
|
1023
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
1024
|
+
forgetAnalyticsData(storeSlug);
|
|
1025
|
+
return null;
|
|
998
1026
|
}
|
|
999
|
-
|
|
1027
|
+
const stored = getItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1028
|
+
const live = stored && typeof stored.id === "string" && typeof stored.at === "number" && now - stored.at < ANALYTICS_SESSION_IDLE_MS ? stored.id : null;
|
|
1029
|
+
if (!options.touch) return live;
|
|
1030
|
+
const landing = pageLanding();
|
|
1031
|
+
const newLanding = landing.r && landing.r !== stored?.r || landing.u && landing.u !== stored?.u;
|
|
1032
|
+
if (live && !newLanding) {
|
|
1033
|
+
setItem(analyticsSessionKey(storeSlug), { ...stored, id: live, at: now }, "sessionStorage");
|
|
1034
|
+
return live;
|
|
1035
|
+
}
|
|
1036
|
+
if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") return null;
|
|
1037
|
+
if (landing.r) {
|
|
1038
|
+
try {
|
|
1039
|
+
globalThis.localStorage.removeItem(storefrontStorageKey(`${UTM_STORAGE_PREFIX}${storeSlug}`));
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const id = crypto.randomUUID();
|
|
1044
|
+
setItem(analyticsSessionKey(storeSlug), { id, at: now, ...landing }, "sessionStorage");
|
|
1045
|
+
setItem(
|
|
1046
|
+
analyticsVisitsKey(storeSlug),
|
|
1047
|
+
[{ id, at: now }, ...readVisits(storeSlug, now)].slice(0, ANALYTICS_VISITS_MAX)
|
|
1048
|
+
);
|
|
1049
|
+
return id;
|
|
1000
1050
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const sign = value < 0 ? -1 : 1;
|
|
1011
|
-
const shifted = shiftDecimal(Math.abs(value), PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1012
|
-
const rounded = Math.round(shifted);
|
|
1013
|
-
if (rounded === 0) return 0;
|
|
1014
|
-
return sign * shiftDecimal(rounded, -PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1051
|
+
function checkoutAttribution(storeSlug, now = Date.now()) {
|
|
1052
|
+
const consent = readAnalyticsConsent();
|
|
1053
|
+
if (consent === "declined") {
|
|
1054
|
+
forgetAnalyticsData(storeSlug);
|
|
1055
|
+
return { declined: true };
|
|
1056
|
+
}
|
|
1057
|
+
if (consent === "unknown") return void 0;
|
|
1058
|
+
const sessionIds = readVisits(storeSlug, now).map((visit2) => visit2.id);
|
|
1059
|
+
return sessionIds.length > 0 ? { session_ids: sessionIds } : void 0;
|
|
1015
1060
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1061
|
+
var engagedMs = 0;
|
|
1062
|
+
var visibleSince = null;
|
|
1063
|
+
var clockInstalled = false;
|
|
1064
|
+
var pageViewed = false;
|
|
1065
|
+
var pageSession = null;
|
|
1066
|
+
function markPageViewed(sessionId) {
|
|
1067
|
+
pageViewed = true;
|
|
1068
|
+
pageSession = sessionId;
|
|
1019
1069
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
var STORAGE_KEYS = {
|
|
1023
|
-
cart: "cart",
|
|
1024
|
-
cartBackup: "cart_backup",
|
|
1025
|
-
meta: "cart_meta",
|
|
1026
|
-
metaBackup: "cart_backup_meta",
|
|
1027
|
-
coupon: "cart_coupon",
|
|
1028
|
-
couponBackup: "cart_coupon_backup"
|
|
1029
|
-
};
|
|
1030
|
-
function getStorageKey(type) {
|
|
1031
|
-
return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
|
|
1070
|
+
function currentPageSession() {
|
|
1071
|
+
return pageSession;
|
|
1032
1072
|
}
|
|
1033
|
-
function
|
|
1034
|
-
return
|
|
1073
|
+
function isPageViewed() {
|
|
1074
|
+
return pageViewed;
|
|
1035
1075
|
}
|
|
1036
|
-
function
|
|
1037
|
-
|
|
1076
|
+
function settleClock(now) {
|
|
1077
|
+
if (visibleSince !== null) {
|
|
1078
|
+
engagedMs += now - visibleSince;
|
|
1079
|
+
visibleSince = document.visibilityState === "visible" ? now : null;
|
|
1080
|
+
}
|
|
1038
1081
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1082
|
+
var consentListenerInstalled = false;
|
|
1083
|
+
function onAnalyticsConsent(onYes) {
|
|
1084
|
+
if (consentListenerInstalled || typeof window === "undefined") return;
|
|
1085
|
+
consentListenerInstalled = true;
|
|
1086
|
+
window.addEventListener("shoppex:consent", () => {
|
|
1087
|
+
if (readAnalyticsConsent() === "granted") onYes();
|
|
1088
|
+
});
|
|
1041
1089
|
}
|
|
1042
|
-
function
|
|
1043
|
-
|
|
1090
|
+
function installEngagedClock(onHidden) {
|
|
1091
|
+
if (clockInstalled || typeof document === "undefined") return;
|
|
1092
|
+
clockInstalled = true;
|
|
1093
|
+
visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
1094
|
+
document.addEventListener("visibilitychange", () => {
|
|
1095
|
+
const now = Date.now();
|
|
1096
|
+
if (document.visibilityState === "visible") {
|
|
1097
|
+
visibleSince ?? (visibleSince = now);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
settleClock(now);
|
|
1101
|
+
visibleSince = null;
|
|
1102
|
+
onHidden();
|
|
1103
|
+
});
|
|
1104
|
+
window.addEventListener("pagehide", () => {
|
|
1105
|
+
settleClock(Date.now());
|
|
1106
|
+
visibleSince = null;
|
|
1107
|
+
onHidden();
|
|
1108
|
+
});
|
|
1109
|
+
window.addEventListener("pageshow", (event) => {
|
|
1110
|
+
if (event.persisted && document.visibilityState === "visible") visibleSince = Date.now();
|
|
1111
|
+
});
|
|
1044
1112
|
}
|
|
1045
|
-
function
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1113
|
+
function takeEngagedMs(now = Date.now()) {
|
|
1114
|
+
settleClock(now);
|
|
1115
|
+
const taken = Math.min(Math.max(0, Math.round(engagedMs)), MAX_ENGAGED_MS_PER_PAGE);
|
|
1116
|
+
engagedMs = 0;
|
|
1117
|
+
return taken;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// ../sdk/src/core/attribution.ts
|
|
1121
|
+
var TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1122
|
+
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
1123
|
+
var MAX_VALUE_LENGTH = 255;
|
|
1124
|
+
function readFromUrl(search) {
|
|
1125
|
+
let params;
|
|
1126
|
+
try {
|
|
1127
|
+
params = new URLSearchParams(search);
|
|
1128
|
+
} catch {
|
|
1129
|
+
return {};
|
|
1050
1130
|
}
|
|
1051
|
-
|
|
1131
|
+
const utm = {};
|
|
1132
|
+
for (const key of UTM_KEYS) {
|
|
1133
|
+
const value = params.get(key)?.trim();
|
|
1134
|
+
if (value) {
|
|
1135
|
+
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return utm;
|
|
1052
1139
|
}
|
|
1053
|
-
function
|
|
1054
|
-
return
|
|
1140
|
+
function storageKey(storeSlug) {
|
|
1141
|
+
return `${UTM_STORAGE_PREFIX}${storeSlug}`;
|
|
1055
1142
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
|
|
1143
|
+
function readStored(storeSlug) {
|
|
1144
|
+
try {
|
|
1145
|
+
const raw = window.localStorage.getItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1146
|
+
if (!raw) return {};
|
|
1147
|
+
const parsed = JSON.parse(raw);
|
|
1148
|
+
if (!parsed || typeof parsed.at !== "number" || typeof parsed.utm !== "object") return {};
|
|
1149
|
+
if (Date.now() - parsed.at > TTL_MS) {
|
|
1150
|
+
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1151
|
+
return {};
|
|
1152
|
+
}
|
|
1153
|
+
const utm = {};
|
|
1154
|
+
for (const key of UTM_KEYS) {
|
|
1155
|
+
const value = parsed.utm?.[key];
|
|
1156
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1157
|
+
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return utm;
|
|
1161
|
+
} catch {
|
|
1162
|
+
return {};
|
|
1059
1163
|
}
|
|
1060
|
-
return Math.floor(value);
|
|
1061
1164
|
}
|
|
1062
|
-
function
|
|
1063
|
-
|
|
1064
|
-
|
|
1165
|
+
function writeStored(storeSlug, utm) {
|
|
1166
|
+
try {
|
|
1167
|
+
const payload = { utm, at: Date.now() };
|
|
1168
|
+
window.localStorage.setItem(storefrontStorageKey(storageKey(storeSlug)), JSON.stringify(payload));
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1065
1171
|
}
|
|
1066
|
-
function
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return null;
|
|
1172
|
+
function resolveUtmParameters(storeSlug) {
|
|
1173
|
+
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
1174
|
+
return void 0;
|
|
1070
1175
|
}
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
function normalizeCartItems(value) {
|
|
1077
|
-
if (!Array.isArray(value)) return [];
|
|
1078
|
-
const normalized = [];
|
|
1079
|
-
for (const entry of value) {
|
|
1080
|
-
if (!entry || typeof entry !== "object") continue;
|
|
1081
|
-
const record2 = entry;
|
|
1082
|
-
const productId = typeof record2.product_id === "string" ? record2.product_id.trim() : "";
|
|
1083
|
-
const variantId = typeof record2.variant_id === "string" ? record2.variant_id.trim() : "";
|
|
1084
|
-
const quantity = Number(record2.quantity);
|
|
1085
|
-
if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
const item = {
|
|
1089
|
-
line_id: "",
|
|
1090
|
-
product_id: productId,
|
|
1091
|
-
variant_id: variantId,
|
|
1092
|
-
quantity: Math.floor(quantity)
|
|
1093
|
-
};
|
|
1094
|
-
if (typeof record2.price_variant_id === "string") {
|
|
1095
|
-
item.price_variant_id = record2.price_variant_id;
|
|
1096
|
-
}
|
|
1097
|
-
if (record2.price_data && typeof record2.price_data === "object") {
|
|
1098
|
-
const priceData = record2.price_data;
|
|
1099
|
-
if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
|
|
1100
|
-
item.price_data = { unit_price: priceData.unit_price };
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
if (typeof record2.pay_what_you_want_price === "number" && Number.isFinite(record2.pay_what_you_want_price)) {
|
|
1104
|
-
item.pay_what_you_want_price = record2.pay_what_you_want_price;
|
|
1105
|
-
}
|
|
1106
|
-
if (Array.isArray(record2.addons)) {
|
|
1107
|
-
item.addons = record2.addons;
|
|
1108
|
-
}
|
|
1109
|
-
if (record2.custom_fields && typeof record2.custom_fields === "object" && !Array.isArray(record2.custom_fields)) {
|
|
1110
|
-
item.custom_fields = record2.custom_fields;
|
|
1111
|
-
}
|
|
1112
|
-
if (typeof record2.title === "string") {
|
|
1113
|
-
item.title = record2.title;
|
|
1114
|
-
}
|
|
1115
|
-
if (typeof record2.variant_title === "string") {
|
|
1116
|
-
item.variant_title = record2.variant_title;
|
|
1117
|
-
}
|
|
1118
|
-
if (typeof record2.image_url === "string") {
|
|
1119
|
-
item.image_url = record2.image_url;
|
|
1120
|
-
}
|
|
1121
|
-
if (Array.isArray(record2.addon_labels)) {
|
|
1122
|
-
item.addon_labels = record2.addon_labels.filter((label) => typeof label === "string");
|
|
1123
|
-
}
|
|
1124
|
-
if (typeof record2.max_quantity === "number" && Number.isFinite(record2.max_quantity)) {
|
|
1125
|
-
item.max_quantity = record2.max_quantity;
|
|
1126
|
-
}
|
|
1127
|
-
if (typeof record2.min_quantity === "number" && Number.isFinite(record2.min_quantity)) {
|
|
1128
|
-
item.min_quantity = record2.min_quantity;
|
|
1176
|
+
const fromUrl = readFromUrl(window.location.search);
|
|
1177
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
1178
|
+
try {
|
|
1179
|
+
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1180
|
+
} catch {
|
|
1129
1181
|
}
|
|
1130
|
-
|
|
1131
|
-
const storedLineId = typeof record2.line_id === "string" ? record2.line_id.trim() : "";
|
|
1132
|
-
item.line_id = storedLineId || computeCartLineId(item);
|
|
1133
|
-
normalized.push(ensureCartLineId(item));
|
|
1182
|
+
return Object.keys(fromUrl).length > 0 ? fromUrl : void 0;
|
|
1134
1183
|
}
|
|
1135
|
-
|
|
1184
|
+
if (Object.keys(fromUrl).length > 0) {
|
|
1185
|
+
writeStored(storeSlug, fromUrl);
|
|
1186
|
+
return fromUrl;
|
|
1187
|
+
}
|
|
1188
|
+
const stored = readStored(storeSlug);
|
|
1189
|
+
return Object.keys(stored).length > 0 ? stored : void 0;
|
|
1136
1190
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1191
|
+
|
|
1192
|
+
// ../sdk/src/core/page-path.ts
|
|
1193
|
+
var MAX_PATH_LENGTH = 256;
|
|
1194
|
+
function resolveStorefrontPagePath() {
|
|
1195
|
+
if (typeof window === "undefined") return void 0;
|
|
1196
|
+
let pathname;
|
|
1197
|
+
try {
|
|
1198
|
+
const location = window.location;
|
|
1199
|
+
pathname = typeof location?.pathname === "string" && location.pathname ? location.pathname : new URL(String(location?.href ?? "")).pathname;
|
|
1200
|
+
} catch {
|
|
1201
|
+
return void 0;
|
|
1202
|
+
}
|
|
1203
|
+
if (!pathname || !pathname.startsWith("/")) return void 0;
|
|
1204
|
+
return pathname.slice(0, MAX_PATH_LENGTH);
|
|
1139
1205
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1206
|
+
|
|
1207
|
+
// ../sdk/src/core/page-tracker.ts
|
|
1208
|
+
var PLATFORM_PAGE_TRACKER_FLAG = "__shoppexPageTracker";
|
|
1209
|
+
function isPlatformPageTrackerActive() {
|
|
1210
|
+
if (typeof window === "undefined") return false;
|
|
1211
|
+
try {
|
|
1212
|
+
return Boolean(window[PLATFORM_PAGE_TRACKER_FLAG]);
|
|
1213
|
+
} catch {
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1142
1216
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1217
|
+
|
|
1218
|
+
// ../sdk/src/modules/analytics.ts
|
|
1219
|
+
var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
|
|
1220
|
+
var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
|
|
1221
|
+
var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
|
|
1222
|
+
var CART_CLAIM_TTL_MS = 1e4;
|
|
1223
|
+
function confirmedCartKey(storeSlug) {
|
|
1224
|
+
return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1145
1225
|
}
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1226
|
+
function inFlightCartKey(storeSlug) {
|
|
1227
|
+
return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1228
|
+
}
|
|
1229
|
+
function cartSequenceKey(storeSlug) {
|
|
1230
|
+
return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
|
|
1231
|
+
}
|
|
1232
|
+
function cartSnapshot(cartValue, itemCount) {
|
|
1233
|
+
return `${cartValue ?? ""}|${itemCount ?? ""}`;
|
|
1234
|
+
}
|
|
1235
|
+
function isCartSequence(value) {
|
|
1236
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1237
|
+
}
|
|
1238
|
+
var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
|
|
1239
|
+
function readCartCounter(storeSlug) {
|
|
1240
|
+
const counter = getItem(cartSequenceKey(storeSlug));
|
|
1241
|
+
if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
|
|
1150
1242
|
return null;
|
|
1151
1243
|
}
|
|
1152
|
-
|
|
1153
|
-
return normalizedCoupon;
|
|
1244
|
+
return counter;
|
|
1154
1245
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1246
|
+
var lastCartOrder = null;
|
|
1247
|
+
function nextCartOrder(storeSlug) {
|
|
1248
|
+
const stored = readCartCounter(storeSlug);
|
|
1249
|
+
const scope = stored?.scope ?? lastCartOrder?.scope ?? createClaimToken();
|
|
1250
|
+
const floor = Math.max(stored?.seq ?? 0, lastCartOrder?.scope === scope ? lastCartOrder.seq : 0);
|
|
1251
|
+
const order2 = { scope, seq: Math.max(Date.now(), floor + 1) };
|
|
1252
|
+
lastCartOrder = order2;
|
|
1253
|
+
setItem(cartSequenceKey(storeSlug), order2);
|
|
1254
|
+
return order2;
|
|
1157
1255
|
}
|
|
1158
|
-
function
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const previous = getCartMetadata();
|
|
1163
|
-
const nextMeta = {
|
|
1164
|
-
created_at: previous?.created_at ?? now,
|
|
1165
|
-
last_modified: now,
|
|
1166
|
-
version: (previous?.version ?? 0) + 1,
|
|
1167
|
-
checksum: computeChecksum(normalizedCart)
|
|
1168
|
-
};
|
|
1169
|
-
writeCartStorage("meta", nextMeta);
|
|
1170
|
-
if (normalizedCart.length === 0) {
|
|
1171
|
-
clearCartCoupon();
|
|
1172
|
-
}
|
|
1256
|
+
function cartSequenceLocks() {
|
|
1257
|
+
if (typeof navigator === "undefined") return null;
|
|
1258
|
+
const locks = navigator.locks;
|
|
1259
|
+
return locks && typeof locks.request === "function" ? locks : null;
|
|
1173
1260
|
}
|
|
1174
|
-
function
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
const nextMeta = {
|
|
1180
|
-
created_at: base?.created_at ?? now,
|
|
1181
|
-
last_modified: now,
|
|
1182
|
-
version: base?.version ?? 1,
|
|
1183
|
-
checksum: computeChecksum(normalizedCart)
|
|
1184
|
-
};
|
|
1185
|
-
writeCartStorage("meta", nextMeta);
|
|
1261
|
+
function reserveCartOrder(storeSlug) {
|
|
1262
|
+
const take = () => nextCartOrder(storeSlug);
|
|
1263
|
+
const locks = cartSequenceLocks();
|
|
1264
|
+
if (!locks) return Promise.resolve(take());
|
|
1265
|
+
return locks.request(cartSequenceKey(storeSlug), take).catch(take);
|
|
1186
1266
|
}
|
|
1187
|
-
function
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1267
|
+
function readConfirmedCart(storeSlug) {
|
|
1268
|
+
const confirmed = getItem(confirmedCartKey(storeSlug));
|
|
1269
|
+
if (!confirmed || typeof confirmed.snapshot !== "string" || !isCartSequence(confirmed.seq)) return null;
|
|
1270
|
+
return confirmed;
|
|
1190
1271
|
}
|
|
1191
|
-
function
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
if (matches.length === 0) {
|
|
1196
|
-
throw new CartError(`No cart line found for ${productId}/${variantId}`);
|
|
1272
|
+
function readInFlightCart(storeSlug) {
|
|
1273
|
+
const claim2 = getItem(inFlightCartKey(storeSlug));
|
|
1274
|
+
if (!claim2 || typeof claim2.snapshot !== "string" || !isCartSequence(claim2.seq) || typeof claim2.token !== "string" || typeof claim2.at !== "number") {
|
|
1275
|
+
return null;
|
|
1197
1276
|
}
|
|
1198
|
-
return
|
|
1277
|
+
return claim2;
|
|
1199
1278
|
}
|
|
1200
|
-
function
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1279
|
+
function isCartReportedOrInFlight(storeSlug, snapshot) {
|
|
1280
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
1281
|
+
const claim2 = readInFlightCart(storeSlug);
|
|
1282
|
+
const liveClaim = claim2 && Date.now() - claim2.at < CART_CLAIM_TTL_MS ? claim2 : null;
|
|
1283
|
+
const expected = liveClaim && (!confirmed || liveClaim.seq > confirmed.seq) ? liveClaim : confirmed;
|
|
1284
|
+
return expected?.snapshot === snapshot;
|
|
1203
1285
|
}
|
|
1204
|
-
function
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
});
|
|
1222
|
-
const existingIndex = cart.findIndex((item) => item.line_id === lineId);
|
|
1223
|
-
const quantityBeforeAdd = existingIndex >= 0 ? cart[existingIndex].quantity : 0;
|
|
1224
|
-
if (existingIndex >= 0) {
|
|
1225
|
-
cart[existingIndex].quantity += normalizedQuantity;
|
|
1226
|
-
if (options?.addons) {
|
|
1227
|
-
cart[existingIndex].addons = options.addons;
|
|
1228
|
-
}
|
|
1229
|
-
if (options?.custom_fields) {
|
|
1230
|
-
cart[existingIndex].custom_fields = options.custom_fields;
|
|
1231
|
-
}
|
|
1232
|
-
if (options?.price_variant_id) {
|
|
1233
|
-
cart[existingIndex].price_variant_id = options.price_variant_id;
|
|
1234
|
-
}
|
|
1235
|
-
if (options?.price_data) {
|
|
1236
|
-
cart[existingIndex].price_data = options.price_data;
|
|
1286
|
+
function createClaimToken() {
|
|
1287
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
1288
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
1289
|
+
}
|
|
1290
|
+
function claimCartReport(storeSlug, seq, cartValue, itemCount) {
|
|
1291
|
+
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
1292
|
+
const token = createClaimToken();
|
|
1293
|
+
setItem(inFlightCartKey(storeSlug), { snapshot, seq, token, at: Date.now() });
|
|
1294
|
+
return (delivered) => {
|
|
1295
|
+
if (delivered) {
|
|
1296
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
1297
|
+
if (!confirmed || seq > confirmed.seq) setItem(confirmedCartKey(storeSlug), { snapshot, seq });
|
|
1237
1298
|
}
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
applyDisplaySnapshot(cart[existingIndex], options);
|
|
1242
|
-
clampQuantityToBounds(cart[existingIndex]);
|
|
1243
|
-
} else {
|
|
1244
|
-
const pushed = {
|
|
1245
|
-
line_id: lineId,
|
|
1246
|
-
product_id: productId,
|
|
1247
|
-
variant_id: variantId,
|
|
1248
|
-
quantity: normalizedQuantity,
|
|
1249
|
-
addons: options?.addons,
|
|
1250
|
-
custom_fields: options?.custom_fields,
|
|
1251
|
-
price_variant_id: options?.price_variant_id,
|
|
1252
|
-
price_data: options?.price_data,
|
|
1253
|
-
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1254
|
-
title: options?.title,
|
|
1255
|
-
variant_title: options?.variant_title,
|
|
1256
|
-
image_url: options?.image_url,
|
|
1257
|
-
addon_labels: options?.addon_labels,
|
|
1258
|
-
max_quantity: options?.max_quantity,
|
|
1259
|
-
min_quantity: options?.min_quantity
|
|
1260
|
-
};
|
|
1261
|
-
clampQuantityToBounds(pushed);
|
|
1262
|
-
cart.push(pushed);
|
|
1263
|
-
}
|
|
1264
|
-
const intendedQuantity = (existingIndex >= 0 ? cart[existingIndex] : cart[cart.length - 1]).quantity;
|
|
1265
|
-
writeCart(cart);
|
|
1266
|
-
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1267
|
-
if (intendedQuantity > quantityBeforeAdd && persistedLine?.quantity === intendedQuantity) {
|
|
1268
|
-
const cartAffiliateCode = getCartCouponSource() === "AFFILIATE" ? getCartCoupon() : null;
|
|
1269
|
-
void trackAffiliateEvent("add_to_cart", cartAffiliateCode ? { code: cartAffiliateCode } : void 0);
|
|
1270
|
-
}
|
|
1299
|
+
if (readInFlightCart(storeSlug)?.token === token) removeItem(inFlightCartKey(storeSlug));
|
|
1300
|
+
};
|
|
1271
1301
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1302
|
+
var cartSendTail = Promise.resolve();
|
|
1303
|
+
var latestCartRequest = 0;
|
|
1304
|
+
function enqueueCartSend(send) {
|
|
1305
|
+
const request2 = ++latestCartRequest;
|
|
1306
|
+
const run = () => send(() => request2 === latestCartRequest);
|
|
1307
|
+
cartSendTail = cartSendTail.then(run, run);
|
|
1308
|
+
return cartSendTail;
|
|
1309
|
+
}
|
|
1310
|
+
async function postPing(body) {
|
|
1311
|
+
const config2 = getConfig();
|
|
1312
|
+
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1313
|
+
let timer;
|
|
1314
|
+
const timedOut = new Promise((resolve) => {
|
|
1315
|
+
timer = setTimeout(() => {
|
|
1316
|
+
controller?.abort();
|
|
1317
|
+
resolve(false);
|
|
1318
|
+
}, CART_CLAIM_TTL_MS);
|
|
1319
|
+
});
|
|
1320
|
+
try {
|
|
1321
|
+
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
|
|
1322
|
+
storeSlug: config2.storeSlug
|
|
1323
|
+
});
|
|
1324
|
+
const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
|
|
1325
|
+
method: "POST",
|
|
1326
|
+
headers: { "Content-Type": "application/json" },
|
|
1327
|
+
body: JSON.stringify(body),
|
|
1328
|
+
signal: controller?.signal
|
|
1329
|
+
}).then((response) => response.ok);
|
|
1330
|
+
return await Promise.race([delivered, timedOut]);
|
|
1331
|
+
} catch {
|
|
1332
|
+
return false;
|
|
1333
|
+
} finally {
|
|
1334
|
+
clearTimeout(timer);
|
|
1283
1335
|
}
|
|
1284
1336
|
}
|
|
1285
|
-
function
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1337
|
+
async function trackPageView(cartValue, itemCount) {
|
|
1338
|
+
if (!isInitialized()) return;
|
|
1339
|
+
if (typeof document === "undefined") return;
|
|
1340
|
+
const config2 = getConfig();
|
|
1341
|
+
const carriesCart = cartValue !== void 0 || itemCount !== void 0;
|
|
1342
|
+
const reservedOrder = carriesCart ? reserveCartOrder(config2.storeSlug) : void 0;
|
|
1343
|
+
if (isPlatformPageTrackerActive()) {
|
|
1344
|
+
if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
|
|
1293
1345
|
return;
|
|
1294
1346
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1347
|
+
const sendView = async () => {
|
|
1348
|
+
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
1349
|
+
const settle2 = order2 ? claimCartReport(config2.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
1350
|
+
const sessionId = analyticsSessionId(config2.storeSlug, { touch: true });
|
|
1351
|
+
const previousSession = currentPageSession();
|
|
1352
|
+
if (previousSession && previousSession !== sessionId) reportPageLeave();
|
|
1353
|
+
markPageViewed(sessionId);
|
|
1354
|
+
if (sessionId) installEngagedClock(reportPageLeave);
|
|
1355
|
+
onAnalyticsConsent(startSessionOnConsent);
|
|
1356
|
+
const delivered = await postPing({
|
|
1357
|
+
referer: document.referrer || void 0,
|
|
1358
|
+
cart_value: cartValue,
|
|
1359
|
+
item_count: itemCount,
|
|
1360
|
+
cart_seq: order2?.seq,
|
|
1361
|
+
cart_seq_scope: order2?.scope,
|
|
1362
|
+
session_id: sessionId ?? void 0,
|
|
1363
|
+
// Time on screen of the page before this one (client-side navigation).
|
|
1364
|
+
engaged_ms: sessionId ? takeEngagedMs() || void 0 : void 0,
|
|
1365
|
+
path: resolveStorefrontPagePath(),
|
|
1366
|
+
utm: resolveUtmParameters(config2.storeSlug)
|
|
1367
|
+
});
|
|
1368
|
+
settle2?.(delivered);
|
|
1369
|
+
};
|
|
1370
|
+
await (reservedOrder ? enqueueCartSend(sendView) : sendView());
|
|
1313
1371
|
}
|
|
1314
|
-
function
|
|
1315
|
-
if (!
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
const lineId = computeCartLineId({
|
|
1324
|
-
product_id: productId,
|
|
1325
|
-
variant_id: variantId,
|
|
1326
|
-
addons: options?.addons,
|
|
1327
|
-
custom_fields: options?.custom_fields,
|
|
1328
|
-
price_variant_id: options?.price_variant_id,
|
|
1329
|
-
price_data: options?.price_data,
|
|
1330
|
-
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1372
|
+
async function trackCartActivity(cartValue, itemCount) {
|
|
1373
|
+
if (!isInitialized()) return;
|
|
1374
|
+
if (typeof document === "undefined") return;
|
|
1375
|
+
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
1376
|
+
const reservedOrder = reserveCartOrder(getConfig().storeSlug);
|
|
1377
|
+
await enqueueCartSend(async (isLatest) => {
|
|
1378
|
+
const order2 = await reservedOrder;
|
|
1379
|
+
if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
|
|
1380
|
+
await sendCartActivity(order2, cartValue, itemCount);
|
|
1331
1381
|
});
|
|
1332
|
-
for (let index = cart.length - 1; index >= 0; index -= 1) {
|
|
1333
|
-
if (cart[index].product_id === productId && cart[index].variant_id === variantId) {
|
|
1334
|
-
cart.splice(index, 1);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
const pushed = {
|
|
1338
|
-
line_id: lineId,
|
|
1339
|
-
product_id: productId,
|
|
1340
|
-
variant_id: variantId,
|
|
1341
|
-
quantity: normalizedQuantity,
|
|
1342
|
-
addons: options?.addons,
|
|
1343
|
-
custom_fields: options?.custom_fields,
|
|
1344
|
-
price_variant_id: options?.price_variant_id,
|
|
1345
|
-
price_data: options?.price_data,
|
|
1346
|
-
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1347
|
-
title: options?.title,
|
|
1348
|
-
variant_title: options?.variant_title,
|
|
1349
|
-
image_url: options?.image_url,
|
|
1350
|
-
addon_labels: options?.addon_labels,
|
|
1351
|
-
max_quantity: options?.max_quantity,
|
|
1352
|
-
min_quantity: options?.min_quantity
|
|
1353
|
-
};
|
|
1354
|
-
clampQuantityToBounds(pushed);
|
|
1355
|
-
cart.push(pushed);
|
|
1356
|
-
writeCart(cart);
|
|
1357
1382
|
}
|
|
1358
|
-
function
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
cart.splice(index, 1);
|
|
1373
|
-
writeCart(cart);
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
cart[index].quantity = normalizedQuantity;
|
|
1377
|
-
}
|
|
1378
|
-
if (updates.addons !== void 0) {
|
|
1379
|
-
cart[index].addons = updates.addons;
|
|
1380
|
-
}
|
|
1381
|
-
if (updates.custom_fields !== void 0) {
|
|
1382
|
-
cart[index].custom_fields = updates.custom_fields;
|
|
1383
|
-
}
|
|
1384
|
-
if (updates.price_variant_id !== void 0) {
|
|
1385
|
-
cart[index].price_variant_id = updates.price_variant_id;
|
|
1386
|
-
}
|
|
1387
|
-
if (updates.price_data !== void 0) {
|
|
1388
|
-
cart[index].price_data = updates.price_data;
|
|
1389
|
-
}
|
|
1390
|
-
if (updates.pay_what_you_want_price !== void 0) {
|
|
1391
|
-
cart[index].pay_what_you_want_price = updates.pay_what_you_want_price;
|
|
1392
|
-
}
|
|
1393
|
-
if (updates.variant_title !== void 0) {
|
|
1394
|
-
if (updates.variant_title === null) delete cart[index].variant_title;
|
|
1395
|
-
else cart[index].variant_title = updates.variant_title;
|
|
1396
|
-
}
|
|
1397
|
-
if (updates.max_quantity !== void 0) {
|
|
1398
|
-
if (updates.max_quantity === null) delete cart[index].max_quantity;
|
|
1399
|
-
else cart[index].max_quantity = updates.max_quantity;
|
|
1400
|
-
}
|
|
1401
|
-
if (updates.min_quantity !== void 0) {
|
|
1402
|
-
if (updates.min_quantity === null) delete cart[index].min_quantity;
|
|
1403
|
-
else cart[index].min_quantity = updates.min_quantity;
|
|
1404
|
-
}
|
|
1405
|
-
clampQuantityToBounds(cart[index]);
|
|
1406
|
-
const nextLineId = computeCartLineId(cart[index]);
|
|
1407
|
-
const duplicateIndex = cart.findIndex((item, itemIndex) => itemIndex !== index && item.line_id === nextLineId);
|
|
1408
|
-
if (duplicateIndex >= 0) {
|
|
1409
|
-
cart[duplicateIndex].quantity += cart[index].quantity;
|
|
1410
|
-
clampQuantityToBounds(cart[duplicateIndex]);
|
|
1411
|
-
cart.splice(index, 1);
|
|
1412
|
-
} else {
|
|
1413
|
-
cart[index].line_id = nextLineId;
|
|
1414
|
-
}
|
|
1415
|
-
if (JSON.stringify(cart) === before) return;
|
|
1416
|
-
writeCart(cart);
|
|
1383
|
+
async function sendCartActivity(order2, cartValue, itemCount) {
|
|
1384
|
+
const config2 = getConfig();
|
|
1385
|
+
const settle2 = claimCartReport(config2.storeSlug, order2.seq, cartValue, itemCount);
|
|
1386
|
+
const delivered = await postPing({
|
|
1387
|
+
event_type: "presence",
|
|
1388
|
+
cart_value: cartValue,
|
|
1389
|
+
item_count: itemCount,
|
|
1390
|
+
cart_seq: order2.seq,
|
|
1391
|
+
cart_seq_scope: order2.scope,
|
|
1392
|
+
// A cart marks the analytics session as one that did not bounce.
|
|
1393
|
+
session_id: analyticsSessionId(config2.storeSlug, { touch: false }) ?? void 0,
|
|
1394
|
+
path: resolveStorefrontPagePath()
|
|
1395
|
+
});
|
|
1396
|
+
settle2(delivered);
|
|
1417
1397
|
}
|
|
1418
|
-
function
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
throw new CartError("line_id is required");
|
|
1422
|
-
}
|
|
1423
|
-
const cart = getCart();
|
|
1424
|
-
const filtered = cart.filter((item) => item.line_id !== normalizedLineId);
|
|
1425
|
-
writeCart(filtered);
|
|
1398
|
+
async function trackProductView(productId) {
|
|
1399
|
+
if (!isInitialized() || typeof document === "undefined" || !productId) return;
|
|
1400
|
+
await sendVisitEvent({ event_type: "product_view", product_id: productId });
|
|
1426
1401
|
}
|
|
1427
|
-
function
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
clearCartCoupon();
|
|
1431
|
-
clearLatestQuoteToken();
|
|
1402
|
+
async function trackCartAdd(productId, variantId, quantity) {
|
|
1403
|
+
if (!isInitialized() || typeof document === "undefined" || quantity < 1) return;
|
|
1404
|
+
await sendVisitEvent({ event_type: "cart_add", product_id: productId, variant_id: variantId, quantity });
|
|
1432
1405
|
}
|
|
1433
|
-
function
|
|
1434
|
-
const
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1406
|
+
async function sendVisitEvent(body) {
|
|
1407
|
+
const config2 = getConfig();
|
|
1408
|
+
if (config2.preview) return;
|
|
1409
|
+
await postPing({
|
|
1410
|
+
...body,
|
|
1411
|
+
referer: document.referrer || void 0,
|
|
1412
|
+
session_id: analyticsSessionId(config2.storeSlug, { touch: true }) ?? void 0,
|
|
1413
|
+
path: resolveStorefrontPagePath(),
|
|
1414
|
+
utm: resolveUtmParameters(config2.storeSlug)
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
function startSessionOnConsent() {
|
|
1418
|
+
const config2 = getConfig();
|
|
1419
|
+
if (!isPageViewed() || analyticsSessionId(config2.storeSlug, { touch: false })) return;
|
|
1420
|
+
const sessionId = analyticsSessionId(config2.storeSlug, { touch: true });
|
|
1421
|
+
if (!sessionId) return;
|
|
1422
|
+
markPageViewed(sessionId);
|
|
1423
|
+
installEngagedClock(reportPageLeave);
|
|
1424
|
+
void postPing({
|
|
1425
|
+
event_type: "session_start",
|
|
1426
|
+
referer: document.referrer || void 0,
|
|
1427
|
+
session_id: sessionId,
|
|
1428
|
+
path: resolveStorefrontPagePath(),
|
|
1429
|
+
utm: resolveUtmParameters(config2.storeSlug)
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
function reportPageLeave() {
|
|
1433
|
+
const config2 = getConfig();
|
|
1434
|
+
const sessionId = currentPageSession();
|
|
1435
|
+
const engagedMs2 = takeEngagedMs();
|
|
1436
|
+
if (!sessionId || engagedMs2 === 0 || readAnalyticsConsent() !== "granted") return;
|
|
1437
|
+
try {
|
|
1438
|
+
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", { storeSlug: config2.storeSlug });
|
|
1439
|
+
void fetch(`${config2.apiBaseUrl}${endpoint}`, {
|
|
1440
|
+
method: "POST",
|
|
1441
|
+
headers: { "Content-Type": "application/json" },
|
|
1442
|
+
body: JSON.stringify({ event_type: "page_leave", session_id: sessionId, engaged_ms: engagedMs2 }),
|
|
1443
|
+
keepalive: true
|
|
1444
|
+
}).catch(() => {
|
|
1452
1445
|
});
|
|
1446
|
+
} catch {
|
|
1453
1447
|
}
|
|
1454
1448
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
if (backupCoupon && typeof backupCoupon === "object" && !Array.isArray(backupCoupon)) {
|
|
1463
|
-
const record2 = backupCoupon;
|
|
1464
|
-
const source = record2.source === "COUPON" || record2.source === "AFFILIATE" ? record2.source : null;
|
|
1465
|
-
if (typeof record2.code === "string" && source) {
|
|
1466
|
-
setCartCoupon(record2.code, source);
|
|
1467
|
-
} else {
|
|
1468
|
-
clearCartCoupon();
|
|
1469
|
-
}
|
|
1470
|
-
} else {
|
|
1471
|
-
clearCartCoupon();
|
|
1472
|
-
}
|
|
1473
|
-
return true;
|
|
1449
|
+
|
|
1450
|
+
// ../sdk/src/utils/cart-line-id.ts
|
|
1451
|
+
function hashString(value) {
|
|
1452
|
+
let hash2 = 2166136261;
|
|
1453
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1454
|
+
hash2 ^= value.charCodeAt(i);
|
|
1455
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
1474
1456
|
}
|
|
1475
|
-
return
|
|
1457
|
+
return (hash2 >>> 0).toString(16);
|
|
1476
1458
|
}
|
|
1477
|
-
function
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
const index = cart.findIndex((item) => item.line_id === mergedLine.line_id);
|
|
1502
|
-
if (index >= 0) {
|
|
1503
|
-
cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
|
|
1504
|
-
clampQuantityToBounds(cart[index]);
|
|
1505
|
-
} else {
|
|
1506
|
-
clampQuantityToBounds(mergedLine);
|
|
1507
|
-
cart.push(mergedLine);
|
|
1508
|
-
}
|
|
1459
|
+
function normalizeAddons(addons) {
|
|
1460
|
+
if (!addons?.length) return [];
|
|
1461
|
+
return [...addons].map((addon) => ({ id: addon.id, quantity: addon.quantity ?? 1 })).sort((a, b) => a.id.localeCompare(b.id));
|
|
1462
|
+
}
|
|
1463
|
+
function normalizeCustomFields(fields) {
|
|
1464
|
+
if (!fields) return {};
|
|
1465
|
+
const entries = Object.entries(fields).filter(([, value]) => typeof value === "string").sort(([a], [b]) => a.localeCompare(b));
|
|
1466
|
+
return Object.fromEntries(entries);
|
|
1467
|
+
}
|
|
1468
|
+
function computeCartLineId(input2) {
|
|
1469
|
+
const payload = {
|
|
1470
|
+
product_id: input2.product_id,
|
|
1471
|
+
variant_id: input2.variant_id,
|
|
1472
|
+
price_variant_id: input2.price_variant_id ?? null,
|
|
1473
|
+
addons: normalizeAddons(input2.addons),
|
|
1474
|
+
custom_fields: normalizeCustomFields(input2.custom_fields),
|
|
1475
|
+
unit_price: typeof input2.price_data?.unit_price === "number" && Number.isFinite(input2.price_data.unit_price) ? input2.price_data.unit_price : null,
|
|
1476
|
+
pay_what_you_want_price: typeof input2.pay_what_you_want_price === "number" && Number.isFinite(input2.pay_what_you_want_price) ? input2.pay_what_you_want_price : null
|
|
1477
|
+
};
|
|
1478
|
+
return hashString(JSON.stringify(payload));
|
|
1479
|
+
}
|
|
1480
|
+
function ensureCartLineId(item) {
|
|
1481
|
+
if (typeof item.line_id === "string" && item.line_id.trim()) {
|
|
1482
|
+
return item;
|
|
1509
1483
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1484
|
+
return { ...item, line_id: computeCartLineId(item) };
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// ../contracts/src/catalog-unit-price.ts
|
|
1488
|
+
var CATALOG_UNIT_PRICE_DECIMAL_PLACES = 3;
|
|
1489
|
+
var PAYABLE_AMOUNT_DECIMAL_PLACES = 2;
|
|
1490
|
+
var CATALOG_UNIT_PRICE_FORMAT_OPTIONS = {
|
|
1491
|
+
maximumFractionDigits: CATALOG_UNIT_PRICE_DECIMAL_PLACES
|
|
1492
|
+
};
|
|
1493
|
+
function roundPayableAmount(value) {
|
|
1494
|
+
if (!Number.isFinite(value)) return value;
|
|
1495
|
+
const sign = value < 0 ? -1 : 1;
|
|
1496
|
+
const shifted = shiftDecimal(Math.abs(value), PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1497
|
+
const rounded = Math.round(shifted);
|
|
1498
|
+
if (rounded === 0) return 0;
|
|
1499
|
+
return sign * shiftDecimal(rounded, -PAYABLE_AMOUNT_DECIMAL_PLACES);
|
|
1500
|
+
}
|
|
1501
|
+
function shiftDecimal(value, places) {
|
|
1502
|
+
const [coefficient, currentExponent = "0"] = String(value).split("e");
|
|
1503
|
+
return Number(`${coefficient}e${Number(currentExponent) + places}`);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// ../sdk/src/modules/cart.ts
|
|
1507
|
+
var STORAGE_KEYS = {
|
|
1508
|
+
cart: "cart",
|
|
1509
|
+
cartBackup: "cart_backup",
|
|
1510
|
+
meta: "cart_meta",
|
|
1511
|
+
metaBackup: "cart_backup_meta",
|
|
1512
|
+
coupon: "cart_coupon",
|
|
1513
|
+
couponBackup: "cart_coupon_backup"
|
|
1514
|
+
};
|
|
1515
|
+
function getStorageKey(type) {
|
|
1516
|
+
return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
|
|
1517
|
+
}
|
|
1518
|
+
function cartStorageArea() {
|
|
1519
|
+
return getConfig().preview ? "sessionStorage" : "localStorage";
|
|
1520
|
+
}
|
|
1521
|
+
function readCartStorage(type) {
|
|
1522
|
+
return getItem(getStorageKey(type), cartStorageArea());
|
|
1523
|
+
}
|
|
1524
|
+
function writeCartStorage(type, value) {
|
|
1525
|
+
setItem(getStorageKey(type), value, cartStorageArea());
|
|
1512
1526
|
}
|
|
1513
|
-
function
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const [fromItem] = cart.splice(fromIndex, 1);
|
|
1522
|
-
const toLineId = computeCartLineId({
|
|
1523
|
-
...fromItem,
|
|
1524
|
-
product_id: toProductId,
|
|
1525
|
-
variant_id: toVariantId
|
|
1526
|
-
});
|
|
1527
|
-
const toIndex = cart.findIndex((item) => item.line_id === toLineId);
|
|
1528
|
-
if (toIndex >= 0) {
|
|
1529
|
-
cart[toIndex].quantity += fromItem.quantity;
|
|
1530
|
-
clampQuantityToBounds(cart[toIndex]);
|
|
1531
|
-
} else {
|
|
1532
|
-
const moved = ensureCartLineId({
|
|
1533
|
-
...fromItem,
|
|
1534
|
-
product_id: toProductId,
|
|
1535
|
-
variant_id: toVariantId,
|
|
1536
|
-
line_id: toLineId
|
|
1537
|
-
});
|
|
1538
|
-
clampQuantityToBounds(moved);
|
|
1539
|
-
cart.push(moved);
|
|
1527
|
+
function removeCartStorage(type) {
|
|
1528
|
+
removeItem(getStorageKey(type), cartStorageArea());
|
|
1529
|
+
}
|
|
1530
|
+
function hashString2(value) {
|
|
1531
|
+
let hash2 = 2166136261;
|
|
1532
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1533
|
+
hash2 ^= value.charCodeAt(i);
|
|
1534
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
1540
1535
|
}
|
|
1541
|
-
|
|
1536
|
+
return (hash2 >>> 0).toString(16);
|
|
1542
1537
|
}
|
|
1543
|
-
function
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (!
|
|
1548
|
-
|
|
1549
|
-
return true;
|
|
1538
|
+
function computeChecksum(cart) {
|
|
1539
|
+
return hashString2(JSON.stringify(cart));
|
|
1540
|
+
}
|
|
1541
|
+
function normalizeQuantity(value) {
|
|
1542
|
+
if (!Number.isFinite(value)) {
|
|
1543
|
+
throw new CartError("quantity must be a finite number");
|
|
1550
1544
|
}
|
|
1551
|
-
return
|
|
1545
|
+
return Math.floor(value);
|
|
1552
1546
|
}
|
|
1553
|
-
function
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
const metadata = getCartMetadata();
|
|
1557
|
-
const backup = readCartStorage("cartBackup") ?? [];
|
|
1558
|
-
const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
|
|
1559
|
-
const totalPrice = cart.reduce((sum, item) => {
|
|
1560
|
-
const unitPrice = item.price_data?.unit_price ?? 0;
|
|
1561
|
-
return sum + roundPayableAmount(unitPrice * item.quantity);
|
|
1562
|
-
}, 0);
|
|
1563
|
-
return {
|
|
1564
|
-
item_count: cart.length,
|
|
1565
|
-
total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
|
|
1566
|
-
last_modified: metadata?.last_modified ?? 0,
|
|
1567
|
-
version: metadata?.version ?? 0,
|
|
1568
|
-
has_backup: backup.length > 0,
|
|
1569
|
-
integrity_valid: integrityValid,
|
|
1570
|
-
total_price: roundPayableAmount(totalPrice),
|
|
1571
|
-
total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
|
|
1572
|
-
};
|
|
1547
|
+
function normalizeCouponCode(value) {
|
|
1548
|
+
const normalized = value?.trim().toUpperCase();
|
|
1549
|
+
return normalized ? normalized : null;
|
|
1573
1550
|
}
|
|
1574
|
-
function
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1551
|
+
function getStoredCartCode() {
|
|
1552
|
+
const raw = readCartStorage("coupon");
|
|
1553
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
const record2 = raw;
|
|
1557
|
+
const code = typeof record2.code === "string" ? normalizeCouponCode(record2.code) : null;
|
|
1558
|
+
const source = record2.source === "COUPON" || record2.source === "AFFILIATE" ? record2.source : null;
|
|
1559
|
+
return code && source ? { code, source } : null;
|
|
1582
1560
|
}
|
|
1583
|
-
function
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1561
|
+
function normalizeCartItems(value) {
|
|
1562
|
+
if (!Array.isArray(value)) return [];
|
|
1563
|
+
const normalized = [];
|
|
1564
|
+
for (const entry of value) {
|
|
1565
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1566
|
+
const record2 = entry;
|
|
1567
|
+
const productId = typeof record2.product_id === "string" ? record2.product_id.trim() : "";
|
|
1568
|
+
const variantId = typeof record2.variant_id === "string" ? record2.variant_id.trim() : "";
|
|
1569
|
+
const quantity = Number(record2.quantity);
|
|
1570
|
+
if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
const item = {
|
|
1574
|
+
line_id: "",
|
|
1575
|
+
product_id: productId,
|
|
1576
|
+
variant_id: variantId,
|
|
1577
|
+
quantity: Math.floor(quantity)
|
|
1578
|
+
};
|
|
1579
|
+
if (typeof record2.price_variant_id === "string") {
|
|
1580
|
+
item.price_variant_id = record2.price_variant_id;
|
|
1581
|
+
}
|
|
1582
|
+
if (record2.price_data && typeof record2.price_data === "object") {
|
|
1583
|
+
const priceData = record2.price_data;
|
|
1584
|
+
if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
|
|
1585
|
+
item.price_data = { unit_price: priceData.unit_price };
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (typeof record2.pay_what_you_want_price === "number" && Number.isFinite(record2.pay_what_you_want_price)) {
|
|
1589
|
+
item.pay_what_you_want_price = record2.pay_what_you_want_price;
|
|
1590
|
+
}
|
|
1591
|
+
if (Array.isArray(record2.addons)) {
|
|
1592
|
+
item.addons = record2.addons;
|
|
1593
|
+
}
|
|
1594
|
+
if (record2.custom_fields && typeof record2.custom_fields === "object" && !Array.isArray(record2.custom_fields)) {
|
|
1595
|
+
item.custom_fields = record2.custom_fields;
|
|
1596
|
+
}
|
|
1597
|
+
if (typeof record2.title === "string") {
|
|
1598
|
+
item.title = record2.title;
|
|
1599
|
+
}
|
|
1600
|
+
if (typeof record2.variant_title === "string") {
|
|
1601
|
+
item.variant_title = record2.variant_title;
|
|
1602
|
+
}
|
|
1603
|
+
if (typeof record2.image_url === "string") {
|
|
1604
|
+
item.image_url = record2.image_url;
|
|
1605
|
+
}
|
|
1606
|
+
if (Array.isArray(record2.addon_labels)) {
|
|
1607
|
+
item.addon_labels = record2.addon_labels.filter((label) => typeof label === "string");
|
|
1608
|
+
}
|
|
1609
|
+
if (typeof record2.max_quantity === "number" && Number.isFinite(record2.max_quantity)) {
|
|
1610
|
+
item.max_quantity = record2.max_quantity;
|
|
1611
|
+
}
|
|
1612
|
+
if (typeof record2.min_quantity === "number" && Number.isFinite(record2.min_quantity)) {
|
|
1613
|
+
item.min_quantity = record2.min_quantity;
|
|
1614
|
+
}
|
|
1615
|
+
clampQuantityToBounds(item);
|
|
1616
|
+
const storedLineId = typeof record2.line_id === "string" ? record2.line_id.trim() : "";
|
|
1617
|
+
item.line_id = storedLineId || computeCartLineId(item);
|
|
1618
|
+
normalized.push(ensureCartLineId(item));
|
|
1588
1619
|
}
|
|
1589
|
-
return
|
|
1620
|
+
return normalized;
|
|
1590
1621
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
function getLatestQuoteToken() {
|
|
1594
|
-
return latestQuoteToken;
|
|
1622
|
+
function getCartMetadata() {
|
|
1623
|
+
return readCartStorage("meta");
|
|
1595
1624
|
}
|
|
1596
|
-
function
|
|
1597
|
-
|
|
1598
|
-
quoteSequence += 1;
|
|
1625
|
+
function getCartCoupon() {
|
|
1626
|
+
return getStoredCartCode()?.code ?? null;
|
|
1599
1627
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
affiliate_code: getAmbientAffiliateCodeForQuote() ?? void 0,
|
|
1609
|
-
currency: normalizedCurrency ?? void 0
|
|
1610
|
-
});
|
|
1611
|
-
if (sequence !== quoteSequence) return response;
|
|
1612
|
-
if (response.success) {
|
|
1613
|
-
latestQuoteToken = typeof response.data?.quote_token === "string" ? response.data.quote_token : null;
|
|
1628
|
+
function getCartCouponSource() {
|
|
1629
|
+
return getStoredCartCode()?.source ?? null;
|
|
1630
|
+
}
|
|
1631
|
+
function setCartCoupon(coupon, source = "COUPON") {
|
|
1632
|
+
const normalizedCoupon = normalizeCouponCode(coupon);
|
|
1633
|
+
if (!normalizedCoupon) {
|
|
1634
|
+
removeCartStorage("coupon");
|
|
1635
|
+
return null;
|
|
1614
1636
|
}
|
|
1615
|
-
|
|
1637
|
+
writeCartStorage("coupon", { code: normalizedCoupon, source });
|
|
1638
|
+
return normalizedCoupon;
|
|
1616
1639
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
var COOKIE_CONSENT_VERSION = 1;
|
|
1620
|
-
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
1621
|
-
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
1622
|
-
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
1623
|
-
function parseCookieConsentValue(value) {
|
|
1624
|
-
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
1625
|
-
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
1640
|
+
function clearCartCoupon() {
|
|
1641
|
+
removeCartStorage("coupon");
|
|
1626
1642
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
try {
|
|
1642
|
-
raw = document.cookie.split(/;\s*/).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
1643
|
-
} catch {
|
|
1644
|
-
return "unknown";
|
|
1643
|
+
function writeCart(cart) {
|
|
1644
|
+
const normalizedCart = normalizeCartItems(cart);
|
|
1645
|
+
writeCartStorage("cart", normalizedCart);
|
|
1646
|
+
const now = Date.now();
|
|
1647
|
+
const previous = getCartMetadata();
|
|
1648
|
+
const nextMeta = {
|
|
1649
|
+
created_at: previous?.created_at ?? now,
|
|
1650
|
+
last_modified: now,
|
|
1651
|
+
version: (previous?.version ?? 0) + 1,
|
|
1652
|
+
checksum: computeChecksum(normalizedCart)
|
|
1653
|
+
};
|
|
1654
|
+
writeCartStorage("meta", nextMeta);
|
|
1655
|
+
if (normalizedCart.length === 0) {
|
|
1656
|
+
clearCartCoupon();
|
|
1645
1657
|
}
|
|
1646
|
-
const choice = parseCookieConsentValue(raw ?? null);
|
|
1647
|
-
if (!choice) return "unknown";
|
|
1648
|
-
return choice.analytics ? "granted" : "declined";
|
|
1649
1658
|
}
|
|
1650
|
-
function
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1659
|
+
function setCartWithMetadata(cart, metadata) {
|
|
1660
|
+
const normalizedCart = normalizeCartItems(cart);
|
|
1661
|
+
writeCartStorage("cart", normalizedCart);
|
|
1662
|
+
const now = Date.now();
|
|
1663
|
+
const base = metadata ?? getCartMetadata();
|
|
1664
|
+
const nextMeta = {
|
|
1665
|
+
created_at: base?.created_at ?? now,
|
|
1666
|
+
last_modified: now,
|
|
1667
|
+
version: base?.version ?? 1,
|
|
1668
|
+
checksum: computeChecksum(normalizedCart)
|
|
1669
|
+
};
|
|
1670
|
+
writeCartStorage("meta", nextMeta);
|
|
1654
1671
|
}
|
|
1655
|
-
function
|
|
1656
|
-
|
|
1657
|
-
|
|
1672
|
+
function getCart() {
|
|
1673
|
+
const raw = readCartStorage("cart");
|
|
1674
|
+
return normalizeCartItems(raw);
|
|
1658
1675
|
}
|
|
1659
|
-
function
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
forgetAnalyticsData(storeSlug);
|
|
1663
|
-
return null;
|
|
1664
|
-
}
|
|
1665
|
-
const stored = getItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1666
|
-
const live = stored && typeof stored.id === "string" && typeof stored.at === "number" && now - stored.at < ANALYTICS_SESSION_IDLE_MS ? stored.id : null;
|
|
1667
|
-
if (!options.touch) return live;
|
|
1668
|
-
if (live) {
|
|
1669
|
-
setItem(analyticsSessionKey(storeSlug), { id: live, at: now }, "sessionStorage");
|
|
1670
|
-
return live;
|
|
1671
|
-
}
|
|
1672
|
-
if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") return null;
|
|
1673
|
-
const id = crypto.randomUUID();
|
|
1674
|
-
setItem(analyticsSessionKey(storeSlug), { id, at: now }, "sessionStorage");
|
|
1675
|
-
setItem(
|
|
1676
|
-
analyticsVisitsKey(storeSlug),
|
|
1677
|
-
[{ id, at: now }, ...readVisits(storeSlug, now)].slice(0, ANALYTICS_VISITS_MAX)
|
|
1676
|
+
function resolveCartLineId(productId, variantId, cart = getCart()) {
|
|
1677
|
+
const matches = cart.filter(
|
|
1678
|
+
(item) => item.product_id === productId && item.variant_id === variantId
|
|
1678
1679
|
);
|
|
1679
|
-
|
|
1680
|
-
}
|
|
1681
|
-
function checkoutAttribution(storeSlug, now = Date.now()) {
|
|
1682
|
-
const consent = readAnalyticsConsent();
|
|
1683
|
-
if (consent === "declined") {
|
|
1684
|
-
forgetAnalyticsData(storeSlug);
|
|
1685
|
-
return { declined: true };
|
|
1680
|
+
if (matches.length === 0) {
|
|
1681
|
+
throw new CartError(`No cart line found for ${productId}/${variantId}`);
|
|
1686
1682
|
}
|
|
1687
|
-
|
|
1688
|
-
const sessionIds = readVisits(storeSlug, now).map((visit2) => visit2.id);
|
|
1689
|
-
return sessionIds.length > 0 ? { session_ids: sessionIds } : void 0;
|
|
1683
|
+
return matches[0].line_id;
|
|
1690
1684
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
var pageViewed = false;
|
|
1695
|
-
var pageSession = null;
|
|
1696
|
-
function markPageViewed(sessionId) {
|
|
1697
|
-
pageViewed = true;
|
|
1698
|
-
pageSession = sessionId;
|
|
1685
|
+
function getCartItemCount() {
|
|
1686
|
+
const cart = getCart();
|
|
1687
|
+
return cart.reduce((sum, item) => sum + item.quantity, 0);
|
|
1699
1688
|
}
|
|
1700
|
-
function
|
|
1701
|
-
|
|
1689
|
+
function addToCart(productId, variantId, quantity = 1, options) {
|
|
1690
|
+
if (!productId || !variantId) {
|
|
1691
|
+
throw new CartError("product_id and variant_id are required");
|
|
1692
|
+
}
|
|
1693
|
+
const normalizedQuantity = normalizeQuantity(quantity);
|
|
1694
|
+
if (normalizedQuantity < 1) {
|
|
1695
|
+
throw new CartError("quantity must be at least 1");
|
|
1696
|
+
}
|
|
1697
|
+
const cart = getCart();
|
|
1698
|
+
const lineId = computeCartLineId({
|
|
1699
|
+
product_id: productId,
|
|
1700
|
+
variant_id: variantId,
|
|
1701
|
+
addons: options?.addons,
|
|
1702
|
+
custom_fields: options?.custom_fields,
|
|
1703
|
+
price_variant_id: options?.price_variant_id,
|
|
1704
|
+
price_data: options?.price_data,
|
|
1705
|
+
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1706
|
+
});
|
|
1707
|
+
const existingIndex = cart.findIndex((item) => item.line_id === lineId);
|
|
1708
|
+
const quantityBeforeAdd = existingIndex >= 0 ? cart[existingIndex].quantity : 0;
|
|
1709
|
+
if (existingIndex >= 0) {
|
|
1710
|
+
cart[existingIndex].quantity += normalizedQuantity;
|
|
1711
|
+
if (options?.addons) {
|
|
1712
|
+
cart[existingIndex].addons = options.addons;
|
|
1713
|
+
}
|
|
1714
|
+
if (options?.custom_fields) {
|
|
1715
|
+
cart[existingIndex].custom_fields = options.custom_fields;
|
|
1716
|
+
}
|
|
1717
|
+
if (options?.price_variant_id) {
|
|
1718
|
+
cart[existingIndex].price_variant_id = options.price_variant_id;
|
|
1719
|
+
}
|
|
1720
|
+
if (options?.price_data) {
|
|
1721
|
+
cart[existingIndex].price_data = options.price_data;
|
|
1722
|
+
}
|
|
1723
|
+
if (options?.pay_what_you_want_price !== void 0) {
|
|
1724
|
+
cart[existingIndex].pay_what_you_want_price = options.pay_what_you_want_price;
|
|
1725
|
+
}
|
|
1726
|
+
applyDisplaySnapshot(cart[existingIndex], options);
|
|
1727
|
+
clampQuantityToBounds(cart[existingIndex]);
|
|
1728
|
+
} else {
|
|
1729
|
+
const pushed = {
|
|
1730
|
+
line_id: lineId,
|
|
1731
|
+
product_id: productId,
|
|
1732
|
+
variant_id: variantId,
|
|
1733
|
+
quantity: normalizedQuantity,
|
|
1734
|
+
addons: options?.addons,
|
|
1735
|
+
custom_fields: options?.custom_fields,
|
|
1736
|
+
price_variant_id: options?.price_variant_id,
|
|
1737
|
+
price_data: options?.price_data,
|
|
1738
|
+
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1739
|
+
title: options?.title,
|
|
1740
|
+
variant_title: options?.variant_title,
|
|
1741
|
+
image_url: options?.image_url,
|
|
1742
|
+
addon_labels: options?.addon_labels,
|
|
1743
|
+
max_quantity: options?.max_quantity,
|
|
1744
|
+
min_quantity: options?.min_quantity
|
|
1745
|
+
};
|
|
1746
|
+
clampQuantityToBounds(pushed);
|
|
1747
|
+
cart.push(pushed);
|
|
1748
|
+
}
|
|
1749
|
+
const intendedQuantity = (existingIndex >= 0 ? cart[existingIndex] : cart[cart.length - 1]).quantity;
|
|
1750
|
+
writeCart(cart);
|
|
1751
|
+
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1752
|
+
if (intendedQuantity > quantityBeforeAdd && persistedLine?.quantity === intendedQuantity) {
|
|
1753
|
+
const cartAffiliateCode = getCartCouponSource() === "AFFILIATE" ? getCartCoupon() : null;
|
|
1754
|
+
void trackAffiliateEvent("add_to_cart", cartAffiliateCode ? { code: cartAffiliateCode } : void 0);
|
|
1755
|
+
void trackCartAdd(productId, variantId, intendedQuantity - quantityBeforeAdd);
|
|
1756
|
+
}
|
|
1702
1757
|
}
|
|
1703
|
-
function
|
|
1704
|
-
|
|
1758
|
+
function clampQuantityToBounds(item) {
|
|
1759
|
+
const min = item.min_quantity;
|
|
1760
|
+
if (typeof min === "number" && Number.isFinite(min) && item.quantity < min) {
|
|
1761
|
+
item.quantity = Math.floor(min);
|
|
1762
|
+
}
|
|
1763
|
+
const max = item.max_quantity;
|
|
1764
|
+
if (typeof max === "number" && Number.isFinite(max) && item.quantity > max) {
|
|
1765
|
+
item.quantity = Math.floor(max);
|
|
1766
|
+
}
|
|
1767
|
+
if (item.quantity < 1) {
|
|
1768
|
+
item.quantity = 1;
|
|
1769
|
+
}
|
|
1705
1770
|
}
|
|
1706
|
-
function
|
|
1707
|
-
if (
|
|
1708
|
-
|
|
1709
|
-
|
|
1771
|
+
function applyDisplaySnapshot(item, options) {
|
|
1772
|
+
if (options?.replace_display_snapshot) {
|
|
1773
|
+
item.title = options.title;
|
|
1774
|
+
item.variant_title = options.variant_title;
|
|
1775
|
+
item.image_url = options.image_url;
|
|
1776
|
+
item.addon_labels = options.addon_labels;
|
|
1777
|
+
item.max_quantity = options.max_quantity;
|
|
1778
|
+
item.min_quantity = options.min_quantity;
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
if (options?.title !== void 0) {
|
|
1782
|
+
item.title = options.title;
|
|
1783
|
+
}
|
|
1784
|
+
if (options?.variant_title !== void 0) {
|
|
1785
|
+
item.variant_title = options.variant_title;
|
|
1786
|
+
}
|
|
1787
|
+
if (options?.image_url !== void 0) {
|
|
1788
|
+
item.image_url = options.image_url;
|
|
1789
|
+
}
|
|
1790
|
+
if (options?.addon_labels !== void 0) {
|
|
1791
|
+
item.addon_labels = options.addon_labels;
|
|
1792
|
+
}
|
|
1793
|
+
if (options?.max_quantity !== void 0) {
|
|
1794
|
+
item.max_quantity = options.max_quantity;
|
|
1795
|
+
}
|
|
1796
|
+
if (options?.min_quantity !== void 0) {
|
|
1797
|
+
item.min_quantity = options.min_quantity;
|
|
1710
1798
|
}
|
|
1711
1799
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1800
|
+
function setCartItem(productId, variantId, quantity = 1, options) {
|
|
1801
|
+
if (!productId || !variantId) {
|
|
1802
|
+
throw new CartError("product_id and variant_id are required");
|
|
1803
|
+
}
|
|
1804
|
+
const normalizedQuantity = normalizeQuantity(quantity);
|
|
1805
|
+
if (normalizedQuantity < 1) {
|
|
1806
|
+
throw new CartError("quantity must be at least 1");
|
|
1807
|
+
}
|
|
1808
|
+
const cart = getCart();
|
|
1809
|
+
const lineId = computeCartLineId({
|
|
1810
|
+
product_id: productId,
|
|
1811
|
+
variant_id: variantId,
|
|
1812
|
+
addons: options?.addons,
|
|
1813
|
+
custom_fields: options?.custom_fields,
|
|
1814
|
+
price_variant_id: options?.price_variant_id,
|
|
1815
|
+
price_data: options?.price_data,
|
|
1816
|
+
pay_what_you_want_price: options?.pay_what_you_want_price
|
|
1718
1817
|
});
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
clockInstalled = true;
|
|
1723
|
-
visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
1724
|
-
document.addEventListener("visibilitychange", () => {
|
|
1725
|
-
const now = Date.now();
|
|
1726
|
-
if (document.visibilityState === "visible") {
|
|
1727
|
-
visibleSince ?? (visibleSince = now);
|
|
1728
|
-
return;
|
|
1818
|
+
for (let index = cart.length - 1; index >= 0; index -= 1) {
|
|
1819
|
+
if (cart[index].product_id === productId && cart[index].variant_id === variantId) {
|
|
1820
|
+
cart.splice(index, 1);
|
|
1729
1821
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1822
|
+
}
|
|
1823
|
+
const pushed = {
|
|
1824
|
+
line_id: lineId,
|
|
1825
|
+
product_id: productId,
|
|
1826
|
+
variant_id: variantId,
|
|
1827
|
+
quantity: normalizedQuantity,
|
|
1828
|
+
addons: options?.addons,
|
|
1829
|
+
custom_fields: options?.custom_fields,
|
|
1830
|
+
price_variant_id: options?.price_variant_id,
|
|
1831
|
+
price_data: options?.price_data,
|
|
1832
|
+
pay_what_you_want_price: options?.pay_what_you_want_price,
|
|
1833
|
+
title: options?.title,
|
|
1834
|
+
variant_title: options?.variant_title,
|
|
1835
|
+
image_url: options?.image_url,
|
|
1836
|
+
addon_labels: options?.addon_labels,
|
|
1837
|
+
max_quantity: options?.max_quantity,
|
|
1838
|
+
min_quantity: options?.min_quantity
|
|
1839
|
+
};
|
|
1840
|
+
clampQuantityToBounds(pushed);
|
|
1841
|
+
cart.push(pushed);
|
|
1842
|
+
writeCart(cart);
|
|
1843
|
+
const persistedLine = getCart().find((item) => item.line_id === lineId);
|
|
1844
|
+
if (persistedLine) void trackCartAdd(productId, variantId, persistedLine.quantity);
|
|
1748
1845
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
var MAX_VALUE_LENGTH = 255;
|
|
1755
|
-
function readFromUrl(search) {
|
|
1756
|
-
let params;
|
|
1757
|
-
try {
|
|
1758
|
-
params = new URLSearchParams(search);
|
|
1759
|
-
} catch {
|
|
1760
|
-
return {};
|
|
1846
|
+
function updateCartItem(lineId, updates) {
|
|
1847
|
+
const cart = getCart();
|
|
1848
|
+
const normalizedLineId = lineId.trim();
|
|
1849
|
+
if (!normalizedLineId) {
|
|
1850
|
+
throw new CartError("line_id is required");
|
|
1761
1851
|
}
|
|
1762
|
-
const
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
if (value) {
|
|
1766
|
-
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1767
|
-
}
|
|
1852
|
+
const index = cart.findIndex((item) => item.line_id === normalizedLineId);
|
|
1853
|
+
if (index < 0) {
|
|
1854
|
+
throw new CartError("Item not found in cart");
|
|
1768
1855
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const raw = window.localStorage.getItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1777
|
-
if (!raw) return {};
|
|
1778
|
-
const parsed = JSON.parse(raw);
|
|
1779
|
-
if (!parsed || typeof parsed.at !== "number" || typeof parsed.utm !== "object") return {};
|
|
1780
|
-
if (Date.now() - parsed.at > TTL_MS) {
|
|
1781
|
-
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1782
|
-
return {};
|
|
1783
|
-
}
|
|
1784
|
-
const utm = {};
|
|
1785
|
-
for (const key of UTM_KEYS) {
|
|
1786
|
-
const value = parsed.utm?.[key];
|
|
1787
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1788
|
-
utm[key] = value.slice(0, MAX_VALUE_LENGTH);
|
|
1789
|
-
}
|
|
1856
|
+
const before = JSON.stringify(cart);
|
|
1857
|
+
if (updates.quantity !== void 0) {
|
|
1858
|
+
const normalizedQuantity = normalizeQuantity(updates.quantity);
|
|
1859
|
+
if (normalizedQuantity < 1) {
|
|
1860
|
+
cart.splice(index, 1);
|
|
1861
|
+
writeCart(cart);
|
|
1862
|
+
return;
|
|
1790
1863
|
}
|
|
1791
|
-
|
|
1792
|
-
} catch {
|
|
1793
|
-
return {};
|
|
1864
|
+
cart[index].quantity = normalizedQuantity;
|
|
1794
1865
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
try {
|
|
1798
|
-
const payload = { utm, at: Date.now() };
|
|
1799
|
-
window.localStorage.setItem(storefrontStorageKey(storageKey(storeSlug)), JSON.stringify(payload));
|
|
1800
|
-
} catch {
|
|
1866
|
+
if (updates.addons !== void 0) {
|
|
1867
|
+
cart[index].addons = updates.addons;
|
|
1801
1868
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
1805
|
-
return void 0;
|
|
1869
|
+
if (updates.custom_fields !== void 0) {
|
|
1870
|
+
cart[index].custom_fields = updates.custom_fields;
|
|
1806
1871
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
try {
|
|
1810
|
-
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1811
|
-
} catch {
|
|
1812
|
-
}
|
|
1813
|
-
return Object.keys(fromUrl).length > 0 ? fromUrl : void 0;
|
|
1872
|
+
if (updates.price_variant_id !== void 0) {
|
|
1873
|
+
cart[index].price_variant_id = updates.price_variant_id;
|
|
1814
1874
|
}
|
|
1815
|
-
if (
|
|
1816
|
-
|
|
1817
|
-
return fromUrl;
|
|
1875
|
+
if (updates.price_data !== void 0) {
|
|
1876
|
+
cart[index].price_data = updates.price_data;
|
|
1818
1877
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
// ../sdk/src/core/page-path.ts
|
|
1824
|
-
var MAX_PATH_LENGTH = 256;
|
|
1825
|
-
function resolveStorefrontPagePath() {
|
|
1826
|
-
if (typeof window === "undefined") return void 0;
|
|
1827
|
-
let pathname;
|
|
1828
|
-
try {
|
|
1829
|
-
const location = window.location;
|
|
1830
|
-
pathname = typeof location?.pathname === "string" && location.pathname ? location.pathname : new URL(String(location?.href ?? "")).pathname;
|
|
1831
|
-
} catch {
|
|
1832
|
-
return void 0;
|
|
1878
|
+
if (updates.pay_what_you_want_price !== void 0) {
|
|
1879
|
+
cart[index].pay_what_you_want_price = updates.pay_what_you_want_price;
|
|
1833
1880
|
}
|
|
1834
|
-
if (
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
// ../sdk/src/core/page-tracker.ts
|
|
1839
|
-
var PLATFORM_PAGE_TRACKER_FLAG = "__shoppexPageTracker";
|
|
1840
|
-
function isPlatformPageTrackerActive() {
|
|
1841
|
-
if (typeof window === "undefined") return false;
|
|
1842
|
-
try {
|
|
1843
|
-
return Boolean(window[PLATFORM_PAGE_TRACKER_FLAG]);
|
|
1844
|
-
} catch {
|
|
1845
|
-
return false;
|
|
1881
|
+
if (updates.variant_title !== void 0) {
|
|
1882
|
+
if (updates.variant_title === null) delete cart[index].variant_title;
|
|
1883
|
+
else cart[index].variant_title = updates.variant_title;
|
|
1846
1884
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
|
|
1851
|
-
var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
|
|
1852
|
-
var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
|
|
1853
|
-
var CART_CLAIM_TTL_MS = 1e4;
|
|
1854
|
-
function confirmedCartKey(storeSlug) {
|
|
1855
|
-
return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1856
|
-
}
|
|
1857
|
-
function inFlightCartKey(storeSlug) {
|
|
1858
|
-
return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
1859
|
-
}
|
|
1860
|
-
function cartSequenceKey(storeSlug) {
|
|
1861
|
-
return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
|
|
1862
|
-
}
|
|
1863
|
-
function cartSnapshot(cartValue, itemCount) {
|
|
1864
|
-
return `${cartValue ?? ""}|${itemCount ?? ""}`;
|
|
1865
|
-
}
|
|
1866
|
-
function isCartSequence(value) {
|
|
1867
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1868
|
-
}
|
|
1869
|
-
var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
|
|
1870
|
-
function readCartCounter(storeSlug) {
|
|
1871
|
-
const counter = getItem(cartSequenceKey(storeSlug));
|
|
1872
|
-
if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
|
|
1873
|
-
return null;
|
|
1885
|
+
if (updates.max_quantity !== void 0) {
|
|
1886
|
+
if (updates.max_quantity === null) delete cart[index].max_quantity;
|
|
1887
|
+
else cart[index].max_quantity = updates.max_quantity;
|
|
1874
1888
|
}
|
|
1875
|
-
|
|
1889
|
+
if (updates.min_quantity !== void 0) {
|
|
1890
|
+
if (updates.min_quantity === null) delete cart[index].min_quantity;
|
|
1891
|
+
else cart[index].min_quantity = updates.min_quantity;
|
|
1892
|
+
}
|
|
1893
|
+
clampQuantityToBounds(cart[index]);
|
|
1894
|
+
const nextLineId = computeCartLineId(cart[index]);
|
|
1895
|
+
const duplicateIndex = cart.findIndex((item, itemIndex) => itemIndex !== index && item.line_id === nextLineId);
|
|
1896
|
+
if (duplicateIndex >= 0) {
|
|
1897
|
+
cart[duplicateIndex].quantity += cart[index].quantity;
|
|
1898
|
+
clampQuantityToBounds(cart[duplicateIndex]);
|
|
1899
|
+
cart.splice(index, 1);
|
|
1900
|
+
} else {
|
|
1901
|
+
cart[index].line_id = nextLineId;
|
|
1902
|
+
}
|
|
1903
|
+
if (JSON.stringify(cart) === before) return;
|
|
1904
|
+
writeCart(cart);
|
|
1876
1905
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
return order2;
|
|
1906
|
+
function removeFromCart(lineId) {
|
|
1907
|
+
const normalizedLineId = lineId.trim();
|
|
1908
|
+
if (!normalizedLineId) {
|
|
1909
|
+
throw new CartError("line_id is required");
|
|
1910
|
+
}
|
|
1911
|
+
const cart = getCart();
|
|
1912
|
+
const filtered = cart.filter((item) => item.line_id !== normalizedLineId);
|
|
1913
|
+
writeCart(filtered);
|
|
1886
1914
|
}
|
|
1887
|
-
function
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1915
|
+
function clearCart() {
|
|
1916
|
+
removeCartStorage("cart");
|
|
1917
|
+
removeCartStorage("meta");
|
|
1918
|
+
clearCartCoupon();
|
|
1919
|
+
clearLatestQuoteToken();
|
|
1891
1920
|
}
|
|
1892
|
-
function
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1921
|
+
function createCartBackup() {
|
|
1922
|
+
const cart = getCart();
|
|
1923
|
+
writeCartStorage("cartBackup", cart);
|
|
1924
|
+
const cartCode = getStoredCartCode();
|
|
1925
|
+
if (cartCode) {
|
|
1926
|
+
writeCartStorage("couponBackup", cartCode);
|
|
1927
|
+
} else {
|
|
1928
|
+
removeCartStorage("couponBackup");
|
|
1929
|
+
}
|
|
1930
|
+
const metadata = getCartMetadata();
|
|
1931
|
+
if (metadata) {
|
|
1932
|
+
writeCartStorage("metaBackup", metadata);
|
|
1933
|
+
} else {
|
|
1934
|
+
const now = Date.now();
|
|
1935
|
+
writeCartStorage("metaBackup", {
|
|
1936
|
+
created_at: now,
|
|
1937
|
+
last_modified: now,
|
|
1938
|
+
version: 1,
|
|
1939
|
+
checksum: computeChecksum(cart)
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1897
1942
|
}
|
|
1898
|
-
function
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1943
|
+
function restoreCartFromBackup() {
|
|
1944
|
+
const backupRaw = readCartStorage("cartBackup");
|
|
1945
|
+
const backup = normalizeCartItems(backupRaw);
|
|
1946
|
+
const backupMeta = readCartStorage("metaBackup");
|
|
1947
|
+
const backupCoupon = readCartStorage("couponBackup");
|
|
1948
|
+
if (backup && backup.length > 0) {
|
|
1949
|
+
setCartWithMetadata(backup, backupMeta);
|
|
1950
|
+
if (backupCoupon && typeof backupCoupon === "object" && !Array.isArray(backupCoupon)) {
|
|
1951
|
+
const record2 = backupCoupon;
|
|
1952
|
+
const source = record2.source === "COUPON" || record2.source === "AFFILIATE" ? record2.source : null;
|
|
1953
|
+
if (typeof record2.code === "string" && source) {
|
|
1954
|
+
setCartCoupon(record2.code, source);
|
|
1955
|
+
} else {
|
|
1956
|
+
clearCartCoupon();
|
|
1957
|
+
}
|
|
1958
|
+
} else {
|
|
1959
|
+
clearCartCoupon();
|
|
1960
|
+
}
|
|
1961
|
+
return true;
|
|
1962
|
+
}
|
|
1963
|
+
return false;
|
|
1902
1964
|
}
|
|
1903
|
-
function
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
|
|
1965
|
+
function mergeBaskets(items) {
|
|
1966
|
+
const cart = getCart();
|
|
1967
|
+
for (const incoming of items) {
|
|
1968
|
+
const productId = typeof incoming.product_id === "string" ? incoming.product_id.trim() : "";
|
|
1969
|
+
const variantId = typeof incoming.variant_id === "string" ? incoming.variant_id.trim() : "";
|
|
1970
|
+
if (!productId || !variantId) {
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
let normalizedQuantity;
|
|
1974
|
+
try {
|
|
1975
|
+
normalizedQuantity = normalizeQuantity(incoming.quantity);
|
|
1976
|
+
} catch {
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (normalizedQuantity < 1) {
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
const mergedLine = ensureCartLineId({
|
|
1983
|
+
...incoming,
|
|
1984
|
+
line_id: incoming.line_id ?? computeCartLineId(incoming),
|
|
1985
|
+
product_id: productId,
|
|
1986
|
+
variant_id: variantId,
|
|
1987
|
+
quantity: normalizedQuantity
|
|
1988
|
+
});
|
|
1989
|
+
const index = cart.findIndex((item) => item.line_id === mergedLine.line_id);
|
|
1990
|
+
if (index >= 0) {
|
|
1991
|
+
cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
|
|
1992
|
+
clampQuantityToBounds(cart[index]);
|
|
1993
|
+
} else {
|
|
1994
|
+
clampQuantityToBounds(mergedLine);
|
|
1995
|
+
cart.push(mergedLine);
|
|
1996
|
+
}
|
|
1907
1997
|
}
|
|
1908
|
-
|
|
1998
|
+
writeCart(cart);
|
|
1999
|
+
return cart;
|
|
1909
2000
|
}
|
|
1910
|
-
function
|
|
1911
|
-
const
|
|
1912
|
-
const
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2001
|
+
function moveBasketItem(fromProductId, fromVariantId, toProductId, toVariantId) {
|
|
2002
|
+
const cart = getCart();
|
|
2003
|
+
const fromIndex = cart.findIndex(
|
|
2004
|
+
(item) => item.product_id === fromProductId && item.variant_id === fromVariantId
|
|
2005
|
+
);
|
|
2006
|
+
if (fromIndex < 0) {
|
|
2007
|
+
throw new CartError("Item not found in cart");
|
|
2008
|
+
}
|
|
2009
|
+
const [fromItem] = cart.splice(fromIndex, 1);
|
|
2010
|
+
const toLineId = computeCartLineId({
|
|
2011
|
+
...fromItem,
|
|
2012
|
+
product_id: toProductId,
|
|
2013
|
+
variant_id: toVariantId
|
|
2014
|
+
});
|
|
2015
|
+
const toIndex = cart.findIndex((item) => item.line_id === toLineId);
|
|
2016
|
+
if (toIndex >= 0) {
|
|
2017
|
+
cart[toIndex].quantity += fromItem.quantity;
|
|
2018
|
+
clampQuantityToBounds(cart[toIndex]);
|
|
2019
|
+
} else {
|
|
2020
|
+
const moved = ensureCartLineId({
|
|
2021
|
+
...fromItem,
|
|
2022
|
+
product_id: toProductId,
|
|
2023
|
+
variant_id: toVariantId,
|
|
2024
|
+
line_id: toLineId
|
|
2025
|
+
});
|
|
2026
|
+
clampQuantityToBounds(moved);
|
|
2027
|
+
cart.push(moved);
|
|
2028
|
+
}
|
|
2029
|
+
writeCart(cart);
|
|
1916
2030
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
|
|
2031
|
+
function validateCartIntegrity() {
|
|
2032
|
+
const cart = getCart();
|
|
2033
|
+
const metadata = getCartMetadata();
|
|
2034
|
+
const checksum = computeChecksum(cart);
|
|
2035
|
+
if (!metadata) {
|
|
2036
|
+
setCartWithMetadata(cart, null);
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
return metadata.checksum === checksum;
|
|
1920
2040
|
}
|
|
1921
|
-
function
|
|
1922
|
-
const
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2041
|
+
function getCartStats() {
|
|
2042
|
+
const cart = getCart();
|
|
2043
|
+
const integrityValid = validateCartIntegrity();
|
|
2044
|
+
const metadata = getCartMetadata();
|
|
2045
|
+
const backup = readCartStorage("cartBackup") ?? [];
|
|
2046
|
+
const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
|
|
2047
|
+
const totalPrice = cart.reduce((sum, item) => {
|
|
2048
|
+
const unitPrice = item.price_data?.unit_price ?? 0;
|
|
2049
|
+
return sum + roundPayableAmount(unitPrice * item.quantity);
|
|
2050
|
+
}, 0);
|
|
2051
|
+
return {
|
|
2052
|
+
item_count: cart.length,
|
|
2053
|
+
total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
|
|
2054
|
+
last_modified: metadata?.last_modified ?? 0,
|
|
2055
|
+
version: metadata?.version ?? 0,
|
|
2056
|
+
has_backup: backup.length > 0,
|
|
2057
|
+
integrity_valid: integrityValid,
|
|
2058
|
+
total_price: roundPayableAmount(totalPrice),
|
|
2059
|
+
total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
|
|
1931
2060
|
};
|
|
1932
2061
|
}
|
|
1933
|
-
|
|
1934
|
-
var latestCartRequest = 0;
|
|
1935
|
-
function enqueueCartSend(send) {
|
|
1936
|
-
const request2 = ++latestCartRequest;
|
|
1937
|
-
const run = () => send(() => request2 === latestCartRequest);
|
|
1938
|
-
cartSendTail = cartSendTail.then(run, run);
|
|
1939
|
-
return cartSendTail;
|
|
1940
|
-
}
|
|
1941
|
-
async function postPing(body) {
|
|
2062
|
+
function getCartPayload(coupon) {
|
|
1942
2063
|
const config2 = getConfig();
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
}, CART_CLAIM_TTL_MS);
|
|
1950
|
-
});
|
|
1951
|
-
try {
|
|
1952
|
-
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
|
|
1953
|
-
storeSlug: config2.storeSlug
|
|
1954
|
-
});
|
|
1955
|
-
const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
|
|
1956
|
-
method: "POST",
|
|
1957
|
-
headers: { "Content-Type": "application/json" },
|
|
1958
|
-
body: JSON.stringify(body),
|
|
1959
|
-
signal: controller?.signal
|
|
1960
|
-
}).then((response) => response.ok);
|
|
1961
|
-
return await Promise.race([delivered, timedOut]);
|
|
1962
|
-
} catch {
|
|
1963
|
-
return false;
|
|
1964
|
-
} finally {
|
|
1965
|
-
clearTimeout(timer);
|
|
1966
|
-
}
|
|
2064
|
+
const normalizedCoupon = normalizeCouponCode(coupon) ?? getCartCoupon() ?? void 0;
|
|
2065
|
+
return {
|
|
2066
|
+
store_slug: config2.storeSlug,
|
|
2067
|
+
items: getCart(),
|
|
2068
|
+
coupon: normalizedCoupon
|
|
2069
|
+
};
|
|
1967
2070
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const reservedOrder = carriesCart ? reserveCartOrder(config2.storeSlug) : void 0;
|
|
1974
|
-
if (isPlatformPageTrackerActive()) {
|
|
1975
|
-
if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
|
|
1976
|
-
return;
|
|
2071
|
+
function getAmbientAffiliateCodeForQuote() {
|
|
2072
|
+
const storedCode = getCartCoupon();
|
|
2073
|
+
const storedSource = getCartCouponSource();
|
|
2074
|
+
if (storedSource === "AFFILIATE" && storedCode) {
|
|
2075
|
+
return storedCode.trim().toLowerCase();
|
|
1977
2076
|
}
|
|
1978
|
-
|
|
1979
|
-
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
1980
|
-
const settle2 = order2 ? claimCartReport(config2.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
1981
|
-
const sessionId = analyticsSessionId(config2.storeSlug, { touch: true });
|
|
1982
|
-
const previousSession = currentPageSession();
|
|
1983
|
-
if (previousSession && previousSession !== sessionId) reportPageLeave();
|
|
1984
|
-
markPageViewed(sessionId);
|
|
1985
|
-
if (sessionId) installEngagedClock(reportPageLeave);
|
|
1986
|
-
onAnalyticsConsent(startSessionOnConsent);
|
|
1987
|
-
const delivered = await postPing({
|
|
1988
|
-
referer: document.referrer || void 0,
|
|
1989
|
-
cart_value: cartValue,
|
|
1990
|
-
item_count: itemCount,
|
|
1991
|
-
cart_seq: order2?.seq,
|
|
1992
|
-
cart_seq_scope: order2?.scope,
|
|
1993
|
-
session_id: sessionId ?? void 0,
|
|
1994
|
-
// Time on screen of the page before this one (client-side navigation).
|
|
1995
|
-
engaged_ms: sessionId ? takeEngagedMs() || void 0 : void 0,
|
|
1996
|
-
path: resolveStorefrontPagePath(),
|
|
1997
|
-
utm: resolveUtmParameters(config2.storeSlug)
|
|
1998
|
-
});
|
|
1999
|
-
settle2?.(delivered);
|
|
2000
|
-
};
|
|
2001
|
-
await (reservedOrder ? enqueueCartSend(sendView) : sendView());
|
|
2077
|
+
return getAffiliateCode();
|
|
2002
2078
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
const reservedOrder = reserveCartOrder(getConfig().storeSlug);
|
|
2008
|
-
await enqueueCartSend(async (isLatest) => {
|
|
2009
|
-
const order2 = await reservedOrder;
|
|
2010
|
-
if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
|
|
2011
|
-
await sendCartActivity(order2, cartValue, itemCount);
|
|
2012
|
-
});
|
|
2079
|
+
var latestQuoteToken = null;
|
|
2080
|
+
var quoteSequence = 0;
|
|
2081
|
+
function getLatestQuoteToken() {
|
|
2082
|
+
return latestQuoteToken;
|
|
2013
2083
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
const delivered = await postPing({
|
|
2018
|
-
event_type: "presence",
|
|
2019
|
-
cart_value: cartValue,
|
|
2020
|
-
item_count: itemCount,
|
|
2021
|
-
cart_seq: order2.seq,
|
|
2022
|
-
cart_seq_scope: order2.scope,
|
|
2023
|
-
// A cart marks the analytics session as one that did not bounce.
|
|
2024
|
-
session_id: analyticsSessionId(config2.storeSlug, { touch: false }) ?? void 0,
|
|
2025
|
-
path: resolveStorefrontPagePath()
|
|
2026
|
-
});
|
|
2027
|
-
settle2(delivered);
|
|
2084
|
+
function clearLatestQuoteToken() {
|
|
2085
|
+
latestQuoteToken = null;
|
|
2086
|
+
quoteSequence += 1;
|
|
2028
2087
|
}
|
|
2029
|
-
function
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2032
|
-
const
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
session_id: sessionId,
|
|
2040
|
-
path: resolveStorefrontPagePath(),
|
|
2041
|
-
utm: resolveUtmParameters(config2.storeSlug)
|
|
2088
|
+
async function quoteCart(coupon, currency) {
|
|
2089
|
+
const sequence = quoteSequence += 1;
|
|
2090
|
+
const payload = getCartPayload(coupon);
|
|
2091
|
+
const normalizedCurrency = normalizeRequestedCurrency(currency) ?? resolveBuyerCurrency(getConfig().currency);
|
|
2092
|
+
const response = await post("/v1/storefront/cart/quote", {
|
|
2093
|
+
shop_slug: payload.store_slug,
|
|
2094
|
+
cart: payload.items,
|
|
2095
|
+
coupon: payload.coupon,
|
|
2096
|
+
affiliate_code: getAmbientAffiliateCodeForQuote() ?? void 0,
|
|
2097
|
+
currency: normalizedCurrency ?? void 0
|
|
2042
2098
|
});
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
const sessionId = currentPageSession();
|
|
2047
|
-
const engagedMs2 = takeEngagedMs();
|
|
2048
|
-
if (!sessionId || engagedMs2 === 0 || readAnalyticsConsent() !== "granted") return;
|
|
2049
|
-
try {
|
|
2050
|
-
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", { storeSlug: config2.storeSlug });
|
|
2051
|
-
void fetch(`${config2.apiBaseUrl}${endpoint}`, {
|
|
2052
|
-
method: "POST",
|
|
2053
|
-
headers: { "Content-Type": "application/json" },
|
|
2054
|
-
body: JSON.stringify({ event_type: "page_leave", session_id: sessionId, engaged_ms: engagedMs2 }),
|
|
2055
|
-
keepalive: true
|
|
2056
|
-
}).catch(() => {
|
|
2057
|
-
});
|
|
2058
|
-
} catch {
|
|
2099
|
+
if (sequence !== quoteSequence) return response;
|
|
2100
|
+
if (response.success) {
|
|
2101
|
+
latestQuoteToken = typeof response.data?.quote_token === "string" ? response.data.quote_token : null;
|
|
2059
2102
|
}
|
|
2103
|
+
return response;
|
|
2060
2104
|
}
|
|
2061
2105
|
|
|
2062
2106
|
// ../sdk/src/storefront-storage-sdk.ts
|
|
@@ -2102,7 +2146,8 @@ var storefrontStorageSdk = {
|
|
|
2102
2146
|
trackAffiliateEvent,
|
|
2103
2147
|
// Analytics
|
|
2104
2148
|
trackPageView,
|
|
2105
|
-
trackCartActivity
|
|
2149
|
+
trackCartActivity,
|
|
2150
|
+
trackProductView
|
|
2106
2151
|
};
|
|
2107
2152
|
|
|
2108
2153
|
// ../../node_modules/.bun/openapi-fetch@0.17.0/node_modules/openapi-fetch/dist/index.mjs
|
|
@@ -2911,15 +2956,21 @@ async function getCategories() {
|
|
|
2911
2956
|
}
|
|
2912
2957
|
|
|
2913
2958
|
// ../contracts/src/redirect-link-template.ts
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2959
|
+
function isInsideDoubleBrace(raw) {
|
|
2960
|
+
return raw.startsWith("{{");
|
|
2961
|
+
}
|
|
2962
|
+
function placeholderMatches(template, patterns) {
|
|
2963
|
+
return [
|
|
2964
|
+
...template.matchAll(patterns.double),
|
|
2965
|
+
...[...template.matchAll(patterns.single)].filter((match) => !isInsideDoubleBrace(match[0]))
|
|
2966
|
+
];
|
|
2967
|
+
}
|
|
2968
|
+
var REMAINING_PLACEHOLDER_PATTERNS = {
|
|
2969
|
+
double: /\{\{[\s\S]*?\}\}/g,
|
|
2970
|
+
single: /\{?\{[^{}]+\}(?!\})/g
|
|
2971
|
+
};
|
|
2918
2972
|
function findDanglingBraceFragments(template) {
|
|
2919
|
-
|
|
2920
|
-
for (const pattern of REMAINING_PLACEHOLDER_PATTERNS) {
|
|
2921
|
-
stripped = stripped.replace(pattern, "");
|
|
2922
|
-
}
|
|
2973
|
+
const stripped = template.replace(REMAINING_PLACEHOLDER_PATTERNS.double, "").replace(REMAINING_PLACEHOLDER_PATTERNS.single, (raw) => isInsideDoubleBrace(raw) ? raw : "");
|
|
2923
2974
|
return /[{}]/.test(stripped) ? [...new Set(stripped.match(/[{}]+/g) ?? [])] : [];
|
|
2924
2975
|
}
|
|
2925
2976
|
function findRemainingProductRedirectPlaceholders(template) {
|
|
@@ -2927,10 +2978,8 @@ function findRemainingProductRedirectPlaceholders(template) {
|
|
|
2927
2978
|
return [];
|
|
2928
2979
|
}
|
|
2929
2980
|
const remaining = /* @__PURE__ */ new Set();
|
|
2930
|
-
for (const
|
|
2931
|
-
|
|
2932
|
-
remaining.add(match[0]);
|
|
2933
|
-
}
|
|
2981
|
+
for (const match of placeholderMatches(template, REMAINING_PLACEHOLDER_PATTERNS)) {
|
|
2982
|
+
remaining.add(match[0]);
|
|
2934
2983
|
}
|
|
2935
2984
|
for (const fragment of findDanglingBraceFragments(template)) {
|
|
2936
2985
|
remaining.add(fragment);
|
|
@@ -24349,6 +24398,27 @@ var customerPortalAffiliateLedgerStatusSchema = external_exports.enum([
|
|
|
24349
24398
|
"split",
|
|
24350
24399
|
"voided"
|
|
24351
24400
|
]);
|
|
24401
|
+
var customerPortalAffiliateCommissionBreakdownSchema = external_exports.object({
|
|
24402
|
+
lines: external_exports.array(external_exports.object({
|
|
24403
|
+
product_id: external_exports.string().nullable(),
|
|
24404
|
+
title: external_exports.string(),
|
|
24405
|
+
base_usd: external_exports.string(),
|
|
24406
|
+
percent: external_exports.number(),
|
|
24407
|
+
source: external_exports.enum(["PRODUCT_DISABLED", "LINK", "AFFILIATE", "TIER", "PRODUCT", "SHOP_DEFAULT"]),
|
|
24408
|
+
amount_usd: external_exports.string()
|
|
24409
|
+
})),
|
|
24410
|
+
base_usd: external_exports.string(),
|
|
24411
|
+
gross_usd: external_exports.string(),
|
|
24412
|
+
cap_usd: nullableStringSchema,
|
|
24413
|
+
cap_applied: external_exports.boolean(),
|
|
24414
|
+
minimum_order_usd: nullableStringSchema,
|
|
24415
|
+
minimum_met: external_exports.boolean(),
|
|
24416
|
+
shareback_usd: external_exports.string(),
|
|
24417
|
+
amount_usd: external_exports.string(),
|
|
24418
|
+
tier_name: nullableStringSchema,
|
|
24419
|
+
attribution_source: external_exports.enum(["CODE", "BINDING"]),
|
|
24420
|
+
bound_at: nullableStringSchema
|
|
24421
|
+
});
|
|
24352
24422
|
var customerPortalAffiliateSummarySchema = external_exports.object({
|
|
24353
24423
|
customer: external_exports.object({
|
|
24354
24424
|
id: external_exports.string(),
|
|
@@ -24368,6 +24438,9 @@ var customerPortalAffiliateSummarySchema = external_exports.object({
|
|
|
24368
24438
|
address: external_exports.string()
|
|
24369
24439
|
}).nullable(),
|
|
24370
24440
|
balance_currency: external_exports.string(),
|
|
24441
|
+
// The shop's IANA zone; `bound_at` is a day on its calendar.
|
|
24442
|
+
// Optional until the backend that sends it is deployed.
|
|
24443
|
+
time_zone: external_exports.string().optional(),
|
|
24371
24444
|
balances: external_exports.object({
|
|
24372
24445
|
pending: external_exports.string(),
|
|
24373
24446
|
available: external_exports.string(),
|
|
@@ -24391,6 +24464,7 @@ var customerPortalAffiliateSummarySchema = external_exports.object({
|
|
|
24391
24464
|
store_credit_amount: external_exports.string(),
|
|
24392
24465
|
base_amount: external_exports.string(),
|
|
24393
24466
|
discount_amount: external_exports.string(),
|
|
24467
|
+
breakdown: customerPortalAffiliateCommissionBreakdownSchema.nullable(),
|
|
24394
24468
|
status: external_exports.string(),
|
|
24395
24469
|
created_at: nullableStringSchema,
|
|
24396
24470
|
paid_at: nullableStringSchema,
|
|
@@ -24457,7 +24531,10 @@ var customerPortalSubscriptionSummarySchema = external_exports.object({
|
|
|
24457
24531
|
can_cancel: external_exports.boolean(),
|
|
24458
24532
|
can_undo_cancel: external_exports.boolean(),
|
|
24459
24533
|
can_manage_payment_method: external_exports.boolean()
|
|
24460
|
-
})
|
|
24534
|
+
}),
|
|
24535
|
+
// The open renewal's hosted checkout ("Pay now"). Optional until the backend
|
|
24536
|
+
// that sends it is deployed: a storefront script can reach buyers first.
|
|
24537
|
+
renewal_checkout_url: nullableStringSchema.optional()
|
|
24461
24538
|
});
|
|
24462
24539
|
var customerLoyaltySchema = external_exports.object({
|
|
24463
24540
|
enabled: external_exports.boolean(),
|
|
@@ -24566,7 +24643,17 @@ var customerPortalDashboardSchema = external_exports.object({
|
|
|
24566
24643
|
label: external_exports.string(),
|
|
24567
24644
|
value: external_exports.string(),
|
|
24568
24645
|
href: nullableStringSchema,
|
|
24569
|
-
created_at: external_exports.string()
|
|
24646
|
+
created_at: external_exports.string(),
|
|
24647
|
+
// Files only: downloads left, end date and why a file closed (BuyerFileAccess).
|
|
24648
|
+
// Optional until the backend that sends it is deployed.
|
|
24649
|
+
access: external_exports.object({
|
|
24650
|
+
state: external_exports.enum(["open", "limit_reached", "expired", "revoked", "refunded"]),
|
|
24651
|
+
downloads_left: external_exports.number().nullable(),
|
|
24652
|
+
download_limit: external_exports.number().nullable(),
|
|
24653
|
+
downloads_used: external_exports.number(),
|
|
24654
|
+
ends_at: nullableStringSchema,
|
|
24655
|
+
time_zone: external_exports.string()
|
|
24656
|
+
}).nullable().optional()
|
|
24570
24657
|
})),
|
|
24571
24658
|
tickets: external_exports.array(external_exports.object({
|
|
24572
24659
|
uniqid: external_exports.string(),
|
|
@@ -24576,6 +24663,22 @@ var customerPortalDashboardSchema = external_exports.object({
|
|
|
24576
24663
|
created_at: nullableStringSchema
|
|
24577
24664
|
}))
|
|
24578
24665
|
});
|
|
24666
|
+
var customerPortalOrderReviewSchema = external_exports.object({
|
|
24667
|
+
can_review: external_exports.boolean(),
|
|
24668
|
+
already_submitted: external_exports.boolean(),
|
|
24669
|
+
feedback_uniqid: nullableStringSchema,
|
|
24670
|
+
product_title: nullableStringSchema,
|
|
24671
|
+
score: external_exports.union([external_exports.literal(1), external_exports.literal(3), external_exports.literal(5)]).nullable(),
|
|
24672
|
+
is_automated: external_exports.boolean(),
|
|
24673
|
+
message: nullableStringSchema,
|
|
24674
|
+
submitted_at: nullableStringSchema,
|
|
24675
|
+
reviewer_name: nullableStringSchema,
|
|
24676
|
+
anonymous: external_exports.boolean(),
|
|
24677
|
+
// The hosted review on the checkout host, null unless `can_review`. A storefront
|
|
24678
|
+
// account links here and never posts (#1152). Optional until the backend that
|
|
24679
|
+
// sends it is deployed.
|
|
24680
|
+
review_url: nullableStringSchema.optional()
|
|
24681
|
+
});
|
|
24579
24682
|
var customerPortalNotificationPreferencesSchema = external_exports.object({
|
|
24580
24683
|
order_created_emails: external_exports.boolean(),
|
|
24581
24684
|
order_completed_emails: external_exports.boolean(),
|
|
@@ -24722,6 +24825,9 @@ function order(id) {
|
|
|
24722
24825
|
schema: customerPortalInvoiceDetailSchema
|
|
24723
24826
|
});
|
|
24724
24827
|
}
|
|
24828
|
+
function orderReview(uniqid) {
|
|
24829
|
+
return requestCustomer(`/invoices/${encodeURIComponent(uniqid)}/review`, { schema: customerPortalOrderReviewSchema });
|
|
24830
|
+
}
|
|
24725
24831
|
async function invoicePdf(id) {
|
|
24726
24832
|
try {
|
|
24727
24833
|
const response = await fetch(`${CUSTOMER_API_PREFIX}/invoices/${encodeURIComponent(id)}/pdf`, {
|
|
@@ -24854,19 +24960,29 @@ function createTicket(payload) {
|
|
|
24854
24960
|
body: {
|
|
24855
24961
|
...payload.title !== void 0 ? { title: payload.title } : {},
|
|
24856
24962
|
message: payload.message,
|
|
24857
|
-
...payload.invoice_id !== void 0 ? { invoice_id: payload.invoice_id } : {}
|
|
24963
|
+
...payload.invoice_id !== void 0 ? { invoice_id: payload.invoice_id } : {},
|
|
24964
|
+
...payload.attachment_ids?.length ? { attachment_ids: payload.attachment_ids } : {}
|
|
24858
24965
|
}
|
|
24859
24966
|
});
|
|
24860
24967
|
}
|
|
24861
24968
|
function ticket(uniqid) {
|
|
24862
24969
|
return requestCustomer(`/tickets/${encodeURIComponent(uniqid)}`);
|
|
24863
24970
|
}
|
|
24864
|
-
function replyToTicket(uniqid, message) {
|
|
24971
|
+
function replyToTicket(uniqid, message, attachmentIds = []) {
|
|
24865
24972
|
return requestCustomer(`/tickets/${encodeURIComponent(uniqid)}/reply`, {
|
|
24866
24973
|
method: "POST",
|
|
24867
|
-
body: { message }
|
|
24974
|
+
body: attachmentIds.length > 0 ? { message, attachment_ids: attachmentIds } : { message }
|
|
24868
24975
|
});
|
|
24869
24976
|
}
|
|
24977
|
+
function uploadTicketImage(file2, ticket2) {
|
|
24978
|
+
const form = new FormData();
|
|
24979
|
+
form.append("file", file2);
|
|
24980
|
+
if (ticket2) form.append("ticket", ticket2);
|
|
24981
|
+
return requestCustomer("/ticket-attachments", { method: "POST", formData: form });
|
|
24982
|
+
}
|
|
24983
|
+
function discardTicketImage(id) {
|
|
24984
|
+
return requestCustomer(`/ticket-attachments/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
24985
|
+
}
|
|
24870
24986
|
function updateProfile(patch) {
|
|
24871
24987
|
return requestCustomer("/profile", {
|
|
24872
24988
|
method: "POST",
|
|
@@ -24977,6 +25093,7 @@ var shoppex = {
|
|
|
24977
25093
|
redeemLoyaltyPoints,
|
|
24978
25094
|
warranties,
|
|
24979
25095
|
claimWarranty,
|
|
25096
|
+
orderReview,
|
|
24980
25097
|
resetLicenseHwid,
|
|
24981
25098
|
subscriptionBillingHistory,
|
|
24982
25099
|
cancelSubscription,
|
|
@@ -24992,6 +25109,8 @@ var shoppex = {
|
|
|
24992
25109
|
createTicket,
|
|
24993
25110
|
ticket,
|
|
24994
25111
|
replyToTicket,
|
|
25112
|
+
uploadTicketImage,
|
|
25113
|
+
discardTicketImage,
|
|
24995
25114
|
updateProfile,
|
|
24996
25115
|
updateAvatar,
|
|
24997
25116
|
removeAvatar,
|
|
@@ -25049,6 +25168,7 @@ if (typeof window !== "undefined") {
|
|
|
25049
25168
|
createAffiliateLink,
|
|
25050
25169
|
createTicket,
|
|
25051
25170
|
dashboard,
|
|
25171
|
+
discardTicketImage,
|
|
25052
25172
|
emailPreferences,
|
|
25053
25173
|
enrollAsReseller,
|
|
25054
25174
|
ensureCartLineId,
|
|
@@ -25080,6 +25200,7 @@ if (typeof window !== "undefined") {
|
|
|
25080
25200
|
normalizeStorefrontCustomFields,
|
|
25081
25201
|
notificationPreferences,
|
|
25082
25202
|
order,
|
|
25203
|
+
orderReview,
|
|
25083
25204
|
orders,
|
|
25084
25205
|
pauseSubscription,
|
|
25085
25206
|
productMatchesSearchQuery,
|
|
@@ -25116,11 +25237,13 @@ if (typeof window !== "undefined") {
|
|
|
25116
25237
|
touchStorefrontPresence,
|
|
25117
25238
|
trackCartActivity,
|
|
25118
25239
|
trackPageView,
|
|
25240
|
+
trackProductView,
|
|
25119
25241
|
undoSubscriptionCancel,
|
|
25120
25242
|
updateAvatar,
|
|
25121
25243
|
updateEmailPreferences,
|
|
25122
25244
|
updateNotificationPreferences,
|
|
25123
25245
|
updateProfile,
|
|
25246
|
+
uploadTicketImage,
|
|
25124
25247
|
validateStorefrontCustomFieldValue,
|
|
25125
25248
|
verifyOtp,
|
|
25126
25249
|
warranties
|