@datalyr/web 1.6.1 → 1.6.3

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.
@@ -2,6 +2,20 @@
2
2
  * Container Script Manager
3
3
  * Loads and manages third-party tracking scripts and pixels
4
4
  */
5
+ /**
6
+ * Identity snapshot read at the moment a third-party pixel initializes.
7
+ * Aligns the browser Pixel's advanced matching with what CAPI sends server-side
8
+ * (meta.js shovels all of {user_id, visitor_id, anonymous_id} into the CAPI
9
+ * external_id array) — so any one of those hashes coincides between surfaces.
10
+ *
11
+ * `externalId` should be the SDK's distinct_id (user_id when identified, else
12
+ * anonymous_id). `email` is only populated after identify(); when present, lets
13
+ * the Pixel match on `em` too.
14
+ */
15
+ export interface PixelIdentity {
16
+ externalId?: string | null;
17
+ email?: string | null;
18
+ }
5
19
  export interface ContainerScript {
6
20
  id: string;
7
21
  name: string;
@@ -50,10 +64,12 @@ export declare class ContainerManager {
50
64
  private sandboxedIframes;
51
65
  private iframeCleanupTimeouts;
52
66
  private messageHandler;
67
+ private getIdentity?;
53
68
  constructor(options: {
54
69
  workspaceId: string;
55
70
  endpoint?: string;
56
71
  debug?: boolean;
72
+ getIdentity?: () => PixelIdentity | undefined;
57
73
  });
58
74
  /**
59
75
  * Initialize container and load scripts
@@ -110,6 +126,12 @@ export declare class ContainerManager {
110
126
  private initializePixels;
111
127
  /**
112
128
  * Initialize Meta (Facebook) Pixel
129
+ *
130
+ * Async because we resolve SHA-256 hashes for advanced matching (Meta's
131
+ * `external_id` / `em`) before calling fbq('init'). Aligning the hashes the
132
+ * browser Pixel sends with what CAPI sends (meta.js shovels user_id /
133
+ * visitor_id / anonymous_id into external_id[], and `em` is sha256 of the
134
+ * lowercased email) is the dedup-quality lift the CAPI side can't fix alone.
113
135
  */
114
136
  private initializeMetaPixel;
115
137
  /**
@@ -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.1
2
+ * @datalyr/web v1.6.3
3
3
  * Datalyr Web SDK - Modern attribution tracking for web applications
4
4
  * (c) 2026 Datalyr Inc.
5
5
  * Released under the MIT License
@@ -247,15 +247,21 @@ class SafeStorage {
247
247
  this.prefix = 'dl_';
248
248
  // Test if storage is available
249
249
  try {
250
+ if (!storage)
251
+ throw new Error('no storage'); // server / SSR
250
252
  const testKey = 'dl_test__' + Math.random();
251
253
  storage.setItem(testKey, '1');
252
254
  storage.removeItem(testKey);
253
255
  this.storage = storage;
254
256
  }
255
257
  catch (_a) {
256
- // Storage not available (Safari private mode, etc.)
258
+ // Storage not available (server-side render, Safari private mode, etc.)
257
259
  this.storage = null;
258
- console.warn('[Datalyr] Storage not available, using memory fallback');
260
+ // Only warn in the browser on the server there's intentionally no
261
+ // storage and the SDK does no real work until init() runs client-side.
262
+ if (typeof window !== 'undefined') {
263
+ console.warn('[Datalyr] Storage not available, using memory fallback');
264
+ }
259
265
  }
260
266
  }
261
267
  get(key, defaultValue = null) {
@@ -564,15 +570,53 @@ class CookieStorage {
564
570
  return '';
565
571
  }
566
572
  }
567
- // Export singleton instances for storage
568
- const storage = new SafeStorage(window.localStorage);
569
- new SafeStorage(window.sessionStorage);
573
+ // Export singleton instances for storage.
574
+ // Guard the browser globals so importing the SDK on the server (SSR / Node)
575
+ // doesn't throw `window is not defined`. On the server these fall back to
576
+ // in-memory storage; real storage is wired when the module re-evaluates in the
577
+ // browser. (Without this, any static `import` of the SDK in a Next.js client
578
+ // component crashes server rendering.)
579
+ const browserLocalStorage = typeof window !== 'undefined' ? window.localStorage : undefined;
580
+ const browserSessionStorage = typeof window !== 'undefined' ? window.sessionStorage : undefined;
581
+ const storage = new SafeStorage(browserLocalStorage);
582
+ new SafeStorage(browserSessionStorage);
570
583
  // Default cookie instance for backwards compatibility
571
584
  const cookies = new CookieStorage();
572
585
 
573
586
  /**
574
587
  * Utility Functions
575
588
  */
589
+ /**
590
+ * SHA-256 hex digest aligned with the worker's CAPI hashing (cloudflare/
591
+ * postback/core/utils.js `sha256()`). Always lowercases + trims the input
592
+ * before hashing — Meta's matching requires byte-for-byte identical hashes
593
+ * on the Pixel side and the CAPI server side, and the worker uniformly
594
+ * normalizes everything it hashes (em, ph, fn, ln, external_id, etc).
595
+ *
596
+ * Pixel-side hashes that don't match CAPI hashes silently fail to dedupe,
597
+ * which is why this helper does the normalization for callers — easy to
598
+ * forget, hard to debug after release. Returns null when Web Crypto isn't
599
+ * available (very old browsers, non-secure contexts) so callers can
600
+ * gracefully skip advanced matching rather than throwing.
601
+ */
602
+ function sha256Hex(input) {
603
+ return __awaiter(this, void 0, void 0, function* () {
604
+ if (typeof crypto === 'undefined' || !crypto.subtle)
605
+ return null;
606
+ if (!input)
607
+ return null;
608
+ try {
609
+ const bytes = new TextEncoder().encode(input.toLowerCase().trim());
610
+ const digest = yield crypto.subtle.digest('SHA-256', bytes);
611
+ return Array.from(new Uint8Array(digest))
612
+ .map((b) => b.toString(16).padStart(2, '0'))
613
+ .join('');
614
+ }
615
+ catch (_a) {
616
+ return null;
617
+ }
618
+ });
619
+ }
576
620
  /**
577
621
  * Generate UUID v4
578
622
  */
@@ -2302,6 +2346,7 @@ class ContainerManager {
2302
2346
  // Container scripts use the same endpoint as tracking (ingest)
2303
2347
  this.endpoint = options.endpoint || 'https://ingest.datalyr.com';
2304
2348
  this.debug = options.debug || false;
2349
+ this.getIdentity = options.getIdentity;
2305
2350
  // Load session scripts from storage
2306
2351
  const sessionScripts = storage.get('dl_session_scripts', []);
2307
2352
  this.sessionLoadedScripts = new Set(sessionScripts);
@@ -2345,9 +2390,11 @@ class ContainerManager {
2345
2390
  // Store scripts and pixels
2346
2391
  this.scripts = data.scripts || [];
2347
2392
  this.pixels = data.pixels || null;
2348
- // Initialize pixels if configured
2393
+ // Initialize pixels if configured. Awaited so advanced-matching hashes
2394
+ // are resolved before the first dl.track() flushes through trackToPixels
2395
+ // (otherwise fbq('init') would lag fbq('track') in fast-path tracks).
2349
2396
  if (this.pixels) {
2350
- this.initializePixels();
2397
+ yield this.initializePixels();
2351
2398
  }
2352
2399
  // Load scripts based on trigger
2353
2400
  this.loadScriptsByTrigger('page_load');
@@ -2639,57 +2686,92 @@ class ContainerManager {
2639
2686
  * Initialize third-party pixels (Meta, Google, TikTok)
2640
2687
  */
2641
2688
  initializePixels() {
2642
- var _a, _b, _c;
2643
- if (!this.pixels)
2644
- return;
2645
- // Initialize Meta Pixel
2646
- if (((_a = this.pixels.meta) === null || _a === void 0 ? void 0 : _a.enabled) && this.pixels.meta.pixel_id) {
2647
- this.initializeMetaPixel(this.pixels.meta);
2648
- }
2649
- // Initialize Google Tag
2650
- if (((_b = this.pixels.google) === null || _b === void 0 ? void 0 : _b.enabled) && this.pixels.google.tag_id) {
2651
- this.initializeGoogleTag(this.pixels.google);
2652
- }
2653
- // Initialize TikTok Pixel
2654
- if (((_c = this.pixels.tiktok) === null || _c === void 0 ? void 0 : _c.enabled) && this.pixels.tiktok.pixel_id) {
2655
- this.initializeTikTokPixel(this.pixels.tiktok);
2656
- }
2689
+ return __awaiter(this, void 0, void 0, function* () {
2690
+ var _a, _b, _c;
2691
+ if (!this.pixels)
2692
+ return;
2693
+ // Initialize Meta Pixel
2694
+ if (((_a = this.pixels.meta) === null || _a === void 0 ? void 0 : _a.enabled) && this.pixels.meta.pixel_id) {
2695
+ yield this.initializeMetaPixel(this.pixels.meta);
2696
+ }
2697
+ // Initialize Google Tag
2698
+ if (((_b = this.pixels.google) === null || _b === void 0 ? void 0 : _b.enabled) && this.pixels.google.tag_id) {
2699
+ this.initializeGoogleTag(this.pixels.google);
2700
+ }
2701
+ // Initialize TikTok Pixel
2702
+ if (((_c = this.pixels.tiktok) === null || _c === void 0 ? void 0 : _c.enabled) && this.pixels.tiktok.pixel_id) {
2703
+ this.initializeTikTokPixel(this.pixels.tiktok);
2704
+ }
2705
+ });
2657
2706
  }
2658
2707
  /**
2659
2708
  * Initialize Meta (Facebook) Pixel
2709
+ *
2710
+ * Async because we resolve SHA-256 hashes for advanced matching (Meta's
2711
+ * `external_id` / `em`) before calling fbq('init'). Aligning the hashes the
2712
+ * browser Pixel sends with what CAPI sends (meta.js shovels user_id /
2713
+ * visitor_id / anonymous_id into external_id[], and `em` is sha256 of the
2714
+ * lowercased email) is the dedup-quality lift the CAPI side can't fix alone.
2660
2715
  */
2661
2716
  initializeMetaPixel(config) {
2662
- try {
2663
- // Load Meta Pixel script
2664
- (function (f, b, e, v, n, t, s) {
2665
- if (f.fbq)
2666
- return;
2667
- n = f.fbq = function () {
2668
- n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
2669
- };
2670
- if (!f._fbq)
2671
- f._fbq = n;
2672
- n.push = n;
2673
- n.loaded = !0;
2674
- n.version = '2.0';
2675
- n.queue = [];
2676
- t = b.createElement(e);
2677
- t.async = !0;
2678
- t.src = v;
2679
- s = b.getElementsByTagName(e)[0];
2680
- s.parentNode.insertBefore(t, s);
2681
- })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
2682
- // Initialize pixel only — do NOT fire PageView here. The SDK's own pageview
2683
- // tracking (track('pageview')) routes through trackToPixels and fires a single
2684
- // mapped PageView with a shared eventID. Firing it again here produced TWO
2685
- // PageViews per load (one un-deduped). Note: if the host app disables pageview
2686
- // tracking entirely, no PageView is sent which is the correct outcome.
2687
- window.fbq('init', config.pixel_id);
2688
- this.log('Meta Pixel initialized:', config.pixel_id);
2689
- }
2690
- catch (error) {
2691
- this.log('Error initializing Meta Pixel:', error);
2692
- }
2717
+ return __awaiter(this, void 0, void 0, function* () {
2718
+ var _a;
2719
+ try {
2720
+ // Load Meta Pixel script
2721
+ (function (f, b, e, v, n, t, s) {
2722
+ if (f.fbq)
2723
+ return;
2724
+ n = f.fbq = function () {
2725
+ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
2726
+ };
2727
+ if (!f._fbq)
2728
+ f._fbq = n;
2729
+ n.push = n;
2730
+ n.loaded = !0;
2731
+ n.version = '2.0';
2732
+ n.queue = [];
2733
+ t = b.createElement(e);
2734
+ t.async = !0;
2735
+ t.src = v;
2736
+ s = b.getElementsByTagName(e)[0];
2737
+ s.parentNode.insertBefore(t, s);
2738
+ })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
2739
+ // Build advanced-matching object. Skipped silently if Web Crypto isn't
2740
+ // available Pixel still initializes, just without advanced matching.
2741
+ // Anonymous_id is stable across the session and always present, so we
2742
+ // never need to re-init on identify(): CAPI carries both anonymous_id
2743
+ // AND user_id in its external_id[] array, so either side matching one
2744
+ // hash slot is enough to dedupe.
2745
+ const advancedMatching = {};
2746
+ const identity = (_a = this.getIdentity) === null || _a === void 0 ? void 0 : _a.call(this);
2747
+ if (identity === null || identity === void 0 ? void 0 : identity.externalId) {
2748
+ const hash = yield sha256Hex(String(identity.externalId));
2749
+ if (hash)
2750
+ advancedMatching.external_id = hash;
2751
+ }
2752
+ if (identity === null || identity === void 0 ? void 0 : identity.email) {
2753
+ const hash = yield sha256Hex(String(identity.email).toLowerCase().trim());
2754
+ if (hash)
2755
+ advancedMatching.em = hash;
2756
+ }
2757
+ // Initialize pixel only — do NOT fire PageView here. The SDK's own pageview
2758
+ // tracking (track('pageview')) routes through trackToPixels and fires a single
2759
+ // mapped PageView with a shared eventID. Firing it again here produced TWO
2760
+ // PageViews per load (one un-deduped). Note: if the host app disables pageview
2761
+ // tracking entirely, no PageView is sent — which is the correct outcome.
2762
+ if (Object.keys(advancedMatching).length > 0) {
2763
+ window.fbq('init', config.pixel_id, advancedMatching);
2764
+ this.log('Meta Pixel initialized with advanced matching:', config.pixel_id, Object.keys(advancedMatching));
2765
+ }
2766
+ else {
2767
+ window.fbq('init', config.pixel_id);
2768
+ this.log('Meta Pixel initialized (no advanced matching):', config.pixel_id);
2769
+ }
2770
+ }
2771
+ catch (error) {
2772
+ this.log('Error initializing Meta Pixel:', error);
2773
+ }
2774
+ });
2693
2775
  }
2694
2776
  /**
2695
2777
  * Initialize Google Tag
@@ -3509,7 +3591,19 @@ class Datalyr {
3509
3591
  this.container = new ContainerManager({
3510
3592
  workspaceId: this.config.workspaceId,
3511
3593
  endpoint: this.config.endpoint,
3512
- debug: this.config.debug
3594
+ debug: this.config.debug,
3595
+ // Lazy: invoked at the moment a third-party pixel inits, AFTER the
3596
+ // /container-scripts roundtrip resolves — so a pre-init identify()
3597
+ // already updated this.identity / this.userProperties. distinctId
3598
+ // mirrors what CAPI puts in external_id[] (user_id || anonymous_id);
3599
+ // email lets the Pixel match on em with the same hash CAPI sends.
3600
+ getIdentity: () => {
3601
+ var _a, _b;
3602
+ return ({
3603
+ externalId: (_a = this.identity) === null || _a === void 0 ? void 0 : _a.getDistinctId(),
3604
+ email: (_b = this.userProperties) === null || _b === void 0 ? void 0 : _b.email,
3605
+ });
3606
+ }
3513
3607
  });
3514
3608
  // Initialize container asynchronously
3515
3609
  yield this.container.init().catch(error => {
@@ -4103,7 +4197,7 @@ class Datalyr {
4103
4197
  resolution_method: 'browser_sdk',
4104
4198
  resolution_confidence: 1.0,
4105
4199
  // SDK metadata (keep in sync with package.json version)
4106
- sdk_version: '1.6.0',
4200
+ sdk_version: '1.6.3',
4107
4201
  sdk_name: 'datalyr-web-sdk'
4108
4202
  };
4109
4203
  return payload;