@shoppexio/storefront 1.0.85 → 1.0.88

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,14 +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 the server already acknowledged, or that a tab
1920
- * of this shop is sending right now, is not sent again, so a cross-tab `storage`
1921
- * echo of a change is not a second ping. A failed send leaves nothing behind:
1922
- * the same state goes out again on the next change or page load.
1926
+ * already counted. A cart state the server holds, or will hold once a send in
1927
+ * flight lands, is not sent again, so a cross-tab `storage` echo of a change is
1928
+ * not a second ping. A failed send leaves nothing behind: the same state goes
1929
+ * out again on the next change or page load.
1923
1930
  *
1924
- * The check runs when the change's turn comes, after this tab's earlier sends
1925
- * settled, never at call time: a revert to the confirmed cart while another
1926
- * state is on the wire must still go out once that send confirms its state.
1931
+ * The sequence is taken at call time; the check runs when the change's turn
1932
+ * comes, after this tab's earlier sends settled: a revert to the confirmed
1933
+ * cart while another state is on the wire must still go out.
1927
1934
  */
1928
1935
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1929
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,14 +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 the server already acknowledged, or that a tab
1920
- * of this shop is sending right now, is not sent again, so a cross-tab `storage`
1921
- * echo of a change is not a second ping. A failed send leaves nothing behind:
1922
- * the same state goes out again on the next change or page load.
1926
+ * already counted. A cart state the server holds, or will hold once a send in
1927
+ * flight lands, is not sent again, so a cross-tab `storage` echo of a change is
1928
+ * not a second ping. A failed send leaves nothing behind: the same state goes
1929
+ * out again on the next change or page load.
1923
1930
  *
1924
- * The check runs when the change's turn comes, after this tab's earlier sends
1925
- * settled, never at call time: a revert to the confirmed cart while another
1926
- * state is on the wire must still go out once that send confirms its state.
1931
+ * The sequence is taken at call time; the check runs when the change's turn
1932
+ * comes, after this tab's earlier sends settled: a revert to the confirmed
1933
+ * cart while another state is on the wire must still go out.
1927
1934
  */
1928
1935
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1929
1936
 
package/dist/index.js CHANGED
@@ -5372,13 +5372,17 @@ function normalizeFeedback(raw, index) {
5372
5372
  created_at
5373
5373
  };
5374
5374
  }
