@datalyr/web 1.7.9 → 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/datalyr.cjs.js +189 -26
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +189 -26
- 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 +189 -26
- 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.esm.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
|
|
@@ -815,7 +815,11 @@ const REDACTED_URL_PARAMS = new Set([
|
|
|
815
815
|
'token', 'access_token', 'refresh_token', 'id_token', 'auth', 'authorization',
|
|
816
816
|
'password', 'pass', 'pwd', 'secret', 'api_key', 'apikey', 'key',
|
|
817
817
|
'session', 'session_id', 'sid', 'email', 'e', 'phone', 'tel',
|
|
818
|
-
'signature', 'sig', 'otp', 'reset', 'hash'
|
|
818
|
+
'signature', 'sig', 'otp', 'reset', 'hash',
|
|
819
|
+
// Klaviyo profile IDs are deterministic customer identifiers. Capture them
|
|
820
|
+
// through the dedicated identity envelope, but never retain them in URLs,
|
|
821
|
+
// referrers, first/last-touch records, or generic event properties.
|
|
822
|
+
'dl_kprofile_id'
|
|
819
823
|
]);
|
|
820
824
|
// BATCH-2(e): `code` is CONDITIONAL, not in the always-redact set above. An OAuth
|
|
821
825
|
// authorization code (account-takeover-grade if it leaks to ad platforms) ALWAYS travels
|
|
@@ -1760,7 +1764,8 @@ class SessionManager {
|
|
|
1760
1764
|
class AttributionManager {
|
|
1761
1765
|
constructor(options = {}) {
|
|
1762
1766
|
this.queryParamsCache = null;
|
|
1763
|
-
this.
|
|
1767
|
+
this.pendingKlaviyoProfileId = null;
|
|
1768
|
+
this.UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id'];
|
|
1764
1769
|
// Updated to match dl.js - includes ALL ad platform click IDs
|
|
1765
1770
|
this.CLICK_IDS = [
|
|
1766
1771
|
'fbclid', // Facebook/Meta
|
|
@@ -1805,7 +1810,11 @@ class AttributionManager {
|
|
|
1805
1810
|
'source', // Generic source (non-UTM)
|
|
1806
1811
|
'campaign', // Generic campaign (non-UTM)
|
|
1807
1812
|
'medium', // Generic medium (non-UTM)
|
|
1808
|
-
'gad_source' // Google Ads source parameter
|
|
1813
|
+
'gad_source', // Google Ads source parameter
|
|
1814
|
+
// Datalyr-owned Klaviyo parameters. These live beside merchant UTMs so
|
|
1815
|
+
// account-level setup never replaces customer values.
|
|
1816
|
+
'dl_ksource',
|
|
1817
|
+
'dl_kmessage_id'
|
|
1809
1818
|
];
|
|
1810
1819
|
// TR-03: ad-cookie fields (Meta/Google Ads/TikTok/Snap) that are MARKETING-scoped. When
|
|
1811
1820
|
// marketing consent is declined these are neither synthesized/written nor shipped. Google
|
|
@@ -1815,6 +1824,69 @@ class AttributionManager {
|
|
|
1815
1824
|
// Merge default tracked params with user-provided ones
|
|
1816
1825
|
this.trackedParams = [...this.DEFAULT_TRACKED_PARAMS, ...(options.trackedParams || [])];
|
|
1817
1826
|
this.marketingAllowedFn = options.marketingAllowed;
|
|
1827
|
+
this.replaceVisibleUrl = options.replaceVisibleUrl;
|
|
1828
|
+
}
|
|
1829
|
+
validKlaviyoProfileId(value) {
|
|
1830
|
+
const id = typeof value === 'string' ? value.trim() : '';
|
|
1831
|
+
if (!id || id.length > 512 || /[\u0000-\u001f\u007f]/.test(id))
|
|
1832
|
+
return null;
|
|
1833
|
+
return id;
|
|
1834
|
+
}
|
|
1835
|
+
/** Capture the dedicated Klaviyo identity parameter before URL redaction.
|
|
1836
|
+
* It deliberately never enters the Attribution object or touch storage. */
|
|
1837
|
+
captureKlaviyoProfileBinding(params) {
|
|
1838
|
+
if (!Object.prototype.hasOwnProperty.call(params, 'dl_kprofile_id'))
|
|
1839
|
+
return;
|
|
1840
|
+
const profileId = this.validKlaviyoProfileId(params.dl_kprofile_id);
|
|
1841
|
+
delete params.dl_kprofile_id;
|
|
1842
|
+
if (profileId && this.isMarketingAllowed())
|
|
1843
|
+
this.pendingKlaviyoProfileId = profileId;
|
|
1844
|
+
try {
|
|
1845
|
+
const url = new URL(window.location.href);
|
|
1846
|
+
if (url.searchParams.has('dl_kprofile_id')) {
|
|
1847
|
+
url.searchParams.delete('dl_kprofile_id');
|
|
1848
|
+
const clean = `${url.pathname}${url.search}${url.hash}`;
|
|
1849
|
+
if (this.replaceVisibleUrl)
|
|
1850
|
+
this.replaceVisibleUrl(clean);
|
|
1851
|
+
else
|
|
1852
|
+
window.history.replaceState(window.history.state, '', clean);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
catch (_a) {
|
|
1856
|
+
// Capture still succeeds when a synthetic test/browser URL cannot parse.
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
/** Called once encryption is ready. The short TTL only bridges async init or
|
|
1860
|
+
* a reload before the landing event is queued; it is not a profile cache. */
|
|
1861
|
+
hydrateKlaviyoProfileBinding() {
|
|
1862
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1863
|
+
const now = Date.now();
|
|
1864
|
+
if (!this.pendingKlaviyoProfileId) {
|
|
1865
|
+
const stored = yield storage.getEncrypted(AttributionManager.KLAVIYO_BINDING_KEY, null);
|
|
1866
|
+
const restored = this.validKlaviyoProfileId(stored === null || stored === void 0 ? void 0 : stored.profileId);
|
|
1867
|
+
if (restored && Number(stored === null || stored === void 0 ? void 0 : stored.expiresAt) > now && this.isMarketingAllowed()) {
|
|
1868
|
+
this.pendingKlaviyoProfileId = restored;
|
|
1869
|
+
}
|
|
1870
|
+
else {
|
|
1871
|
+
storage.remove(AttributionManager.KLAVIYO_BINDING_KEY);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
if (this.pendingKlaviyoProfileId) {
|
|
1875
|
+
yield storage.setEncrypted(AttributionManager.KLAVIYO_BINDING_KEY, {
|
|
1876
|
+
profileId: this.pendingKlaviyoProfileId,
|
|
1877
|
+
expiresAt: now + AttributionManager.KLAVIYO_BINDING_TTL_MS,
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
/** One-shot binding for the next queued event. */
|
|
1883
|
+
consumeKlaviyoProfileBinding() {
|
|
1884
|
+
if (!this.isMarketingAllowed())
|
|
1885
|
+
this.pendingKlaviyoProfileId = null;
|
|
1886
|
+
const profileId = this.pendingKlaviyoProfileId;
|
|
1887
|
+
this.pendingKlaviyoProfileId = null;
|
|
1888
|
+
storage.remove(AttributionManager.KLAVIYO_BINDING_KEY);
|
|
1889
|
+
return profileId;
|
|
1818
1890
|
}
|
|
1819
1891
|
// TR-03: default (no predicate / no signal) = allowed → byte-identical to prior behavior.
|
|
1820
1892
|
isMarketingAllowed() {
|
|
@@ -1837,6 +1909,7 @@ class AttributionManager {
|
|
|
1837
1909
|
if (!this.queryParamsCache) {
|
|
1838
1910
|
this.queryParamsCache = params;
|
|
1839
1911
|
}
|
|
1912
|
+
this.captureKlaviyoProfileBinding(params);
|
|
1840
1913
|
const attribution = {
|
|
1841
1914
|
timestamp: Date.now()
|
|
1842
1915
|
};
|
|
@@ -1851,8 +1924,13 @@ class AttributionManager {
|
|
|
1851
1924
|
const value = params[utm];
|
|
1852
1925
|
if (value) {
|
|
1853
1926
|
attribution[utm] = value; // canonical: utm_source, utm_campaign, ...
|
|
1854
|
-
|
|
1855
|
-
|
|
1927
|
+
// The five original UTM dimensions keep their legacy stripped aliases.
|
|
1928
|
+
// `utm_id` is intentionally canonical-only: writing it to a generic `id`
|
|
1929
|
+
// property would collide with event/customer identifiers downstream.
|
|
1930
|
+
if (utm !== 'utm_id') {
|
|
1931
|
+
const key = utm.replace('utm_', '');
|
|
1932
|
+
attribution[key] = value; // alias: source, campaign, ...
|
|
1933
|
+
}
|
|
1856
1934
|
}
|
|
1857
1935
|
}
|
|
1858
1936
|
// Capture click IDs. The first present (by CLICK_IDS priority) is the primary
|
|
@@ -1889,6 +1967,12 @@ class AttributionManager {
|
|
|
1889
1967
|
attribution[param] = value;
|
|
1890
1968
|
}
|
|
1891
1969
|
}
|
|
1970
|
+
// The namespaced source is Datalyr's routing authority. Keep any
|
|
1971
|
+
// merchant-owned utm_source intact while still classifying this visit as
|
|
1972
|
+
// Klaviyo everywhere source-level attribution is displayed.
|
|
1973
|
+
if (attribution.dl_ksource === 'klaviyo') {
|
|
1974
|
+
attribution.source = 'klaviyo';
|
|
1975
|
+
}
|
|
1892
1976
|
// Capture referrer. 9.A.4: redact secret/PII query-param values (reset tokens,
|
|
1893
1977
|
// OAuth codes, emails) — this attribution object is stamped onto every event AND
|
|
1894
1978
|
// persisted into dl_first_touch/dl_last_touch, so it must be clean at capture.
|
|
@@ -2152,6 +2236,21 @@ class AttributionManager {
|
|
|
2152
2236
|
current = Object.assign(Object.assign({}, fallback), { referrer: current.referrer, referrerHost: current.referrerHost, landingPage: current.landingPage, landingPath: current.landingPath });
|
|
2153
2237
|
}
|
|
2154
2238
|
}
|
|
2239
|
+
// A real signal WITHOUT its own link tag must not erase the stored one. The
|
|
2240
|
+
// creator/bio-link journey is click-link-today, return-from-instagram-next-week:
|
|
2241
|
+
// that organic revisit scores hasRealAttribution (external referrer) with no
|
|
2242
|
+
// `lyr`, and before this carry-forward storeLastTouch below replaced the tagged
|
|
2243
|
+
// touch wholesale — every later event (and the Track page's conversion count,
|
|
2244
|
+
// which keys on the event's own lyr) silently lost the link. Semantics = "last
|
|
2245
|
+
// non-empty lyr within the window", the same answer the server's resolveLyr
|
|
2246
|
+
// argMax gives webhook conversions; a NEW ?lyr= still wins because current.lyr
|
|
2247
|
+
// is already set. source/medium/campaign still update — lyr is an independent
|
|
2248
|
+
// dimension (which link), not a channel claim.
|
|
2249
|
+
if (hasRealAttribution && !current.lyr) {
|
|
2250
|
+
const storedLyr = (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.lyr) || (firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.lyr);
|
|
2251
|
+
if (storedLyr)
|
|
2252
|
+
current.lyr = storedLyr;
|
|
2253
|
+
}
|
|
2155
2254
|
// Capture advertising cookies automatically
|
|
2156
2255
|
const adCookies = this.captureAdCookies();
|
|
2157
2256
|
// Only (re)write first/last touch when this pageview carried a REAL signal — never
|
|
@@ -2328,6 +2427,8 @@ class AttributionManager {
|
|
|
2328
2427
|
}
|
|
2329
2428
|
}
|
|
2330
2429
|
}
|
|
2430
|
+
AttributionManager.KLAVIYO_BINDING_KEY = 'dl_klaviyo_profile_binding';
|
|
2431
|
+
AttributionManager.KLAVIYO_BINDING_TTL_MS = 10 * 60 * 1000;
|
|
2331
2432
|
|
|
2332
2433
|
/**
|
|
2333
2434
|
* Event Queue and Batching Module
|
|
@@ -4988,6 +5089,7 @@ class Datalyr {
|
|
|
4988
5089
|
this.initialized = false;
|
|
4989
5090
|
this.errors = [];
|
|
4990
5091
|
this.MAX_ERRORS = 50;
|
|
5092
|
+
this.shopifyConsentUnresolvable = false; // loadFeatures failed or never yielded customerPrivacy → stop fail-closing
|
|
4991
5093
|
this.lastSpaPath = null; // dedups SPA pageviews (replaceState-on-mount double-fire)
|
|
4992
5094
|
// Shopify loads Customer Privacy asynchronously. Keep the initial pageview
|
|
4993
5095
|
// pending until initialization is complete and analytics consent is known,
|
|
@@ -5067,7 +5169,15 @@ class Datalyr {
|
|
|
5067
5169
|
// TR-03: gate ad-signal synthesis + shipping on LIVE marketing consent. Returns true by
|
|
5068
5170
|
// default (no consent signal) so behavior is unchanged for the common case; false only on
|
|
5069
5171
|
// an explicit decline (Shopify marketing:false / setConsent marketing|sale=false).
|
|
5070
|
-
marketingAllowed: () => this.consentAllowsMarketing()
|
|
5172
|
+
marketingAllowed: () => this.consentAllowsMarketing(),
|
|
5173
|
+
// Bypass our SPA wrapper when removing the one-time profile parameter,
|
|
5174
|
+
// otherwise the privacy cleanup itself would manufacture a pageview.
|
|
5175
|
+
replaceVisibleUrl: (url) => {
|
|
5176
|
+
var _a;
|
|
5177
|
+
const replace = (_a = this.originalReplaceState) !== null && _a !== void 0 ? _a : history.replaceState;
|
|
5178
|
+
replace.call(history, history.state, '', url);
|
|
5179
|
+
this.lastSpaPath = window.location.pathname + window.location.search + window.location.hash;
|
|
5180
|
+
},
|
|
5071
5181
|
});
|
|
5072
5182
|
this.queue = new EventQueue(this.config);
|
|
5073
5183
|
this.fingerprint = new FingerprintCollector({
|
|
@@ -5157,6 +5267,7 @@ class Datalyr {
|
|
|
5157
5267
|
// in the IdentityManager constructor. It never overwrites an id already
|
|
5158
5268
|
// set by an explicit identify() earlier in this page load.
|
|
5159
5269
|
yield this.identity.hydrateEncryptedUserId();
|
|
5270
|
+
yield this.attribution.hydrateKlaviyoProfileBinding();
|
|
5160
5271
|
this.log('Encryption initialized, user properties loaded');
|
|
5161
5272
|
}
|
|
5162
5273
|
catch (encErr) {
|
|
@@ -5420,6 +5531,14 @@ class Datalyr {
|
|
|
5420
5531
|
referrerParams.set('utm_content', lastTouch.content);
|
|
5421
5532
|
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.term)
|
|
5422
5533
|
referrerParams.set('utm_term', lastTouch.term);
|
|
5534
|
+
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.utm_id)
|
|
5535
|
+
referrerParams.set('utm_id', lastTouch.utm_id);
|
|
5536
|
+
// The trackable-link tag rides the referrer too — the edge worker's own
|
|
5537
|
+
// server-side appendPlayReferrer packs it, and ingest parses it back out
|
|
5538
|
+
// of install_referrer_url. Without it, interstitial-mode Android installs
|
|
5539
|
+
// lose the deterministic link tag that a bare 302 would have carried.
|
|
5540
|
+
if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.lyr)
|
|
5541
|
+
referrerParams.set('lyr', lastTouch.lyr);
|
|
5423
5542
|
try {
|
|
5424
5543
|
const url = new URL(options.appStoreUrl);
|
|
5425
5544
|
url.searchParams.set('referrer', referrerParams.toString());
|
|
@@ -6420,6 +6539,16 @@ class Datalyr {
|
|
|
6420
6539
|
// Add attribution data (caller wins on collisions)
|
|
6421
6540
|
const attributionData = this.attribution.getAttributionData();
|
|
6422
6541
|
assignMissing(eventData, attributionData);
|
|
6542
|
+
// A Klaviyo landing URL may carry the profile ID as a dedicated custom
|
|
6543
|
+
// parameter. Emit it once through the deterministic external-id envelope;
|
|
6544
|
+
// never copy it into generic attribution or user-visible URL fields.
|
|
6545
|
+
const klaviyoProfileId = this.attribution.consumeKlaviyoProfileBinding();
|
|
6546
|
+
if (klaviyoProfileId) {
|
|
6547
|
+
const existingExternalIds = eventData.external_ids;
|
|
6548
|
+
eventData.external_ids = Object.assign(Object.assign({}, (existingExternalIds && typeof existingExternalIds === 'object' && !Array.isArray(existingExternalIds)
|
|
6549
|
+
? existingExternalIds
|
|
6550
|
+
: {})), { klaviyo: klaviyoProfileId });
|
|
6551
|
+
}
|
|
6423
6552
|
// Add session metrics (SDK-authoritative — session_id et al. must not be overridden)
|
|
6424
6553
|
const sessionMetrics = this.session.getMetrics();
|
|
6425
6554
|
Object.assign(eventData, sessionMetrics);
|
|
@@ -6487,7 +6616,7 @@ class Datalyr {
|
|
|
6487
6616
|
// drift from the package again (it sat at 1.7.4 across the 1.7.5 release). The
|
|
6488
6617
|
// build guard (scripts/check-bundle.js, run by build:check) still verifies the
|
|
6489
6618
|
// deployable bundles carry the package.json version. (FSR-103)
|
|
6490
|
-
sdk_version: '1.7.
|
|
6619
|
+
sdk_version: '1.7.12',
|
|
6491
6620
|
sdk_name: 'datalyr-web-sdk',
|
|
6492
6621
|
// A3-25: versioned-envelope stamp. Every SDK emits schema_version so the ingest/contract
|
|
6493
6622
|
// layer can key on ONE canonical envelope version (snake_case fields, event_name/event_data
|
|
@@ -6576,6 +6705,27 @@ class Datalyr {
|
|
|
6576
6705
|
return false;
|
|
6577
6706
|
}
|
|
6578
6707
|
}
|
|
6708
|
+
/**
|
|
6709
|
+
* TR-15 refinement: fail closed ONLY while the Customer Privacy API can still
|
|
6710
|
+
* arrive (Shopify.loadFeatures exists and hasn't failed). A storefront with no
|
|
6711
|
+
* loadFeatures (headless installs, `platform:'shopify'` on a non-Shopify page)
|
|
6712
|
+
* has NO path to a consent answer — blocking there is not a pre-load window,
|
|
6713
|
+
* it is zero events forever, silently. No signal possible → null (fail open);
|
|
6714
|
+
* false is reserved for an explicit decline from the API.
|
|
6715
|
+
*/
|
|
6716
|
+
shopifyConsentPendingBlock() {
|
|
6717
|
+
var _a;
|
|
6718
|
+
if (!this.isShopifyStorefront())
|
|
6719
|
+
return null;
|
|
6720
|
+
if (this.shopifyConsentUnresolvable)
|
|
6721
|
+
return null;
|
|
6722
|
+
try {
|
|
6723
|
+
return typeof ((_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.loadFeatures) === 'function' ? false : null;
|
|
6724
|
+
}
|
|
6725
|
+
catch (_b) {
|
|
6726
|
+
return null;
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6579
6729
|
/**
|
|
6580
6730
|
* Shopify's analytics-consent signal: true/false when the Customer Privacy API is
|
|
6581
6731
|
* present and answers, null when there's no signal (API absent / unexpected shape)
|
|
@@ -6585,7 +6735,7 @@ class Datalyr {
|
|
|
6585
6735
|
try {
|
|
6586
6736
|
const cp = this.getShopifyCustomerPrivacy();
|
|
6587
6737
|
if (!cp)
|
|
6588
|
-
return this.
|
|
6738
|
+
return this.shopifyConsentPendingBlock();
|
|
6589
6739
|
if (typeof cp.analyticsProcessingAllowed === 'function') {
|
|
6590
6740
|
const allowed = cp.analyticsProcessingAllowed();
|
|
6591
6741
|
return typeof allowed === 'boolean' ? allowed : null;
|
|
@@ -6595,10 +6745,10 @@ class Datalyr {
|
|
|
6595
6745
|
const allowed = cp.userCanBeTracked();
|
|
6596
6746
|
return typeof allowed === 'boolean' ? allowed : null;
|
|
6597
6747
|
}
|
|
6598
|
-
return this.
|
|
6748
|
+
return this.shopifyConsentPendingBlock();
|
|
6599
6749
|
}
|
|
6600
6750
|
catch (_a) {
|
|
6601
|
-
return this.
|
|
6751
|
+
return this.shopifyConsentPendingBlock();
|
|
6602
6752
|
}
|
|
6603
6753
|
}
|
|
6604
6754
|
/**
|
|
@@ -6610,15 +6760,15 @@ class Datalyr {
|
|
|
6610
6760
|
try {
|
|
6611
6761
|
const cp = this.getShopifyCustomerPrivacy();
|
|
6612
6762
|
if (!cp || typeof cp.marketingAllowed !== 'function') {
|
|
6613
|
-
return this.
|
|
6763
|
+
return this.shopifyConsentPendingBlock();
|
|
6614
6764
|
}
|
|
6615
6765
|
const allowed = cp.marketingAllowed();
|
|
6616
6766
|
return typeof allowed === 'boolean'
|
|
6617
6767
|
? allowed
|
|
6618
|
-
: (this.
|
|
6768
|
+
: (this.shopifyConsentPendingBlock());
|
|
6619
6769
|
}
|
|
6620
6770
|
catch (_a) {
|
|
6621
|
-
return this.
|
|
6771
|
+
return this.shopifyConsentPendingBlock();
|
|
6622
6772
|
}
|
|
6623
6773
|
}
|
|
6624
6774
|
/**
|
|
@@ -6654,18 +6804,31 @@ class Datalyr {
|
|
|
6654
6804
|
// banner interaction. Guarded — a missing/changed loadFeatures must never break tracking.
|
|
6655
6805
|
const shopify = window.Shopify;
|
|
6656
6806
|
if (shopify && typeof shopify.loadFeatures === 'function') {
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6807
|
+
// One retry before declaring the API unreachable: a TRANSIENT loadFeatures
|
|
6808
|
+
// failure must not fail-open the whole session for a previously-declined
|
|
6809
|
+
// shopper. Only after the retry (or a success that still yields no
|
|
6810
|
+
// customerPrivacy) is the pre-load window declared over — then the
|
|
6811
|
+
// fail-closed gate releases and consent re-evaluates (a decline gates).
|
|
6812
|
+
const attempt = (retriesLeft) => {
|
|
6813
|
+
shopify.loadFeatures([{ name: 'consent-tracking-api', version: '0.1' }], (error) => {
|
|
6814
|
+
if (error && retriesLeft > 0) {
|
|
6815
|
+
this.log('Shopify loadFeatures(consent-tracking-api) failed, retrying:', error);
|
|
6816
|
+
setTimeout(() => attempt(retriesLeft - 1), 1000);
|
|
6817
|
+
return;
|
|
6818
|
+
}
|
|
6819
|
+
if (error)
|
|
6820
|
+
this.log('Shopify loadFeatures(consent-tracking-api) failed:', error);
|
|
6821
|
+
if (error || !this.getShopifyCustomerPrivacy())
|
|
6822
|
+
this.shopifyConsentUnresolvable = true;
|
|
6823
|
+
try {
|
|
6824
|
+
this.onShopifyConsentChanged();
|
|
6825
|
+
}
|
|
6826
|
+
catch (e) {
|
|
6827
|
+
this.log('Shopify post-load consent eval failed:', e);
|
|
6828
|
+
}
|
|
6829
|
+
});
|
|
6830
|
+
};
|
|
6831
|
+
attempt(1);
|
|
6669
6832
|
}
|
|
6670
6833
|
}
|
|
6671
6834
|
catch (error) {
|