@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @datalyr/web
2
2
 
3
- Browser SDK for event tracking, user identity, and attribution. Version 1.4.1.
3
+ Browser SDK for event tracking, user identity, and attribution. Version 1.7.0.
4
4
 
5
5
  ## Table of Contents
6
6
 
@@ -116,7 +116,7 @@ Every event includes:
116
116
  workspace_id: 'wk_xxxxx',
117
117
  source: 'web',
118
118
  timestamp: '2024-01-15T10:30:00Z',
119
- sdk_version: '1.4.1',
119
+ sdk_version: '1.7.0',
120
120
  sdk_name: 'datalyr-web-sdk'
121
121
  }
122
122
  ```
@@ -187,7 +187,7 @@ datalyr.init({
187
187
  // Auto-Identify (opt-in)
188
188
  autoIdentify?: boolean, // Default: false - Automatically identify users
189
189
  autoIdentifyForms?: boolean, // Default: true - Capture email from forms (when autoIdentify enabled)
190
- autoIdentifyAPI?: boolean, // Default: true - Capture email from API responses (when autoIdentify enabled)
190
+ autoIdentifyAPI?: boolean, // Default: false - Capture email from API responses (off: same-origin scan can mis-identify; opt in explicitly)
191
191
  autoIdentifyShopify?: boolean, // Default: true - Capture email from Shopify endpoints (when autoIdentify enabled)
192
192
  autoIdentifyTrustedDomains?: string[], // Default: [] - Additional domains to trust for API capture
193
193
 
@@ -198,6 +198,21 @@ datalyr.init({
198
198
 
199
199
  ---
200
200
 
201
+ ## Remote config (Identity Bridge)
202
+
203
+ These options can be controlled from the dashboard (**Settings → Identity & Attribution**) with **no code change** — they're delivered to the SDK at runtime in the `/container-scripts` response and merged in:
204
+
205
+ - `autoIdentify`, `autoIdentifyForms`, `autoIdentifyAPI`, `autoIdentifyShopify`
206
+ - `shopifyCartAttributes`
207
+ - `checkoutChampDomains`
208
+ - `respectGlobalPrivacyControl`, `respectDoNotTrack`, `privacyMode`
209
+
210
+ **Precedence, per key:** built-in default ← dashboard ← explicit `init()`. An explicit value in `init()` always wins, so you can pin a value in code and still manage everything else from the dashboard. Changes propagate in ~5 minutes (edge-cached). An older worker that doesn't send the `config` envelope is a no-op — built-in defaults stand.
211
+
212
+ > `platform` is **not** remote-configurable — set it at install time via the `data-platform` attribute. The CheckoutChamp behaviors run before the remote config loads, so a dashboard-set platform wouldn't drive them.
213
+
214
+ ---
215
+
201
216
  ## API Reference
202
217
 
203
218
  ### Event Tracking
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Remote SDK config (Auto Identity Bridge).
3
+ *
4
+ * The `/container-scripts` response carries a `config` object — the workspace
5
+ * `sdk_config` with server-computed defaults already applied (e.g. auto-identify
6
+ * OFF for sensitive/health verticals). This module folds that remote config
7
+ * UNDER the caller's explicit init() options, so the dashboard controls behavior
8
+ * with zero snippet edits while explicit code always wins.
9
+ *
10
+ * Precedence per key: explicit init() value > remote config > built-in default
11
+ */
12
+ import type { DatalyrConfig } from './types';
13
+ /** Subset of DatalyrConfig the worker may deliver via the config envelope. */
14
+ export interface SdkRemoteConfig {
15
+ autoIdentify?: boolean;
16
+ autoIdentifyForms?: boolean;
17
+ autoIdentifyAPI?: boolean;
18
+ autoIdentifyShopify?: boolean;
19
+ shopifyCartAttributes?: boolean;
20
+ checkoutChampDomains?: string[];
21
+ respectGlobalPrivacyControl?: boolean;
22
+ respectDoNotTrack?: boolean;
23
+ privacyMode?: 'standard' | 'strict';
24
+ }
25
+ /**
26
+ * Fold `remote` into `config` IN PLACE. Precedence per key:
27
+ * explicit init() value > remote (dashboard) > built-in default
28
+ *
29
+ * `explicitKeys` = the keys the CALLER passed to init() (before built-in
30
+ * defaults were merged in). For any remote key NOT in that set, remote
31
+ * OVERRIDES the built-in default. This is essential because some keys
32
+ * (respectDoNotTrack, respectGlobalPrivacyControl, privacyMode) get a built-in
33
+ * default at init() and are therefore never `undefined` — a naive
34
+ * "fill-if-undefined" would silently ignore the dashboard for them.
35
+ *
36
+ * No-op when there's no remote config (older worker / container disabled /
37
+ * failed fetch) — built-in defaults then stand.
38
+ */
39
+ export declare function applyRemoteConfig(config: DatalyrConfig, remote?: SdkRemoteConfig | null, explicitKeys?: ReadonlySet<string>): void;
40
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAMhC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;CACrC;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,EAC/B,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,IAAI,CAUN"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
@@ -2,6 +2,7 @@
2
2
  * Container Script Manager
3
3
  * Loads and manages third-party tracking scripts and pixels
4
4
  */
5
+ import type { SdkRemoteConfig } from './config';
5
6
  /**
6
7
  * Identity snapshot read at the moment a third-party pixel initializes.
7
8
  * Aligns the browser Pixel's advanced matching with what CAPI sends server-side
@@ -57,6 +58,10 @@ export declare class ContainerManager {
57
58
  private loadedScripts;
58
59
  private sessionLoadedScripts;
59
60
  private pixels;
61
+ /** SDK runtime config from the /container-scripts `config` envelope (the
62
+ * dashboard sdk_config + server-computed defaults). undefined if the worker
63
+ * doesn't send it — caller then falls back to built-in defaults. */
64
+ private remoteConfig?;
60
65
  private workspaceId;
61
66
  private endpoint;
62
67
  private debug;
@@ -75,6 +80,11 @@ export declare class ContainerManager {
75
80
  * Initialize container and load scripts
76
81
  */
77
82
  init(): Promise<void>;
83
+ /**
84
+ * The SDK runtime config delivered by /container-scripts, or undefined if the
85
+ * response omitted it. The SDK merges this under explicit init() options.
86
+ */
87
+ getRemoteConfig(): SdkRemoteConfig | undefined;
78
88
  /**
79
89
  * Load scripts by trigger type
80
90
  */
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @datalyr/web v1.6.4
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
@@ -2404,9 +2404,10 @@ class ContainerManager {
2404
2404
  throw new Error(`Failed to fetch container scripts: ${response.status}`);
2405
2405
  }
2406
2406
  const data = yield response.json();
2407
- // Store scripts and pixels
2407
+ // Store scripts, pixels, and the SDK runtime config envelope.
2408
2408
  this.scripts = data.scripts || [];
2409
2409
  this.pixels = data.pixels || null;
2410
+ this.remoteConfig = (data.config && typeof data.config === 'object') ? data.config : undefined;
2410
2411
  // Initialize pixels if configured. Awaited so advanced-matching hashes
2411
2412
  // are resolved before the first dl.track() flushes through trackToPixels
2412
2413
  // (otherwise fbq('init') would lag fbq('track') in fast-path tracks).
@@ -2436,6 +2437,13 @@ class ContainerManager {
2436
2437
  }
2437
2438
  });
2438
2439
  }
2440
+ /**
2441
+ * The SDK runtime config delivered by /container-scripts, or undefined if the
2442
+ * response omitted it. The SDK merges this under explicit init() options.
2443
+ */
2444
+ getRemoteConfig() {
2445
+ return this.remoteConfig;
2446
+ }
2439
2447
  /**
2440
2448
  * Load scripts by trigger type
2441
2449
  */
@@ -3093,7 +3101,7 @@ class AutoIdentifyManager {
3093
3101
  this.config = {
3094
3102
  enabled: config.enabled !== false,
3095
3103
  captureFromForms: config.captureFromForms !== false,
3096
- captureFromAPI: config.captureFromAPI !== false,
3104
+ captureFromAPI: config.captureFromAPI === true, // default OFF: same-origin response-scan can mis-identify (e.g. admin views)
3097
3105
  captureFromShopify: config.captureFromShopify !== false,
3098
3106
  trustedDomains: config.trustedDomains || [],
3099
3107
  debug: config.debug || false
@@ -3502,12 +3510,57 @@ class AutoIdentifyManager {
3502
3510
  }
3503
3511
  }
3504
3512
 
3513
+ /** Keys the remote config is allowed to fill on DatalyrConfig. */
3514
+ const REMOTE_KEYS = [
3515
+ 'autoIdentify',
3516
+ 'autoIdentifyForms',
3517
+ 'autoIdentifyAPI',
3518
+ 'autoIdentifyShopify',
3519
+ 'shopifyCartAttributes',
3520
+ 'checkoutChampDomains',
3521
+ 'respectGlobalPrivacyControl',
3522
+ 'respectDoNotTrack',
3523
+ 'privacyMode',
3524
+ ];
3525
+ /**
3526
+ * Fold `remote` into `config` IN PLACE. Precedence per key:
3527
+ * explicit init() value > remote (dashboard) > built-in default
3528
+ *
3529
+ * `explicitKeys` = the keys the CALLER passed to init() (before built-in
3530
+ * defaults were merged in). For any remote key NOT in that set, remote
3531
+ * OVERRIDES the built-in default. This is essential because some keys
3532
+ * (respectDoNotTrack, respectGlobalPrivacyControl, privacyMode) get a built-in
3533
+ * default at init() and are therefore never `undefined` — a naive
3534
+ * "fill-if-undefined" would silently ignore the dashboard for them.
3535
+ *
3536
+ * No-op when there's no remote config (older worker / container disabled /
3537
+ * failed fetch) — built-in defaults then stand.
3538
+ */
3539
+ function applyRemoteConfig(config, remote, explicitKeys) {
3540
+ if (!remote)
3541
+ return;
3542
+ const target = config;
3543
+ for (const key of REMOTE_KEYS) {
3544
+ const remoteVal = remote[key];
3545
+ if (remoteVal === undefined || remoteVal === null)
3546
+ continue;
3547
+ // Explicit init() always wins; otherwise remote overrides the built-in default.
3548
+ if (explicitKeys === null || explicitKeys === void 0 ? void 0 : explicitKeys.has(key))
3549
+ continue;
3550
+ target[key] = remoteVal;
3551
+ }
3552
+ }
3553
+
3505
3554
  /**
3506
3555
  * Datalyr Web SDK
3507
3556
  * Modern attribution tracking for web applications
3508
3557
  */
3509
3558
  class Datalyr {
3510
3559
  constructor() {
3560
+ // Keys the caller passed to init() (before built-in defaults were merged).
3561
+ // Lets the remote-config merge override built-in defaults while always
3562
+ // deferring to an explicit init() value. See applyRemoteConfig.
3563
+ this.explicitConfigKeys = new Set();
3511
3564
  this.superProperties = {};
3512
3565
  this.userProperties = {};
3513
3566
  this.optedOut = false;
@@ -3530,6 +3583,10 @@ class Datalyr {
3530
3583
  if (!config.workspaceId) {
3531
3584
  throw new Error('[Datalyr] workspaceId is required');
3532
3585
  }
3586
+ // Snapshot the keys the caller explicitly set, BEFORE built-in defaults are
3587
+ // merged in — so the remote-config merge can override built-in defaults
3588
+ // while still deferring to anything the caller passed explicitly.
3589
+ this.explicitConfigKeys = new Set(Object.keys(config));
3533
3590
  // Set default config values
3534
3591
  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);
3535
3592
  // platform:'shopify' implies cart-attribute sync: stamp visitor_id + Meta
@@ -3606,6 +3663,7 @@ class Datalyr {
3606
3663
  return this.initializationPromise;
3607
3664
  }
3608
3665
  this.initializationPromise = (() => __awaiter(this, void 0, void 0, function* () {
3666
+ var _a;
3609
3667
  try {
3610
3668
  // SEC-03 Fix: Initialize encryption for PII data
3611
3669
  const deviceId = this.identity.getAnonymousId();
@@ -3640,6 +3698,26 @@ class Datalyr {
3640
3698
  yield this.container.init().catch(error => {
3641
3699
  this.log('Container initialization failed:', error);
3642
3700
  });
3701
+ // Checkout Champ thank-you/upsell page: co-fire the browser Meta Pixel
3702
+ // Purchase with the SAME deterministic event_id the CC webhook stamps
3703
+ // server-side, so Meta dedupes the browser event against the server-side
3704
+ // CAPI event (dedup = event_name + event_id). Pixel-only — the CC Export
3705
+ // Profile webhook owns the server event + CAPI postback. No-op anywhere
3706
+ // except a post-purchase page (keyed on CC's sessionStorage orderData).
3707
+ // Must run AFTER container.init() so fbq is loaded + init'd.
3708
+ if (this.config.platform === 'checkoutchamp') {
3709
+ this.fireCheckoutChampPurchasePixel();
3710
+ }
3711
+ }
3712
+ // Fold the remote /container-scripts config UNDER explicit init()
3713
+ // overrides (precedence: defaults <- remote <- explicit). No-op if the
3714
+ // container is disabled or the worker didn't send `config`. This is what
3715
+ // makes the dashboard toggles take effect with no snippet edits.
3716
+ applyRemoteConfig(this.config, (_a = this.container) === null || _a === void 0 ? void 0 : _a.getRemoteConfig(), this.explicitConfigKeys);
3717
+ // Privacy-strict turns auto-identify (email capture) OFF regardless of
3718
+ // the dashboard/remote value — privacy wins over the bridge.
3719
+ if (this.config.privacyMode === 'strict') {
3720
+ this.config.autoIdentify = false;
3643
3721
  }
3644
3722
  // autoIdentify default for the CC bridge:
3645
3723
  // - platform === 'checkoutchamp' (CC funnel pages) OR
@@ -3651,8 +3729,10 @@ class Datalyr {
3651
3729
  if (ccBridgeActive && this.config.autoIdentify === undefined) {
3652
3730
  this.config.autoIdentify = true;
3653
3731
  }
3654
- // Initialize auto-identify if explicitly enabled (opt-in)
3655
- if (this.config.autoIdentify === true) {
3732
+ // Initialize auto-identify when enabled (explicit or remote) AND
3733
+ // tracking is allowed. The shouldTrack() gate keeps capture from even
3734
+ // setting up its form/API interceptors for opted-out / DNT / GPC users.
3735
+ if (this.config.autoIdentify === true && this.shouldTrack()) {
3656
3736
  this.autoIdentify = new AutoIdentifyManager({
3657
3737
  enabled: true,
3658
3738
  captureFromForms: this.config.autoIdentifyForms,
@@ -3678,7 +3758,7 @@ class Datalyr {
3678
3758
  // Lets server-side order webhooks recover the browser visitor + Meta click
3679
3759
  // signals (the postback webhook reads these as note_attributes). Inert unless
3680
3760
  // enabled; best-effort and never blocks init.
3681
- if (this.config.shopifyCartAttributes === true) {
3761
+ if (this.config.shopifyCartAttributes === true && this.shouldTrack()) {
3682
3762
  this.syncShopifyCartAttributes().catch((error) => {
3683
3763
  this.log('Shopify cart attribute sync failed:', error);
3684
3764
  });
@@ -4275,6 +4355,93 @@ class Datalyr {
4275
4355
  this.log("restoreFromURL failed:", error);
4276
4356
  }
4277
4357
  }
4358
+ /**
4359
+ * Checkout Champ Meta Pixel ⇄ CAPI deduplication.
4360
+ *
4361
+ * On a CC thank-you / upsell page, fire the BROWSER Meta Pixel Purchase with the
4362
+ * exact same event_id the CC webhook stamps server-side, so Meta collapses the
4363
+ * browser Pixel event and the server-side CAPI event into one (dedup key =
4364
+ * event_name + event_id). This gives the EMQ lift of a matched browser+server
4365
+ * event without double-counting conversions in Ads Manager.
4366
+ *
4367
+ * PIXEL-ONLY by design. We do NOT enqueue a server event here: the CC Export
4368
+ * Profile webhook (webhooks/platforms/checkoutchamp.js) already ingests the
4369
+ * purchase and fires CAPI. Calling track() here would create a second server
4370
+ * event (source='web') AND double-fire CAPI — defeating the whole point.
4371
+ *
4372
+ * The event_id MUST stay byte-identical to the server formula:
4373
+ * webhooks/platforms/checkoutchamp.js:250
4374
+ * generateEventId('checkoutchamp', `${event_type}_${order_id}`)
4375
+ * webhooks/core/ingest.js:122 → `${platform}_${webhookEventId}`
4376
+ * ⇒ `checkoutchamp_purchase_<order_id>`
4377
+ * where <order_id> is the value CC posts to the webhook via its [orderId] macro.
4378
+ * We read the browser-side counterpart from CC's own client-side order object:
4379
+ * JSON.parse(sessionStorage.getItem('orderData')).orderId
4380
+ * (CC docs: referenced in custom scripts as `orderDataTmp.orderId`).
4381
+ *
4382
+ * ASSUMPTION TO VERIFY ON A REAL CC TEST ORDER: that sessionStorage
4383
+ * orderData.orderId === the [orderId] CC sends to the postback. If a merchant's
4384
+ * CC plan exposes a different id client-side, the two event_ids won't match and
4385
+ * Meta will show duplicates — caught by the test order in the setup checklist.
4386
+ *
4387
+ * v1 scope: the primary order only. Per-upsell Pixel dedup (each upsell is its
4388
+ * own order_id + parent_order_id server-side) needs the upsell sessionStorage
4389
+ * shape confirmed on a live funnel first — tracked as a follow-up.
4390
+ */
4391
+ fireCheckoutChampPurchasePixel() {
4392
+ var _a;
4393
+ if (typeof window === "undefined")
4394
+ return;
4395
+ try {
4396
+ // Needs the container (where the Meta Pixel lives). trackToPixels itself
4397
+ // no-ops unless a Meta pixel is enabled + fbq is present, so an
4398
+ // unconfigured workspace silently does nothing.
4399
+ if (!this.container)
4400
+ return;
4401
+ const raw = (_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem("orderData");
4402
+ if (!raw)
4403
+ return; // not a post-purchase page — nothing to dedupe
4404
+ const order = JSON.parse(raw) || {};
4405
+ const orderId = order.orderId;
4406
+ if (!orderId)
4407
+ return;
4408
+ // orderData persists across upsell page loads — fire the primary Purchase
4409
+ // Pixel once per order. (Meta also dedupes by event_id over 48h, so this is
4410
+ // belt-and-suspenders against a per-page re-fire.)
4411
+ const guardKey = `__dl_cc_purchase_${orderId}`;
4412
+ if (window.sessionStorage.getItem(guardKey))
4413
+ return;
4414
+ window.sessionStorage.setItem(guardKey, "1");
4415
+ // KEEP IN SYNC with the server formula above.
4416
+ const eventId = `checkoutchamp_purchase_${orderId}`;
4417
+ // value/currency are for EMQ quality only — Meta dedup is event_id+name, so
4418
+ // a mismatch here never breaks dedup. Best-effort parse of CC's fields.
4419
+ const value = Number(order.totalAmount);
4420
+ const properties = {
4421
+ order_id: String(orderId),
4422
+ content_type: "product",
4423
+ };
4424
+ if (Number.isFinite(value))
4425
+ properties.value = value;
4426
+ const currency = order.currencyCode || order.currency;
4427
+ if (currency)
4428
+ properties.currency = String(currency);
4429
+ if (order.productId)
4430
+ properties.content_ids = [String(order.productId)];
4431
+ // Pixel-only co-fire. 'purchase' maps to Meta 'Purchase' in trackToPixels,
4432
+ // matching the server-side rule's platform_event_name.
4433
+ this.container.trackToPixels("purchase", properties, eventId);
4434
+ this.log("Checkout Champ Purchase Pixel co-fired (CAPI dedup):", {
4435
+ eventId,
4436
+ value: properties.value,
4437
+ currency: properties.currency,
4438
+ });
4439
+ }
4440
+ catch (error) {
4441
+ // Never let dedup co-fire break the page or init.
4442
+ this.log("fireCheckoutChampPurchasePixel failed:", error);
4443
+ }
4444
+ }
4278
4445
  /**
4279
4446
  * Storefront → Checkout Champ link stamping. Finds every `<a href>` whose host
4280
4447
  * matches the configured CC domain list and appends `?_dl_vid=…&_dl_fbc=…&
@@ -4459,7 +4626,7 @@ class Datalyr {
4459
4626
  resolution_method: 'browser_sdk',
4460
4627
  resolution_confidence: 1.0,
4461
4628
  // SDK metadata (keep in sync with package.json version)
4462
- sdk_version: '1.6.4',
4629
+ sdk_version: '1.6.5',
4463
4630
  sdk_name: 'datalyr-web-sdk'
4464
4631
  };
4465
4632
  return payload;