@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/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  customerWarrantyClaimSchema,
10
10
  customerWarrantyListSchema,
11
11
  storefrontStorageKey
12
- } from "./chunk-ESI3HWQF.js";
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
- // ../sdk/src/utils/cart-line-id.ts
859
- function hashString(value) {
860
- let hash = 2166136261;
861
- for (let i = 0; i < value.length; i += 1) {
862
- hash ^= value.charCodeAt(i);
863
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
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
- return (hash >>> 0).toString(16);
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 normalizeAddons(addons) {
868
- if (!addons?.length) return [];
869
- return [...addons].map((addon) => ({ id: addon.id, quantity: addon.quantity ?? 1 })).sort((a, b) => a.id.localeCompare(b.id));
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 normalizeCustomFields(fields) {
872
- if (!fields) return {};
873
- const entries = Object.entries(fields).filter(([, value]) => typeof value === "string").sort(([a], [b]) => a.localeCompare(b));
874
- return Object.fromEntries(entries);
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 computeCartLineId(input) {
877
- const payload = {
878
- product_id: input.product_id,
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 ensureCartLineId(item) {
889
- if (typeof item.line_id === "string" && item.line_id.trim()) {
890
- return item;
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
- return { ...item, line_id: computeCartLineId(item) };
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
- // ../contracts/src/catalog-unit-price.ts
896
- var CATALOG_UNIT_PRICE_DECIMAL_PLACES = 3;
897
- var PAYABLE_AMOUNT_DECIMAL_PLACES = 2;
898
- var CATALOG_UNIT_PRICE_FORMAT_OPTIONS = {
899
- maximumFractionDigits: CATALOG_UNIT_PRICE_DECIMAL_PLACES
900
- };
901
- function roundPayableAmount(value) {
902
- if (!Number.isFinite(value)) return value;
903
- const sign = value < 0 ? -1 : 1;
904
- const shifted = shiftDecimal(Math.abs(value), PAYABLE_AMOUNT_DECIMAL_PLACES);
905
- const rounded = Math.round(shifted);
906
- if (rounded === 0) return 0;
907
- return sign * shiftDecimal(rounded, -PAYABLE_AMOUNT_DECIMAL_PLACES);
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 shiftDecimal(value, places) {
910
- const [coefficient, currentExponent = "0"] = String(value).split("e");
911
- return Number(`${coefficient}e${Number(currentExponent) + places}`);
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/modules/cart.ts
915
- var STORAGE_KEYS = {
916
- cart: "cart",
917
- cartBackup: "cart_backup",
918
- meta: "cart_meta",
919
- metaBackup: "cart_backup_meta",
920
- coupon: "cart_coupon",
921
- couponBackup: "cart_coupon_backup"
922
- };
923
- function getStorageKey(type) {
924
- return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
925
- }
926
- function cartStorageArea() {
927
- return getConfig().preview ? "sessionStorage" : "localStorage";
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 readCartStorage(type) {
930
- return getItem(getStorageKey(type), cartStorageArea());
1010
+ function storageKey(storeSlug) {
1011
+ return `${UTM_STORAGE_PREFIX}${storeSlug}`;
931
1012
  }
932
- function writeCartStorage(type, value) {
933
- setItem(getStorageKey(type), value, cartStorageArea());
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 removeCartStorage(type) {
936
- removeItem(getStorageKey(type), cartStorageArea());
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 hashString2(value) {
939
- let hash = 2166136261;
940
- for (let i = 0; i < value.length; i += 1) {
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
- return (hash >>> 0).toString(16);
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
- function computeChecksum(cart) {
947
- return hashString2(JSON.stringify(cart));
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
- function normalizeQuantity(value) {
950
- if (!Number.isFinite(value)) {
951
- throw new CartError("quantity must be a finite number");
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
- function normalizeCouponCode(value) {
956
- const normalized = value?.trim().toUpperCase();
957
- return normalized ? normalized : null;
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 getStoredCartCode() {
960
- const raw = readCartStorage("coupon");
961
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
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
- const record = raw;
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
- function normalizeCartItems(value) {
970
- if (!Array.isArray(value)) return [];
971
- const normalized = [];
972
- for (const entry of value) {
973
- if (!entry || typeof entry !== "object") continue;
974
- const record = entry;
975
- const productId = typeof record.product_id === "string" ? record.product_id.trim() : "";
976
- const variantId = typeof record.variant_id === "string" ? record.variant_id.trim() : "";
977
- const quantity = Number(record.quantity);
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 getCartMetadata() {
1031
- return readCartStorage("meta");
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 getCartCoupon() {
1034
- return getStoredCartCode()?.code ?? null;
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 getCartCouponSource() {
1037
- return getStoredCartCode()?.source ?? null;
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 setCartCoupon(coupon, source = "COUPON") {
1040
- const normalizedCoupon = normalizeCouponCode(coupon);
1041
- if (!normalizedCoupon) {
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
- writeCartStorage("coupon", { code: normalizedCoupon, source });
1046
- return normalizedCoupon;
1147
+ return claim;
1047
1148
  }
1048
- function clearCartCoupon() {
1049
- removeCartStorage("coupon");
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 writeCart(cart) {
1052
- const normalizedCart = normalizeCartItems(cart);
1053
- writeCartStorage("cart", normalizedCart);
1054
- const now = Date.now();
1055
- const previous = getCartMetadata();
1056
- const nextMeta = {
1057
- created_at: previous?.created_at ?? now,
1058
- last_modified: now,
1059
- version: (previous?.version ?? 0) + 1,
1060
- checksum: computeChecksum(normalizedCart)
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
- writeCartStorage("meta", nextMeta);
1063
- if (normalizedCart.length === 0) {
1064
- clearCartCoupon();
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 setCartWithMetadata(cart, metadata) {
1068
- const normalizedCart = normalizeCartItems(cart);
1069
- writeCartStorage("cart", normalizedCart);
1070
- const now = Date.now();
1071
- const base = metadata ?? getCartMetadata();
1072
- const nextMeta = {
1073
- created_at: base?.created_at ?? now,
1074
- last_modified: now,
1075
- version: base?.version ?? 1,
1076
- checksum: computeChecksum(normalizedCart)
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
- writeCartStorage("meta", nextMeta);
1240
+ await (reservedOrder ? enqueueCartSend(sendView) : sendView());
1079
1241
  }
1080
- function getCart() {
1081
- const raw = readCartStorage("cart");
1082
- return normalizeCartItems(raw);
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 resolveCartLineId(productId, variantId, cart = getCart()) {
1085
- const matches = cart.filter(
1086
- (item) => item.product_id === productId && item.variant_id === variantId
1087
- );
1088
- if (matches.length === 0) {
1089
- throw new CartError(`No cart line found for ${productId}/${variantId}`);
1090
- }
1091
- return matches[0].line_id;
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 getCartItemCount() {
1094
- const cart = getCart();
1095
- return cart.reduce((sum, item) => sum + item.quantity, 0);
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 addToCart(productId, variantId, quantity = 1, options) {
1098
- if (!productId || !variantId) {
1099
- throw new CartError("product_id and variant_id are required");
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
- const normalizedQuantity = normalizeQuantity(quantity);
1102
- if (normalizedQuantity < 1) {
1103
- throw new CartError("quantity must be at least 1");
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
- const cart = getCart();
1106
- const lineId = computeCartLineId({
1107
- product_id: productId,
1108
- variant_id: variantId,
1109
- addons: options?.addons,
1110
- custom_fields: options?.custom_fields,
1111
- price_variant_id: options?.price_variant_id,
1112
- price_data: options?.price_data,
1113
- pay_what_you_want_price: options?.pay_what_you_want_price
1114
- });
1115
- const existingIndex = cart.findIndex((item) => item.line_id === lineId);
1116
- const quantityBeforeAdd = existingIndex >= 0 ? cart[existingIndex].quantity : 0;
1117
- if (existingIndex >= 0) {
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 claimCartReport(storeSlug, seq, cartValue, itemCount) {
1676
- const snapshot = cartSnapshot(cartValue, itemCount);
1677
- const token = createClaimToken();
1678
- setItem(inFlightCartKey(storeSlug), { snapshot, seq, token, at: Date.now() });
1679
- return (delivered) => {
1680
- if (delivered) {
1681
- const confirmed = readConfirmedCart(storeSlug);
1682
- if (!confirmed || seq > confirmed.seq) setItem(confirmedCartKey(storeSlug), { snapshot, seq });
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
- if (readInFlightCart(storeSlug)?.token === token) removeItem(inFlightCartKey(storeSlug));
1685
- };
1831
+ return true;
1832
+ }
1833
+ return false;
1686
1834
  }
1687
- var cartSendTail = Promise.resolve();
1688
- var latestCartRequest = 0;
1689
- function enqueueCartSend(send) {
1690
- const request2 = ++latestCartRequest;
1691
- const run = () => send(() => request2 === latestCartRequest);
1692
- cartSendTail = cartSendTail.then(run, run);
1693
- return cartSendTail;
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
- async function postPing(body) {
1696
- const config = getConfig();
1697
- const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
1698
- let timer;
1699
- const timedOut = new Promise((resolve) => {
1700
- timer = setTimeout(() => {
1701
- controller?.abort();
1702
- resolve(false);
1703
- }, CART_CLAIM_TTL_MS);
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
- try {
1706
- const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
1707
- storeSlug: config.storeSlug
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
- const delivered = fetch(`${config.apiBaseUrl}${endpoint}`, {
1710
- method: "POST",
1711
- headers: { "Content-Type": "application/json" },
1712
- body: JSON.stringify(body),
1713
- signal: controller?.signal
1714
- }).then((response) => response.ok);
1715
- return await Promise.race([delivered, timedOut]);
1716
- } catch {
1717
- return false;
1718
- } finally {
1719
- clearTimeout(timer);
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
- async function trackPageView(cartValue, itemCount) {
1723
- if (!isInitialized()) return;
1724
- if (typeof document === "undefined") return;
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 carriesCart = cartValue !== void 0 || itemCount !== void 0;
1727
- const reservedOrder = carriesCart ? reserveCartOrder(config.storeSlug) : void 0;
1728
- if (isPlatformPageTrackerActive()) {
1729
- if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
1730
- return;
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
- async function trackCartActivity(cartValue, itemCount) {
1750
- if (!isInitialized()) return;
1751
- if (typeof document === "undefined") return;
1752
- const snapshot = cartSnapshot(cartValue, itemCount);
1753
- const reservedOrder = reserveCartOrder(getConfig().storeSlug);
1754
- await enqueueCartSend(async (isLatest) => {
1755
- const order2 = await reservedOrder;
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
- async function sendCartActivity(order2, cartValue, itemCount) {
1761
- const config = getConfig();
1762
- const settle = claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount);
1763
- const delivered = await postPing({
1764
- event_type: "presence",
1765
- cart_value: cartValue,
1766
- item_count: itemCount,
1767
- cart_seq: order2.seq,
1768
- cart_seq_scope: order2.scope,
1769
- connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
1770
- path: resolveStorefrontPagePath()
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
- settle(delivered);
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