@shoppexio/storefront 1.0.84 → 1.0.85

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.85
4
+
5
+ - Cart reporting keeps two shared values: the cart state the server confirmed (written only after a 2xx response) and a short-lived in-flight claim that lets another tab skip the `storage` echo while a send runs. Only the newest claim settles: a send whose claim was replaced confirms and clears nothing, and a failed send (network error, non-OK response, or no answer within 10 seconds) writes nothing, so an unsent cart can never look reported. A tab sends its cart states one at a time and in order, dropping a waiting state once a newer one is asked for, so an older cart cannot reach the server after a newer one. Whether a change is already reported is decided when its turn comes, so reverting to the confirmed cart while another state is being sent still goes out.
6
+ - On code storefronts, `trackPageView(cartValue, itemCount)` passes a partial cart through unchanged instead of filling the missing field with 0.
7
+ - Storefront client-error reports cut `endpoint`/`message` to 500 and `page_url`/`request_url` to 2000 characters, the bounds the ping endpoint enforces, so a long page URL no longer gets the report rejected.
8
+
3
9
  ## 1.0.84
4
10
 
5
11
  - `touchStorefrontPresence()` sends `event_type: "presence"`. The server refreshes the live session but no longer counts the ping as a shop view, so an idle tab with `sx-online-users` (or any theme that touches presence on a timer) stops adding a view every few minutes.
