@shoppexio/storefront 1.0.101 → 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 +8 -0
- package/README.md +2 -2
- package/dist/index.cjs +196 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +196 -26
- 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) {
|
|
@@ -2827,10 +2988,16 @@ function buildCheckoutUrlFromInvoiceId(checkoutBaseUrl, invoiceId) {
|
|
|
2827
2988
|
return null;
|
|
2828
2989
|
}
|
|
2829
2990
|
}
|
|
2991
|
+
function readStorefrontConsentValue() {
|
|
2992
|
+
if (typeof document === "undefined" || typeof document.cookie !== "string") return null;
|
|
2993
|
+
const value = document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${COOKIE_CONSENT_COOKIE}=`))?.slice(COOKIE_CONSENT_COOKIE.length + 1);
|
|
2994
|
+
return parseCookieConsentValue(value) ? value : null;
|
|
2995
|
+
}
|
|
2830
2996
|
function appendCheckoutUrlOptions(checkoutUrl, options) {
|
|
2831
2997
|
const normalizedEmail = normalizeEmail(options.email);
|
|
2832
2998
|
const normalizedLocale = typeof options.locale === "string" ? options.locale.trim() : "";
|
|
2833
|
-
|
|
2999
|
+
const consent = readStorefrontConsentValue();
|
|
3000
|
+
if (!normalizedEmail && !normalizedLocale && !consent) {
|
|
2834
3001
|
return checkoutUrl;
|
|
2835
3002
|
}
|
|
2836
3003
|
try {
|
|
@@ -2838,9 +3005,10 @@ function appendCheckoutUrlOptions(checkoutUrl, options) {
|
|
|
2838
3005
|
if (normalizedLocale) {
|
|
2839
3006
|
parsedCheckoutUrl.searchParams.set("locale", normalizedLocale);
|
|
2840
3007
|
}
|
|
2841
|
-
if (normalizedEmail) {
|
|
3008
|
+
if (normalizedEmail || consent) {
|
|
2842
3009
|
const hashParams = new URLSearchParams(parsedCheckoutUrl.hash.startsWith("#") ? parsedCheckoutUrl.hash.slice(1) : parsedCheckoutUrl.hash);
|
|
2843
|
-
hashParams.set(CHECKOUT_PREFILL_EMAIL_HASH_KEY, normalizedEmail);
|
|
3010
|
+
if (normalizedEmail) hashParams.set(CHECKOUT_PREFILL_EMAIL_HASH_KEY, normalizedEmail);
|
|
3011
|
+
if (consent) hashParams.set(COOKIE_CONSENT_CHECKOUT_HASH_KEY, consent);
|
|
2844
3012
|
parsedCheckoutUrl.hash = hashParams.toString();
|
|
2845
3013
|
}
|
|
2846
3014
|
return parsedCheckoutUrl.toString();
|
|
@@ -2954,7 +3122,10 @@ async function performCheckout(resolvedOptions) {
|
|
|
2954
3122
|
// automatically so the server can refuse an invoice priced above it.
|
|
2955
3123
|
// Undefined when nothing has been quoted this session — the endpoint
|
|
2956
3124
|
// treats that exactly as an older SDK.
|
|
2957
|
-
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)
|
|
2958
3129
|
};
|
|
2959
3130
|
const createAttempt = acquireCheckoutCreateIdempotency(checkoutRequestTarget, createIntent);
|
|
2960
3131
|
const response = await post(
|
|
@@ -3803,7 +3974,6 @@ async function touchStorefrontPresence() {
|
|
|
3803
3974
|
{
|
|
3804
3975
|
event_type: "presence",
|
|
3805
3976
|
referer: document.referrer || void 0,
|
|
3806
|
-
connection_id: getStorefrontConnectionId(config.storeSlug) ?? void 0,
|
|
3807
3977
|
path: resolveStorefrontPagePath(),
|
|
3808
3978
|
utm: resolveUtmParameters(config.storeSlug)
|
|
3809
3979
|
},
|