@datalyr/react-native 1.2.1 → 1.3.1
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/CHANGELOG.md +19 -0
- package/README.md +145 -9
- package/android/build.gradle +54 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
- package/datalyr-react-native.podspec +2 -2
- package/ios/DatalyrSKAdNetwork.m +400 -1
- package/ios/PrivacyInfo.xcprivacy +48 -0
- package/lib/ConversionValueEncoder.d.ts +13 -1
- package/lib/ConversionValueEncoder.js +57 -23
- package/lib/datalyr-sdk.d.ts +31 -2
- package/lib/datalyr-sdk.js +138 -30
- package/lib/index.d.ts +5 -1
- package/lib/index.js +4 -1
- package/lib/integrations/index.d.ts +3 -1
- package/lib/integrations/index.js +2 -1
- package/lib/integrations/meta-integration.d.ts +1 -0
- package/lib/integrations/meta-integration.js +4 -3
- package/lib/integrations/play-install-referrer.d.ts +78 -0
- package/lib/integrations/play-install-referrer.js +166 -0
- package/lib/integrations/tiktok-integration.d.ts +1 -0
- package/lib/integrations/tiktok-integration.js +4 -3
- package/lib/journey.d.ts +106 -0
- package/lib/journey.js +258 -0
- package/lib/native/DatalyrNativeBridge.d.ts +42 -3
- package/lib/native/DatalyrNativeBridge.js +63 -9
- package/lib/native/SKAdNetworkBridge.d.ts +142 -0
- package/lib/native/SKAdNetworkBridge.js +328 -0
- package/lib/network-status.d.ts +84 -0
- package/lib/network-status.js +281 -0
- package/lib/types.d.ts +51 -0
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +52 -2
- package/package.json +13 -4
- package/src/ConversionValueEncoder.ts +67 -26
- package/src/datalyr-sdk-expo.ts +55 -6
- package/src/datalyr-sdk.ts +161 -38
- package/src/expo.ts +4 -0
- package/src/index.ts +7 -1
- package/src/integrations/index.ts +3 -1
- package/src/integrations/meta-integration.ts +4 -3
- package/src/integrations/play-install-referrer.ts +218 -0
- package/src/integrations/tiktok-integration.ts +4 -3
- package/src/journey.ts +338 -0
- package/src/native/DatalyrNativeBridge.ts +99 -13
- package/src/native/SKAdNetworkBridge.ts +481 -2
- package/src/network-status.ts +312 -0
- package/src/types.ts +74 -6
- package/src/utils.ts +62 -6
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { SKANCoarseValue, SKANConversionResult } from './native/SKAdNetworkBridge';
|
|
2
|
+
|
|
3
|
+
// SKAN 4.0 compatible event mapping
|
|
2
4
|
interface EventMapping {
|
|
3
5
|
bits: number[];
|
|
4
6
|
revenueBits?: number[];
|
|
5
7
|
priority: number;
|
|
8
|
+
coarseValue?: SKANCoarseValue; // SKAN 4.0: low, medium, high
|
|
9
|
+
lockWindow?: boolean; // SKAN 4.0: lock the conversion window after this event
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
interface ConversionTemplate {
|
|
@@ -18,11 +22,21 @@ export class ConversionValueEncoder {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
|
-
* Encode an event into Apple's 0-63 conversion value format
|
|
25
|
+
* Encode an event into Apple's 0-63 conversion value format (SKAN 3.0 compatible)
|
|
26
|
+
* @deprecated Use encodeWithSKAN4 for iOS 16.1+
|
|
22
27
|
*/
|
|
23
28
|
encode(event: string, properties?: Record<string, any>): number {
|
|
29
|
+
return this.encodeWithSKAN4(event, properties).fineValue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Encode an event with full SKAN 4.0 support (fine value, coarse value, lock window)
|
|
34
|
+
*/
|
|
35
|
+
encodeWithSKAN4(event: string, properties?: Record<string, any>): SKANConversionResult {
|
|
24
36
|
const mapping = this.template.events[event];
|
|
25
|
-
if (!mapping)
|
|
37
|
+
if (!mapping) {
|
|
38
|
+
return { fineValue: 0, coarseValue: 'low', lockWindow: false, priority: 0 };
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
let conversionValue = 0;
|
|
28
42
|
|
|
@@ -32,18 +46,30 @@ export class ConversionValueEncoder {
|
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
// Set revenue bits if revenue is provided
|
|
49
|
+
let coarseValue: SKANCoarseValue = mapping.coarseValue || 'medium';
|
|
35
50
|
if (mapping.revenueBits && properties) {
|
|
36
51
|
const revenue = properties.revenue || properties.value || 0;
|
|
37
52
|
const revenueTier = this.getRevenueTier(revenue);
|
|
38
|
-
|
|
53
|
+
|
|
39
54
|
for (let i = 0; i < Math.min(mapping.revenueBits.length, 3); i++) {
|
|
40
55
|
if ((revenueTier >> i) & 1) {
|
|
41
56
|
conversionValue |= (1 << mapping.revenueBits[i]);
|
|
42
57
|
}
|
|
43
58
|
}
|
|
59
|
+
|
|
60
|
+
// Upgrade coarse value based on revenue
|
|
61
|
+
coarseValue = this.getCoarseValueForRevenue(revenue);
|
|
44
62
|
}
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
// Ensure value is within 0-63 range
|
|
65
|
+
const fineValue = Math.min(conversionValue, 63);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
fineValue,
|
|
69
|
+
coarseValue,
|
|
70
|
+
lockWindow: mapping.lockWindow || false,
|
|
71
|
+
priority: mapping.priority
|
|
72
|
+
};
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
/**
|
|
@@ -59,43 +85,58 @@ export class ConversionValueEncoder {
|
|
|
59
85
|
if (revenue < 250) return 6; // $100-250
|
|
60
86
|
return 7; // $250+
|
|
61
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Map revenue to SKAN 4.0 coarse value
|
|
91
|
+
*/
|
|
92
|
+
private getCoarseValueForRevenue(revenue: number): SKANCoarseValue {
|
|
93
|
+
if (revenue < 10) return 'low'; // $0-10 = low value
|
|
94
|
+
if (revenue < 50) return 'medium'; // $10-50 = medium value
|
|
95
|
+
return 'high'; // $50+ = high value
|
|
96
|
+
}
|
|
62
97
|
}
|
|
63
98
|
|
|
64
|
-
// Industry templates
|
|
99
|
+
// Industry templates with SKAN 4.0 support
|
|
65
100
|
export const ConversionTemplates = {
|
|
101
|
+
// E-commerce template - optimized for online stores
|
|
102
|
+
// SKAN 4.0: purchase locks window, high-value events get "high" coarse value
|
|
66
103
|
ecommerce: {
|
|
67
104
|
name: 'ecommerce',
|
|
68
105
|
events: {
|
|
69
|
-
purchase: { bits: [0], revenueBits: [1, 2, 3], priority: 100 },
|
|
70
|
-
add_to_cart: { bits: [4], priority: 30 },
|
|
71
|
-
begin_checkout: { bits: [5], priority: 50 },
|
|
72
|
-
signup: { bits: [6], priority: 20 },
|
|
73
|
-
subscribe: { bits: [0, 1], revenueBits: [2, 3, 4], priority: 90 },
|
|
74
|
-
view_item: { bits: [7], priority: 10 }
|
|
106
|
+
purchase: { bits: [0], revenueBits: [1, 2, 3], priority: 100, coarseValue: 'high' as SKANCoarseValue, lockWindow: true },
|
|
107
|
+
add_to_cart: { bits: [4], priority: 30, coarseValue: 'low' as SKANCoarseValue },
|
|
108
|
+
begin_checkout: { bits: [5], priority: 50, coarseValue: 'medium' as SKANCoarseValue },
|
|
109
|
+
signup: { bits: [6], priority: 20, coarseValue: 'low' as SKANCoarseValue },
|
|
110
|
+
subscribe: { bits: [0, 1], revenueBits: [2, 3, 4], priority: 90, coarseValue: 'high' as SKANCoarseValue, lockWindow: true },
|
|
111
|
+
view_item: { bits: [7], priority: 10, coarseValue: 'low' as SKANCoarseValue }
|
|
75
112
|
}
|
|
76
113
|
} as ConversionTemplate,
|
|
77
|
-
|
|
114
|
+
|
|
115
|
+
// Gaming template - optimized for mobile games
|
|
116
|
+
// SKAN 4.0: purchase locks window, tutorial completion is medium value
|
|
78
117
|
gaming: {
|
|
79
118
|
name: 'gaming',
|
|
80
119
|
events: {
|
|
81
|
-
level_complete: { bits: [0], priority: 40 },
|
|
82
|
-
tutorial_complete: { bits: [1], priority: 60 },
|
|
83
|
-
purchase: { bits: [2], revenueBits: [3, 4, 5], priority: 100 },
|
|
84
|
-
achievement_unlocked: { bits: [6], priority: 30 },
|
|
85
|
-
session_start: { bits: [7], priority: 10 },
|
|
86
|
-
ad_watched: { bits: [0, 6], priority: 20 }
|
|
120
|
+
level_complete: { bits: [0], priority: 40, coarseValue: 'medium' as SKANCoarseValue },
|
|
121
|
+
tutorial_complete: { bits: [1], priority: 60, coarseValue: 'medium' as SKANCoarseValue },
|
|
122
|
+
purchase: { bits: [2], revenueBits: [3, 4, 5], priority: 100, coarseValue: 'high' as SKANCoarseValue, lockWindow: true },
|
|
123
|
+
achievement_unlocked: { bits: [6], priority: 30, coarseValue: 'low' as SKANCoarseValue },
|
|
124
|
+
session_start: { bits: [7], priority: 10, coarseValue: 'low' as SKANCoarseValue },
|
|
125
|
+
ad_watched: { bits: [0, 6], priority: 20, coarseValue: 'low' as SKANCoarseValue }
|
|
87
126
|
}
|
|
88
127
|
} as ConversionTemplate,
|
|
89
|
-
|
|
128
|
+
|
|
129
|
+
// Subscription template - optimized for subscription apps
|
|
130
|
+
// SKAN 4.0: subscribe/upgrade lock window, trial is medium value
|
|
90
131
|
subscription: {
|
|
91
132
|
name: 'subscription',
|
|
92
133
|
events: {
|
|
93
|
-
trial_start: { bits: [0], priority: 70 },
|
|
94
|
-
subscribe: { bits: [1], revenueBits: [2, 3, 4], priority: 100 },
|
|
95
|
-
upgrade: { bits: [1, 5], revenueBits: [2, 3, 4], priority: 90 },
|
|
96
|
-
cancel: { bits: [6], priority: 20 },
|
|
97
|
-
signup: { bits: [7], priority: 30 },
|
|
98
|
-
payment_method_added: { bits: [0, 7], priority: 50 }
|
|
134
|
+
trial_start: { bits: [0], priority: 70, coarseValue: 'medium' as SKANCoarseValue },
|
|
135
|
+
subscribe: { bits: [1], revenueBits: [2, 3, 4], priority: 100, coarseValue: 'high' as SKANCoarseValue, lockWindow: true },
|
|
136
|
+
upgrade: { bits: [1, 5], revenueBits: [2, 3, 4], priority: 90, coarseValue: 'high' as SKANCoarseValue, lockWindow: true },
|
|
137
|
+
cancel: { bits: [6], priority: 20, coarseValue: 'low' as SKANCoarseValue },
|
|
138
|
+
signup: { bits: [7], priority: 30, coarseValue: 'low' as SKANCoarseValue },
|
|
139
|
+
payment_method_added: { bits: [0, 7], priority: 50, coarseValue: 'medium' as SKANCoarseValue }
|
|
99
140
|
}
|
|
100
141
|
} as ConversionTemplate
|
|
101
142
|
};
|
package/src/datalyr-sdk-expo.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { createHttpClient, HttpClient } from './http-client';
|
|
31
31
|
import { createEventQueue, EventQueue } from './event-queue';
|
|
32
32
|
import { attributionManager, AttributionData } from './attribution';
|
|
33
|
+
import { journeyManager } from './journey';
|
|
33
34
|
import { createAutoEventsManager, AutoEventsManager } from './auto-events';
|
|
34
35
|
import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
|
|
35
36
|
import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
@@ -115,6 +116,24 @@ export class DatalyrSDKExpo {
|
|
|
115
116
|
await attributionManager.initialize();
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// Initialize journey tracking (for first-touch, last-touch, touchpoints)
|
|
120
|
+
await journeyManager.initialize();
|
|
121
|
+
|
|
122
|
+
// Record initial attribution to journey if this is a new session with attribution
|
|
123
|
+
const initialAttribution = attributionManager.getAttributionData();
|
|
124
|
+
if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
|
|
125
|
+
await journeyManager.recordAttribution(this.state.sessionId, {
|
|
126
|
+
source: initialAttribution.utm_source || initialAttribution.campaign_source,
|
|
127
|
+
medium: initialAttribution.utm_medium || initialAttribution.campaign_medium,
|
|
128
|
+
campaign: initialAttribution.utm_campaign || initialAttribution.campaign_name,
|
|
129
|
+
fbclid: initialAttribution.fbclid,
|
|
130
|
+
gclid: initialAttribution.gclid,
|
|
131
|
+
ttclid: initialAttribution.ttclid,
|
|
132
|
+
clickIdType: initialAttribution.fbclid ? 'fbclid' : initialAttribution.gclid ? 'gclid' : initialAttribution.ttclid ? 'ttclid' : undefined,
|
|
133
|
+
lyr: initialAttribution.lyr,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
if (this.state.config.enableAutoEvents) {
|
|
119
138
|
this.autoEventsManager = new AutoEventsManager(
|
|
120
139
|
this.track.bind(this),
|
|
@@ -441,8 +460,32 @@ export class DatalyrSDKExpo {
|
|
|
441
460
|
return this.state.anonymousId;
|
|
442
461
|
}
|
|
443
462
|
|
|
444
|
-
|
|
445
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Get detailed attribution data (includes journey tracking data)
|
|
465
|
+
*/
|
|
466
|
+
getAttributionData(): AttributionData & Record<string, any> {
|
|
467
|
+
const attribution = attributionManager.getAttributionData();
|
|
468
|
+
const journeyData = journeyManager.getAttributionData();
|
|
469
|
+
|
|
470
|
+
// Merge attribution with journey data
|
|
471
|
+
return {
|
|
472
|
+
...attribution,
|
|
473
|
+
...journeyData,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get journey tracking summary
|
|
479
|
+
*/
|
|
480
|
+
getJourneySummary() {
|
|
481
|
+
return journeyManager.getJourneySummary();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get full customer journey (all touchpoints)
|
|
486
|
+
*/
|
|
487
|
+
getJourney() {
|
|
488
|
+
return journeyManager.getJourney();
|
|
446
489
|
}
|
|
447
490
|
|
|
448
491
|
async setAttributionData(data: Partial<AttributionData>): Promise<void> {
|
|
@@ -477,6 +520,10 @@ export class DatalyrSDKExpo {
|
|
|
477
520
|
}
|
|
478
521
|
}
|
|
479
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Track event with automatic SKAdNetwork conversion value encoding
|
|
525
|
+
* Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
|
|
526
|
+
*/
|
|
480
527
|
async trackWithSKAdNetwork(event: string, properties?: EventData): Promise<void> {
|
|
481
528
|
await this.track(event, properties);
|
|
482
529
|
|
|
@@ -487,13 +534,15 @@ export class DatalyrSDKExpo {
|
|
|
487
534
|
return;
|
|
488
535
|
}
|
|
489
536
|
|
|
490
|
-
|
|
537
|
+
// Use SKAN 4.0 encoding (includes coarse value and lock window)
|
|
538
|
+
const result = DatalyrSDKExpo.conversionEncoder.encodeWithSKAN4(event, properties);
|
|
491
539
|
|
|
492
|
-
if (
|
|
493
|
-
|
|
540
|
+
if (result.fineValue > 0 || result.priority > 0) {
|
|
541
|
+
// Use SKAN 4.0 method (automatically falls back to SKAN 3.0 on older iOS)
|
|
542
|
+
const success = await SKAdNetworkBridge.updatePostbackConversionValue(result);
|
|
494
543
|
|
|
495
544
|
if (DatalyrSDKExpo.debugEnabled) {
|
|
496
|
-
debugLog(`
|
|
545
|
+
debugLog(`SKAN: event=${event}, fine=${result.fineValue}, coarse=${result.coarseValue}, lock=${result.lockWindow}, success=${success}`, properties);
|
|
497
546
|
}
|
|
498
547
|
}
|
|
499
548
|
}
|
package/src/datalyr-sdk.ts
CHANGED
|
@@ -27,11 +27,13 @@ import {
|
|
|
27
27
|
import { createHttpClient, HttpClient } from './http-client';
|
|
28
28
|
import { createEventQueue, EventQueue } from './event-queue';
|
|
29
29
|
import { attributionManager, AttributionData } from './attribution';
|
|
30
|
+
import { journeyManager } from './journey';
|
|
30
31
|
import { createAutoEventsManager, AutoEventsManager, SessionData } from './auto-events';
|
|
31
32
|
import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
|
|
32
33
|
import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
33
|
-
import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
|
|
34
|
+
import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
34
35
|
import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
36
|
+
import { networkStatusManager } from './network-status';
|
|
35
37
|
|
|
36
38
|
export class DatalyrSDK {
|
|
37
39
|
private state: SDKState;
|
|
@@ -39,6 +41,7 @@ export class DatalyrSDK {
|
|
|
39
41
|
private eventQueue: EventQueue;
|
|
40
42
|
private autoEventsManager: AutoEventsManager | null = null;
|
|
41
43
|
private appStateSubscription: any = null;
|
|
44
|
+
private networkStatusUnsubscribe: (() => void) | null = null;
|
|
42
45
|
private static conversionEncoder?: ConversionValueEncoder;
|
|
43
46
|
private static debugEnabled = false;
|
|
44
47
|
|
|
@@ -111,17 +114,35 @@ export class DatalyrSDK {
|
|
|
111
114
|
maxRetryCount: this.state.config.maxRetries || 3,
|
|
112
115
|
});
|
|
113
116
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// PARALLEL INITIALIZATION: IDs and core managers
|
|
118
|
+
// Run ID creation and core manager initialization in parallel for faster startup
|
|
119
|
+
const [visitorId, anonymousId, sessionId] = await Promise.all([
|
|
120
|
+
getOrCreateVisitorId(),
|
|
121
|
+
getOrCreateAnonymousId(),
|
|
122
|
+
getOrCreateSessionId(),
|
|
123
|
+
// These run concurrently but don't return values we need to capture
|
|
124
|
+
this.loadPersistedUserData(),
|
|
125
|
+
this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
|
|
126
|
+
journeyManager.initialize(),
|
|
127
|
+
]);
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
this.state.visitorId = visitorId;
|
|
130
|
+
this.state.anonymousId = anonymousId;
|
|
131
|
+
this.state.sessionId = sessionId;
|
|
132
|
+
|
|
133
|
+
// Record initial attribution to journey if this is a new session with attribution
|
|
134
|
+
const initialAttribution = attributionManager.getAttributionData();
|
|
135
|
+
if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
|
|
136
|
+
await journeyManager.recordAttribution(this.state.sessionId, {
|
|
137
|
+
source: initialAttribution.utm_source || initialAttribution.campaign_source,
|
|
138
|
+
medium: initialAttribution.utm_medium || initialAttribution.campaign_medium,
|
|
139
|
+
campaign: initialAttribution.utm_campaign || initialAttribution.campaign_name,
|
|
140
|
+
fbclid: initialAttribution.fbclid,
|
|
141
|
+
gclid: initialAttribution.gclid,
|
|
142
|
+
ttclid: initialAttribution.ttclid,
|
|
143
|
+
clickIdType: initialAttribution.fbclid ? 'fbclid' : initialAttribution.gclid ? 'gclid' : initialAttribution.ttclid ? 'ttclid' : undefined,
|
|
144
|
+
lyr: initialAttribution.lyr,
|
|
145
|
+
});
|
|
125
146
|
}
|
|
126
147
|
|
|
127
148
|
// Initialize auto-events manager (asynchronously to avoid blocking)
|
|
@@ -150,7 +171,7 @@ export class DatalyrSDK {
|
|
|
150
171
|
}
|
|
151
172
|
}, 50);
|
|
152
173
|
|
|
153
|
-
// Initialize SKAdNetwork conversion encoder
|
|
174
|
+
// Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
|
|
154
175
|
if (config.skadTemplate) {
|
|
155
176
|
const template = ConversionTemplates[config.skadTemplate];
|
|
156
177
|
if (template) {
|
|
@@ -164,31 +185,47 @@ export class DatalyrSDK {
|
|
|
164
185
|
}
|
|
165
186
|
}
|
|
166
187
|
|
|
167
|
-
//
|
|
188
|
+
// PARALLEL INITIALIZATION: Network monitoring and platform integrations
|
|
189
|
+
// These are independent and can run concurrently for faster startup
|
|
190
|
+
const platformInitPromises: Promise<void>[] = [
|
|
191
|
+
// Network monitoring
|
|
192
|
+
this.initializeNetworkMonitoring(),
|
|
193
|
+
// Apple Search Ads (iOS only)
|
|
194
|
+
appleSearchAdsIntegration.initialize(config.debug),
|
|
195
|
+
// Google Play Install Referrer (Android only)
|
|
196
|
+
playInstallReferrerIntegration.initialize(),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Add Meta initialization if configured
|
|
168
200
|
if (config.meta?.appId) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
201
|
+
platformInitPromises.push(
|
|
202
|
+
metaIntegration.initialize(config.meta, config.debug).then(async () => {
|
|
203
|
+
// After Meta initializes, fetch deferred deep link
|
|
204
|
+
if (config.enableAttribution !== false) {
|
|
205
|
+
const deferredLink = await metaIntegration.fetchDeferredDeepLink();
|
|
206
|
+
if (deferredLink) {
|
|
207
|
+
await this.handleDeferredDeepLink(deferredLink);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
);
|
|
178
212
|
}
|
|
179
213
|
|
|
180
|
-
//
|
|
214
|
+
// Add TikTok initialization if configured
|
|
181
215
|
if (config.tiktok?.appId && config.tiktok?.tiktokAppId) {
|
|
182
|
-
|
|
216
|
+
platformInitPromises.push(
|
|
217
|
+
tiktokIntegration.initialize(config.tiktok, config.debug)
|
|
218
|
+
);
|
|
183
219
|
}
|
|
184
220
|
|
|
185
|
-
//
|
|
186
|
-
await
|
|
221
|
+
// Wait for all platform integrations to complete
|
|
222
|
+
await Promise.all(platformInitPromises);
|
|
187
223
|
|
|
188
224
|
debugLog('Platform integrations initialized', {
|
|
189
225
|
meta: metaIntegration.isAvailable(),
|
|
190
226
|
tiktok: tiktokIntegration.isAvailable(),
|
|
191
227
|
appleSearchAds: appleSearchAdsIntegration.isAvailable(),
|
|
228
|
+
playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
|
|
192
229
|
});
|
|
193
230
|
|
|
194
231
|
// SDK initialized successfully - set state before tracking install event
|
|
@@ -490,6 +527,7 @@ export class DatalyrSDK {
|
|
|
490
527
|
currentUserId?: string;
|
|
491
528
|
queueStats: any;
|
|
492
529
|
attribution: any;
|
|
530
|
+
journey: any;
|
|
493
531
|
} {
|
|
494
532
|
return {
|
|
495
533
|
initialized: this.state.initialized,
|
|
@@ -500,6 +538,7 @@ export class DatalyrSDK {
|
|
|
500
538
|
currentUserId: this.state.currentUserId,
|
|
501
539
|
queueStats: this.eventQueue.getStats(),
|
|
502
540
|
attribution: attributionManager.getAttributionSummary(),
|
|
541
|
+
journey: journeyManager.getJourneySummary(),
|
|
503
542
|
};
|
|
504
543
|
}
|
|
505
544
|
|
|
@@ -511,10 +550,31 @@ export class DatalyrSDK {
|
|
|
511
550
|
}
|
|
512
551
|
|
|
513
552
|
/**
|
|
514
|
-
* Get detailed attribution data
|
|
553
|
+
* Get detailed attribution data (includes journey tracking data)
|
|
515
554
|
*/
|
|
516
|
-
getAttributionData(): AttributionData {
|
|
517
|
-
|
|
555
|
+
getAttributionData(): AttributionData & Record<string, any> {
|
|
556
|
+
const attribution = attributionManager.getAttributionData();
|
|
557
|
+
const journeyData = journeyManager.getAttributionData();
|
|
558
|
+
|
|
559
|
+
// Merge attribution with journey data
|
|
560
|
+
return {
|
|
561
|
+
...attribution,
|
|
562
|
+
...journeyData,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Get journey tracking summary
|
|
568
|
+
*/
|
|
569
|
+
getJourneySummary() {
|
|
570
|
+
return journeyManager.getJourneySummary();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get full customer journey (all touchpoints)
|
|
575
|
+
*/
|
|
576
|
+
getJourney() {
|
|
577
|
+
return journeyManager.getJourney();
|
|
518
578
|
}
|
|
519
579
|
|
|
520
580
|
/**
|
|
@@ -571,15 +631,16 @@ export class DatalyrSDK {
|
|
|
571
631
|
|
|
572
632
|
/**
|
|
573
633
|
* Track event with automatic SKAdNetwork conversion value encoding
|
|
634
|
+
* Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
|
|
574
635
|
*/
|
|
575
636
|
async trackWithSKAdNetwork(
|
|
576
|
-
event: string,
|
|
637
|
+
event: string,
|
|
577
638
|
properties?: EventData
|
|
578
639
|
): Promise<void> {
|
|
579
640
|
// Existing tracking (keep exactly as-is)
|
|
580
641
|
await this.track(event, properties);
|
|
581
642
|
|
|
582
|
-
//
|
|
643
|
+
// Automatic SKAdNetwork encoding with SKAN 4.0 support
|
|
583
644
|
if (!DatalyrSDK.conversionEncoder) {
|
|
584
645
|
if (DatalyrSDK.debugEnabled) {
|
|
585
646
|
errorLog('SKAdNetwork encoder not initialized. Pass skadTemplate in initialize()');
|
|
@@ -587,13 +648,15 @@ export class DatalyrSDK {
|
|
|
587
648
|
return;
|
|
588
649
|
}
|
|
589
650
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
651
|
+
// Use SKAN 4.0 encoding (includes coarse value and lock window)
|
|
652
|
+
const result = DatalyrSDK.conversionEncoder.encodeWithSKAN4(event, properties);
|
|
653
|
+
|
|
654
|
+
if (result.fineValue > 0 || result.priority > 0) {
|
|
655
|
+
// Use SKAN 4.0 method (automatically falls back to SKAN 3.0 on older iOS)
|
|
656
|
+
const success = await SKAdNetworkBridge.updatePostbackConversionValue(result);
|
|
657
|
+
|
|
595
658
|
if (DatalyrSDK.debugEnabled) {
|
|
596
|
-
debugLog(`
|
|
659
|
+
debugLog(`SKAN: event=${event}, fine=${result.fineValue}, coarse=${result.coarseValue}, lock=${result.lockWindow}, success=${success}`, properties);
|
|
597
660
|
}
|
|
598
661
|
} else if (DatalyrSDK.debugEnabled) {
|
|
599
662
|
debugLog(`No conversion value generated for event: ${event}`);
|
|
@@ -837,11 +900,12 @@ export class DatalyrSDK {
|
|
|
837
900
|
/**
|
|
838
901
|
* Get platform integration status
|
|
839
902
|
*/
|
|
840
|
-
getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean } {
|
|
903
|
+
getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean; playInstallReferrer: boolean } {
|
|
841
904
|
return {
|
|
842
905
|
meta: metaIntegration.isAvailable(),
|
|
843
906
|
tiktok: tiktokIntegration.isAvailable(),
|
|
844
907
|
appleSearchAds: appleSearchAdsIntegration.isAvailable(),
|
|
908
|
+
playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
|
|
845
909
|
};
|
|
846
910
|
}
|
|
847
911
|
|
|
@@ -853,6 +917,15 @@ export class DatalyrSDK {
|
|
|
853
917
|
return appleSearchAdsIntegration.getAttributionData();
|
|
854
918
|
}
|
|
855
919
|
|
|
920
|
+
/**
|
|
921
|
+
* Get Google Play Install Referrer attribution data (Android only)
|
|
922
|
+
* Returns referrer data if available, null otherwise
|
|
923
|
+
*/
|
|
924
|
+
getPlayInstallReferrer(): Record<string, any> | null {
|
|
925
|
+
const data = playInstallReferrerIntegration.getReferrerData();
|
|
926
|
+
return data ? playInstallReferrerIntegration.getAttributionData() : null;
|
|
927
|
+
}
|
|
928
|
+
|
|
856
929
|
/**
|
|
857
930
|
* Update tracking authorization status on all platform SDKs
|
|
858
931
|
* Call this AFTER the user responds to the ATT permission dialog
|
|
@@ -1035,6 +1108,45 @@ export class DatalyrSDK {
|
|
|
1035
1108
|
}
|
|
1036
1109
|
}
|
|
1037
1110
|
|
|
1111
|
+
/**
|
|
1112
|
+
* Initialize network status monitoring
|
|
1113
|
+
* Automatically updates event queue when network status changes
|
|
1114
|
+
*/
|
|
1115
|
+
private async initializeNetworkMonitoring(): Promise<void> {
|
|
1116
|
+
try {
|
|
1117
|
+
await networkStatusManager.initialize();
|
|
1118
|
+
|
|
1119
|
+
// Update event queue with current network status
|
|
1120
|
+
this.state.isOnline = networkStatusManager.isOnline();
|
|
1121
|
+
this.eventQueue.setOnlineStatus(this.state.isOnline);
|
|
1122
|
+
|
|
1123
|
+
// Subscribe to network changes
|
|
1124
|
+
this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
|
|
1125
|
+
const isOnline = state.isConnected && (state.isInternetReachable !== false);
|
|
1126
|
+
this.state.isOnline = isOnline;
|
|
1127
|
+
this.eventQueue.setOnlineStatus(isOnline);
|
|
1128
|
+
|
|
1129
|
+
// Track network status change event (only if SDK is fully initialized)
|
|
1130
|
+
if (this.state.initialized) {
|
|
1131
|
+
this.track('$network_status_change', {
|
|
1132
|
+
is_online: isOnline,
|
|
1133
|
+
network_type: state.type,
|
|
1134
|
+
is_internet_reachable: state.isInternetReachable,
|
|
1135
|
+
}).catch(() => {
|
|
1136
|
+
// Ignore errors for network status events
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
errorLog('Error initializing network monitoring (non-blocking):', error as Error);
|
|
1144
|
+
// Default to online if monitoring fails
|
|
1145
|
+
this.state.isOnline = true;
|
|
1146
|
+
this.eventQueue.setOnlineStatus(true);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1038
1150
|
/**
|
|
1039
1151
|
* Set up app state monitoring for lifecycle events (optimized)
|
|
1040
1152
|
*/
|
|
@@ -1055,6 +1167,8 @@ export class DatalyrSDK {
|
|
|
1055
1167
|
} else if (nextAppState === 'active') {
|
|
1056
1168
|
// App became active, ensure we have fresh session if needed
|
|
1057
1169
|
this.refreshSession();
|
|
1170
|
+
// Refresh network status when coming back from background
|
|
1171
|
+
networkStatusManager.refresh();
|
|
1058
1172
|
// Notify auto-events manager for session handling
|
|
1059
1173
|
if (this.autoEventsManager) {
|
|
1060
1174
|
this.autoEventsManager.handleAppForeground();
|
|
@@ -1095,6 +1209,15 @@ export class DatalyrSDK {
|
|
|
1095
1209
|
this.appStateSubscription = null;
|
|
1096
1210
|
}
|
|
1097
1211
|
|
|
1212
|
+
// Remove network status listener
|
|
1213
|
+
if (this.networkStatusUnsubscribe) {
|
|
1214
|
+
this.networkStatusUnsubscribe();
|
|
1215
|
+
this.networkStatusUnsubscribe = null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Destroy network status manager
|
|
1219
|
+
networkStatusManager.destroy();
|
|
1220
|
+
|
|
1098
1221
|
// Destroy event queue
|
|
1099
1222
|
this.eventQueue.destroy();
|
|
1100
1223
|
|
package/src/expo.ts
CHANGED
|
@@ -22,6 +22,10 @@ export * from './types';
|
|
|
22
22
|
// Export attribution manager
|
|
23
23
|
export { attributionManager } from './attribution';
|
|
24
24
|
|
|
25
|
+
// Export journey tracking
|
|
26
|
+
export { journeyManager } from './journey';
|
|
27
|
+
export type { TouchAttribution, TouchPoint } from './journey';
|
|
28
|
+
|
|
25
29
|
// Export auto-events
|
|
26
30
|
export { createAutoEventsManager, AutoEventsManager } from './auto-events';
|
|
27
31
|
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ export { Datalyr };
|
|
|
12
12
|
// Export types and utilities
|
|
13
13
|
export * from './types';
|
|
14
14
|
export { attributionManager } from './attribution';
|
|
15
|
+
export { journeyManager } from './journey';
|
|
16
|
+
export type { TouchAttribution, TouchPoint } from './journey';
|
|
15
17
|
export { createAutoEventsManager, AutoEventsManager } from './auto-events';
|
|
16
18
|
|
|
17
19
|
// Re-export utilities for advanced usage
|
|
@@ -27,7 +29,11 @@ export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
|
|
|
27
29
|
export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
28
30
|
|
|
29
31
|
// Export platform integrations
|
|
30
|
-
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
|
|
32
|
+
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
33
|
+
|
|
34
|
+
// Export network status manager
|
|
35
|
+
export { networkStatusManager } from './network-status';
|
|
36
|
+
export type { NetworkState, NetworkStateListener } from './network-status';
|
|
31
37
|
|
|
32
38
|
// Export native bridge types
|
|
33
39
|
export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform SDK Integrations
|
|
3
|
-
* Meta (Facebook), TikTok,
|
|
3
|
+
* Meta (Facebook), TikTok, Apple Search Ads, and Google Play Install Referrer SDK wrappers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export { MetaIntegration, metaIntegration } from './meta-integration';
|
|
7
7
|
export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
|
|
8
8
|
export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
|
|
9
|
+
export { playInstallReferrerIntegration } from './play-install-referrer';
|
|
10
|
+
export type { PlayInstallReferrer } from './play-install-referrer';
|
|
@@ -20,14 +20,15 @@ export class MetaIntegration {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Initialize Meta SDK with configuration
|
|
23
|
+
* Supported on both iOS and Android via native modules
|
|
23
24
|
*/
|
|
24
25
|
async initialize(config: MetaConfig, debug: boolean = false): Promise<void> {
|
|
25
26
|
this.debug = debug;
|
|
26
27
|
this.config = config;
|
|
27
28
|
|
|
28
|
-
// Only available on iOS via native
|
|
29
|
-
if (Platform.OS !== 'ios') {
|
|
30
|
-
this.log('Meta SDK only available on iOS');
|
|
29
|
+
// Only available on iOS and Android via native modules
|
|
30
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
31
|
+
this.log('Meta SDK only available on iOS and Android');
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|