package/dist/index.cjs CHANGED
@@ -23870,6 +23870,8 @@ function removeItem(key) {
23870
23870
  // ../sdk/src/core/telemetry.ts
23871
23871
  var CONNECTION_ID_STORAGE_PREFIX = "presence_connection_";
23872
23872
  var CLIENT_ERROR_DEDUPE_WINDOW_MS = 15e3;
23873
+ var CLIENT_ERROR_TEXT_MAX_LENGTH = 500;
23874
+ var CLIENT_ERROR_URL_MAX_LENGTH = 2e3;
23873
23875
  function createPresenceConnectionId() {
23874
23876
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
23875
23877
  return crypto.randomUUID();
@@ -23920,14 +23922,14 @@ async function reportStorefrontClientError(payload) {
23920
23922
  client_error: {
23921
23923
  source: payload.source ?? "sdk",
23922
23924
  phase: payload.phase ?? "request",
23923
- endpoint: payload.endpoint,
23925
+ endpoint: payload.endpoint.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
23924
23926
  method: payload.method,
23925
- message: payload.message,
23927
+ message: payload.message.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
23926
23928
  status_code: payload.statusCode,
23927
23929
  attempt_count: payload.attemptCount,
23928
23930
  response_received: payload.responseReceived ?? false,
23929
- page_url: payload.pageUrl ?? window.location.href,
23930
- request_url: payload.requestUrl,
23931
+ page_url: (payload.pageUrl ?? window.location.href).slice(0, CLIENT_ERROR_URL_MAX_LENGTH),
23932
+ request_url: payload.requestUrl?.slice(0, CLIENT_ERROR_URL_MAX_LENGTH),
23931
23933
  online: payload.online ?? (typeof navigator !== "undefined" ? navigator.onLine : void 0),
23932
23934
  visibility_state: payload.visibilityState ?? (typeof document !== "undefined" ? document.visibilityState : void 0)
23933
23935
  }
@@ -26635,72 +26637,122 @@ function isPlatformPageTrackerActive() {
26635
26637
  }
26636
26638
 
26637
26639
  // ../sdk/src/modules/analytics.ts
26638
- var REPORTED_CART_STORAGE_PREFIX = "reported_cart_";
26639
- function reportedCartKey(storeSlug) {
26640
- return `${REPORTED_CART_STORAGE_PREFIX}${storeSlug}`;
26640
+ var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
26641
+ var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
26642
+ var CART_CLAIM_TTL_MS = 1e4;
26643
+ function confirmedCartKey(storeSlug) {
26644
+ return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
26645
+ }
26646
+ function inFlightCartKey(storeSlug) {
26647
+ return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
26641
26648
  }
26642
26649
  function cartSnapshot(cartValue, itemCount) {
26643
26650
  return `${cartValue ?? ""}|${itemCount ?? ""}`;
26644
26651
  }
26645
- function rememberReportedCart(storeSlug, cartValue, itemCount) {
26646
- setItem(reportedCartKey(storeSlug), cartSnapshot(cartValue, itemCount));
26647
- }
26648
- async function trackPageView(cartValue, itemCount) {
26649
- if (!isInitialized()) return;
26650
- if (typeof document === "undefined") return;
26651
- const carriesCart = cartValue !== void 0 || itemCount !== void 0;
26652
- if (isPlatformPageTrackerActive()) {
26653
- if (carriesCart) await sendCartActivity(cartValue ?? 0, itemCount ?? 0);
26654
- return;
26652
+ function readInFlightCart(storeSlug) {
26653
+ const claim2 = getItem(inFlightCartKey(storeSlug));
26654
+ if (!claim2 || typeof claim2.snapshot !== "string" || typeof claim2.token !== "string" || typeof claim2.at !== "number") {
26655
+ return null;
26655
26656
  }
26657
+ return claim2;
26658
+ }
26659
+ function isCartReportedOrInFlight(storeSlug, snapshot) {
26660
+ if (getItem(confirmedCartKey(storeSlug)) === snapshot) return true;
26661
+ const claim2 = readInFlightCart(storeSlug);
26662
+ return claim2 !== null && claim2.snapshot === snapshot && Date.now() - claim2.at < CART_CLAIM_TTL_MS;
26663
+ }
26664
+ function createClaimToken() {
26665
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
26666
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
26667
+ }
26668
+ function claimCartReport(storeSlug, cartValue, itemCount) {
26669
+ const snapshot = cartSnapshot(cartValue, itemCount);
26670
+ const token = createClaimToken();
26671
+ setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
26672
+ return (delivered) => {
26673
+ if (readInFlightCart(storeSlug)?.token !== token) return;
26674
+ if (delivered) setItem(confirmedCartKey(storeSlug), snapshot);
26675
+ removeItem(inFlightCartKey(storeSlug));
26676
+ };
26677
+ }
26678
+ var cartSendTail = Promise.resolve();
26679
+ var latestCartRequest = 0;
26680
+ function enqueueCartSend(send) {
26681
+ const request2 = ++latestCartRequest;
26682
+ const run = () => send(() => request2 === latestCartRequest);
26683
+ cartSendTail = cartSendTail.then(run, run);
26684
+ return cartSendTail;
26685
+ }
26686
+ async function postPing(body) {
26656
26687
  const config2 = getConfig();
26657
- const connectionId = getStorefrontConnectionId(config2.storeSlug);
26658
- if (carriesCart) rememberReportedCart(config2.storeSlug, cartValue, itemCount);
26688
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
26689
+ let timer;
26690
+ const timedOut = new Promise((resolve) => {
26691
+ timer = setTimeout(() => {
26692
+ controller?.abort();
26693
+ resolve(false);
26694
+ }, CART_CLAIM_TTL_MS);
26695
+ });
26659
26696
  try {
26660
26697
  const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
26661
26698
  storeSlug: config2.storeSlug
26662
26699
  });
26663
- await fetch(`${config2.apiBaseUrl}${endpoint}`, {
26700
+ const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
26664
26701
  method: "POST",
26665
26702
  headers: { "Content-Type": "application/json" },
26666
- body: JSON.stringify({
26667
- referer: document.referrer || void 0,
26668
- cart_value: cartValue ?? void 0,
26669
- item_count: itemCount ?? void 0,
26670
- connection_id: connectionId ?? void 0,
26671
- path: resolveStorefrontPagePath(),
26672
- utm: resolveUtmParameters(config2.storeSlug)
26673
- })
26674
- });
26703
+ body: JSON.stringify(body),
26704
+ signal: controller?.signal
26705
+ }).then((response) => response.ok);
26706
+ return await Promise.race([delivered, timedOut]);
26675
26707
  } catch {
26708
+ return false;
26709
+ } finally {
26710
+ clearTimeout(timer);
26676
26711
  }
26677
26712
  }
26713
+ async function trackPageView(cartValue, itemCount) {
26714
+ if (!isInitialized()) return;
26715
+ if (typeof document === "undefined") return;
26716
+ const carriesCart = cartValue !== void 0 || itemCount !== void 0;
26717
+ if (isPlatformPageTrackerActive()) {
26718
+ if (carriesCart) await enqueueCartSend(() => sendCartActivity(cartValue, itemCount));
26719
+ return;
26720
+ }
26721
+ const config2 = getConfig();
26722
+ const sendView = async () => {
26723
+ const settle2 = carriesCart ? claimCartReport(config2.storeSlug, cartValue, itemCount) : void 0;
26724
+ const delivered = await postPing({
26725
+ referer: document.referrer || void 0,
26726
+ cart_value: cartValue,
26727
+ item_count: itemCount,
26728
+ connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26729
+ path: resolveStorefrontPagePath(),
26730
+ utm: resolveUtmParameters(config2.storeSlug)
26731
+ });
26732
+ settle2?.(delivered);
26733
+ };
26734
+ await (carriesCart ? enqueueCartSend(sendView) : sendView());
26735
+ }
26678
26736
  async function trackCartActivity(cartValue, itemCount) {
26679
26737
  if (!isInitialized()) return;
26680
26738
  if (typeof document === "undefined") return;
26681
- if (getItem(reportedCartKey(getConfig().storeSlug)) === cartSnapshot(cartValue, itemCount)) return;
26682
- await sendCartActivity(cartValue, itemCount);
26739
+ const snapshot = cartSnapshot(cartValue, itemCount);
26740
+ await enqueueCartSend(async (isLatest) => {
26741
+ if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
26742
+ await sendCartActivity(cartValue, itemCount);
26743
+ });
26683
26744
  }
26684
26745
  async function sendCartActivity(cartValue, itemCount) {
26685
26746
  const config2 = getConfig();
26686
- rememberReportedCart(config2.storeSlug, cartValue, itemCount);
26687
- try {
26688
- const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
26689
- storeSlug: config2.storeSlug
26690
- });
26691
- await fetch(`${config2.apiBaseUrl}${endpoint}`, {
26692
- method: "POST",
26693
- headers: { "Content-Type": "application/json" },
26694
- body: JSON.stringify({
26695
- event_type: "presence",
26696
- cart_value: cartValue,
26697
- item_count: itemCount,
26698
- connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26699
- path: resolveStorefrontPagePath()
26700
- })
26701
- });
26702
- } catch {
26703
- }
26747
+ const settle2 = claimCartReport(config2.storeSlug, cartValue, itemCount);
26748
+ const delivered = await postPing({
26749
+ event_type: "presence",
26750
+ cart_value: cartValue,
26751
+ item_count: itemCount,
26752
+ connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26753
+ path: resolveStorefrontPagePath()
26754
+ });
26755
+ settle2(delivered);
26704
26756
  }
26705
26757
 
26706
26758
  // ../sdk/src/modules/presence.ts