@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/dist/index.d.cts CHANGED
@@ -1701,7 +1701,14 @@ declare function validateCoupon(code: string, productOrOptions?: string | Coupon
1701
1701
  * Note: Shoppex has shop-level feedback, not product-level reviews.
1702
1702
  */
1703
1703
 
1704
- declare function getShopReviewsPage(cursor?: string | null): Promise<SDKResponse<ShopReviewsPage>>;
1704
+ interface ShopReviewsPageOptions {
1705
+ /**
1706
+ * One product's reviews, by the product's public id (the `id` the catalog
1707
+ * carries). The page's `summary` then counts that product's reviews only.
1708
+ */
1709
+ product?: string | null;
1710
+ }
1711
+ declare function getShopReviewsPage(cursor?: string | null, options?: ShopReviewsPageOptions): Promise<SDKResponse<ShopReviewsPage>>;
1705
1712
  declare function getShopReviews(): Promise<SDKResponse<Feedback[]>>;
1706
1713
 
1707
1714
  /**
@@ -1916,9 +1923,14 @@ declare function trackPageView(cartValue?: number, itemCount?: number): Promise<
1916
1923
  /**
1917
1924
  * Reports a cart change for the live visitor map without counting a page view
1918
1925
  * (`event_type: 'presence'`). Use it when the cart changes on a page that was
1919
- * already counted. A cart state that any tab of this shop already reported is
1920
- * not sent again, so a cross-tab `storage` echo of a change is not a second
1921
- * ping.
1926
+ * already counted. A cart state the server already acknowledged, or that a tab
1927
+ * of this shop is sending right now, is not sent again, so a cross-tab `storage`
1928
+ * echo of a change is not a second ping. A failed send leaves nothing behind:
1929
+ * the same state goes out again on the next change or page load.
1930
+ *
1931
+ * The check runs when the change's turn comes, after this tab's earlier sends
1932
+ * settled, never at call time: a revert to the confirmed cart while another
1933
+ * state is on the wire must still go out once that send confirms its state.
1922
1934
  */
1923
1935
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1924
1936
 
package/dist/index.d.ts CHANGED
@@ -1701,7 +1701,14 @@ declare function validateCoupon(code: string, productOrOptions?: string | Coupon
1701
1701
  * Note: Shoppex has shop-level feedback, not product-level reviews.
1702
1702
  */
1703
1703
 
1704
- declare function getShopReviewsPage(cursor?: string | null): Promise<SDKResponse<ShopReviewsPage>>;
1704
+ interface ShopReviewsPageOptions {
1705
+ /**
1706
+ * One product's reviews, by the product's public id (the `id` the catalog
1707
+ * carries). The page's `summary` then counts that product's reviews only.
1708
+ */
1709
+ product?: string | null;
1710
+ }
1711
+ declare function getShopReviewsPage(cursor?: string | null, options?: ShopReviewsPageOptions): Promise<SDKResponse<ShopReviewsPage>>;
1705
1712
  declare function getShopReviews(): Promise<SDKResponse<Feedback[]>>;
1706
1713
 
1707
1714
  /**
@@ -1916,9 +1923,14 @@ declare function trackPageView(cartValue?: number, itemCount?: number): Promise<
1916
1923
  /**
1917
1924
  * Reports a cart change for the live visitor map without counting a page view
1918
1925
  * (`event_type: 'presence'`). Use it when the cart changes on a page that was
1919
- * already counted. A cart state that any tab of this shop already reported is
1920
- * not sent again, so a cross-tab `storage` echo of a change is not a second
1921
- * ping.
1926
+ * already counted. A cart state the server already acknowledged, or that a tab
1927
+ * of this shop is sending right now, is not sent again, so a cross-tab `storage`
1928
+ * echo of a change is not a second ping. A failed send leaves nothing behind:
1929
+ * the same state goes out again on the next change or page load.
1930
+ *
1931
+ * The check runs when the change's turn comes, after this tab's earlier sends
1932
+ * settled, never at call time: a revert to the confirmed cart while another
1933
+ * state is on the wire must still go out once that send confirms its state.
1922
1934
  */
1923
1935
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1924
1936
 
package/dist/index.js CHANGED
@@ -3318,6 +3318,8 @@ function removeItem(key) {
3318
3318
  // ../sdk/src/core/telemetry.ts
3319
3319
  var CONNECTION_ID_STORAGE_PREFIX = "presence_connection_";
3320
3320
  var CLIENT_ERROR_DEDUPE_WINDOW_MS = 15e3;
3321
+ var CLIENT_ERROR_TEXT_MAX_LENGTH = 500;
3322
+ var CLIENT_ERROR_URL_MAX_LENGTH = 2e3;
3321
3323
  function createPresenceConnectionId() {
3322
3324
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3323
3325
  return crypto.randomUUID();
@@ -3368,14 +3370,14 @@ async function reportStorefrontClientError(payload) {
3368
3370
  client_error: {
3369
3371
  source: payload.source ?? "sdk",
3370
3372
  phase: payload.phase ?? "request",
3371
- endpoint: payload.endpoint,
3373
+ endpoint: payload.endpoint.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
3372
3374
  method: payload.method,
3373
- message: payload.message,
3375
+ message: payload.message.slice(0, CLIENT_ERROR_TEXT_MAX_LENGTH),
3374
3376
  status_code: payload.statusCode,
3375
3377
  attempt_count: payload.attemptCount,
3376
3378
  response_received: payload.responseReceived ?? false,
3377
- page_url: payload.pageUrl ?? window.location.href,
3378
- request_url: payload.requestUrl,
3379
+ page_url: (payload.pageUrl ?? window.location.href).slice(0, CLIENT_ERROR_URL_MAX_LENGTH),
3380
+ request_url: payload.requestUrl?.slice(0, CLIENT_ERROR_URL_MAX_LENGTH),
3379
3381
  online: payload.online ?? (typeof navigator !== "undefined" ? navigator.onLine : void 0),
3380
3382
  visibility_state: payload.visibilityState ?? (typeof document !== "undefined" ? document.visibilityState : void 0)
3381
3383
  }
@@ -5370,13 +5372,17 @@ function normalizeFeedback(raw, index) {
5370
5372
  created_at
5371
5373
  };
5372
5374
  }
5373
- async function getShopReviewsPage(cursor) {
5375
+ async function getShopReviewsPage(cursor, options = {}) {
5374
5376
  const config = getConfig();
5375
5377
  const query = new URLSearchParams();
5376
5378
  query.set("limit", String(REVIEWS_PAGE_LIMIT));
5377
5379
  if (typeof cursor === "string" && cursor.trim().length > 0) {
5378
5380
  query.set("cursor", cursor);
5379
5381
  }
5382
+ const product = typeof options.product === "string" ? options.product.trim() : "";
5383
+ if (product) {
5384
+ query.set("product", product);
5385
+ }
5380
5386
  const querySuffix = `?${query.toString()}`;
5381
5387
  const response = await get(
5382
5388
  `${buildEndpoint("/v1/storefront/feedback/shop/:storeSlug", {
@@ -5384,7 +5390,7 @@ async function getShopReviewsPage(cursor) {
5384
5390
  })}${querySuffix}`,
5385
5391
  {
5386
5392
  cache: {
5387
- key: `reviews:${config.storeSlug}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
5393
+ key: `reviews:${config.storeSlug}:${product || "all"}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
5388
5394
  ttl: REVIEWS_CACHE_TTL,
5389
5395
  staleWhileRevalidate: true
5390
5396
  }
@@ -5399,10 +5405,7 @@ async function getShopReviewsPage(cursor) {
5399
5405
  }
5400
5406
  };
5401
5407
  }
5402
- return {
5403
- success: false,
5404
- message: response.message
5405
- };
5408
+ return forwardFailure(response);
5406
5409
  }
5407
5410
  async function getShopReviews() {
5408
5411
  const allFeedback = [];
@@ -5872,10 +5875,7 @@ async function getPages() {
5872
5875
  data: response.data.pages
5873
5876
  };
5874
5877
  }
5875
- return {
5876
- success: false,
5877
- message: response.message
5878
- };
5878
+ return forwardFailure(response);
5879
5879
  }
5880
5880
  async function getPage(slug) {
5881
5881
  const config = getConfig();
@@ -5898,10 +5898,7 @@ async function getPage(slug) {
5898
5898
  data: response.data.page
5899
5899
  };
5900
5900
  }
5901
- return {
5902
- success: false,
5903
- message: response.message
5904
- };
5901
+ return forwardFailure(response);
5905
5902
  }
5906
5903
 
5907
5904
  // ../sdk/src/modules/navigation.ts
@@ -6083,72 +6080,122 @@ function isPlatformPageTrackerActive() {
6083
6080
  }
6084
6081
 
6085
6082
  // ../sdk/src/modules/analytics.ts
6086
- var REPORTED_CART_STORAGE_PREFIX = "reported_cart_";
6087
- function reportedCartKey(storeSlug) {
6088
- return `${REPORTED_CART_STORAGE_PREFIX}${storeSlug}`;
6083
+ var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
6084
+ var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
6085
+ var CART_CLAIM_TTL_MS = 1e4;
6086
+ function confirmedCartKey(storeSlug) {
6087
+ return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
6088
+ }
6089
+ function inFlightCartKey(storeSlug) {
6090
+ return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
6089
6091
  }
6090
6092
  function cartSnapshot(cartValue, itemCount) {
6091
6093
  return `${cartValue ?? ""}|${itemCount ?? ""}`;
6092
6094
  }
6093
- function rememberReportedCart(storeSlug, cartValue, itemCount) {
6094
- setItem(reportedCartKey(storeSlug), cartSnapshot(cartValue, itemCount));
6095
- }
6096
- async function trackPageView(cartValue, itemCount) {
6097
- if (!isInitialized()) return;
6098
- if (typeof document === "undefined") return;
6099
- const carriesCart = cartValue !== void 0 || itemCount !== void 0;
6100
- if (isPlatformPageTrackerActive()) {
6101
- if (carriesCart) await sendCartActivity(cartValue ?? 0, itemCount ?? 0);
6102
- return;
6095
+ function readInFlightCart(storeSlug) {
6096
+ const claim = getItem(inFlightCartKey(storeSlug));
6097
+ if (!claim || typeof claim.snapshot !== "string" || typeof claim.token !== "string" || typeof claim.at !== "number") {
6098
+ return null;
6103
6099
  }
6100
+ return claim;
6101
+ }
6102
+ function isCartReportedOrInFlight(storeSlug, snapshot) {
6103
+ if (getItem(confirmedCartKey(storeSlug)) === snapshot) return true;
6104
+ const claim = readInFlightCart(storeSlug);
6105
+ return claim !== null && claim.snapshot === snapshot && Date.now() - claim.at < CART_CLAIM_TTL_MS;
6106
+ }
6107
+ function createClaimToken() {
6108
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
6109
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
6110
+ }
6111
+ function claimCartReport(storeSlug, cartValue, itemCount) {
6112
+ const snapshot = cartSnapshot(cartValue, itemCount);
6113
+ const token = createClaimToken();
6114
+ setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
6115
+ return (delivered) => {
6116
+ if (readInFlightCart(storeSlug)?.token !== token) return;
6117
+ if (delivered) setItem(confirmedCartKey(storeSlug), snapshot);
6118
+ removeItem(inFlightCartKey(storeSlug));
6119
+ };
6120
+ }
6121
+ var cartSendTail = Promise.resolve();
6122
+ var latestCartRequest = 0;
6123
+ function enqueueCartSend(send) {
6124
+ const request2 = ++latestCartRequest;
6125
+ const run = () => send(() => request2 === latestCartRequest);
6126
+ cartSendTail = cartSendTail.then(run, run);
6127
+ return cartSendTail;
6128
+ }
6129
+ async function postPing(body) {
6104
6130
  const config = getConfig();
6105
- const connectionId = getStorefrontConnectionId(config.storeSlug);
6106
- if (carriesCart) rememberReportedCart(config.storeSlug, cartValue, itemCount);
6131
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6132
+ let timer;
6133
+ const timedOut = new Promise((resolve) => {
6134
+ timer = setTimeout(() => {
6135
+ controller?.abort();
6136
+ resolve(false);
6137
+ }, CART_CLAIM_TTL_MS);
6138
+ });
6107
6139
  try {
6108
6140
  const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
6109
6141
  storeSlug: config.storeSlug
6110
6142
  });
6111
- await fetch(`${config.apiBaseUrl}${endpoint}`, {
6143
+ const delivered = fetch(`${config.apiBaseUrl}${endpoint}`, {
6112
6144
  method: "POST",
6113
6145
  headers: { "Content-Type": "application/json" },
6114
- body: JSON.stringify({
6115
- referer: document.referrer || void 0,
6116
- cart_value: cartValue ?? void 0,
6117
- item_count: itemCount ?? void 0,
6118
- connection_id: connectionId ?? void 0,
6119
- path: resolveStorefrontPagePath(),
6120
- utm: resolveUtmParameters(config.storeSlug)
6121
- })
6122
- });
6146
+ body: JSON.stringify(body),
6147
+ signal: controller?.signal
6148
+ }).then((response) => response.ok);
6149
+ return await Promise.race([delivered, timedOut]);
6123
6150
  } catch {
6151
+ return false;
6152
+ } finally {
6153
+ clearTimeout(timer);
6124
6154
  }
6125
6155
  }
6156
+ async function trackPageView(cartValue, itemCount) {
6157
+ if (!isInitialized()) return;
6158
+ if (typeof document === "undefined") return;
6159
+ const carriesCart = cartValue !== void 0 || itemCount !== void 0;
6160
+ if (isPlatformPageTrackerActive()) {
6161
+ if (carriesCart) await enqueueCartSend(() => sendCartActivity(cartValue, itemCount));
6162
+ return;
6163
+ }
6164
+ const config = getConfig();
6165
+ const sendView = async () => {
6166
+ const settle = carriesCart ? claimCartReport(config.storeSlug, cartValue, itemCount) : void 0;
6167
+ const delivered = await postPing({
6168
+ referer: document.referrer || void 0,
6169
+ cart_value: cartValue,
6170
+ item_count: itemCount,
6171
+ connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6172
+ path: resolveStorefrontPagePath(),
6173
+ utm: resolveUtmParameters(config.storeSlug)
6174
+ });
6175
+ settle?.(delivered);
6176
+ };
6177
+ await (carriesCart ? enqueueCartSend(sendView) : sendView());
6178
+ }
6126
6179
  async function trackCartActivity(cartValue, itemCount) {
6127
6180
  if (!isInitialized()) return;
6128
6181
  if (typeof document === "undefined") return;
6129
- if (getItem(reportedCartKey(getConfig().storeSlug)) === cartSnapshot(cartValue, itemCount)) return;
6130
- await sendCartActivity(cartValue, itemCount);
6182
+ const snapshot = cartSnapshot(cartValue, itemCount);
6183
+ await enqueueCartSend(async (isLatest) => {
6184
+ if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
6185
+ await sendCartActivity(cartValue, itemCount);
6186
+ });
6131
6187
  }
6132
6188
  async function sendCartActivity(cartValue, itemCount) {
6133
6189
  const config = getConfig();
6134
- rememberReportedCart(config.storeSlug, cartValue, itemCount);
6135
- try {
6136
- const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
6137
- storeSlug: config.storeSlug
6138
- });
6139
- await fetch(`${config.apiBaseUrl}${endpoint}`, {
6140
- method: "POST",
6141
- headers: { "Content-Type": "application/json" },
6142
- body: JSON.stringify({
6143
- event_type: "presence",
6144
- cart_value: cartValue,
6145
- item_count: itemCount,
6146
- connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6147
- path: resolveStorefrontPagePath()
6148
- })
6149
- });
6150
- } catch {
6151
- }
6190
+ const settle = claimCartReport(config.storeSlug, cartValue, itemCount);
6191
+ const delivered = await postPing({
6192
+ event_type: "presence",
6193
+ cart_value: cartValue,
6194
+ item_count: itemCount,
6195
+ connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6196
+ path: resolveStorefrontPagePath()
6197
+ });
6198
+ settle(delivered);
6152
6199
  }
6153
6200
 
6154
6201
  // ../sdk/src/modules/presence.ts