@shoppexio/storefront 1.0.102 → 1.0.103
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 +4 -0
- package/README.md +2 -2
- package/dist/index.cjs +186 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +186 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -273,27 +273,9 @@ function buildEndpoint(template, params) {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
// ../sdk/src/core/telemetry.ts
|
|
276
|
-
var CONNECTION_ID_STORAGE_PREFIX = "presence_connection_";
|
|
277
276
|
var CLIENT_ERROR_DEDUPE_WINDOW_MS = 15e3;
|
|
278
277
|
var CLIENT_ERROR_TEXT_MAX_LENGTH = 500;
|
|
279
278
|
var CLIENT_ERROR_URL_MAX_LENGTH = 2e3;
|
|
280
|
-
function createPresenceConnectionId() {
|
|
281
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
282
|
-
return crypto.randomUUID();
|
|
283
|
-
}
|
|
284
|
-
return `spx_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
285
|
-
}
|
|
286
|
-
function getStorefrontConnectionId(storeSlug) {
|
|
287
|
-
if (typeof window === "undefined") return null;
|
|
288
|
-
const storageKey2 = `${CONNECTION_ID_STORAGE_PREFIX}${storeSlug}`;
|
|
289
|
-
const existing = getItem(storageKey2);
|
|
290
|
-
if (existing && existing.trim()) {
|
|
291
|
-
return existing;
|
|
292
|
-
}
|
|
293
|
-
const nextId = createPresenceConnectionId();
|
|
294
|
-
setItem(storageKey2, nextId);
|
|
295
|
-
return nextId;
|
|
296
|
-
}
|
|
297
279
|
function getRecentClientErrorKey(payload) {
|
|
298
280
|
return [
|
|
299
281
|
payload.method,
|
|
@@ -323,7 +305,6 @@ async function reportStorefrontClientError(payload) {
|
|
|
323
305
|
const body = JSON.stringify({
|
|
324
306
|
event_type: "client_error",
|
|
325
307
|
referer: document.referrer || void 0,
|
|
326
|
-
connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
|
|
327
308
|
client_error: {
|
|
328
309
|
source: payload.source ?? "sdk",
|
|
329
310
|
phase: payload.phase ?? "request",
|
|
@@ -1508,6 +1489,138 @@ async function quoteCart(coupon, currency) {
|
|
|
1508
1489
|
return response;
|
|
1509
1490
|
}
|
|
1510
1491
|
|
|
1492
|
+
// ../contracts/src/storefront-consent-choice.ts
|
|
1493
|
+
var COOKIE_CONSENT_VERSION = 1;
|
|
1494
|
+
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
1495
|
+
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
1496
|
+
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
1497
|
+
function parseCookieConsentValue(value) {
|
|
1498
|
+
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
1499
|
+
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// ../sdk/src/core/analytics-session.ts
|
|
1503
|
+
var ANALYTICS_SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
1504
|
+
var ANALYTICS_VISITS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1505
|
+
var ANALYTICS_VISITS_MAX = 20;
|
|
1506
|
+
var MAX_ENGAGED_MS_PER_PAGE = 30 * 60 * 1e3;
|
|
1507
|
+
var analyticsSessionKey = (storeSlug) => `analytics_session_${storeSlug}`;
|
|
1508
|
+
var analyticsVisitsKey = (storeSlug) => `analytics_visits_${storeSlug}`;
|
|
1509
|
+
function readAnalyticsConsent() {
|
|
1510
|
+
if (typeof document === "undefined" || typeof navigator === "undefined") return "unknown";
|
|
1511
|
+
const privacy = navigator;
|
|
1512
|
+
if (privacy.globalPrivacyControl === true || privacy.doNotTrack === "1") return "declined";
|
|
1513
|
+
const prefix = `${COOKIE_CONSENT_COOKIE}=`;
|
|
1514
|
+
let raw;
|
|
1515
|
+
try {
|
|
1516
|
+
raw = document.cookie.split(/;\s*/).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
1517
|
+
} catch {
|
|
1518
|
+
return "unknown";
|
|
1519
|
+
}
|
|
1520
|
+
const choice = parseCookieConsentValue(raw ?? null);
|
|
1521
|
+
if (!choice) return "unknown";
|
|
1522
|
+
return choice.analytics ? "granted" : "declined";
|
|
1523
|
+
}
|
|
1524
|
+
function readVisits(storeSlug, now) {
|
|
1525
|
+
const stored = getItem(analyticsVisitsKey(storeSlug));
|
|
1526
|
+
if (!Array.isArray(stored)) return [];
|
|
1527
|
+
return stored.filter((visit) => typeof visit?.id === "string" && typeof visit.at === "number" && now - visit.at < ANALYTICS_VISITS_WINDOW_MS);
|
|
1528
|
+
}
|
|
1529
|
+
function forgetAnalyticsData(storeSlug) {
|
|
1530
|
+
removeItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1531
|
+
removeItem(analyticsVisitsKey(storeSlug));
|
|
1532
|
+
}
|
|
1533
|
+
function analyticsSessionId(storeSlug, options, now = Date.now()) {
|
|
1534
|
+
removeItem(`presence_connection_${storeSlug}`);
|
|
1535
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
1536
|
+
forgetAnalyticsData(storeSlug);
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
const stored = getItem(analyticsSessionKey(storeSlug), "sessionStorage");
|
|
1540
|
+
const live = stored && typeof stored.id === "string" && typeof stored.at === "number" && now - stored.at < ANALYTICS_SESSION_IDLE_MS ? stored.id : null;
|
|
1541
|
+
if (!options.touch) return live;
|
|
1542
|
+
if (live) {
|
|
1543
|
+
setItem(analyticsSessionKey(storeSlug), { id: live, at: now }, "sessionStorage");
|
|
1544
|
+
return live;
|
|
1545
|
+
}
|
|
1546
|
+
if (typeof crypto === "undefined" || typeof crypto.randomUUID !== "function") return null;
|
|
1547
|
+
const id = crypto.randomUUID();
|
|
1548
|
+
setItem(analyticsSessionKey(storeSlug), { id, at: now }, "sessionStorage");
|
|
1549
|
+
setItem(
|
|
1550
|
+
analyticsVisitsKey(storeSlug),
|
|
1551
|
+
[{ id, at: now }, ...readVisits(storeSlug, now)].slice(0, ANALYTICS_VISITS_MAX)
|
|
1552
|
+
);
|
|
1553
|
+
return id;
|
|
1554
|
+
}
|
|
1555
|
+
function checkoutAttribution(storeSlug, now = Date.now()) {
|
|
1556
|
+
const consent = readAnalyticsConsent();
|
|
1557
|
+
if (consent === "declined") {
|
|
1558
|
+
forgetAnalyticsData(storeSlug);
|
|
1559
|
+
return { declined: true };
|
|
1560
|
+
}
|
|
1561
|
+
if (consent === "unknown") return void 0;
|
|
1562
|
+
const sessionIds = readVisits(storeSlug, now).map((visit) => visit.id);
|
|
1563
|
+
return sessionIds.length > 0 ? { session_ids: sessionIds } : void 0;
|
|
1564
|
+
}
|
|
1565
|
+
var engagedMs = 0;
|
|
1566
|
+
var visibleSince = null;
|
|
1567
|
+
var clockInstalled = false;
|
|
1568
|
+
var pageViewed = false;
|
|
1569
|
+
var pageSession = null;
|
|
1570
|
+
function markPageViewed(sessionId) {
|
|
1571
|
+
pageViewed = true;
|
|
1572
|
+
pageSession = sessionId;
|
|
1573
|
+
}
|
|
1574
|
+
function currentPageSession() {
|
|
1575
|
+
return pageSession;
|
|
1576
|
+
}
|
|
1577
|
+
function isPageViewed() {
|
|
1578
|
+
return pageViewed;
|
|
1579
|
+
}
|
|
1580
|
+
function settleClock(now) {
|
|
1581
|
+
if (visibleSince !== null) {
|
|
1582
|
+
engagedMs += now - visibleSince;
|
|
1583
|
+
visibleSince = document.visibilityState === "visible" ? now : null;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
var consentListenerInstalled = false;
|
|
1587
|
+
function onAnalyticsConsent(onYes) {
|
|
1588
|
+
if (consentListenerInstalled || typeof window === "undefined") return;
|
|
1589
|
+
consentListenerInstalled = true;
|
|
1590
|
+
window.addEventListener("shoppex:consent", () => {
|
|
1591
|
+
if (readAnalyticsConsent() === "granted") onYes();
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
function installEngagedClock(onHidden) {
|
|
1595
|
+
if (clockInstalled || typeof document === "undefined") return;
|
|
1596
|
+
clockInstalled = true;
|
|
1597
|
+
visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
1598
|
+
document.addEventListener("visibilitychange", () => {
|
|
1599
|
+
const now = Date.now();
|
|
1600
|
+
if (document.visibilityState === "visible") {
|
|
1601
|
+
visibleSince ?? (visibleSince = now);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
settleClock(now);
|
|
1605
|
+
visibleSince = null;
|
|
1606
|
+
onHidden();
|
|
1607
|
+
});
|
|
1608
|
+
window.addEventListener("pagehide", () => {
|
|
1609
|
+
settleClock(Date.now());
|
|
1610
|
+
visibleSince = null;
|
|
1611
|
+
onHidden();
|
|
1612
|
+
});
|
|
1613
|
+
window.addEventListener("pageshow", (event) => {
|
|
1614
|
+
if (event.persisted && document.visibilityState === "visible") visibleSince = Date.now();
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
function takeEngagedMs(now = Date.now()) {
|
|
1618
|
+
settleClock(now);
|
|
1619
|
+
const taken = Math.min(Math.max(0, Math.round(engagedMs)), MAX_ENGAGED_MS_PER_PAGE);
|
|
1620
|
+
engagedMs = 0;
|
|
1621
|
+
return taken;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1511
1624
|
// ../sdk/src/core/attribution.ts
|
|
1512
1625
|
var STORAGE_PREFIX2 = "shoppex:utm:";
|
|
1513
1626
|
var TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -1566,6 +1679,13 @@ function resolveUtmParameters(storeSlug) {
|
|
|
1566
1679
|
return void 0;
|
|
1567
1680
|
}
|
|
1568
1681
|
const fromUrl = readFromUrl(window.location.search);
|
|
1682
|
+
if (readAnalyticsConsent() !== "granted") {
|
|
1683
|
+
try {
|
|
1684
|
+
window.localStorage.removeItem(storefrontStorageKey(storageKey(storeSlug)));
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
return Object.keys(fromUrl).length > 0 ? fromUrl : void 0;
|
|
1688
|
+
}
|
|
1569
1689
|
if (Object.keys(fromUrl).length > 0) {
|
|
1570
1690
|
writeStored(storeSlug, fromUrl);
|
|
1571
1691
|
return fromUrl;
|
|
@@ -1732,13 +1852,21 @@ async function trackPageView(cartValue, itemCount) {
|
|
|
1732
1852
|
const sendView = async () => {
|
|
1733
1853
|
const order2 = reservedOrder ? await reservedOrder : void 0;
|
|
1734
1854
|
const settle = order2 ? claimCartReport(config.storeSlug, order2.seq, cartValue, itemCount) : void 0;
|
|
1855
|
+
const sessionId = analyticsSessionId(config.storeSlug, { touch: true });
|
|
1856
|
+
const previousSession = currentPageSession();
|
|
1857
|
+
if (previousSession && previousSession !== sessionId) reportPageLeave();
|
|
1858
|
+
markPageViewed(sessionId);
|
|
1859
|
+
if (sessionId) installEngagedClock(reportPageLeave);
|
|
1860
|
+
onAnalyticsConsent(startSessionOnConsent);
|
|
1735
1861
|
const delivered = await postPing({
|
|
1736
1862
|
referer: document.referrer || void 0,
|
|
1737
1863
|
cart_value: cartValue,
|
|
1738
1864
|
item_count: itemCount,
|
|
1739
1865
|
cart_seq: order2?.seq,
|
|
1740
1866
|
cart_seq_scope: order2?.scope,
|
|
1741
|
-
|
|
1867
|
+
session_id: sessionId ?? void 0,
|
|
1868
|
+
// Time on screen of the page before this one (client-side navigation).
|
|
1869
|
+
engaged_ms: sessionId ? takeEngagedMs() || void 0 : void 0,
|
|
1742
1870
|
path: resolveStorefrontPagePath(),
|
|
1743
1871
|
utm: resolveUtmParameters(config.storeSlug)
|
|
1744
1872
|
});
|
|
@@ -1766,11 +1894,44 @@ async function sendCartActivity(order2, cartValue, itemCount) {
|
|
|
1766
1894
|
item_count: itemCount,
|
|
1767
1895
|
cart_seq: order2.seq,
|
|
1768
1896
|
cart_seq_scope: order2.scope,
|
|
1769
|
-
|
|
1897
|
+
// A cart marks the analytics session as one that did not bounce.
|
|
1898
|
+
session_id: analyticsSessionId(config.storeSlug, { touch: false }) ?? void 0,
|
|
1770
1899
|
path: resolveStorefrontPagePath()
|
|
1771
1900
|
});
|
|
1772
1901
|
settle(delivered);
|
|
1773
1902
|
}
|
|
1903
|
+
function startSessionOnConsent() {
|
|
1904
|
+
const config = getConfig();
|
|
1905
|
+
if (!isPageViewed() || analyticsSessionId(config.storeSlug, { touch: false })) return;
|
|
1906
|
+
const sessionId = analyticsSessionId(config.storeSlug, { touch: true });
|
|
1907
|
+
if (!sessionId) return;
|
|
1908
|
+
markPageViewed(sessionId);
|
|
1909
|
+
installEngagedClock(reportPageLeave);
|
|
1910
|
+
void postPing({
|
|
1911
|
+
event_type: "session_start",
|
|
1912
|
+
referer: document.referrer || void 0,
|
|
1913
|
+
session_id: sessionId,
|
|
1914
|
+
path: resolveStorefrontPagePath(),
|
|
1915
|
+
utm: resolveUtmParameters(config.storeSlug)
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
function reportPageLeave() {
|
|
1919
|
+
const config = getConfig();
|
|
1920
|
+
const sessionId = currentPageSession();
|
|
1921
|
+
const engagedMs2 = takeEngagedMs();
|
|
1922
|
+
if (!sessionId || engagedMs2 === 0 || readAnalyticsConsent() !== "granted") return;
|
|
1923
|
+
try {
|
|
1924
|
+
const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", { storeSlug: config.storeSlug });
|
|
1925
|
+
void fetch(`${config.apiBaseUrl}${endpoint}`, {
|
|
1926
|
+
method: "POST",
|
|
1927
|
+
headers: { "Content-Type": "application/json" },
|
|
1928
|
+
body: JSON.stringify({ event_type: "page_leave", session_id: sessionId, engaged_ms: engagedMs2 }),
|
|
1929
|
+
keepalive: true
|
|
1930
|
+
}).catch(() => {
|
|
1931
|
+
});
|
|
1932
|
+
} catch {
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1774
1935
|
|
|
1775
1936
|
// ../sdk/src/storefront-storage-sdk.ts
|
|
1776
1937
|
function init(storeSlugOrOptions, options) {
|
|
@@ -2663,16 +2824,6 @@ function isSafeHttpsRedirectTemplateUrl(url) {
|
|
|
2663
2824
|
}
|
|
2664
2825
|
}
|
|
2665
2826
|
|
|
2666
|
-
// ../contracts/src/storefront-consent-choice.ts
|
|
2667
|
-
var COOKIE_CONSENT_VERSION = 1;
|
|
2668
|
-
var COOKIE_CONSENT_COOKIE = "shoppex_consent";
|
|
2669
|
-
var COOKIE_CONSENT_CHECKOUT_HASH_KEY = "shoppex_consent";
|
|
2670
|
-
var COOKIE_CONSENT_VALUE = new RegExp(`^v${COOKIE_CONSENT_VERSION}\\.f([01])\\.a([01])\\.m([01])$`);
|
|
2671
|
-
function parseCookieConsentValue(value) {
|
|
2672
|
-
const match = value ? COOKIE_CONSENT_VALUE.exec(value) : null;
|
|
2673
|
-
return match ? { functional: match[1] === "1", analytics: match[2] === "1", marketing: match[3] === "1" } : null;
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
2827
|
// ../sdk/src/modules/checkout.ts
|
|
2677
2828
|
var CheckoutCreateError = class _CheckoutCreateError extends Error {
|
|
2678
2829
|
constructor(message, options = {}) {
|
|
@@ -2971,7 +3122,10 @@ async function performCheckout(resolvedOptions) {
|
|
|
2971
3122
|
// automatically so the server can refuse an invoice priced above it.
|
|
2972
3123
|
// Undefined when nothing has been quoted this session — the endpoint
|
|
2973
3124
|
// treats that exactly as an older SDK.
|
|
2974
|
-
quote_token: resolvedOptions.quoteToken !== void 0 ? resolvedOptions.quoteToken ?? void 0 : getLatestQuoteToken() ?? void 0
|
|
3125
|
+
quote_token: resolvedOptions.quoteToken !== void 0 ? resolvedOptions.quoteToken ?? void 0 : getLatestQuoteToken() ?? void 0,
|
|
3126
|
+
// Where the buyer came from: their analytics sessions after a yes on the
|
|
3127
|
+
// cookie banner, `declined` after a no. The server looks up the sources.
|
|
3128
|
+
attribution: checkoutAttribution(config.storeSlug)
|
|
2975
3129
|
};
|
|
2976
3130
|
const createAttempt = acquireCheckoutCreateIdempotency(checkoutRequestTarget, createIntent);
|
|
2977
3131
|
const response = await post(
|
|
@@ -3820,7 +3974,6 @@ async function touchStorefrontPresence() {
|
|
|
3820
3974
|
{
|
|
3821
3975
|
event_type: "presence",
|
|
3822
3976
|
referer: document.referrer || void 0,
|
|
3823
|
-
connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
|
|
3824
3977
|
path: resolveStorefrontPagePath(),
|
|
3825
3978
|
utm: resolveUtmParameters(config.storeSlug)
|
|
3826
3979
|
},
|