@shoppexio/storefront 1.0.87 → 1.0.89
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 +11 -0
- package/dist/index.cjs +80 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -8
- package/dist/index.d.ts +17 -8
- package/dist/index.js +79 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.89
|
|
4
|
+
|
|
5
|
+
- `getShopReviewsPage(cursor, { product })` fails with `code: "reviews_product_filter_unsupported"` (exported as `PRODUCT_FILTER_UNSUPPORTED`) when the API answers without echoing the filter it applied, instead of returning the whole shop's reviews as if they were the product's. `ShopReviewsPage.product` carries the echoed id.
|
|
6
|
+
|
|
7
|
+
## 1.0.88
|
|
8
|
+
|
|
9
|
+
- Cart pings carry `cart_seq` and `cart_seq_scope`, a sequence number taken when the cart state is reported and shared by all tabs of the shop (the current time in ms, or one above the highest sequence any tab handed out). The counter record holds the scope (created on first use) and is updated under a Web Lock (`navigator.locks`), so two tabs never hand out the same sequence or create two scopes; where Web Locks are unavailable it is updated without the lock. The server keeps the cart with the highest sequence of the scope and ignores an older one that arrives later, so a slow ping from one tab can no longer overwrite a newer cart another tab already reported.
|
|
10
|
+
- The confirmed cart state records its sequence and is only replaced by a delivered state with a higher one, the same rule the server applies. A cart state counts as already reported when it is the state the server will hold: a newer state another tab is sending, else the confirmed state. So reverting to the confirmed cart while another tab sends a newer one still goes out.
|
|
11
|
+
- Each tab still sends its cart states one at a time and drops a waiting state once a newer one is asked for; this now only saves pings, the order comes from the sequence.
|
|
12
|
+
- Page views and heartbeats without a cart are unchanged and carry no sequence.
|
|
13
|
+
|
|
3
14
|
## 1.0.87
|
|
4
15
|
|
|
5
16
|
- `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.
|
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(src_exports, {
|
|
|
29
29
|
NetworkError: () => NetworkError,
|
|
30
30
|
NotInitializedError: () => NotInitializedError,
|
|
31
31
|
PAYABLE_AMOUNT_DECIMAL_PLACES: () => PAYABLE_AMOUNT_DECIMAL_PLACES,
|
|
32
|
+
PRODUCT_FILTER_UNSUPPORTED: () => PRODUCT_FILTER_UNSUPPORTED,
|
|
32
33
|
ShoppexError: () => ShoppexError,
|
|
33
34
|
ValidationError: () => ValidationError,
|
|
34
35
|
acceptResellerInvite: () => acceptResellerInvite,
|
|
@@ -25924,6 +25925,7 @@ function normalizeFeedback(raw, index) {
|
|
|
25924
25925
|
created_at
|
|
25925
25926
|
};
|
|
25926
25927
|
}
|
|
25928
|
+
var PRODUCT_FILTER_UNSUPPORTED = "reviews_product_filter_unsupported";
|
|
25927
25929
|
async function getShopReviewsPage(cursor, options = {}) {
|
|
25928
25930
|
const config2 = getConfig();
|
|
25929
25931
|
const query = new URLSearchParams();
|
|
@@ -25949,6 +25951,15 @@ async function getShopReviewsPage(cursor, options = {}) {
|
|
|
25949
25951
|
}
|
|
25950
25952
|
);
|
|
25951
25953
|
if (response.success && response.data) {
|
|
25954
|
+
if (product && response.data.product !== product) {
|
|
25955
|
+
return {
|
|
25956
|
+
success: false,
|
|
25957
|
+
message: "Reviews for a single product are not available from this store yet.",
|
|
25958
|
+
code: PRODUCT_FILTER_UNSUPPORTED,
|
|
25959
|
+
status: 200,
|
|
25960
|
+
responseReceived: true
|
|
25961
|
+
};
|
|
25962
|
+
}
|
|
25952
25963
|
return {
|
|
25953
25964
|
success: true,
|
|
25954
25965
|
data: {
|
|
@@ -26634,6 +26645,7 @@ function isPlatformPageTrackerActive() {
|
|
|
26634
26645
|
// ../sdk/src/modules/analytics.ts
|
|
26635
26646
|
var CONFIRMED_CART_STORAGE_PREFIX = "reported_cart_";
|
|
26636
26647
|
var IN_FLIGHT_CART_STORAGE_PREFIX = "reporting_cart_";
|
|
26648
|
+
var CART_SEQUENCE_STORAGE_PREFIX = "presence_cart_seq_";
|
|
26637
26649
|
var CART_CLAIM_TTL_MS = 1e4;
|
|
26638
26650
|
function confirmedCartKey(storeSlug) {
|
|
26639
26651
|
return `${CONFIRMED_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
@@ -26641,33 +26653,77 @@ function confirmedCartKey(storeSlug) {
|
|
|
26641
26653
|
function inFlightCartKey(storeSlug) {
|
|
26642
26654
|
return `${IN_FLIGHT_CART_STORAGE_PREFIX}${storeSlug}`;
|
|
26643
26655
|
}
|
|
26656
|
+
function cartSequenceKey(storeSlug) {
|
|
26657
|
+
return `${CART_SEQUENCE_STORAGE_PREFIX}${storeSlug}`;
|
|
26658
|
+
}
|
|
26644
26659
|
function cartSnapshot(cartValue, itemCount) {
|
|
26645
26660
|
return `${cartValue ?? ""}|${itemCount ?? ""}`;
|
|
26646
26661
|
}
|
|
26662
|
+
function isCartSequence(value) {
|
|
26663
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
26664
|
+
}
|
|
26665
|
+
var CART_SEQUENCE_SCOPE_MAX_LENGTH = 64;
|
|
26666
|
+
function readCartCounter(storeSlug) {
|
|
26667
|
+
const counter = getItem(cartSequenceKey(storeSlug));
|
|
26668
|
+
if (!counter || typeof counter.scope !== "string" || counter.scope.length === 0 || counter.scope.length > CART_SEQUENCE_SCOPE_MAX_LENGTH || !isCartSequence(counter.seq)) {
|
|
26669
|
+
return null;
|
|
26670
|
+
}
|
|
26671
|
+
return counter;
|
|
26672
|
+
}
|
|
26673
|
+
var lastCartOrder = null;
|
|
26674
|
+
function nextCartOrder(storeSlug) {
|
|
26675
|
+
const stored = readCartCounter(storeSlug);
|
|
26676
|
+
const scope = stored?.scope ?? lastCartOrder?.scope ?? createClaimToken();
|
|
26677
|
+
const floor = Math.max(stored?.seq ?? 0, lastCartOrder?.scope === scope ? lastCartOrder.seq : 0);
|
|
26678
|
+
const order2 = { scope, seq: Math.max(Date.now(), floor + 1) };
|
|
26679
|
+
lastCartOrder = order2;
|
|
26680
|
+
setItem(cartSequenceKey(storeSlug), order2);
|
|
26681
|
+
return order2;
|
|
26682
|
+
}
|
|
26683
|
+
function cartSequenceLocks() {
|
|
26684
|
+
if (typeof navigator === "undefined") return null;
|
|
26685
|
+
const locks = navigator.locks;
|
|
26686
|
+
return locks && typeof locks.request === "function" ? locks : null;
|
|
26687
|
+
}
|
|
26688
|
+
function reserveCartOrder(storeSlug) {
|
|
26689
|
+
const take = () => nextCartOrder(storeSlug);
|
|
26690
|
+
const locks = cartSequenceLocks();
|
|
26691
|
+
if (!locks) return Promise.resolve(take());
|
|
26692
|
+
return locks.request(cartSequenceKey(storeSlug), take).catch(take);
|
|
26693
|
+
}
|
|
26694
|
+
function readConfirmedCart(storeSlug) {
|
|
26695
|
+
const confirmed = getItem(confirmedCartKey(storeSlug));
|
|
26696
|
+
if (!confirmed || typeof confirmed.snapshot !== "string" || !isCartSequence(confirmed.seq)) return null;
|
|
26697
|
+
return confirmed;
|
|
26698
|
+
}
|
|
26647
26699
|
function readInFlightCart(storeSlug) {
|
|
26648
26700
|
const claim2 = getItem(inFlightCartKey(storeSlug));
|
|
26649
|
-
if (!claim2 || typeof claim2.snapshot !== "string" || typeof claim2.token !== "string" || typeof claim2.at !== "number") {
|
|
26701
|
+
if (!claim2 || typeof claim2.snapshot !== "string" || !isCartSequence(claim2.seq) || typeof claim2.token !== "string" || typeof claim2.at !== "number") {
|
|
26650
26702
|
return null;
|
|
26651
26703
|
}
|
|
26652
26704
|
return claim2;
|
|
26653
26705
|
}
|
|
26654
26706
|
function isCartReportedOrInFlight(storeSlug, snapshot) {
|
|
26655
|
-
|
|
26707
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
26656
26708
|
const claim2 = readInFlightCart(storeSlug);
|
|
26657
|
-
|
|
26709
|
+
const liveClaim = claim2 && Date.now() - claim2.at < CART_CLAIM_TTL_MS ? claim2 : null;
|
|
26710
|
+
const expected = liveClaim && (!confirmed || liveClaim.seq > confirmed.seq) ? liveClaim : confirmed;
|
|
26711
|
+
return expected?.snapshot === snapshot;
|
|
26658
26712
|
}
|
|
26659
26713
|
function createClaimToken() {
|
|
26660
26714
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
26661
26715
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
26662
26716
|
}
|
|
26663
|
-
function claimCartReport(storeSlug, cartValue, itemCount) {
|
|
26717
|
+
function claimCartReport(storeSlug, seq, cartValue, itemCount) {
|
|
26664
26718
|
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
26665
26719
|
const token = createClaimToken();
|
|
26666
|
-
setItem(inFlightCartKey(storeSlug), { snapshot, token, at: Date.now() });
|
|
26720
|
+
setItem(inFlightCartKey(storeSlug), { snapshot, seq, token, at: Date.now() });
|
|
26667
26721
|
return (delivered) => {
|
|
26668
|
-
if (
|
|
26669
|
-
|
|
26670
|
-
|
|
26722
|
+
if (delivered) {
|
|
26723
|
+
const confirmed = readConfirmedCart(storeSlug);
|
|
26724
|
+
if (!confirmed || seq > confirmed.seq) setItem(confirmedCartKey(storeSlug), { snapshot, seq });
|
|
26725
|
+
}
|
|
26726
|
+
if (readInFlightCart(storeSlug)?.token === token) removeItem(inFlightCartKey(storeSlug));
|
|
26671
26727
|
};
|
|
26672
26728
|
}
|
|
26673
26729
|
var cartSendTail = Promise.resolve();
|
|
@@ -26708,42 +26764,50 @@ async function postPing(body) {
|
|
|
26708
26764
|
async function trackPageView(cartValue, itemCount) {
|
|
26709
26765
|
if (!isInitialized()) return;
|
|
26710
26766
|
if (typeof document === "undefined") return;
|
|
26767
|
+
const config2 = getConfig();
|
|
26711
26768
|
const carriesCart = cartValue !== void 0 || itemCount !== void 0;
|
|
26769
|
+
const reservedOrder = carriesCart ? reserveCartOrder(config2.storeSlug) : void 0;
|
|
26712
26770
|
if (isPlatformPageTrackerActive()) {
|
|
26713
|
-
if (
|
|
26771
|
+
if (reservedOrder) await enqueueCartSend(async () => sendCartActivity(await reservedOrder, cartValue, itemCount));
|
|
26714
26772
|
return;
|
|
26715
26773
|
}
|
|
26716
|
-
const config2 = getConfig();
|
|
26717
26774
|
const sendView = async () => {
|
|
26718
|
-
const
|
|
26775
|
+
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
26776
|
+
const settle2 = order2 ? claimCartReport(config2.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
26719
26777
|
const delivered = await postPing({
|
|
26720
26778
|
referer: document.referrer || void 0,
|
|
26721
26779
|
cart_value: cartValue,
|
|
26722
26780
|
item_count: itemCount,
|
|
26781
|
+
cart_seq: order2?.seq,
|
|
26782
|
+
cart_seq_scope: order2?.scope,
|
|
26723
26783
|
connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
|
|
26724
26784
|
path: resolveStorefrontPagePath(),
|
|
26725
26785
|
utm: resolveUtmParameters(config2.storeSlug)
|
|
26726
26786
|
});
|
|
26727
26787
|
settle2?.(delivered);
|
|
26728
26788
|
};
|
|
26729
|
-
await (
|
|
26789
|
+
await (reservedOrder ? enqueueCartSend(sendView) : sendView());
|
|
26730
26790
|
}
|
|
26731
26791
|
async function trackCartActivity(cartValue, itemCount) {
|
|
26732
26792
|
if (!isInitialized()) return;
|
|
26733
26793
|
if (typeof document === "undefined") return;
|
|
26734
26794
|
const snapshot = cartSnapshot(cartValue, itemCount);
|
|
26795
|
+
const reservedOrder = reserveCartOrder(getConfig().storeSlug);
|
|
26735
26796
|
await enqueueCartSend(async (isLatest) => {
|
|
26797
|
+
const order2 = await reservedOrder;
|
|
26736
26798
|
if (!isLatest() || isCartReportedOrInFlight(getConfig().storeSlug, snapshot)) return;
|
|
26737
|
-
await sendCartActivity(cartValue, itemCount);
|
|
26799
|
+
await sendCartActivity(order2, cartValue, itemCount);
|
|
26738
26800
|
});
|
|
26739
26801
|
}
|
|
26740
|
-
async function sendCartActivity(cartValue, itemCount) {
|
|
26802
|
+
async function sendCartActivity(order2, cartValue, itemCount) {
|
|
26741
26803
|
const config2 = getConfig();
|
|
26742
|
-
const settle2 = claimCartReport(config2.storeSlug, cartValue, itemCount);
|
|
26804
|
+
const settle2 = claimCartReport(config2.storeSlug, order2.seq, cartValue, itemCount);
|
|
26743
26805
|
const delivered = await postPing({
|
|
26744
26806
|
event_type: "presence",
|
|
26745
26807
|
cart_value: cartValue,
|
|
26746
26808
|
item_count: itemCount,
|
|
26809
|
+
cart_seq: order2.seq,
|
|
26810
|
+
cart_seq_scope: order2.scope,
|
|
26747
26811
|
connection_id: getStorefrontConnectionId(config2.storeSlug) ?? void 0,
|
|
26748
26812
|
path: resolveStorefrontPagePath()
|
|
26749
26813
|
});
|
|
@@ -27054,6 +27118,7 @@ if (typeof window !== "undefined") {
|
|
|
27054
27118
|
NetworkError,
|
|
27055
27119
|
NotInitializedError,
|
|
27056
27120
|
PAYABLE_AMOUNT_DECIMAL_PLACES,
|
|
27121
|
+
PRODUCT_FILTER_UNSUPPORTED,
|
|
27057
27122
|
ShoppexError,
|
|
27058
27123
|
ValidationError,
|
|
27059
27124
|
acceptResellerInvite,
|