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