@datalyr/web 1.2.1 → 1.3.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
@@ -19,6 +19,7 @@ Browser analytics and attribution SDK for web applications. Track events, identi
19
19
  - [Attribution](#attribution)
20
20
  - [Automatic Capture](#automatic-capture)
21
21
  - [Custom Parameters](#custom-parameters)
22
+ - [Web-to-App Attribution](#web-to-app-attribution)
22
23
  - [Event Queue](#event-queue)
23
24
  - [Offline Support](#offline-support)
24
25
  - [Privacy and Consent](#privacy-and-consent)
@@ -340,6 +341,50 @@ datalyr.init({
340
341
 
341
342
  ---
342
343
 
344
+ ## Web-to-App Attribution
345
+
346
+ Track users who click a "Download App" button on your website and attribute their app install back to the original ad click.
347
+
348
+ ### How It Works
349
+
350
+ 1. User clicks an ad → lands on your prelander page (web SDK loaded, captures `fbclid`, UTMs, IP)
351
+ 2. User clicks "Download App" → `trackAppDownloadClick()` fires event with full attribution
352
+ 3. User installs from App Store / Play Store
353
+ 4. First app open → mobile SDK recovers the web attribution:
354
+ - **Android**: Deterministic match via Play Store `referrer` URL parameter (~95% accuracy)
355
+ - **iOS**: IP-based match against recent web events within 24 hours (~90%+ accuracy)
356
+
357
+ ### Usage
358
+
359
+ ```javascript
360
+ // On your prelander "Download" button click handler
361
+ document.querySelector('#download-btn').addEventListener('click', () => {
362
+ datalyr.trackAppDownloadClick({
363
+ targetPlatform: 'ios', // or 'android'
364
+ appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
365
+ });
366
+ });
367
+ ```
368
+
369
+ For Android, pass the Play Store URL and the SDK will automatically encode attribution params into the `referrer` parameter:
370
+
371
+ ```javascript
372
+ datalyr.trackAppDownloadClick({
373
+ targetPlatform: 'android',
374
+ appStoreUrl: 'https://play.google.com/store/apps/details?id=com.yourapp',
375
+ });
376
+ ```
377
+
378
+ The method fires a `$app_download_click` event (with all attribution data), flushes the event queue via `sendBeacon`, then redirects the user to the store URL.
379
+
380
+ ### Requirements
381
+
382
+ - Web SDK must be initialized on the prelander page
383
+ - Mobile SDK (`@datalyr/react-native` or `@datalyr/swift`) must be installed in the app
384
+ - Attribution is recovered automatically on first app launch — no additional mobile code needed
385
+
386
+ ---
387
+
343
388
  ## Event Queue
344
389
 
345
390
  Events are batched for efficiency.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @datalyr/web v1.2.1
2
+ * @datalyr/web v1.3.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
@@ -731,8 +731,36 @@ function getRootDomain() {
731
731
  // Handle .co.uk, .com.au, etc
732
732
  const tld = parts[parts.length - 1];
733
733
  const sld = parts[parts.length - 2];
734
- // Common two-part TLDs
735
- const twoPartTlds = ['co.uk', 'com.au', 'co.nz', 'co.jp', 'co.in', 'co.za'];
734
+ // Common two-part TLDs (country-specific domains)
735
+ // Note: The CookieStorage.getAutoDomain() probe method handles unknown TLDs dynamically,
736
+ // but this list provides faster resolution for known patterns
737
+ const twoPartTlds = [
738
+ // United Kingdom
739
+ 'co.uk', 'org.uk', 'net.uk', 'ac.uk', 'gov.uk', 'me.uk',
740
+ // Australia
741
+ 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au',
742
+ // New Zealand
743
+ 'co.nz', 'net.nz', 'org.nz', 'govt.nz',
744
+ // Japan
745
+ 'co.jp', 'ne.jp', 'or.jp', 'ac.jp', 'go.jp',
746
+ // India
747
+ 'co.in', 'net.in', 'org.in', 'gov.in', 'ac.in',
748
+ // South Africa
749
+ 'co.za', 'net.za', 'org.za', 'gov.za',
750
+ // Brazil
751
+ 'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br',
752
+ // South Korea
753
+ 'co.kr', 'ne.kr', 'or.kr', 'go.kr', 'ac.kr',
754
+ // China
755
+ 'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn',
756
+ // Other Asia-Pacific
757
+ 'co.id', 'co.th', 'com.sg', 'com.my', 'com.ph', 'com.vn',
758
+ 'com.tw', 'com.hk',
759
+ // Latin America
760
+ 'com.mx', 'com.ar', 'com.co', 'com.pe', 'com.cl',
761
+ // Europe
762
+ 'co.il', 'co.at', 'co.hu', 'co.pl'
763
+ ];
736
764
  const lastTwo = `${sld}.${tld}`;
737
765
  if (twoPartTlds.includes(lastTwo) && parts.length >= 3) {
738
766
  return '.' + parts.slice(-3).join('.');
@@ -804,6 +832,22 @@ class IdentityManager {
804
832
  * Get or create anonymous ID (device/browser identifier)
805
833
  */
806
834
  getOrCreateAnonymousId() {
835
+ // 0. Check URL parameter first (cross-domain linking via _dl_vid)
836
+ // This enables tracking continuity when users navigate between different root domains
837
+ try {
838
+ const urlParams = new URLSearchParams(window.location.search);
839
+ const urlVisitorId = urlParams.get('_dl_vid');
840
+ if (urlVisitorId && urlVisitorId.startsWith('anon_')) {
841
+ // Valid visitor ID from URL - use it and persist
842
+ this.setRootDomainCookie('__dl_visitor_id', urlVisitorId);
843
+ storage.set('dl_anonymous_id', urlVisitorId);
844
+ return urlVisitorId;
845
+ }
846
+ }
847
+ catch (e) {
848
+ // URL parsing failed - continue with cookie/localStorage checks
849
+ console.warn('[Datalyr] Failed to parse URL for _dl_vid:', e);
850
+ }
807
851
  // 1. Check root domain cookie first (works across subdomains)
808
852
  let anonymousId = cookies.get('__dl_visitor_id');
809
853
  if (anonymousId) {
@@ -3514,6 +3558,60 @@ class Datalyr {
3514
3558
  this.trackError(error, { event: eventName });
3515
3559
  }
3516
3560
  }
3561
+ /**
3562
+ * Track an app download click and redirect to the app store.
3563
+ * Fires a $app_download_click event with full attribution data,
3564
+ * then redirects the user to the appropriate store URL.
3565
+ * For Android, encodes attribution params into the Play Store referrer
3566
+ * so the mobile SDK can retrieve them deterministically after install.
3567
+ */
3568
+ trackAppDownloadClick(options) {
3569
+ if (!this.initialized) {
3570
+ console.warn('[Datalyr] SDK not initialized. Call init() first.');
3571
+ return;
3572
+ }
3573
+ if (!this.shouldTrack())
3574
+ return;
3575
+ // Fire the event with target platform info — attribution data is
3576
+ // automatically merged by createEventPayload via getAttributionData()
3577
+ this.track('$app_download_click', {
3578
+ target_platform: options.targetPlatform,
3579
+ app_store_url: options.appStoreUrl,
3580
+ });
3581
+ // Flush immediately via sendBeacon before page navigates away
3582
+ this.queue.forceFlush();
3583
+ // For Android: append referrer param to Play Store URL with click attribution
3584
+ if (options.targetPlatform === 'android' && options.appStoreUrl.includes('play.google.com')) {
3585
+ const lastTouch = this.attribution.getLastTouch();
3586
+ const referrerParams = new URLSearchParams();
3587
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.clickId)
3588
+ referrerParams.set('dl_click_id', lastTouch.clickId);
3589
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.clickIdType)
3590
+ referrerParams.set('dl_click_id_type', lastTouch.clickIdType);
3591
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.source)
3592
+ referrerParams.set('utm_source', lastTouch.source);
3593
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.medium)
3594
+ referrerParams.set('utm_medium', lastTouch.medium);
3595
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.campaign)
3596
+ referrerParams.set('utm_campaign', lastTouch.campaign);
3597
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.content)
3598
+ referrerParams.set('utm_content', lastTouch.content);
3599
+ if (lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.term)
3600
+ referrerParams.set('utm_term', lastTouch.term);
3601
+ try {
3602
+ const url = new URL(options.appStoreUrl);
3603
+ url.searchParams.set('referrer', referrerParams.toString());
3604
+ window.location.href = url.toString();
3605
+ }
3606
+ catch (_a) {
3607
+ // If URL parsing fails, redirect without referrer
3608
+ window.location.href = options.appStoreUrl;
3609
+ }
3610
+ }
3611
+ else {
3612
+ window.location.href = options.appStoreUrl;
3613
+ }
3614
+ }
3517
3615
  /**
3518
3616
  * Identify a user
3519
3617
  */