@datalyr/web 1.6.4 → 1.7.0
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/README.md +18 -3
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/container.d.ts +10 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/datalyr.cjs.js +174 -7
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +174 -7
- 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 +174 -7
- 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 +35 -0
- package/dist/index.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.
|
|
2
|
+
* @datalyr/web v1.7.0
|
|
3
3
|
* Datalyr Web SDK - Modern attribution tracking for web applications
|
|
4
4
|
* (c) 2026 Datalyr Inc.
|
|
5
5
|
* Released under the MIT License
|
|
@@ -2400,9 +2400,10 @@ class ContainerManager {
|
|
|
2400
2400
|
throw new Error(`Failed to fetch container scripts: ${response.status}`);
|
|
2401
2401
|
}
|
|
2402
2402
|
const data = yield response.json();
|
|
2403
|
-
// Store scripts and
|
|
2403
|
+
// Store scripts, pixels, and the SDK runtime config envelope.
|
|
2404
2404
|
this.scripts = data.scripts || [];
|
|
2405
2405
|
this.pixels = data.pixels || null;
|
|
2406
|
+
this.remoteConfig = (data.config && typeof data.config === 'object') ? data.config : undefined;
|
|
2406
2407
|
// Initialize pixels if configured. Awaited so advanced-matching hashes
|
|
2407
2408
|
// are resolved before the first dl.track() flushes through trackToPixels
|
|
2408
2409
|
// (otherwise fbq('init') would lag fbq('track') in fast-path tracks).
|
|
@@ -2432,6 +2433,13 @@ class ContainerManager {
|
|
|
2432
2433
|
}
|
|
2433
2434
|
});
|
|
2434
2435
|
}
|
|
2436
|
+
/**
|
|
2437
|
+
* The SDK runtime config delivered by /container-scripts, or undefined if the
|
|
2438
|
+
* response omitted it. The SDK merges this under explicit init() options.
|
|
2439
|
+
*/
|
|
2440
|
+
getRemoteConfig() {
|
|
2441
|
+
return this.remoteConfig;
|
|
2442
|
+
}
|
|
2435
2443
|
/**
|
|
2436
2444
|
* Load scripts by trigger type
|
|
2437
2445
|
*/
|
|
@@ -3089,7 +3097,7 @@ class AutoIdentifyManager {
|
|
|
3089
3097
|
this.config = {
|
|
3090
3098
|
enabled: config.enabled !== false,
|
|
3091
3099
|
captureFromForms: config.captureFromForms !== false,
|
|
3092
|
-
captureFromAPI: config.captureFromAPI
|
|
3100
|
+
captureFromAPI: config.captureFromAPI === true, // default OFF: same-origin response-scan can mis-identify (e.g. admin views)
|
|
3093
3101
|
captureFromShopify: config.captureFromShopify !== false,
|
|
3094
3102
|
trustedDomains: config.trustedDomains || [],
|
|
3095
3103
|
debug: config.debug || false
|
|
@@ -3498,12 +3506,57 @@ class AutoIdentifyManager {
|
|
|
3498
3506
|
}
|
|
3499
3507
|
}
|
|
3500
3508
|
|
|
3509
|
+
/** Keys the remote config is allowed to fill on DatalyrConfig. */
|
|
3510
|
+
const REMOTE_KEYS = [
|
|
3511
|
+
'autoIdentify',
|
|
3512
|
+
'autoIdentifyForms',
|
|
3513
|
+
'autoIdentifyAPI',
|
|
3514
|
+
'autoIdentifyShopify',
|
|
3515
|
+
'shopifyCartAttributes',
|
|
3516
|
+
'checkoutChampDomains',
|
|
3517
|
+
'respectGlobalPrivacyControl',
|
|
3518
|
+
'respectDoNotTrack',
|
|
3519
|
+
'privacyMode',
|
|
3520
|
+
];
|
|
3521
|
+
/**
|
|
3522
|
+
* Fold `remote` into `config` IN PLACE. Precedence per key:
|
|
3523
|
+
* explicit init() value > remote (dashboard) > built-in default
|
|
3524
|
+
*
|
|
3525
|
+
* `explicitKeys` = the keys the CALLER passed to init() (before built-in
|
|
3526
|
+
* defaults were merged in). For any remote key NOT in that set, remote
|
|
3527
|
+
* OVERRIDES the built-in default. This is essential because some keys
|
|
3528
|
+
* (respectDoNotTrack, respectGlobalPrivacyControl, privacyMode) get a built-in
|
|
3529
|
+
* default at init() and are therefore never `undefined` — a naive
|
|
3530
|
+
* "fill-if-undefined" would silently ignore the dashboard for them.
|
|
3531
|
+
*
|
|
3532
|
+
* No-op when there's no remote config (older worker / container disabled /
|
|
3533
|
+
* failed fetch) — built-in defaults then stand.
|
|
3534
|
+
*/
|
|
3535
|
+
function applyRemoteConfig(config, remote, explicitKeys) {
|
|
3536
|
+
if (!remote)
|
|
3537
|
+
return;
|
|
3538
|
+
const target = config;
|
|
3539
|
+
for (const key of REMOTE_KEYS) {
|
|
3540
|
+
const remoteVal = remote[key];
|
|
3541
|
+
if (remoteVal === undefined || remoteVal === null)
|
|
3542
|
+
continue;
|
|
3543
|
+
// Explicit init() always wins; otherwise remote overrides the built-in default.
|
|
3544
|
+
if (explicitKeys === null || explicitKeys === void 0 ? void 0 : explicitKeys.has(key))
|
|
3545
|
+
continue;
|
|
3546
|
+
target[key] = remoteVal;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3501
3550
|
/**
|
|
3502
3551
|
* Datalyr Web SDK
|
|
3503
3552
|
* Modern attribution tracking for web applications
|
|
3504
3553
|
*/
|
|
3505
3554
|
class Datalyr {
|
|
3506
3555
|
constructor() {
|
|
3556
|
+
// Keys the caller passed to init() (before built-in defaults were merged).
|
|
3557
|
+
// Lets the remote-config merge override built-in defaults while always
|
|
3558
|
+
// deferring to an explicit init() value. See applyRemoteConfig.
|
|
3559
|
+
this.explicitConfigKeys = new Set();
|
|
3507
3560
|
this.superProperties = {};
|
|
3508
3561
|
this.userProperties = {};
|
|
3509
3562
|
this.optedOut = false;
|
|
@@ -3526,6 +3579,10 @@ class Datalyr {
|
|
|
3526
3579
|
if (!config.workspaceId) {
|
|
3527
3580
|
throw new Error('[Datalyr] workspaceId is required');
|
|
3528
3581
|
}
|
|
3582
|
+
// Snapshot the keys the caller explicitly set, BEFORE built-in defaults are
|
|
3583
|
+
// merged in — so the remote-config merge can override built-in defaults
|
|
3584
|
+
// while still deferring to anything the caller passed explicitly.
|
|
3585
|
+
this.explicitConfigKeys = new Set(Object.keys(config));
|
|
3529
3586
|
// Set default config values
|
|
3530
3587
|
this.config = Object.assign({ endpoint: 'https://ingest.datalyr.com', debug: false, batchSize: 10, flushInterval: 5000, flushAt: 10, criticalEvents: undefined, highPriorityEvents: undefined, sessionTimeout: 60 * 60 * 1000, trackSessions: true, attributionWindow: 90 * 24 * 60 * 60 * 1000, trackedParams: [], respectDoNotTrack: false, respectGlobalPrivacyControl: true, privacyMode: 'standard', cookieDomain: 'auto', cookieExpires: 365, secureCookie: 'auto', sameSite: 'Lax', cookiePrefix: '__dl_', enablePerformanceTracking: true, enableFingerprinting: true, maxRetries: 5, retryDelay: 1000, maxOfflineQueueSize: 100, trackSPA: true, trackPageViews: true, fallbackEndpoints: [], plugins: [] }, config);
|
|
3531
3588
|
// platform:'shopify' implies cart-attribute sync: stamp visitor_id + Meta
|
|
@@ -3602,6 +3659,7 @@ class Datalyr {
|
|
|
3602
3659
|
return this.initializationPromise;
|
|
3603
3660
|
}
|
|
3604
3661
|
this.initializationPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
3662
|
+
var _a;
|
|
3605
3663
|
try {
|
|
3606
3664
|
// SEC-03 Fix: Initialize encryption for PII data
|
|
3607
3665
|
const deviceId = this.identity.getAnonymousId();
|
|
@@ -3636,6 +3694,26 @@ class Datalyr {
|
|
|
3636
3694
|
yield this.container.init().catch(error => {
|
|
3637
3695
|
this.log('Container initialization failed:', error);
|
|
3638
3696
|
});
|
|
3697
|
+
// Checkout Champ thank-you/upsell page: co-fire the browser Meta Pixel
|
|
3698
|
+
// Purchase with the SAME deterministic event_id the CC webhook stamps
|
|
3699
|
+
// server-side, so Meta dedupes the browser event against the server-side
|
|
3700
|
+
// CAPI event (dedup = event_name + event_id). Pixel-only — the CC Export
|
|
3701
|
+
// Profile webhook owns the server event + CAPI postback. No-op anywhere
|
|
3702
|
+
// except a post-purchase page (keyed on CC's sessionStorage orderData).
|
|
3703
|
+
// Must run AFTER container.init() so fbq is loaded + init'd.
|
|
3704
|
+
if (this.config.platform === 'checkoutchamp') {
|
|
3705
|
+
this.fireCheckoutChampPurchasePixel();
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
// Fold the remote /container-scripts config UNDER explicit init()
|
|
3709
|
+
// overrides (precedence: defaults <- remote <- explicit). No-op if the
|
|
3710
|
+
// container is disabled or the worker didn't send `config`. This is what
|
|
3711
|
+
// makes the dashboard toggles take effect with no snippet edits.
|
|
3712
|
+
applyRemoteConfig(this.config, (_a = this.container) === null || _a === void 0 ? void 0 : _a.getRemoteConfig(), this.explicitConfigKeys);
|
|
3713
|
+
// Privacy-strict turns auto-identify (email capture) OFF regardless of
|
|
3714
|
+
// the dashboard/remote value — privacy wins over the bridge.
|
|
3715
|
+
if (this.config.privacyMode === 'strict') {
|
|
3716
|
+
this.config.autoIdentify = false;
|
|
3639
3717
|
}
|
|
3640
3718
|
// autoIdentify default for the CC bridge:
|
|
3641
3719
|
// - platform === 'checkoutchamp' (CC funnel pages) OR
|
|
@@ -3647,8 +3725,10 @@ class Datalyr {
|
|
|
3647
3725
|
if (ccBridgeActive && this.config.autoIdentify === undefined) {
|
|
3648
3726
|
this.config.autoIdentify = true;
|
|
3649
3727
|
}
|
|
3650
|
-
// Initialize auto-identify
|
|
3651
|
-
|
|
3728
|
+
// Initialize auto-identify when enabled (explicit or remote) AND
|
|
3729
|
+
// tracking is allowed. The shouldTrack() gate keeps capture from even
|
|
3730
|
+
// setting up its form/API interceptors for opted-out / DNT / GPC users.
|
|
3731
|
+
if (this.config.autoIdentify === true && this.shouldTrack()) {
|
|
3652
3732
|
this.autoIdentify = new AutoIdentifyManager({
|
|
3653
3733
|
enabled: true,
|
|
3654
3734
|
captureFromForms: this.config.autoIdentifyForms,
|
|
@@ -3674,7 +3754,7 @@ class Datalyr {
|
|
|
3674
3754
|
// Lets server-side order webhooks recover the browser visitor + Meta click
|
|
3675
3755
|
// signals (the postback webhook reads these as note_attributes). Inert unless
|
|
3676
3756
|
// enabled; best-effort and never blocks init.
|
|
3677
|
-
if (this.config.shopifyCartAttributes === true) {
|
|
3757
|
+
if (this.config.shopifyCartAttributes === true && this.shouldTrack()) {
|
|
3678
3758
|
this.syncShopifyCartAttributes().catch((error) => {
|
|
3679
3759
|
this.log('Shopify cart attribute sync failed:', error);
|
|
3680
3760
|
});
|
|
@@ -4271,6 +4351,93 @@ class Datalyr {
|
|
|
4271
4351
|
this.log("restoreFromURL failed:", error);
|
|
4272
4352
|
}
|
|
4273
4353
|
}
|
|
4354
|
+
/**
|
|
4355
|
+
* Checkout Champ Meta Pixel ⇄ CAPI deduplication.
|
|
4356
|
+
*
|
|
4357
|
+
* On a CC thank-you / upsell page, fire the BROWSER Meta Pixel Purchase with the
|
|
4358
|
+
* exact same event_id the CC webhook stamps server-side, so Meta collapses the
|
|
4359
|
+
* browser Pixel event and the server-side CAPI event into one (dedup key =
|
|
4360
|
+
* event_name + event_id). This gives the EMQ lift of a matched browser+server
|
|
4361
|
+
* event without double-counting conversions in Ads Manager.
|
|
4362
|
+
*
|
|
4363
|
+
* PIXEL-ONLY by design. We do NOT enqueue a server event here: the CC Export
|
|
4364
|
+
* Profile webhook (webhooks/platforms/checkoutchamp.js) already ingests the
|
|
4365
|
+
* purchase and fires CAPI. Calling track() here would create a second server
|
|
4366
|
+
* event (source='web') AND double-fire CAPI — defeating the whole point.
|
|
4367
|
+
*
|
|
4368
|
+
* The event_id MUST stay byte-identical to the server formula:
|
|
4369
|
+
* webhooks/platforms/checkoutchamp.js:250
|
|
4370
|
+
* generateEventId('checkoutchamp', `${event_type}_${order_id}`)
|
|
4371
|
+
* webhooks/core/ingest.js:122 → `${platform}_${webhookEventId}`
|
|
4372
|
+
* ⇒ `checkoutchamp_purchase_<order_id>`
|
|
4373
|
+
* where <order_id> is the value CC posts to the webhook via its [orderId] macro.
|
|
4374
|
+
* We read the browser-side counterpart from CC's own client-side order object:
|
|
4375
|
+
* JSON.parse(sessionStorage.getItem('orderData')).orderId
|
|
4376
|
+
* (CC docs: referenced in custom scripts as `orderDataTmp.orderId`).
|
|
4377
|
+
*
|
|
4378
|
+
* ASSUMPTION TO VERIFY ON A REAL CC TEST ORDER: that sessionStorage
|
|
4379
|
+
* orderData.orderId === the [orderId] CC sends to the postback. If a merchant's
|
|
4380
|
+
* CC plan exposes a different id client-side, the two event_ids won't match and
|
|
4381
|
+
* Meta will show duplicates — caught by the test order in the setup checklist.
|
|
4382
|
+
*
|
|
4383
|
+
* v1 scope: the primary order only. Per-upsell Pixel dedup (each upsell is its
|
|
4384
|
+
* own order_id + parent_order_id server-side) needs the upsell sessionStorage
|
|
4385
|
+
* shape confirmed on a live funnel first — tracked as a follow-up.
|
|
4386
|
+
*/
|
|
4387
|
+
fireCheckoutChampPurchasePixel() {
|
|
4388
|
+
var _a;
|
|
4389
|
+
if (typeof window === "undefined")
|
|
4390
|
+
return;
|
|
4391
|
+
try {
|
|
4392
|
+
// Needs the container (where the Meta Pixel lives). trackToPixels itself
|
|
4393
|
+
// no-ops unless a Meta pixel is enabled + fbq is present, so an
|
|
4394
|
+
// unconfigured workspace silently does nothing.
|
|
4395
|
+
if (!this.container)
|
|
4396
|
+
return;
|
|
4397
|
+
const raw = (_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem("orderData");
|
|
4398
|
+
if (!raw)
|
|
4399
|
+
return; // not a post-purchase page — nothing to dedupe
|
|
4400
|
+
const order = JSON.parse(raw) || {};
|
|
4401
|
+
const orderId = order.orderId;
|
|
4402
|
+
if (!orderId)
|
|
4403
|
+
return;
|
|
4404
|
+
// orderData persists across upsell page loads — fire the primary Purchase
|
|
4405
|
+
// Pixel once per order. (Meta also dedupes by event_id over 48h, so this is
|
|
4406
|
+
// belt-and-suspenders against a per-page re-fire.)
|
|
4407
|
+
const guardKey = `__dl_cc_purchase_${orderId}`;
|
|
4408
|
+
if (window.sessionStorage.getItem(guardKey))
|
|
4409
|
+
return;
|
|
4410
|
+
window.sessionStorage.setItem(guardKey, "1");
|
|
4411
|
+
// KEEP IN SYNC with the server formula above.
|
|
4412
|
+
const eventId = `checkoutchamp_purchase_${orderId}`;
|
|
4413
|
+
// value/currency are for EMQ quality only — Meta dedup is event_id+name, so
|
|
4414
|
+
// a mismatch here never breaks dedup. Best-effort parse of CC's fields.
|
|
4415
|
+
const value = Number(order.totalAmount);
|
|
4416
|
+
const properties = {
|
|
4417
|
+
order_id: String(orderId),
|
|
4418
|
+
content_type: "product",
|
|
4419
|
+
};
|
|
4420
|
+
if (Number.isFinite(value))
|
|
4421
|
+
properties.value = value;
|
|
4422
|
+
const currency = order.currencyCode || order.currency;
|
|
4423
|
+
if (currency)
|
|
4424
|
+
properties.currency = String(currency);
|
|
4425
|
+
if (order.productId)
|
|
4426
|
+
properties.content_ids = [String(order.productId)];
|
|
4427
|
+
// Pixel-only co-fire. 'purchase' maps to Meta 'Purchase' in trackToPixels,
|
|
4428
|
+
// matching the server-side rule's platform_event_name.
|
|
4429
|
+
this.container.trackToPixels("purchase", properties, eventId);
|
|
4430
|
+
this.log("Checkout Champ Purchase Pixel co-fired (CAPI dedup):", {
|
|
4431
|
+
eventId,
|
|
4432
|
+
value: properties.value,
|
|
4433
|
+
currency: properties.currency,
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
4436
|
+
catch (error) {
|
|
4437
|
+
// Never let dedup co-fire break the page or init.
|
|
4438
|
+
this.log("fireCheckoutChampPurchasePixel failed:", error);
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4274
4441
|
/**
|
|
4275
4442
|
* Storefront → Checkout Champ link stamping. Finds every `<a href>` whose host
|
|
4276
4443
|
* matches the configured CC domain list and appends `?_dl_vid=…&_dl_fbc=…&
|
|
@@ -4455,7 +4622,7 @@ class Datalyr {
|
|
|
4455
4622
|
resolution_method: 'browser_sdk',
|
|
4456
4623
|
resolution_confidence: 1.0,
|
|
4457
4624
|
// SDK metadata (keep in sync with package.json version)
|
|
4458
|
-
sdk_version: '1.6.
|
|
4625
|
+
sdk_version: '1.6.5',
|
|
4459
4626
|
sdk_name: 'datalyr-web-sdk'
|
|
4460
4627
|
};
|
|
4461
4628
|
return payload;
|