@shoppexio/storefront 1.0.82 → 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,18 @@
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
+
9
+ ## 1.0.84
10
+
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.
12
+ - New `trackCartActivity(cartValue, itemCount)`: reports a cart change for the live visitor map as a presence ping, without counting a page view. A cart state that any tab of the shop already reported is not sent again, so the cross-tab `storage` echo of one change is not a second ping.
13
+ - On code storefronts (platform page tracker active), `trackPageView(cartValue, itemCount)` sends the cart as cart activity instead of a second counted view.
14
+ - `trackPageView()` is otherwise unchanged: one call counts one view.
15
+
3
16
  ## 1.0.82
4
17
 
5
18
  - Page views and presence send the page path (query and fragment dropped, long paths capped), so the dashboard can show the top live page.
package/README.md CHANGED
@@ -151,6 +151,8 @@ await refreshLiveUsers();
151
151
  setInterval(refreshLiveUsers, 30_000);
152
152
  ```
153
153
 
154
+ `touchStorefrontPresence()` keeps the visitor online but never counts as a page view, so it is safe on a timer. Report page views with `trackPageView()` (once per page load or navigation) and later cart changes with `trackCartActivity(cartValue, itemCount)`, which updates the live visitor map without counting a view.
155
+
154
156
  ## Not this package
155
157
 
156
158
  If you want the authenticated Developer API wrapper for backend integrations, use `@shoppexio/sdk`. Do not ship `@shoppexio/sdk` in a browser bundle; use this Storefront SDK for headless storefronts.
package/dist/index.cjs CHANGED
@@ -109,6 +109,7 @@ __export(src_exports, {
109
109
  subscriptionBillingHistory: () => subscriptionBillingHistory,
110
110
  ticket: () => ticket,
111
111
  touchStorefrontPresence: () => touchStorefrontPresence,
112
+ trackCartActivity: () => trackCartActivity,
112
113
  trackPageView: () => trackPageView,
113
114
  updateAvatar: () => updateAvatar,
114
115
  updateEmailPreferences: () => updateEmailPreferences,
@@ -23869,6 +23870,8 @@ function removeItem(key) {
23869
23870
  // ../sdk/src/core/telemetry.ts
23870
23871
  var CONNECTION_ID_STORAGE_PREFIX = "presence_connection_";
23871
23872
  var CLIENT_ERROR_DEDUPE_WINDOW_MS = 15e3;
23873
+ var CLIENT_ERROR_TEXT_MAX_LENGTH = 500;
23874
+ var CLIENT_ERROR_URL_MAX_LENGTH = 2e3;
23872
23875
  function createPresenceConnectionId() {
23873
23876
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
23874
23877
  return crypto.randomUUID();
@@ -23919,14 +23922,14 @@ async function reportStorefrontClientError(payload) {
23919
23922
  client_error: {
23920
23923
  source: payload.source ?? "sdk",
23921
23924
  phase: payload.phase ?? "request",
23922
- endpoint: payload.endpoint,
23925
+ endpoint: payload.endpoint.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
23923
23926
  method: payload.method,
23924
- message: payload.message,
23927
+ message: payload.message.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
23925
23928
  status_code: payload.statusCode,
23926
23929
  attempt_count: payload.attemptCount,
23927
23930
  response_received: payload.responseReceived ?? false,
23928
- page_url: payload.pageUrl ?? window.location.href,
23929
- 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),
23930
23933
  online: payload.online ?? (typeof navigator !== "undefined" ? navigator.onLine : void 0),
23931
23934
  visibility_state: payload.visibilityState ?? (typeof document !== "undefined" ? document.visibilityState : void 0)
23932
23935
  }
@@ -26634,32 +26637,123 @@ function isPlatformPageTrackerActive() {
26634
26637
  }
26635
26638
 
26636
26639
  // ../sdk/src/modules/analytics.ts
26637
- async function trackPageView(cartValue, itemCount) {
26638
- if (!isInitialized()) return;
26639
- if (typeof document === "undefined") return;
26640
- const carriesCart = cartValue !== void 0 || itemCount !== void 0;
26641
- if (isPlatformPageTrackerActive() && !carriesCart) return;
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}`;
26648
+ }
26649
+ function cartSnapshot(cartValue, itemCount) {
26650
+ return `${cartValue ?? ""}|${itemCount ?? ""}`;
26651
+ }
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;
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) {
26642
26687
  const config2 = getConfig();
26643
- const connectionId = getStorefrontConnectionId(config2.storeSlug);
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
+ });
26644
26696
  try {
26645
26697
  const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
26646
26698
  storeSlug: config2.storeSlug
26647
26699
  });
26648
- await fetch(`${config2.apiBaseUrl}${endpoint}`, {
26700
+ const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
26649
26701
  method: "POST",
26650
26702
  headers: { "Content-Type": "application/json" },
26651
- body: JSON.stringify({
26652
- referer: document.referrer || void 0,
26653
- cart_value: cartValue ?? void 0,
26654
- item_count: itemCount ?? void 0,
26655
- connection_id: connectionId ?? void 0,
26656
- path: resolveStorefrontPagePath(),
26657
- utm: resolveUtmParameters(config2.storeSlug)
26658
- })
26659
- });
26703
+ body: JSON.stringify(body),
26704
+ signal: controller?.signal
26705
+ }).then((response) => response.ok);
26706
+ return await Promise.race([delivered, timedOut]);
26660
26707
  } catch {
26708
+ return false;
26709
+ } finally {
26710
+ clearTimeout(timer);
26661
26711
  }
26662
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
+ }
26736
+ async function trackCartActivity(cartValue, itemCount) {
26737
+ if (!isInitialized()) return;
26738
+ if (typeof document === "undefined") return;
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
+ });
26744
+ }
26745
+ async function sendCartActivity(cartValue, itemCount) {
26746
+ const config2 = getConfig();
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);
26756
+ }
26663
26757
 
26664
26758
  // ../sdk/src/modules/presence.ts
26665
26759
  async function getStorefrontRecentSales() {
@@ -26710,6 +26804,7 @@ async function touchStorefrontPresence() {
26710
26804
  return post(
26711
26805
  endpoint,
26712
26806
  {
26807
+ event_type: "presence",
26713
26808
  referer: document.referrer || void 0,
26714
26809
  connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26715
26810
  path: resolveStorefrontPagePath(),
@@ -26929,6 +27024,7 @@ var shoppex = {
26929
27024
  getMenuSlotTitles,
26930
27025
  // Analytics
26931
27026
  trackPageView,
27027
+ trackCartActivity,
26932
27028
  getStorefrontOnlineUsers,
26933
27029
  getStorefrontRecentSales,
26934
27030
  touchStorefrontPresence,
@@ -27042,6 +27138,7 @@ if (typeof window !== "undefined") {
27042
27138
  subscriptionBillingHistory,
27043
27139
  ticket,
27044
27140
  touchStorefrontPresence,
27141
+ trackCartActivity,
27045
27142
  trackPageView,
27046
27143
  updateAvatar,
27047
27144
  updateEmailPreferences,