5375
- async function getShopReviewsPage(cursor) {
5375
+ async function getShopReviewsPage(cursor, options = {}) {
5376
5376
  const config = getConfig();
5377
5377
  const query = new URLSearchParams();
5378
5378
  query.set("limit", String(REVIEWS_PAGE_LIMIT));
5379
5379
  if (typeof cursor === "string" && cursor.trim().length > 0) {
5380
5380
  query.set("cursor", cursor);
5381
5381
  }
5382
+ const product = typeof options.product === "string" ? options.product.trim() : "";
5383
+ if (product) {
5384
+ query.set("product", product);
5385
+ }
5382
5386
  const querySuffix = `?${query.toString()}`;
5383
5387
  const response = await get(
5384
5388
  `${buildEndpoint("/v1/storefront/feedback/shop/:storeSlug", {
@@ -5386,7 +5390,7 @@ async function getShopReviewsPage(cursor) {
5386
5390
  })}${querySuffix}`,
5387
5391
  {
5388
5392
  cache: {
5389
- key: `reviews:${config.storeSlug}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
5393
+ key: `reviews:${config.storeSlug}:${product || "all"}:${cursor ?? "start"}:${REVIEWS_PAGE_LIMIT}`,
5390
5394
  ttl: REVIEWS_CACHE_TTL,
5391
5395
  staleWhileRevalidate: true
5392
5396
  }
@@ -5401,10 +5405,7 @@ async function getShopReviewsPage(cursor) {
5401
5405
  }
5402
5406
  };
5403
5407
  }
5404
- return {
5405
- success: false,
5406
- message: response.message
5407
- };
5408
+ return forwardFailure(response);
5408
5409
  }
5409
5410
  async function getShopReviews() {
5410
5411
  const allFeedback = [];
@@ -5874,10 +5875,7 @@ async function getPages() {
5874
5875
  data: response.data.pages
5875
5876
  };
5876
5877
  }
5877
- return {
5878
- success: false,
5879
- message: response.message
5880
- };
5878
+ return forwardFailure(response);
5881
5879
  }
5882
5880
  async function getPage(slug) {
5883
5881
  const config = getConfig();
@@ -5900,10 +5898,7 @@ async function getPage(slug) {
5900
5898
  data: response.data.page
5901
5899
  };
5902
5900
  }
5903
- return {
5904
- success: false,
5905
- message: response.message
5906
- };
5901
+ return forwardFailure(response);
5907
5902
  }
5908
5903
 
5909
5904
  // ../sdk/src/modules/navigation.ts
@@ -6087,6 +6082,7 @@ function isPlatformPageTrackerActive() {
6087
6082
  // ../sdk/src/modules/analytics.ts
6088
6083
  var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
6089
6084
  var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
6085
+ var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
6090
6086
  var CART_CLAIM_TTL_MS = 1e4;
6091
6087
  function confirmedCartKey(storeSlug) {
6092
6088
  return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
@@ -6094,33 +6090,77 @@ function confirmedCartKey(storeSlug) {
6094
6090
  function inFlightCartKey(storeSlug) {
6095
6091
  return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
6096
6092
  }
6093
+ function cartSequenceKey(storeSlug) {
6094
+ return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
6095
+ }
6097
6096
  function cartSnapshot(cartValue, itemCount) {
6098
6097
  return `${cartValue ?? ""}|${itemCount ?? ""}`;
6099
6098
  }
6099
+ function isCartSequence(value) {
6100
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
6101
+ }
6102
+ var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
6103
+ function readCartCounter(storeSlug) {
6104
+ const counter = getItem(cartSequenceKey(storeSlug));
6105
+ if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
6106
+ return null;
6107
+ }
6108
+ return counter;
6109
+ }
6110
+ var lastCartOrder = null;
6111
+ function nextCartOrder(storeSlug) {
6112
+ const stored = readCartCounter(storeSlug);
6113
+ const scope = stored?.scope ?? lastCartOrder?.scope ?? createClaimToken();
6114
+ const floor = Math.max(stored?.seq ?? 0, lastCartOrder?.scope === scope ? lastCartOrder.seq : 0);
6115
+ const order2 = { scope, seq: Math.max(Date.now(), floor + 1) };
6116
+ lastCartOrder = order2;
6117
+ setItem(cartSequenceKey(storeSlug), order2);
6118
+ return order2;
6119
+ }
6120
+ function cartSequenceLocks() {
6121
+ if (typeof navigator === "undefined") return null;
6122
+ const locks = navigator.locks;
6123
+ return locks && typeof locks.request === "function" ? locks : null;
6124
+ }
6125
+ function reserveCartOrder(storeSlug) {
6126
+ const take = () => nextCartOrder(storeSlug);
6127
+ const locks = cartSequenceLocks();
6128
+ if (!locks) return Promise.resolve(take());
6129
+ return locks.request(cartSequenceKey(storeSlug), take).catch(take);
6130
+ }
6131
+ function readConfirmedCart(storeSlug) {
6132
+ const confirmed = getItem(confirmedCartKey(storeSlug));
6133
+ if (!confirmed || typeof confirmed.snapshot !== "string" || !isCartSequence(confirmed.seq)) return null;
6134
+ return confirmed;
6135
+ }
6100
6136
  function readInFlightCart(storeSlug) {
6101
6137
  const claim = getItem(inFlightCartKey(storeSlug));
6102
- if (!claim || typeof claim.snapshot !== "string" || typeof claim.token !== "string" || typeof claim.at !== "number") {
6138
+ if (!claim || typeof claim.snapshot !== "string" || !isCartSequence(claim.seq) || typeof claim.token !== "string" || typeof claim.at !== "number") {
6103
6139
  return null;
6104
6140
  }
6105
6141
  return claim;
6106
6142
  }
6107
6143
  function isCartReportedOrInFlight(storeSlug, snapshot) {
6108
- if (getItem(confirmedCartKey(storeSlug)) === snapshot) return true;
6144
+ const confirmed = readConfirmedCart(storeSlug);
6109
6145
  const claim = readInFlightCart(storeSlug);
6110
- return claim !== null && claim.snapshot === snapshot && Date.now() - claim.at < CART_CLAIM_TTL_MS;
6146
+ const liveClaim = claim && Date.now() - claim.at < CART_CLAIM_TTL_MS ? claim : null;
6147
+ const expected = liveClaim && (!confirmed || liveClaim.seq > confirmed.seq) ? liveClaim : confirmed;
6148
+ return expected?.snapshot === snapshot;
6111
6149
  }
6112
6150
  function createClaimToken() {
6113
6151
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
6114
6152
  return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
6115
6153
  }
6116
- function claimCartReport(storeSlug, cartValue, itemCount) {
6154
+ function claimCartReport(storeSlug, seq, cartValue, itemCount) {
6117
6155
  const snapshot = cartSnapshot(cartValue, itemCount);
6118
6156
  const token = createClaimToken();
6119
- setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
6157
+ setItem(inFlightCartKey(storeSlug), { snapshot, seq, token, at: Date.now() });
6120
6158
  return (delivered) => {
6121
- if (readInFlightCart(storeSlug)?.token !== token) return;
6122
- if (delivered) setItem(confirmedCartKey(storeSlug), snapshot);
6123
- removeItem(inFlightCartKey(storeSlug));
6159
+ if (delivered) {
6160
+ const confirmed = readConfirmedCart(storeSlug);
6161
+ if (!confirmed || seq > confirmed.seq) setItem(confirmedCartKey(storeSlug), { snapshot, seq });
6162
+ }
6163
+ if (readInFlightCart(storeSlug)?.token === token) removeItem(inFlightCartKey(storeSlug));
6124
6164
  };
6125
6165
  }
6126
6166
  var cartSendTail = Promise.resolve();
@@ -6161,42 +6201,50 @@ async function postPing(body) {
6161
6201
  async function trackPageView(cartValue, itemCount) {
6162
6202
  if (!isInitialized()) return;
6163
6203
  if (typeof document === "undefined") return;
6204
+ const config = getConfig();
6164
6205
  const carriesCart = cartValue !== void 0 || itemCount !== void 0;
6206
+ const reservedOrder = carriesCart ? reserveCartOrder(config.storeSlug) : void 0;
6165
6207
  if (isPlatformPageTrackerActive()) {
6166
- if (carriesCart) await enqueueCartSend(() => sendCartActivity(cartValue, itemCount));
6208
+ if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
6167
6209
  return;
6168
6210
  }
6169
- const config = getConfig();
6170
6211
  const sendView = async () => {
6171
- const settle = carriesCart ? claimCartReport(config.storeSlug, cartValue, itemCount) : void 0;
6212
+ const order2 = reservedOrder ? await reservedOrder : void 0;
6213
+ const settle = order2 ? claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount) : void 0;
6172
6214
  const delivered = await postPing({
6173
6215
  referer: document.referrer || void 0,
6174
6216
  cart_value: cartValue,
6175
6217
  item_count: itemCount,
6218
+ cart_seq: order2?.seq,
6219
+ cart_seq_scope: order2?.scope,
6176
6220
  connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6177
6221
  path: resolveStorefrontPagePath(),
6178
6222
  utm: resolveUtmParameters(config.storeSlug)
6179
6223
  });
6180
6224
  settle?.(delivered);
6181
6225
  };
6182
- await (carriesCart ? enqueueCartSend(sendView) : sendView());
6226
+ await (reservedOrder ? enqueueCartSend(sendView) : sendView());
6183
6227
  }
6184
6228
  async function trackCartActivity(cartValue, itemCount) {
6185
6229
  if (!isInitialized()) return;
6186
6230
  if (typeof document === "undefined") return;
6187
6231
  const snapshot = cartSnapshot(cartValue, itemCount);
6232
+ const reservedOrder = reserveCartOrder(getConfig().storeSlug);
6188
6233
  await enqueueCartSend(async (isLatest) => {
6234
+ const order2 = await reservedOrder;
6189
6235
  if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
6190
- await sendCartActivity(cartValue, itemCount);
6236
+ await sendCartActivity(order2, cartValue, itemCount);
6191
6237
  });
6192
6238
  }
6193
- async function sendCartActivity(cartValue, itemCount) {
6239
+ async function sendCartActivity(order2, cartValue, itemCount) {
6194
6240
  const config = getConfig();
6195
- const settle = claimCartReport(config.storeSlug, cartValue, itemCount);
6241
+ const settle = claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount);
6196
6242
  const delivered = await postPing({
6197
6243
  event_type: "presence",
6198
6244
  cart_value: cartValue,
6199
6245
  item_count: itemCount,
6246
+ cart_seq: order2.seq,
6247
+ cart_seq_scope: order2.scope,
6200
6248
  connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6201
6249
  path: resolveStorefrontPagePath()
6202
6250
  });