@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 +18 -0
- package/lib/datalyr-sdk.d.ts +7 -0
- package/lib/datalyr-sdk.js +76 -0
- package/package.json +1 -1
- package/src/datalyr-sdk.ts +88 -0
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:
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -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
|
*/
|
package/lib/datalyr-sdk.js
CHANGED
|
@@ -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.
|
|
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",
|
package/src/datalyr-sdk.ts
CHANGED
|
@@ -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
|
*/
|