@datalyr/react-native 1.4.9 → 1.5.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 @@ Mobile analytics and attribution SDK for React Native and Expo. Track events, id
19
19
  - [Attribution](#attribution)
20
20
  - [Automatic Capture](#automatic-capture)
21
21
  - [Deferred Deep Links](#deferred-deep-links)
22
+ - [Web-to-App Attribution](#web-to-app-attribution)
22
23
  - [Event Queue](#event-queue)
23
24
  - [Auto Events](#auto-events)
24
25
  - [SKAdNetwork](#skadnetwork)
@@ -356,6 +357,23 @@ if (deferred) {
356
357
  }
357
358
  ```
358
359
 
360
+ ### Web-to-App Attribution
361
+
362
+ Automatically recover attribution from a web prelander when users install the app from an ad.
363
+
364
+ **How it works:**
365
+ - **Android**: Attribution params are passed through the Play Store `referrer` URL parameter (set by the web SDK's `trackAppDownloadClick()`). The mobile SDK reads these via the Play Install Referrer API — deterministic, ~95% accuracy.
366
+ - **iOS**: On first install, the SDK calls the Datalyr API to match the device's IP against recent `$app_download_click` web events within 24 hours — ~90%+ accuracy for immediate installs.
367
+
368
+ No additional mobile code is needed. Attribution is recovered automatically during `initialize()` on first install, before the `app_install` event fires.
369
+
370
+ After a match, the SDK:
371
+ 1. Merges web attribution (click IDs, UTMs, cookies) into the mobile session
372
+ 2. Tracks a `$web_attribution_matched` event for analytics
373
+ 3. All subsequent events (including purchases) carry the matched attribution
374
+
375
+ **Fallback:** If IP matching misses (e.g., VPN toggle during install), email-based attribution is still recovered when `identify()` is called with the user's email.
376
+
359
377
  ### Manual Attribution
360
378
 
361
379
  Set attribution programmatically:
@@ -34,6 +34,13 @@ export declare class DatalyrSDK {
34
34
  * Called automatically during identify() if email is provided
35
35
  */
36
36
  private fetchAndMergeWebAttribution;
37
+ /**
38
+ * Fetch deferred web attribution on first app install.
39
+ * Uses IP-based matching (iOS) or Play Store referrer (Android) to recover
40
+ * attribution data (fbclid, utm_*, etc.) from a prelander web visit.
41
+ * Called automatically during initialize() when a fresh install is detected.
42
+ */
43
+ private fetchDeferredWebAttribution;
37
44
  /**
38
45
  * Alias a user (connect anonymous user to known user)
39
46
  */
@@ -187,6 +187,11 @@ export class DatalyrSDK {
187
187
  this.state.initialized = true;
188
188
  // Check for app install (after SDK is marked as initialized)
189
189
  if (attributionManager.isInstall()) {
190
+ // iOS: Attempt deferred web-to-app attribution via IP matching before tracking install
191
+ // Android: Play Store referrer is handled by playInstallReferrerIntegration
192
+ if (Platform.OS === 'ios') {
193
+ await this.fetchDeferredWebAttribution();
194
+ }
190
195
  const installData = await attributionManager.trackInstall();
191
196
  await this.track('app_install', {
192
197
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
@@ -368,6 +373,77 @@ export class DatalyrSDK {
368
373
  // Non-blocking - continue even if attribution fetch fails
369
374
  }
370
375
  }
376
+ /**
377
+ * Fetch deferred web attribution on first app install.
378
+ * Uses IP-based matching (iOS) or Play Store referrer (Android) to recover
379
+ * attribution data (fbclid, utm_*, etc.) from a prelander web visit.
380
+ * Called automatically during initialize() when a fresh install is detected.
381
+ */
382
+ async fetchDeferredWebAttribution() {
383
+ var _a;
384
+ if (!((_a = this.state.config) === null || _a === void 0 ? void 0 : _a.apiKey)) {
385
+ debugLog('API key not available for deferred attribution fetch');
386
+ return;
387
+ }
388
+ try {
389
+ debugLog('Fetching deferred web attribution via IP matching...');
390
+ const baseUrl = this.state.config.endpoint || 'https://api.datalyr.com';
391
+ const controller = new AbortController();
392
+ const timeout = setTimeout(() => controller.abort(), 10000);
393
+ const response = await fetch(`${baseUrl}/attribution/deferred-lookup`, {
394
+ method: 'POST',
395
+ headers: {
396
+ 'Content-Type': 'application/json',
397
+ 'X-Datalyr-API-Key': this.state.config.apiKey,
398
+ },
399
+ body: JSON.stringify({ platform: Platform.OS }),
400
+ signal: controller.signal,
401
+ });
402
+ clearTimeout(timeout);
403
+ if (!response.ok) {
404
+ debugLog('Deferred attribution lookup failed:', response.status);
405
+ return;
406
+ }
407
+ const result = await response.json();
408
+ if (!result.found || !result.attribution) {
409
+ debugLog('No deferred web attribution found for this IP');
410
+ return;
411
+ }
412
+ const webAttribution = result.attribution;
413
+ debugLog('Deferred web attribution found:', {
414
+ visitor_id: webAttribution.visitor_id,
415
+ has_fbclid: !!webAttribution.fbclid,
416
+ has_gclid: !!webAttribution.gclid,
417
+ utm_source: webAttribution.utm_source,
418
+ });
419
+ // Merge web attribution into current session
420
+ attributionManager.mergeWebAttribution(webAttribution);
421
+ // Track match event for analytics
422
+ await this.track('$web_attribution_matched', {
423
+ web_visitor_id: webAttribution.visitor_id,
424
+ web_user_id: webAttribution.user_id,
425
+ fbclid: webAttribution.fbclid,
426
+ gclid: webAttribution.gclid,
427
+ ttclid: webAttribution.ttclid,
428
+ gbraid: webAttribution.gbraid,
429
+ wbraid: webAttribution.wbraid,
430
+ fbp: webAttribution.fbp,
431
+ fbc: webAttribution.fbc,
432
+ utm_source: webAttribution.utm_source,
433
+ utm_medium: webAttribution.utm_medium,
434
+ utm_campaign: webAttribution.utm_campaign,
435
+ utm_content: webAttribution.utm_content,
436
+ utm_term: webAttribution.utm_term,
437
+ web_timestamp: webAttribution.timestamp,
438
+ match_method: 'ip',
439
+ });
440
+ debugLog('Successfully merged deferred web attribution');
441
+ }
442
+ catch (error) {
443
+ errorLog('Error fetching deferred web attribution:', error);
444
+ // Non-blocking - email-based fallback will catch this on identify()
445
+ }
446
+ }
371
447
  /**
372
448
  * Alias a user (connect anonymous user to known user)
373
449
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.4.9",
3
+ "version": "1.5.0",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs for iOS and Android",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -243,6 +243,12 @@ export class DatalyrSDK {
243
243
 
244
244
  // Check for app install (after SDK is marked as initialized)
245
245
  if (attributionManager.isInstall()) {
246
+ // iOS: Attempt deferred web-to-app attribution via IP matching before tracking install
247
+ // Android: Play Store referrer is handled by playInstallReferrerIntegration
248
+ if (Platform.OS === 'ios') {
249
+ await this.fetchDeferredWebAttribution();
250
+ }
251
+
246
252
  const installData = await attributionManager.trackInstall();
247
253
  await this.track('app_install', {
248
254
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
@@ -453,6 +459,88 @@ export class DatalyrSDK {
453
459
  }
454
460
  }
455
461
 
462
+ /**
463
+ * Fetch deferred web attribution on first app install.
464
+ * Uses IP-based matching (iOS) or Play Store referrer (Android) to recover
465
+ * attribution data (fbclid, utm_*, etc.) from a prelander web visit.
466
+ * Called automatically during initialize() when a fresh install is detected.
467
+ */
468
+ private async fetchDeferredWebAttribution(): Promise<void> {
469
+ if (!this.state.config?.apiKey) {
470
+ debugLog('API key not available for deferred attribution fetch');
471
+ return;
472
+ }
473
+
474
+ try {
475
+ debugLog('Fetching deferred web attribution via IP matching...');
476
+
477
+ const baseUrl = this.state.config.endpoint || 'https://api.datalyr.com';
478
+ const controller = new AbortController();
479
+ const timeout = setTimeout(() => controller.abort(), 10000);
480
+
481
+ const response = await fetch(`${baseUrl}/attribution/deferred-lookup`, {
482
+ method: 'POST',
483
+ headers: {
484
+ 'Content-Type': 'application/json',
485
+ 'X-Datalyr-API-Key': this.state.config.apiKey,
486
+ },
487
+ body: JSON.stringify({ platform: Platform.OS }),
488
+ signal: controller.signal,
489
+ });
490
+
491
+ clearTimeout(timeout);
492
+
493
+ if (!response.ok) {
494
+ debugLog('Deferred attribution lookup failed:', response.status);
495
+ return;
496
+ }
497
+
498
+ const result = await response.json() as { found: boolean; attribution?: any };
499
+
500
+ if (!result.found || !result.attribution) {
501
+ debugLog('No deferred web attribution found for this IP');
502
+ return;
503
+ }
504
+
505
+ const webAttribution = result.attribution;
506
+ debugLog('Deferred web attribution found:', {
507
+ visitor_id: webAttribution.visitor_id,
508
+ has_fbclid: !!webAttribution.fbclid,
509
+ has_gclid: !!webAttribution.gclid,
510
+ utm_source: webAttribution.utm_source,
511
+ });
512
+
513
+ // Merge web attribution into current session
514
+ attributionManager.mergeWebAttribution(webAttribution);
515
+
516
+ // Track match event for analytics
517
+ await this.track('$web_attribution_matched', {
518
+ web_visitor_id: webAttribution.visitor_id,
519
+ web_user_id: webAttribution.user_id,
520
+ fbclid: webAttribution.fbclid,
521
+ gclid: webAttribution.gclid,
522
+ ttclid: webAttribution.ttclid,
523
+ gbraid: webAttribution.gbraid,
524
+ wbraid: webAttribution.wbraid,
525
+ fbp: webAttribution.fbp,
526
+ fbc: webAttribution.fbc,
527
+ utm_source: webAttribution.utm_source,
528
+ utm_medium: webAttribution.utm_medium,
529
+ utm_campaign: webAttribution.utm_campaign,
530
+ utm_content: webAttribution.utm_content,
531
+ utm_term: webAttribution.utm_term,
532
+ web_timestamp: webAttribution.timestamp,
533
+ match_method: 'ip',
534
+ });
535
+
536
+ debugLog('Successfully merged deferred web attribution');
537
+
538
+ } catch (error) {
539
+ errorLog('Error fetching deferred web attribution:', error as Error);
540
+ // Non-blocking - email-based fallback will catch this on identify()
541
+ }
542
+ }
543
+
456
544
  /**
457
545
  * Alias a user (connect anonymous user to known user)
458
546
  */