@datalyr/web 1.7.8 → 1.7.12
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/attribution.d.ts +14 -0
- package/dist/attribution.d.ts.map +1 -1
- package/dist/container.d.ts +7 -3
- package/dist/container.d.ts.map +1 -1
- package/dist/container.test.d.ts +2 -0
- package/dist/container.test.d.ts.map +1 -0
- package/dist/datalyr.cjs.js +254 -31
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +254 -31
- package/dist/datalyr.esm.js.map +1 -1
- package/dist/datalyr.esm.min.js +1 -1
- package/dist/datalyr.esm.min.js.map +1 -1
- package/dist/datalyr.js +254 -31
- package/dist/datalyr.js.map +1 -1
- package/dist/datalyr.min.js +1 -1
- package/dist/datalyr.min.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/datalyr.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @datalyr/web v1.7.
|
|
2
|
+
* @datalyr/web v1.7.12
|
|
3
3
|
* Datalyr Web SDK - Modern attribution tracking for web applications
|
|
4
4
|
* (c) 2026 Datalyr Inc.
|
|
5
5
|
* Released under the MIT License
|
|
@@ -818,7 +818,11 @@ var Datalyr = (function (exports) {
|
|
|
818
818
|
'token', 'access_token', 'refresh_token', 'id_token', 'auth', 'authorization',
|
|
819
819
|
'password', 'pass', 'pwd', 'secret', 'api_key', 'apikey', 'key',
|
|
820
820
|
'session', 'session_id', 'sid', 'email', 'e', 'phone', 'tel',
|
|
821
|
-
'signature', 'sig', 'otp', 'reset', 'hash'
|
|
821
|
+
'signature', 'sig', 'otp', 'reset', 'hash',
|
|
822
|
+
// Klaviyo profile IDs are deterministic customer identifiers. Capture them
|
|
823
|
+
// through the dedicated identity envelope, but never retain them in URLs,
|
|
824
|
+
// referrers, first/last-touch records, or generic event properties.
|
|
825
|
+
'dl_kprofile_id'
|
|
822
826
|
]);
|
|
823
827
|
// BATCH-2(e): `code` is CONDITIONAL, not in the always-redact set above. An OAuth
|
|
824
828
|
// authorization code (account-takeover-grade if it leaks to ad platforms) ALWAYS travels
|
|
@@ -1763,7 +1767,8 @@ var Datalyr = (function (exports) {
|
|
|
1763
1767
|
class AttributionManager {
|
|
1764
1768
|
constructor(options = {}) {
|
|
1765
1769
|
this.queryParamsCache = null;
|
|
1766
|
-
this.
|
|
1770
|
+
this.pendingKlaviyoProfileId = null;
|
|
1771
|
+
this.UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id'];
|
|
1767
1772
|
// Updated to match dl.js - includes ALL ad platform click IDs
|
|
1768
1773
|
this.CLICK_IDS = [
|
|
1769
1774
|
'fbclid', // Facebook/Meta
|
|
@@ -1808,7 +1813,11 @@ var Datalyr = (function (exports) {
|
|
|
1808
1813
|
'source', // Generic source (non-UTM)
|
|
1809
1814
|
'campaign', // Generic campaign (non-UTM)
|
|
1810
1815
|
'medium', // Generic medium (non-UTM)
|
|
1811
|
-
'gad_source' // Google Ads source parameter
|
|
1816
|
+
'gad_source', // Google Ads source parameter
|
|
1817
|
+
// Datalyr-owned Klaviyo parameters. These live beside merchant UTMs so
|
|
1818
|
+
// account-level setup never replaces customer values.
|
|
1819
|
+
'dl_ksource',
|
|
1820
|
+
'dl_kmessage_id'
|
|
1812
1821
|
];
|
|
1813
1822
|
// TR-03: ad-cookie fields (Meta/Google Ads/TikTok/Snap) that are MARKETING-scoped. When
|
|
1814
1823
|
// marketing consent is declined these are neither synthesized/written nor shipped. Google
|
|
@@ -1818,6 +1827,69 @@ var Datalyr = (function (exports) {
|
|
|
1818
1827
|
// Merge default tracked params with user-provided ones
|
|
1819
1828
|
this.trackedParams = [...this.DEFAULT_TRACKED_PARAMS, ...(options.trackedParams || [])];
|
|
1820
1829
|
this.marketingAllowedFn = options.marketingAllowed;
|
|
1830
|
+
this.replaceVisibleUrl = options.replaceVisibleUrl;
|
|
1831
|
+
}
|
|
1832
|
+
validKlaviyoProfileId(value) {
|
|
1833
|
+
const id = typeof value === 'string' ? value.trim() : '';
|
|
1834
|
+
if (!id || id.length > 512 || /[\u0000-\u001f\u007f]/.test(id))
|
|
1835
|
+
return null;
|
|
1836
|
+
return id;
|
|
1837
|
+
}
|
|
1838
|
+
/** Capture the dedicated Klaviyo identity parameter before URL redaction.
|
|
1839
|
+
* It deliberately never enters the Attribution object or touch storage. */
|
|
1840
|
+
captureKlaviyoProfileBinding(params) {
|
|
1841
|
+
if (!Object.prototype.hasOwnProperty.call(params, 'dl_kprofile_id'))
|
|
1842
|
+
return;
|
|
1843
|
+
const profileId = this.validKlaviyoProfileId(params.dl_kprofile_id);
|
|
1844
|
+
delete params.dl_kprofile_id;
|
|
1845
|
+
if (profileId && this.isMarketingAllowed())
|
|
1846
|
+
this.pendingKlaviyoProfileId = profileId;
|
|
1847
|
+
try {
|
|
1848
|
+
const url = new URL(window.location.href);
|
|
1849
|
+
if (url.searchParams.has('dl_kprofile_id')) {
|
|
1850
|
+
url.searchParams.delete('dl_kprofile_id');
|
|
1851
|
+
const clean = `${url.pathname}${url.search}${url.hash}`;
|
|
1852
|
+
if (this.replaceVisibleUrl)
|
|
1853
|
+
this.replaceVisibleUrl(clean);
|
|
1854
|
+
else
|
|
1855
|
+
window.history.replaceState(window.history.state, '', clean);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
catch (_a) {
|
|
1859
|
+
// Capture still succeeds when a synthetic test/browser URL cannot parse.
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
/** Called once encryption is ready. The short TTL only bridges async init or
|
|
1863
|
+
* a reload before the landing event is queued; it is not a profile cache. */
|
|
1864
|
+
hydrateKlaviyoProfileBinding() {
|
|
1865
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1866
|
+
const now = Date.now();
|
|
1867
|
+
if (!this.pendingKlaviyoProfileId) {
|
|
1868
|
+
const stored = yield storage.getEncrypted(AttributionManager.KLAVIYO_BINDING_KEY, null);
|
|
1869
|
+
const restored = this.validKlaviyoProfileId(stored === null || stored === void 0 ? void 0 : stored.profileId);
|
|
1870
|
+
if (restored && Number(stored === null || stored === void 0 ? void 0 : stored.expiresAt) > now && this.isMarketingAllowed()) {
|
|
1871
|
+
this.pendingKlaviyoProfileId = restored;
|
|
1872
|
+
}
|
|
1873
|
+
else {
|
|
1874
|
+
storage.remove(AttributionManager.KLAVIYO_BINDING_KEY);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (this.pendingKlaviyoProfileId) {
|
|
1878
|
+
yield storage.setEncrypted(AttributionManager.KLAVIYO_BINDING_KEY, {
|
|
1879
|
+
profileId: this.pendingKlaviyoProfileId,
|
|
1880
|
+
expiresAt: now + AttributionManager.KLAVIYO_BINDING_TTL_MS,
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
/** One-shot binding for the next queued event. */
|
|
1886
|
+
consumeKlaviyoProfileBinding() {
|
|
1887
|
+
if (!this.isMarketingAllowed())
|
|
1888
|
+
this.pendingKlaviyoProfileId = null;
|
|
1889
|
+
const profileId = this.pendingKlaviyoProfileId;
|
|
1890
|
+
this.pendingKlaviyoProfileId = null;
|
|
1891
|
+
storage.remove(AttributionManager.KLAVIYO_BINDING_KEY);
|
|
1892
|
+
return profileId;
|
|
1821
1893
|
}
|
|
1822
1894
|
// TR-03: default (no predicate / no signal) = allowed → byte-identical to prior behavior.
|
|
1823
1895
|
isMarketingAllowed() {
|
|
@@ -1840,6 +1912,7 @@ var Datalyr = (function (exports) {
|
|
|
1840
1912
|
if (!this.queryParamsCache) {
|
|
1841
1913
|
this.queryParamsCache = params;
|
|
1842
1914
|
}
|
|
1915
|
+
this.captureKlaviyoProfileBinding(params);
|
|
1843
1916
|
const attribution = {
|
|
1844
1917
|
timestamp: Date.now()
|
|
1845
1918
|
};
|
|
@@ -1854,8 +1927,13 @@ var Datalyr = (function (exports) {
|
|
|
1854
1927
|
const value = params[utm];
|
|
1855
1928
|
if (value) {
|
|
1856
1929
|
attribution[utm] = value; // canonical: utm_source, utm_campaign, ...
|
|
1857
|
-
|
|
1858
|
-
|
|
1930
|
+
// The five original UTM dimensions keep their legacy stripped aliases.
|
|
1931
|
+
// `utm_id` is intentionally canonical-only: writing it to a generic `id`
|
|
1932
|
+
// property would collide with event/customer identifiers downstream.
|
|
1933
|
+
if (utm !== 'utm_id') {
|
|
1934
|
+
const key = utm.replace('utm_', '');
|
|
1935
|
+
attribution[key] = value; // alias: source, campaign, ...
|
|
1936
|
+
}
|
|
1859
1937
|
}
|
|
1860
1938
|
}
|
|
1861
1939
|
// Capture click IDs. The first present (by CLICK_IDS priority) is the primary
|
|
@@ -1892,6 +1970,12 @@ var Datalyr = (function (exports) {
|
|
|
1892
1970
|
attribution[param] = value;
|
|
1893
1971
|
}
|
|
1894
1972
|
}
|
|
1973
|
+
// The namespaced source is Datalyr's routing authority. Keep any
|
|
1974
|
+
// merchant-owned utm_source intact while still classifying this visit as
|
|
1975
|
+
// Klaviyo everywhere source-level attribution is displayed.
|
|
1976
|
+
if (attribution.dl_ksource === 'klaviyo') {
|
|
1977
|
+
attribution.source = 'klaviyo';
|
|
1978
|
+
}
|
|
1895
1979
|
// Capture referrer. 9.A.4: redact secret/PII query-param values (reset tokens,
|
|
1896
1980
|
// OAuth codes, emails) — this attribution object is stamped onto every event AND
|
|
1897
1981
|
// persisted into dl_first_touch/dl_last_touch, so it must be clean at capture.
|
|
@@ -2155,6 +2239,21 @@ var Datalyr = (function (exports) {
|
|
|
2155
2239
|
current = Object.assign(Object.assign({}, fallback), { referrer: current.referrer, referrerHost: current.referrerHost, landingPage: current.landingPage, landingPath: current.landingPath });
|
|
2156
2240
|
}
|
|
2157
2241
|
}
|
|
2242
|
+
// A real signal WITHOUT its own link tag must not erase the stored one. The
|
|
2243
|
+
// creator/bio-link journey is click-link-today, return-from-instagram-next-week:
|
|
2244
|
+
// that organic revisit scores hasRealAttribution (external referrer) with no
|
|
2245
|
+
// `lyr`, and before this carry-forward storeLastTouch below replaced the tagged
|
|
2246
|
+
// touch wholesale — every later event (and the Track page's conversion count,
|
|
2247
|
+
// which keys on the event's own lyr) silently lost the link. Semantics = "last
|
|
2248
|
+
// non-empty lyr within the window", the same answer the server's resolveLyr
|
|
2249
|
+
// argMax gives webhook conversions; a NEW ?lyr= still wins because current.lyr
|
|
2250
|
+
// is already set. source/medium/campaign still update — lyr is an independent
|
|
2251
|
+
// dimension (which link), not a channel claim.
|
|
2252
|
+
if (hasRealAttribution && !current.lyr) {
|
|
2253
|
+
const storedLyr = (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.lyr) || (firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.lyr);
|
|
2254
|
+
if (storedLyr)
|
|
2255
|
+
current.lyr = storedLyr;
|
|
2256
|
+
}
|
|
2158
2257
|
// Capture advertising cookies automatically
|
|
2159
2258
|
const adCookies = this.captureAdCookies();
|
|
2160
2259
|
// Only (re)write first/last touch when this pageview carried a REAL signal — never
|
|
@@ -2331,6 +2430,8 @@ var Datalyr = (function (exports) {
|
|
|
2331
2430
|
}
|
|
2332
2431
|
}
|
|
2333
2432
|
}
|
|
2433
|
+
AttributionManager.KLAVIYO_BINDING_KEY = 'dl_klaviyo_profile_binding';
|
|
2434
|
+
AttributionManager.KLAVIYO_BINDING_TTL_MS = 10 * 60 * 1000;
|
|
2334
2435
|
|
|
2335
2436
|
/**
|
|
2336
2437
|
* Event Queue and Batching Module
|
|
@@ -3690,12 +3791,10 @@ var Datalyr = (function (exports) {
|
|
|
3690
3791
|
img.dataset.datalyrPixel = script.id;
|
|
3691
3792
|
document.body.appendChild(img);
|
|
3692
3793
|
}
|
|
3693
|
-
/**
|
|
3694
|
-
* Initialize third-party pixels (Meta, Google, TikTok)
|
|
3695
|
-
*/
|
|
3794
|
+
/** Initialize configured third-party pixels in the merchant's page context. */
|
|
3696
3795
|
initializePixels() {
|
|
3697
3796
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3698
|
-
var _a, _b, _c;
|
|
3797
|
+
var _a, _b, _c, _e;
|
|
3699
3798
|
if (!this.pixels)
|
|
3700
3799
|
return;
|
|
3701
3800
|
// Initialize Meta Pixel
|
|
@@ -3710,8 +3809,58 @@ var Datalyr = (function (exports) {
|
|
|
3710
3809
|
if (((_c = this.pixels.tiktok) === null || _c === void 0 ? void 0 : _c.enabled) && this.pixels.tiktok.pixel_id) {
|
|
3711
3810
|
this.initializeTikTokPixel(this.pixels.tiktok);
|
|
3712
3811
|
}
|
|
3812
|
+
// The Whop Pixel needs only the merchant's public company ID. Loading it
|
|
3813
|
+
// here lets Whop link this external-site visitor to a later hosted or
|
|
3814
|
+
// embedded Whop checkout without a Datalyr visitor_id in checkout metadata.
|
|
3815
|
+
if (((_e = this.pixels.whop) === null || _e === void 0 ? void 0 : _e.enabled) && this.pixels.whop.company_id) {
|
|
3816
|
+
this.initializeWhopPixel(this.pixels.whop);
|
|
3817
|
+
}
|
|
3713
3818
|
});
|
|
3714
3819
|
}
|
|
3820
|
+
/** Initialize Whop's official first-party attribution pixel. */
|
|
3821
|
+
initializeWhopPixel(config) {
|
|
3822
|
+
var _a, _b;
|
|
3823
|
+
try {
|
|
3824
|
+
const host = window;
|
|
3825
|
+
if (!host.whop) {
|
|
3826
|
+
const queue = host.whop = {
|
|
3827
|
+
q: [],
|
|
3828
|
+
t: Date.now(),
|
|
3829
|
+
s: [],
|
|
3830
|
+
o: 'https://t.whop.tw',
|
|
3831
|
+
track: function (...args) {
|
|
3832
|
+
queue.q.push([Date.now(), ...args]);
|
|
3833
|
+
},
|
|
3834
|
+
setScope: function (...args) {
|
|
3835
|
+
queue.s = args.filter((value) => typeof value === 'string');
|
|
3836
|
+
queue.q.push([Date.now(), 'setScope', ...queue.s]);
|
|
3837
|
+
},
|
|
3838
|
+
scope: function (...scope) {
|
|
3839
|
+
return {
|
|
3840
|
+
track: function (...args) {
|
|
3841
|
+
queue.q.push([Date.now(), ...args, { __scope: scope }]);
|
|
3842
|
+
},
|
|
3843
|
+
};
|
|
3844
|
+
},
|
|
3845
|
+
};
|
|
3846
|
+
const script = document.createElement('script');
|
|
3847
|
+
script.async = true;
|
|
3848
|
+
script.src = 'https://t.whop.tw/s.js';
|
|
3849
|
+
const firstScript = document.getElementsByTagName('script')[0];
|
|
3850
|
+
(_a = firstScript === null || firstScript === void 0 ? void 0 : firstScript.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(script, firstScript);
|
|
3851
|
+
if (!firstScript)
|
|
3852
|
+
document.head.appendChild(script);
|
|
3853
|
+
}
|
|
3854
|
+
if (typeof ((_b = host.whop) === null || _b === void 0 ? void 0 : _b.setScope) !== 'function') {
|
|
3855
|
+
throw new Error('Existing window.whop does not expose setScope');
|
|
3856
|
+
}
|
|
3857
|
+
host.whop.setScope(config.company_id);
|
|
3858
|
+
this.log('Whop Pixel initialized:', config.company_id);
|
|
3859
|
+
}
|
|
3860
|
+
catch (error) {
|
|
3861
|
+
this.log('Error initializing Whop Pixel:', error);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3715
3864
|
/**
|
|
3716
3865
|
* Initialize Meta (Facebook) Pixel
|
|
3717
3866
|
*
|
|
@@ -3867,7 +4016,7 @@ var Datalyr = (function (exports) {
|
|
|
3867
4016
|
* Track event to all initialized pixels
|
|
3868
4017
|
*/
|
|
3869
4018
|
trackToPixels(eventName, properties = {}, eventId) {
|
|
3870
|
-
var _a, _b, _c, _e, _f, _g, _h, _j, _k, _l;
|
|
4019
|
+
var _a, _b, _c, _e, _f, _g, _h, _j, _k, _l, _m, _p;
|
|
3871
4020
|
// Datalyr-internal events ($identify, $group, $alias, $auto_identify,
|
|
3872
4021
|
// $app_download_click, …) are not conversions — never forward them to ad
|
|
3873
4022
|
// pixels. (Previously they fired as noise custom events with the $ stripped.)
|
|
@@ -3964,6 +4113,18 @@ var Datalyr = (function (exports) {
|
|
|
3964
4113
|
this.log('Error tracking TikTok Pixel event:', error);
|
|
3965
4114
|
}
|
|
3966
4115
|
}
|
|
4116
|
+
// Whop records checkout and payment events server-side. Datalyr only sends
|
|
4117
|
+
// page views here, including SPA navigations, so purchases are never doubled.
|
|
4118
|
+
if (((_p = (_m = this.pixels) === null || _m === void 0 ? void 0 : _m.whop) === null || _p === void 0 ? void 0 : _p.enabled) &&
|
|
4119
|
+
window.whop &&
|
|
4120
|
+
(eventName === 'pageview' || eventName === 'page_view')) {
|
|
4121
|
+
try {
|
|
4122
|
+
window.whop.track('page');
|
|
4123
|
+
}
|
|
4124
|
+
catch (error) {
|
|
4125
|
+
this.log('Error tracking Whop Pixel page:', error);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
3967
4128
|
}
|
|
3968
4129
|
/**
|
|
3969
4130
|
* Manually trigger a custom script
|
|
@@ -4931,6 +5092,7 @@ var Datalyr = (function (exports) {
|
|
|
4931
5092
|
this.initialized = false;
|
|
4932
5093
|
this.errors = [];
|
|
4933
5094
|
this.MAX_ERRORS = 50;
|
|
5095
|
+
this.shopifyConsentUnresolvable = false; // loadFeatures failed or never yielded customerPrivacy → stop fail-closing
|
|
4934
5096
|
this.lastSpaPath = null; // dedups SPA pageviews (replaceState-on-mount double-fire)
|
|
4935
5097
|
// Shopify loads Customer Privacy asynchronously. Keep the initial pageview
|
|
4936
5098
|
// pending until initialization is complete and analytics consent is known,
|
|
@@ -5010,7 +5172,15 @@ var Datalyr = (function (exports) {
|
|
|
5010
5172
|
// TR-03: gate ad-signal synthesis + shipping on LIVE marketing consent. Returns true by
|
|
5011
5173
|
// default (no consent signal) so behavior is unchanged for the common case; false only on
|
|
5012
5174
|
// an explicit decline (Shopify marketing:false / setConsent marketing|sale=false).
|
|
5013
|
-
marketingAllowed: () => this.consentAllowsMarketing()
|
|
5175
|
+
marketingAllowed: () => this.consentAllowsMarketing(),
|
|
5176
|
+
// Bypass our SPA wrapper when removing the one-time profile parameter,
|
|
5177
|
+
// otherwise the privacy cleanup itself would manufacture a pageview.
|
|
5178
|
+
replaceVisibleUrl: (url) => {
|
|
5179
|
+
var _a;
|
|
5180
|
+
const replace = (_a = this.originalReplaceState) !== null && _a !== void 0 ? _a : history.replaceState;
|
|
5181
|
+
replace.call(history, history.state, '', url);
|
|
5182
|
+
this.lastSpaPath = window.location.pathname + window.location.search + window.location.hash;
|
|
5183
|
+
},
|
|
5014
5184
|
});
|
|
5015
5185
|
this.queue = new EventQueue(this.config);
|
|
5016
5186
|
this.fingerprint = new FingerprintCollector({
|
|
@@ -5100,6 +5270,7 @@ var Datalyr = (function (exports) {
|
|
|
5100
5270
|
// in the IdentityManager constructor. It never overwrites an id already
|
|
5101
5271
|
// set by an explicit identify() earlier in this page load.
|
|
5102
5272
|
yield this.identity.hydrateEncryptedUserId();
|
|
5273
|
+
yield this.attribution.hydrateKlaviyoProfileBinding();
|
|
5103
5274
|
this.log('Encryption initialized, user properties loaded');
|
|
5104
5275
|
}
|
|
5105
5276
|
catch (encErr) {
|
|
@@ -5363,6 +5534,14 @@ var Datalyr = (function (exports) {
|
|
|
5363
5534
|
referrerParams.set('utm_content', lastTouch.content);
|
|
5364
5535
|
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.term)
|
|
5365
5536
|
referrerParams.set('utm_term', lastTouch.term);
|
|
5537
|
+
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.utm_id)
|
|
5538
|
+
referrerParams.set('utm_id', lastTouch.utm_id);
|
|
5539
|
+
// The trackable-link tag rides the referrer too — the edge worker's own
|
|
5540
|
+
// server-side appendPlayReferrer packs it, and ingest parses it back out
|
|
5541
|
+
// of install_referrer_url. Without it, interstitial-mode Android installs
|
|
5542
|
+
// lose the deterministic link tag that a bare 302 would have carried.
|
|
5543
|
+
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.lyr)
|
|
5544
|
+
referrerParams.set('lyr', lastTouch.lyr);
|
|
5366
5545
|
try {
|
|
5367
5546
|
const url = new URL(options.appStoreUrl);
|
|
5368
5547
|
url.searchParams.set('referrer', referrerParams.toString());
|
|
@@ -6363,6 +6542,16 @@ var Datalyr = (function (exports) {
|
|
|
6363
6542
|
// Add attribution data (caller wins on collisions)
|
|
6364
6543
|
const attributionData = this.attribution.getAttributionData();
|
|
6365
6544
|
assignMissing(eventData, attributionData);
|
|
6545
|
+
// A Klaviyo landing URL may carry the profile ID as a dedicated custom
|
|
6546
|
+
// parameter. Emit it once through the deterministic external-id envelope;
|
|
6547
|
+
// never copy it into generic attribution or user-visible URL fields.
|
|
6548
|
+
const klaviyoProfileId = this.attribution.consumeKlaviyoProfileBinding();
|
|
6549
|
+
if (klaviyoProfileId) {
|
|
6550
|
+
const existingExternalIds = eventData.external_ids;
|
|
6551
|
+
eventData.external_ids = Object.assign(Object.assign({}, (existingExternalIds && typeof existingExternalIds === 'object' && !Array.isArray(existingExternalIds)
|
|
6552
|
+
? existingExternalIds
|
|
6553
|
+
: {})), { klaviyo: klaviyoProfileId });
|
|
6554
|
+
}
|
|
6366
6555
|
// Add session metrics (SDK-authoritative — session_id et al. must not be overridden)
|
|
6367
6556
|
const sessionMetrics = this.session.getMetrics();
|
|
6368
6557
|
Object.assign(eventData, sessionMetrics);
|
|
@@ -6430,7 +6619,7 @@ var Datalyr = (function (exports) {
|
|
|
6430
6619
|
// drift from the package again (it sat at 1.7.4 across the 1.7.5 release). The
|
|
6431
6620
|
// build guard (scripts/check-bundle.js, run by build:check) still verifies the
|
|
6432
6621
|
// deployable bundles carry the package.json version. (FSR-103)
|
|
6433
|
-
sdk_version: '1.7.
|
|
6622
|
+
sdk_version: '1.7.12',
|
|
6434
6623
|
sdk_name: 'datalyr-web-sdk',
|
|
6435
6624
|
// A3-25: versioned-envelope stamp. Every SDK emits schema_version so the ingest/contract
|
|
6436
6625
|
// layer can key on ONE canonical envelope version (snake_case fields, event_name/event_data
|
|
@@ -6519,6 +6708,27 @@ var Datalyr = (function (exports) {
|
|
|
6519
6708
|
return false;
|
|
6520
6709
|
}
|
|
6521
6710
|
}
|
|
6711
|
+
/**
|
|
6712
|
+
* TR-15 refinement: fail closed ONLY while the Customer Privacy API can still
|
|
6713
|
+
* arrive (Shopify.loadFeatures exists and hasn't failed). A storefront with no
|
|
6714
|
+
* loadFeatures (headless installs, `platform:'shopify'` on a non-Shopify page)
|
|
6715
|
+
* has NO path to a consent answer — blocking there is not a pre-load window,
|
|
6716
|
+
* it is zero events forever, silently. No signal possible → null (fail open);
|
|
6717
|
+
* false is reserved for an explicit decline from the API.
|
|
6718
|
+
*/
|
|
6719
|
+
shopifyConsentPendingBlock() {
|
|
6720
|
+
var _a;
|
|
6721
|
+
if (!this.isShopifyStorefront())
|
|
6722
|
+
return null;
|
|
6723
|
+
if (this.shopifyConsentUnresolvable)
|
|
6724
|
+
return null;
|
|
6725
|
+
try {
|
|
6726
|
+
return typeof ((_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.loadFeatures) === 'function' ? false : null;
|
|
6727
|
+
}
|
|
6728
|
+
catch (_b) {
|
|
6729
|
+
return null;
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6522
6732
|
/**
|
|
6523
6733
|
* Shopify's analytics-consent signal: true/false when the Customer Privacy API is
|
|
6524
6734
|
* present and answers, null when there's no signal (API absent / unexpected shape)
|
|
@@ -6528,7 +6738,7 @@ var Datalyr = (function (exports) {
|
|
|
6528
6738
|
try {
|
|
6529
6739
|
const cp = this.getShopifyCustomerPrivacy();
|
|
6530
6740
|
if (!cp)
|
|
6531
|
-
return this.
|
|
6741
|
+
return this.shopifyConsentPendingBlock();
|
|
6532
6742
|
if (typeof cp.analyticsProcessingAllowed === 'function') {
|
|
6533
6743
|
const allowed = cp.analyticsProcessingAllowed();
|
|
6534
6744
|
return typeof allowed === 'boolean' ? allowed : null;
|
|
@@ -6538,10 +6748,10 @@ var Datalyr = (function (exports) {
|
|
|
6538
6748
|
const allowed = cp.userCanBeTracked();
|
|
6539
6749
|
return typeof allowed === 'boolean' ? allowed : null;
|
|
6540
6750
|
}
|
|
6541
|
-
return this.
|
|
6751
|
+
return this.shopifyConsentPendingBlock();
|
|
6542
6752
|
}
|
|
6543
6753
|
catch (_a) {
|
|
6544
|
-
return this.
|
|
6754
|
+
return this.shopifyConsentPendingBlock();
|
|
6545
6755
|
}
|
|
6546
6756
|
}
|
|
6547
6757
|
/**
|
|
@@ -6553,15 +6763,15 @@ var Datalyr = (function (exports) {
|
|
|
6553
6763
|
try {
|
|
6554
6764
|
const cp = this.getShopifyCustomerPrivacy();
|
|
6555
6765
|
if (!cp || typeof cp.marketingAllowed !== 'function') {
|
|
6556
|
-
return this.
|
|
6766
|
+
return this.shopifyConsentPendingBlock();
|
|
6557
6767
|
}
|
|
6558
6768
|
const allowed = cp.marketingAllowed();
|
|
6559
6769
|
return typeof allowed === 'boolean'
|
|
6560
6770
|
? allowed
|
|
6561
|
-
: (this.
|
|
6771
|
+
: (this.shopifyConsentPendingBlock());
|
|
6562
6772
|
}
|
|
6563
6773
|
catch (_a) {
|
|
6564
|
-
return this.
|
|
6774
|
+
return this.shopifyConsentPendingBlock();
|
|
6565
6775
|
}
|
|
6566
6776
|
}
|
|
6567
6777
|
/**
|
|
@@ -6597,18 +6807,31 @@ var Datalyr = (function (exports) {
|
|
|
6597
6807
|
// banner interaction. Guarded — a missing/changed loadFeatures must never break tracking.
|
|
6598
6808
|
const shopify = window.Shopify;
|
|
6599
6809
|
if (shopify && typeof shopify.loadFeatures === 'function') {
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6810
|
+
// One retry before declaring the API unreachable: a TRANSIENT loadFeatures
|
|
6811
|
+
// failure must not fail-open the whole session for a previously-declined
|
|
6812
|
+
// shopper. Only after the retry (or a success that still yields no
|
|
6813
|
+
// customerPrivacy) is the pre-load window declared over — then the
|
|
6814
|
+
// fail-closed gate releases and consent re-evaluates (a decline gates).
|
|
6815
|
+
const attempt = (retriesLeft) => {
|
|
6816
|
+
shopify.loadFeatures([{ name: 'consent-tracking-api', version: '0.1' }], (error) => {
|
|
6817
|
+
if (error && retriesLeft > 0) {
|
|
6818
|
+
this.log('Shopify loadFeatures(consent-tracking-api) failed, retrying:', error);
|
|
6819
|
+
setTimeout(() => attempt(retriesLeft - 1), 1000);
|
|
6820
|
+
return;
|
|
6821
|
+
}
|
|
6822
|
+
if (error)
|
|
6823
|
+
this.log('Shopify loadFeatures(consent-tracking-api) failed:', error);
|
|
6824
|
+
if (error || !this.getShopifyCustomerPrivacy())
|
|
6825
|
+
this.shopifyConsentUnresolvable = true;
|
|
6826
|
+
try {
|
|
6827
|
+
this.onShopifyConsentChanged();
|
|
6828
|
+
}
|
|
6829
|
+
catch (e) {
|
|
6830
|
+
this.log('Shopify post-load consent eval failed:', e);
|
|
6831
|
+
}
|
|
6832
|
+
});
|
|
6833
|
+
};
|
|
6834
|
+
attempt(1);
|
|
6612
6835
|
}
|
|
6613
6836
|
}
|
|
6614
6837
|
catch (error) {
|