@shoppexio/storefront 1.0.84 → 1.0.87

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.87
4
+
5
+ - `getPage()`, `getPages()` and `getShopReviewsPage()` keep the transport `status` (and `code`) on a failure, like the other modules, so a storefront can tell a page that does not exist (404) from an API it could not reach.
6
+ - `getShopReviewsPage(cursor, { product })` narrows a page to one product by its public id (the catalog `id`). The page's `summary` then counts that product's reviews only, so a product's review list can page on demand with a correct breakdown.
7
+
8
+ ## 1.0.85
9
+
10
+ - 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.
11
+ - On code storefronts, `trackPageView(cartValue, itemCount)` passes a partial cart through unchanged instead of filling the missing field with 0.
12
+ - 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.
13
+
3
14
  ## 1.0.84
4
15
 
5
16
  - `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/README.md CHANGED
@@ -151,6 +151,8 @@ await refreshLiveUsers();
151
151
  setInterval(refreshLiveUsers, 30_000);
152
152
  ```
153
153
 
154
+ `getShopReviewsPage(cursor, { product })` pages the shop's reviews on demand; pass a product's `id` to page only its reviews, with a `summary` for that product.
155
+
154
156
  `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
157
 
156
158
  ## Not this package
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
  }
@@ -25922,13 +25924,17 @@ function normalizeFeedback(raw, index) {
25922
25924
  created_at
25923
25925
  };
25924
25926
  }
25925
- async function getShopReviewsPage(cursor) {
25927
+ async function getShopReviewsPage(cursor, options = {}) {
25926
25928
  const config2 = getConfig();
25927
25929
  const query = new URLSearchParams();
25928
25930
  query.set("limit", String(REVIEWS_PAGE_LIMIT));
25929
25931
  if (typeof cursor === "string" && cursor.trim().length > 0) {
25930
25932
  query.set("cursor", cursor);
25931
25933
  }
25934
+ const product = typeof options.product === "string" ? options.product.trim() : "";
25935
+ if (product) {
25936
+ query.set("product", product);
25937
+ }
25932
25938
  const querySuffix = `?${query.toString()}`;
25933
25939
  const response = await get(
25934
25940
  `${buildEndpoint("/v1/storefront/feedback/shop/:storeSlug", {
@@ -25936,7 +25942,7 @@ async function getShopReviewsPage(cursor) {
25936
25942
  })}${querySuffix}`,
25937
25943
  {
25938
25944
  cache: {
25939
- key: `reviews:${config2.storeSlug}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
25945
+ key: `reviews:${config2.storeSlug}:${product || "all"}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
25940
25946
  ttl: REVIEWS_CACHE_TTL,
25941
25947
  staleWhileRevalidate: true
25942
25948
  }
@@ -25951,10 +25957,7 @@ async function getShopReviewsPage(cursor) {
25951
25957
  }
25952
25958
  };
25953
25959
  }
25954
- return {
25955
- success: false,
25956
- message: response.message
25957
- };
25960
+ return forwardFailure(response);
25958
25961
  }
25959
25962
  async function getShopReviews() {
25960
25963
  const allFeedback = [];
@@ -26424,10 +26427,7 @@ async function getPages() {
26424
26427
  data: response.data.pages
26425
26428
  };
26426
26429
  }
26427
- return {
26428
- success: false,
26429
- message: response.message
26430
- };
26430
+ return forwardFailure(response);
26431
26431
  }
26432
26432
  async function getPage(slug) {
26433
26433
  const config2 = getConfig();
@@ -26450,10 +26450,7 @@ async function getPage(slug) {
26450
26450
  data: response.data.page
26451
26451
  };
26452
26452
  }
26453
- return {
26454
- success: false,
26455
- message: response.message
26456
- };
26453
+ return forwardFailure(response);
26457
26454
  }
26458
26455
 
26459
26456
  // ../sdk/src/modules/navigation.ts
@@ -26635,72 +26632,122 @@ function isPlatformPageTrackerActive() {
26635
26632
  }
26636
26633
 
26637
26634
  // ../sdk/src/modules/analytics.ts
26638
- var REPORTED_CART_STORAGE_PREFIX = "reported_cart_";
26639
- function reportedCartKey(storeSlug) {
26640
- return `${REPORTED_CART_STORAGE_PREFIX}${storeSlug}`;
26635
+ var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
26636
+ var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
26637
+ var CART_CLAIM_TTL_MS = 1e4;
26638
+ function confirmedCartKey(storeSlug) {
26639
+ return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
26640
+ }
26641
+ function inFlightCartKey(storeSlug) {
26642
+ return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
26641
26643
  }
26642
26644
  function cartSnapshot(cartValue, itemCount) {
26643
26645
  return `${cartValue ?? ""}|${itemCount ?? ""}`;
26644
26646
  }
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;
26647
+ function readInFlightCart(storeSlug) {
26648
+ const claim2 = getItem(inFlightCartKey(storeSlug));
26649
+ if (!claim2 || typeof claim2.snapshot !== "string" || typeof claim2.token !== "string" || typeof claim2.at !== "number") {
26650
+ return null;
26655
26651
  }
26652
+ return claim2;
26653
+ }
26654
+ function isCartReportedOrInFlight(storeSlug, snapshot) {
26655
+ if (getItem(confirmedCartKey(storeSlug)) === snapshot) return true;
26656
+ const claim2 = readInFlightCart(storeSlug);
26657
+ return claim2 !== null && claim2.snapshot === snapshot && Date.now() - claim2.at < CART_CLAIM_TTL_MS;
26658
+ }
26659
+ function createClaimToken() {
26660
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
26661
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
26662
+ }
26663
+ function claimCartReport(storeSlug, cartValue, itemCount) {
26664
+ const snapshot = cartSnapshot(cartValue, itemCount);
26665
+ const token = createClaimToken();
26666
+ setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
26667
+ return (delivered) => {
26668
+ if (readInFlightCart(storeSlug)?.token !== token) return;
26669
+ if (delivered) setItem(confirmedCartKey(storeSlug), snapshot);
26670
+ removeItem(inFlightCartKey(storeSlug));
26671
+ };
26672
+ }
26673
+ var cartSendTail = Promise.resolve();
26674
+ var latestCartRequest = 0;
26675
+ function enqueueCartSend(send) {
26676
+ const request2 = ++latestCartRequest;
26677
+ const run = () => send(() => request2 === latestCartRequest);
26678
+ cartSendTail = cartSendTail.then(run, run);
26679
+ return cartSendTail;
26680
+ }
26681
+ async function postPing(body) {
26656
26682
  const config2 = getConfig();
26657
- const connectionId = getStorefrontConnectionId(config2.storeSlug);
26658
- if (carriesCart) rememberReportedCart(config2.storeSlug, cartValue, itemCount);
26683
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
26684
+ let timer;
26685
+ const timedOut = new Promise((resolve) => {
26686
+ timer = setTimeout(() => {
26687
+ controller?.abort();
26688
+ resolve(false);
26689
+ }, CART_CLAIM_TTL_MS);
26690
+ });
26659
26691
  try {
26660
26692
  const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
26661
26693
  storeSlug: config2.storeSlug
26662
26694
  });
26663
- await fetch(`${config2.apiBaseUrl}${endpoint}`, {
26695
+ const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
26664
26696
  method: "POST",
26665
26697
  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
- });
26698
+ body: JSON.stringify(body),
26699
+ signal: controller?.signal
26700
+ }).then((response) => response.ok);
26701
+ return await Promise.race([delivered, timedOut]);
26675
26702
  } catch {
26703
+ return false;
26704
+ } finally {
26705
+ clearTimeout(timer);
26676
26706
  }
26677
26707
  }
26708
+ async function trackPageView(cartValue, itemCount) {
26709
+ if (!isInitialized()) return;
26710
+ if (typeof document === "undefined") return;
26711
+ const carriesCart = cartValue !== void 0 || itemCount !== void 0;
26712
+ if (isPlatformPageTrackerActive()) {
26713
+ if (carriesCart) await enqueueCartSend(() => sendCartActivity(cartValue, itemCount));
26714
+ return;
26715
+ }
26716
+ const config2 = getConfig();
26717
+ const sendView = async () => {
26718
+ const settle2 = carriesCart ? claimCartReport(config2.storeSlug, cartValue, itemCount) : void 0;
26719
+ const delivered = await postPing({
26720
+ referer: document.referrer || void 0,
26721
+ cart_value: cartValue,
26722
+ item_count: itemCount,
26723
+ connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26724
+ path: resolveStorefrontPagePath(),
26725
+ utm: resolveUtmParameters(config2.storeSlug)
26726
+ });
26727
+ settle2?.(delivered);
26728
+ };
26729
+ await (carriesCart ? enqueueCartSend(sendView) : sendView());
26730
+ }
26678
26731
  async function trackCartActivity(cartValue, itemCount) {
26679
26732
  if (!isInitialized()) return;
26680
26733
  if (typeof document === "undefined") return;
26681
- if (getItem(reportedCartKey(getConfig().storeSlug)) === cartSnapshot(cartValue, itemCount)) return;
26682
- await sendCartActivity(cartValue, itemCount);
26734
+ const snapshot = cartSnapshot(cartValue, itemCount);
26735
+ await enqueueCartSend(async (isLatest) => {
26736
+ if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
26737
+ await sendCartActivity(cartValue, itemCount);
26738
+ });
26683
26739
  }
26684
26740
  async function sendCartActivity(cartValue, itemCount) {
26685
26741
  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
- }
26742
+ const settle2 = claimCartReport(config2.storeSlug, cartValue, itemCount);
26743
+ const delivered = await postPing({
26744
+ event_type: "presence",
26745
+ cart_value: cartValue,
26746
+ item_count: itemCount,
26747
+ connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
26748
+ path: resolveStorefrontPagePath()
26749
+ });
26750
+ settle2(delivered);
26704
26751
  }
26705
26752
 
26706
26753
  // ../sdk/src/modules/presence.ts