@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/dist/index.d.cts CHANGED
@@ -1916,9 +1916,14 @@ declare function trackPageView(cartValue?: number, itemCount?: number): Promise<
1916
1916
  /**
1917
1917
  * Reports a cart change for the live visitor map without counting a page view
1918
1918
  * (`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.
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.
1923
+ *
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.
1922
1927
  */
1923
1928
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1924
1929
 
package/dist/index.d.ts CHANGED
@@ -1916,9 +1916,14 @@ declare function trackPageView(cartValue?: number, itemCount?: number): Promise<
1916
1916
  /**
1917
1917
  * Reports a cart change for the live visitor map without counting a page view
1918
1918
  * (`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.
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.
1923
+ *
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.
1922
1927
  */
1923
1928
  declare function trackCartActivity(cartValue: number, itemCount: number): Promise<void>;
1924
1929
 
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
  }
@@ -6083,72 +6085,122 @@ function isPlatformPageTrackerActive() {
6083
6085
  }
6084
6086
 
6085
6087
  // ../sdk/src/modules/analytics.ts
6086
- var REPORTED_CART_STORAGE_PREFIX = "reported_cart_";
6087
- function reportedCartKey(storeSlug) {
6088
- return `${REPORTED_CART_STORAGE_PREFIX}${storeSlug}`;
6088
+ var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
6089
+ var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
6090
+ var CART_CLAIM_TTL_MS = 1e4;
6091
+ function confirmedCartKey(storeSlug) {
6092
+ return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
6093
+ }
6094
+ function inFlightCartKey(storeSlug) {
6095
+ return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
6089
6096
  }
6090
6097
  function cartSnapshot(cartValue, itemCount) {
6091
6098
  return `${cartValue ?? ""}|${itemCount ?? ""}`;
6092
6099
  }
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;
6100
+ function readInFlightCart(storeSlug) {
6101
+ const claim = getItem(inFlightCartKey(storeSlug));
6102
+ if (!claim || typeof claim.snapshot !== "string" || typeof claim.token !== "string" || typeof claim.at !== "number") {
6103
+ return null;
6103
6104
  }
6105
+ return claim;
6106
+ }
6107
+ function isCartReportedOrInFlight(storeSlug, snapshot) {
6108
+ if (getItem(confirmedCartKey(storeSlug)) === snapshot) return true;
6109
+ const claim = readInFlightCart(storeSlug);
6110
+ return claim !== null && claim.snapshot === snapshot && Date.now() - claim.at < CART_CLAIM_TTL_MS;
6111
+ }
6112
+ function createClaimToken() {
6113
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
6114
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
6115
+ }
6116
+ function claimCartReport(storeSlug, cartValue, itemCount) {
6117
+ const snapshot = cartSnapshot(cartValue, itemCount);
6118
+ const token = createClaimToken();
6119
+ setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
6120
+ return (delivered) => {
6121
+ if (readInFlightCart(storeSlug)?.token !== token) return;
6122
+ if (delivered) setItem(confirmedCartKey(storeSlug), snapshot);
6123
+ removeItem(inFlightCartKey(storeSlug));
6124
+ };
6125
+ }
6126
+ var cartSendTail = Promise.resolve();
6127
+ var latestCartRequest = 0;
6128
+ function enqueueCartSend(send) {
6129
+ const request2 = ++latestCartRequest;
6130
+ const run = () => send(() => request2 === latestCartRequest);
6131
+ cartSendTail = cartSendTail.then(run, run);
6132
+ return cartSendTail;
6133
+ }
6134
+ async function postPing(body) {
6104
6135
  const config = getConfig();
6105
- const connectionId = getStorefrontConnectionId(config.storeSlug);
6106
- if (carriesCart) rememberReportedCart(config.storeSlug, cartValue, itemCount);
6136
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6137
+ let timer;
6138
+ const timedOut = new Promise((resolve) => {
6139
+ timer = setTimeout(() => {
6140
+ controller?.abort();
6141
+ resolve(false);
6142
+ }, CART_CLAIM_TTL_MS);
6143
+ });
6107
6144
  try {
6108
6145
  const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
6109
6146
  storeSlug: config.storeSlug
6110
6147
  });
6111
- await fetch(`${config.apiBaseUrl}${endpoint}`, {
6148
+ const delivered = fetch(`${config.apiBaseUrl}${endpoint}`, {
6112
6149
  method: "POST",
6113
6150
  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
- });
6151
+ body: JSON.stringify(body),
6152
+ signal: controller?.signal
6153
+ }).then((response) => response.ok);
6154
+ return await Promise.race([delivered, timedOut]);
6123
6155
  } catch {
6156
+ return false;
6157
+ } finally {
6158
+ clearTimeout(timer);
6124
6159
  }
6125
6160
  }
6161
+ async function trackPageView(cartValue, itemCount) {
6162
+ if (!isInitialized()) return;
6163
+ if (typeof document === "undefined") return;
6164
+ const carriesCart = cartValue !== void 0 || itemCount !== void 0;
6165
+ if (isPlatformPageTrackerActive()) {
6166
+ if (carriesCart) await enqueueCartSend(() => sendCartActivity(cartValue, itemCount));
6167
+ return;
6168
+ }
6169
+ const config = getConfig();
6170
+ const sendView = async () => {
6171
+ const settle = carriesCart ? claimCartReport(config.storeSlug, cartValue, itemCount) : void 0;
6172
+ const delivered = await postPing({
6173
+ referer: document.referrer || void 0,
6174
+ cart_value: cartValue,
6175
+ item_count: itemCount,
6176
+ connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6177
+ path: resolveStorefrontPagePath(),
6178
+ utm: resolveUtmParameters(config.storeSlug)
6179
+ });
6180
+ settle?.(delivered);
6181
+ };
6182
+ await (carriesCart ? enqueueCartSend(sendView) : sendView());
6183
+ }
6126
6184
  async function trackCartActivity(cartValue, itemCount) {
6127
6185
  if (!isInitialized()) return;
6128
6186
  if (typeof document === "undefined") return;
6129
- if (getItem(reportedCartKey(getConfig().storeSlug)) === cartSnapshot(cartValue, itemCount)) return;
6130
- await sendCartActivity(cartValue, itemCount);
6187
+ const snapshot = cartSnapshot(cartValue, itemCount);
6188
+ await enqueueCartSend(async (isLatest) => {
6189
+ if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
6190
+ await sendCartActivity(cartValue, itemCount);
6191
+ });
6131
6192
  }
6132
6193
  async function sendCartActivity(cartValue, itemCount) {
6133
6194
  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
- }
6195
+ const settle = claimCartReport(config.storeSlug, cartValue, itemCount);
6196
+ const delivered = await postPing({
6197
+ event_type: "presence",
6198
+ cart_value: cartValue,
6199
+ item_count: itemCount,
6200
+ connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
6201
+ path: resolveStorefrontPagePath()
6202
+ });
6203
+ settle(delivered);
6152
6204
  }
6153
6205
 
6154
6206
  // ../sdk/src/modules/presence.ts