@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 +13 -0
- package/README.md +2 -0
- package/dist/index.cjs +117 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +116 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
26638
|
-
|
|
26639
|
-
|
|
26640
|
-
|
|
26641
|
-
|
|
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
|
|
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
|
-
|
|
26700
|
+
const delivered = fetch(`${config2.apiBaseUrl}${endpoint}`, {
|
|
26649
26701
|
method: "POST",
|
|
26650
26702
|
headers: { "Content-Type": "application/json" },
|
|
26651
|
-
body: JSON.stringify(
|
|
26652
|
-
|
|
26653
|
-
|
|
26654
|
-
|
|
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,
|