@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +145 -9
  3. package/android/build.gradle +54 -0
  4. package/android/src/main/AndroidManifest.xml +14 -0
  5. package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
  6. package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
  7. package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
  8. package/datalyr-react-native.podspec +2 -2
  9. package/ios/DatalyrSKAdNetwork.m +400 -1
  10. package/ios/PrivacyInfo.xcprivacy +48 -0
  11. package/lib/ConversionValueEncoder.d.ts +13 -1
  12. package/lib/ConversionValueEncoder.js +57 -23
  13. package/lib/datalyr-sdk.d.ts +31 -2
  14. package/lib/datalyr-sdk.js +138 -30
  15. package/lib/index.d.ts +5 -1
  16. package/lib/index.js +4 -1
  17. package/lib/integrations/index.d.ts +3 -1
  18. package/lib/integrations/index.js +2 -1
  19. package/lib/integrations/meta-integration.d.ts +1 -0
  20. package/lib/integrations/meta-integration.js +4 -3
  21. package/lib/integrations/play-install-referrer.d.ts +78 -0
  22. package/lib/integrations/play-install-referrer.js +166 -0
  23. package/lib/integrations/tiktok-integration.d.ts +1 -0
  24. package/lib/integrations/tiktok-integration.js +4 -3
  25. package/lib/journey.d.ts +106 -0
  26. package/lib/journey.js +258 -0
  27. package/lib/native/DatalyrNativeBridge.d.ts +42 -3
  28. package/lib/native/DatalyrNativeBridge.js +63 -9
  29. package/lib/native/SKAdNetworkBridge.d.ts +142 -0
  30. package/lib/native/SKAdNetworkBridge.js +328 -0
  31. package/lib/network-status.d.ts +84 -0
  32. package/lib/network-status.js +281 -0
  33. package/lib/types.d.ts +51 -0
  34. package/lib/utils.d.ts +6 -1
  35. package/lib/utils.js +52 -2
  36. package/package.json +13 -4
  37. package/src/ConversionValueEncoder.ts +67 -26
  38. package/src/datalyr-sdk-expo.ts +55 -6
  39. package/src/datalyr-sdk.ts +161 -38
  40. package/src/expo.ts +4 -0
  41. package/src/index.ts +7 -1
  42. package/src/integrations/index.ts +3 -1
  43. package/src/integrations/meta-integration.ts +4 -3
  44. package/src/integrations/play-install-referrer.ts +218 -0
  45. package/src/integrations/tiktok-integration.ts +4 -3
  46. package/src/journey.ts +338 -0
  47. package/src/native/DatalyrNativeBridge.ts +99 -13
  48. package/src/native/SKAdNetworkBridge.ts +481 -2
  49. package/src/network-status.ts +312 -0
  50. package/src/types.ts +74 -6
  51. package/src/utils.ts +62 -6
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>NSPrivacyTracking</key>
6
+ <false/>
7
+ <key>NSPrivacyTrackingDomains</key>
8
+ <array/>
9
+ <key>NSPrivacyCollectedDataTypes</key>
10
+ <array>
11
+ <dict>
12
+ <key>NSPrivacyCollectedDataType</key>
13
+ <string>NSPrivacyCollectedDataTypeDeviceID</string>
14
+ <key>NSPrivacyCollectedDataTypeLinked</key>
15
+ <false/>
16
+ <key>NSPrivacyCollectedDataTypeTracking</key>
17
+ <false/>
18
+ <key>NSPrivacyCollectedDataTypePurposes</key>
19
+ <array>
20
+ <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
21
+ </array>
22
+ </dict>
23
+ <dict>
24
+ <key>NSPrivacyCollectedDataType</key>
25
+ <string>NSPrivacyCollectedDataTypeProductInteraction</string>
26
+ <key>NSPrivacyCollectedDataTypeLinked</key>
27
+ <false/>
28
+ <key>NSPrivacyCollectedDataTypeTracking</key>
29
+ <false/>
30
+ <key>NSPrivacyCollectedDataTypePurposes</key>
31
+ <array>
32
+ <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
33
+ </array>
34
+ </dict>
35
+ </array>
36
+ <key>NSPrivacyAccessedAPITypes</key>
37
+ <array>
38
+ <dict>
39
+ <key>NSPrivacyAccessedAPIType</key>
40
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
41
+ <key>NSPrivacyAccessedAPITypeReasons</key>
42
+ <array>
43
+ <string>CA92.1</string>
44
+ </array>
45
+ </dict>
46
+ </array>
47
+ </dict>
48
+ </plist>
@@ -1,7 +1,10 @@
1
+ import { SKANCoarseValue, SKANConversionResult } from './native/SKAdNetworkBridge';
1
2
  interface EventMapping {
2
3
  bits: number[];
3
4
  revenueBits?: number[];
4
5
  priority: number;
6
+ coarseValue?: SKANCoarseValue;
7
+ lockWindow?: boolean;
5
8
  }
6
9
  interface ConversionTemplate {
7
10
  name: string;
@@ -11,13 +14,22 @@ export declare class ConversionValueEncoder {
11
14
  private template;
12
15
  constructor(template: ConversionTemplate);
13
16
  /**
14
- * Encode an event into Apple's 0-63 conversion value format
17
+ * Encode an event into Apple's 0-63 conversion value format (SKAN 3.0 compatible)
18
+ * @deprecated Use encodeWithSKAN4 for iOS 16.1+
15
19
  */
16
20
  encode(event: string, properties?: Record<string, any>): number;
21
+ /**
22
+ * Encode an event with full SKAN 4.0 support (fine value, coarse value, lock window)
23
+ */
24
+ encodeWithSKAN4(event: string, properties?: Record<string, any>): SKANConversionResult;
17
25
  /**
18
26
  * Map revenue amount to 3-bit tier (0-7)
19
27
  */
20
28
  private getRevenueTier;
29
+ /**
30
+ * Map revenue to SKAN 4.0 coarse value
31
+ */
32
+ private getCoarseValueForRevenue;
21
33
  }
22
34
  export declare const ConversionTemplates: {
23
35
  ecommerce: ConversionTemplate;
@@ -3,18 +3,27 @@ export class ConversionValueEncoder {
3
3
  this.template = template;
4
4
  }
5
5
  /**
6
- * Encode an event into Apple's 0-63 conversion value format
6
+ * Encode an event into Apple's 0-63 conversion value format (SKAN 3.0 compatible)
7
+ * @deprecated Use encodeWithSKAN4 for iOS 16.1+
7
8
  */
8
9
  encode(event, properties) {
10
+ return this.encodeWithSKAN4(event, properties).fineValue;
11
+ }
12
+ /**
13
+ * Encode an event with full SKAN 4.0 support (fine value, coarse value, lock window)
14
+ */
15
+ encodeWithSKAN4(event, properties) {
9
16
  const mapping = this.template.events[event];
10
- if (!mapping)
11
- return 0;
17
+ if (!mapping) {
18
+ return { fineValue: 0, coarseValue: 'low', lockWindow: false, priority: 0 };
19
+ }
12
20
  let conversionValue = 0;
13
21
  // Set event bits
14
22
  for (const bit of mapping.bits) {
15
23
  conversionValue |= (1 << bit);
16
24
  }
17
25
  // Set revenue bits if revenue is provided
26
+ let coarseValue = mapping.coarseValue || 'medium';
18
27
  if (mapping.revenueBits && properties) {
19
28
  const revenue = properties.revenue || properties.value || 0;
20
29
  const revenueTier = this.getRevenueTier(revenue);
@@ -23,8 +32,17 @@ export class ConversionValueEncoder {
23
32
  conversionValue |= (1 << mapping.revenueBits[i]);
24
33
  }
25
34
  }
35
+ // Upgrade coarse value based on revenue
36
+ coarseValue = this.getCoarseValueForRevenue(revenue);
26
37
  }
27
- return Math.min(conversionValue, 63);
38
+ // Ensure value is within 0-63 range
39
+ const fineValue = Math.min(conversionValue, 63);
40
+ return {
41
+ fineValue,
42
+ coarseValue,
43
+ lockWindow: mapping.lockWindow || false,
44
+ priority: mapping.priority
45
+ };
28
46
  }
29
47
  /**
30
48
  * Map revenue amount to 3-bit tier (0-7)
@@ -46,40 +64,56 @@ export class ConversionValueEncoder {
46
64
  return 6; // $100-250
47
65
  return 7; // $250+
48
66
  }
67
+ /**
68
+ * Map revenue to SKAN 4.0 coarse value
69
+ */
70
+ getCoarseValueForRevenue(revenue) {
71
+ if (revenue < 10)
72
+ return 'low'; // $0-10 = low value
73
+ if (revenue < 50)
74
+ return 'medium'; // $10-50 = medium value
75
+ return 'high'; // $50+ = high value
76
+ }
49
77
  }
50
- // Industry templates
78
+ // Industry templates with SKAN 4.0 support
51
79
  export const ConversionTemplates = {
80
+ // E-commerce template - optimized for online stores
81
+ // SKAN 4.0: purchase locks window, high-value events get "high" coarse value
52
82
  ecommerce: {
53
83
  name: 'ecommerce',
54
84
  events: {
55
- purchase: { bits: [0], revenueBits: [1, 2, 3], priority: 100 },
56
- add_to_cart: { bits: [4], priority: 30 },
57
- begin_checkout: { bits: [5], priority: 50 },
58
- signup: { bits: [6], priority: 20 },
59
- subscribe: { bits: [0, 1], revenueBits: [2, 3, 4], priority: 90 },
60
- view_item: { bits: [7], priority: 10 }
85
+ purchase: { bits: [0], revenueBits: [1, 2, 3], priority: 100, coarseValue: 'high', lockWindow: true },
86
+ add_to_cart: { bits: [4], priority: 30, coarseValue: 'low' },
87
+ begin_checkout: { bits: [5], priority: 50, coarseValue: 'medium' },
88
+ signup: { bits: [6], priority: 20, coarseValue: 'low' },
89
+ subscribe: { bits: [0, 1], revenueBits: [2, 3, 4], priority: 90, coarseValue: 'high', lockWindow: true },
90
+ view_item: { bits: [7], priority: 10, coarseValue: 'low' }
61
91
  }
62
92
  },
93
+ // Gaming template - optimized for mobile games
94
+ // SKAN 4.0: purchase locks window, tutorial completion is medium value
63
95
  gaming: {
64
96
  name: 'gaming',
65
97
  events: {
66
- level_complete: { bits: [0], priority: 40 },
67
- tutorial_complete: { bits: [1], priority: 60 },
68
- purchase: { bits: [2], revenueBits: [3, 4, 5], priority: 100 },
69
- achievement_unlocked: { bits: [6], priority: 30 },
70
- session_start: { bits: [7], priority: 10 },
71
- ad_watched: { bits: [0, 6], priority: 20 }
98
+ level_complete: { bits: [0], priority: 40, coarseValue: 'medium' },
99
+ tutorial_complete: { bits: [1], priority: 60, coarseValue: 'medium' },
100
+ purchase: { bits: [2], revenueBits: [3, 4, 5], priority: 100, coarseValue: 'high', lockWindow: true },
101
+ achievement_unlocked: { bits: [6], priority: 30, coarseValue: 'low' },
102
+ session_start: { bits: [7], priority: 10, coarseValue: 'low' },
103
+ ad_watched: { bits: [0, 6], priority: 20, coarseValue: 'low' }
72
104
  }
73
105
  },
106
+ // Subscription template - optimized for subscription apps
107
+ // SKAN 4.0: subscribe/upgrade lock window, trial is medium value
74
108
  subscription: {
75
109
  name: 'subscription',
76
110
  events: {
77
- trial_start: { bits: [0], priority: 70 },
78
- subscribe: { bits: [1], revenueBits: [2, 3, 4], priority: 100 },
79
- upgrade: { bits: [1, 5], revenueBits: [2, 3, 4], priority: 90 },
80
- cancel: { bits: [6], priority: 20 },
81
- signup: { bits: [7], priority: 30 },
82
- payment_method_added: { bits: [0, 7], priority: 50 }
111
+ trial_start: { bits: [0], priority: 70, coarseValue: 'medium' },
112
+ subscribe: { bits: [1], revenueBits: [2, 3, 4], priority: 100, coarseValue: 'high', lockWindow: true },
113
+ upgrade: { bits: [1, 5], revenueBits: [2, 3, 4], priority: 90, coarseValue: 'high', lockWindow: true },
114
+ cancel: { bits: [6], priority: 20, coarseValue: 'low' },
115
+ signup: { bits: [7], priority: 30, coarseValue: 'low' },
116
+ payment_method_added: { bits: [0, 7], priority: 50, coarseValue: 'medium' }
83
117
  }
84
118
  }
85
119
  };
@@ -8,6 +8,7 @@ export declare class DatalyrSDK {
8
8
  private eventQueue;
9
9
  private autoEventsManager;
10
10
  private appStateSubscription;
11
+ private networkStatusUnsubscribe;
11
12
  private static conversionEncoder?;
12
13
  private static debugEnabled;
13
14
  constructor();
@@ -56,15 +57,30 @@ export declare class DatalyrSDK {
56
57
  currentUserId?: string;
57
58
  queueStats: any;
58
59
  attribution: any;
60
+ journey: any;
59
61
  };
60
62
  /**
61
63
  * Get the persistent anonymous ID
62
64
  */
63
65
  getAnonymousId(): string;
64
66
  /**
65
- * Get detailed attribution data
67
+ * Get detailed attribution data (includes journey tracking data)
66
68
  */
67
- getAttributionData(): AttributionData;
69
+ getAttributionData(): AttributionData & Record<string, any>;
70
+ /**
71
+ * Get journey tracking summary
72
+ */
73
+ getJourneySummary(): {
74
+ hasFirstTouch: boolean;
75
+ hasLastTouch: boolean;
76
+ touchpointCount: number;
77
+ daysSinceFirstTouch: number;
78
+ sources: string[];
79
+ };
80
+ /**
81
+ * Get full customer journey (all touchpoints)
82
+ */
83
+ getJourney(): import("./journey").TouchPoint[];
68
84
  /**
69
85
  * Set custom attribution data (for testing or manual attribution)
70
86
  */
@@ -91,6 +107,7 @@ export declare class DatalyrSDK {
91
107
  updateAutoEventsConfig(config: Partial<AutoEventConfig>): void;
92
108
  /**
93
109
  * Track event with automatic SKAdNetwork conversion value encoding
110
+ * Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
94
111
  */
95
112
  trackWithSKAdNetwork(event: string, properties?: EventData): Promise<void>;
96
113
  /**
@@ -140,12 +157,18 @@ export declare class DatalyrSDK {
140
157
  meta: boolean;
141
158
  tiktok: boolean;
142
159
  appleSearchAds: boolean;
160
+ playInstallReferrer: boolean;
143
161
  };
144
162
  /**
145
163
  * Get Apple Search Ads attribution data
146
164
  * Returns attribution if user installed via Apple Search Ads, null otherwise
147
165
  */
148
166
  getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
167
+ /**
168
+ * Get Google Play Install Referrer attribution data (Android only)
169
+ * Returns referrer data if available, null otherwise
170
+ */
171
+ getPlayInstallReferrer(): Record<string, any> | null;
149
172
  /**
150
173
  * Update tracking authorization status on all platform SDKs
151
174
  * Call this AFTER the user responds to the ATT permission dialog
@@ -171,6 +194,11 @@ export declare class DatalyrSDK {
171
194
  * Persist user data to storage
172
195
  */
173
196
  private persistUserData;
197
+ /**
198
+ * Initialize network status monitoring
199
+ * Automatically updates event queue when network status changes
200
+ */
201
+ private initializeNetworkMonitoring;
174
202
  /**
175
203
  * Set up app state monitoring for lifecycle events (optimized)
176
204
  */
@@ -221,6 +249,7 @@ export declare class Datalyr {
221
249
  currentUserId?: string;
222
250
  queueStats: any;
223
251
  attribution: any;
252
+ journey: any;
224
253
  };
225
254
  static getAnonymousId(): string;
226
255
  static getAttributionData(): AttributionData;
@@ -3,14 +3,17 @@ import { getOrCreateVisitorId, getOrCreateAnonymousId, getOrCreateSessionId, cre
3
3
  import { createHttpClient, HttpClient } from './http-client';
4
4
  import { createEventQueue, EventQueue } from './event-queue';
5
5
  import { attributionManager } from './attribution';
6
+ import { journeyManager } from './journey';
6
7
  import { AutoEventsManager } from './auto-events';
7
8
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
8
9
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
9
- import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
10
+ import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
11
+ import { networkStatusManager } from './network-status';
10
12
  export class DatalyrSDK {
11
13
  constructor() {
12
14
  this.autoEventsManager = null;
13
15
  this.appStateSubscription = null;
16
+ this.networkStatusUnsubscribe = null;
14
17
  // Initialize state with defaults
15
18
  this.state = {
16
19
  initialized: false,
@@ -72,15 +75,33 @@ export class DatalyrSDK {
72
75
  flushInterval: this.state.config.flushInterval || 30000,
73
76
  maxRetryCount: this.state.config.maxRetries || 3,
74
77
  });
75
- // Initialize visitor ID, anonymous ID and session
76
- this.state.visitorId = await getOrCreateVisitorId();
77
- this.state.anonymousId = await getOrCreateAnonymousId();
78
- this.state.sessionId = await getOrCreateSessionId();
79
- // Load persisted user data
80
- await this.loadPersistedUserData();
81
- // Initialize attribution manager
82
- if (this.state.config.enableAttribution) {
83
- await attributionManager.initialize();
78
+ // PARALLEL INITIALIZATION: IDs and core managers
79
+ // Run ID creation and core manager initialization in parallel for faster startup
80
+ const [visitorId, anonymousId, sessionId] = await Promise.all([
81
+ getOrCreateVisitorId(),
82
+ getOrCreateAnonymousId(),
83
+ getOrCreateSessionId(),
84
+ // These run concurrently but don't return values we need to capture
85
+ this.loadPersistedUserData(),
86
+ this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
87
+ journeyManager.initialize(),
88
+ ]);
89
+ this.state.visitorId = visitorId;
90
+ this.state.anonymousId = anonymousId;
91
+ this.state.sessionId = sessionId;
92
+ // Record initial attribution to journey if this is a new session with attribution
93
+ const initialAttribution = attributionManager.getAttributionData();
94
+ if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
95
+ await journeyManager.recordAttribution(this.state.sessionId, {
96
+ source: initialAttribution.utm_source || initialAttribution.campaign_source,
97
+ medium: initialAttribution.utm_medium || initialAttribution.campaign_medium,
98
+ campaign: initialAttribution.utm_campaign || initialAttribution.campaign_name,
99
+ fbclid: initialAttribution.fbclid,
100
+ gclid: initialAttribution.gclid,
101
+ ttclid: initialAttribution.ttclid,
102
+ clickIdType: initialAttribution.fbclid ? 'fbclid' : initialAttribution.gclid ? 'gclid' : initialAttribution.ttclid ? 'ttclid' : undefined,
103
+ lyr: initialAttribution.lyr,
104
+ });
84
105
  }
85
106
  // Initialize auto-events manager (asynchronously to avoid blocking)
86
107
  if (this.state.config.enableAutoEvents) {
@@ -105,7 +126,7 @@ export class DatalyrSDK {
105
126
  errorLog('Error setting up app state monitoring (non-blocking):', error);
106
127
  }
107
128
  }, 50);
108
- // Initialize SKAdNetwork conversion encoder
129
+ // Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
109
130
  if (config.skadTemplate) {
110
131
  const template = ConversionTemplates[config.skadTemplate];
111
132
  if (template) {
@@ -117,27 +138,39 @@ export class DatalyrSDK {
117
138
  }
118
139
  }
119
140
  }
120
- // Initialize Meta SDK if configured
141
+ // PARALLEL INITIALIZATION: Network monitoring and platform integrations
142
+ // These are independent and can run concurrently for faster startup
143
+ const platformInitPromises = [
144
+ // Network monitoring
145
+ this.initializeNetworkMonitoring(),
146
+ // Apple Search Ads (iOS only)
147
+ appleSearchAdsIntegration.initialize(config.debug),
148
+ // Google Play Install Referrer (Android only)
149
+ playInstallReferrerIntegration.initialize(),
150
+ ];
151
+ // Add Meta initialization if configured
121
152
  if ((_b = config.meta) === null || _b === void 0 ? void 0 : _b.appId) {
122
- await metaIntegration.initialize(config.meta, config.debug);
123
- // Fetch deferred deep link and merge with attribution
124
- if (config.enableAttribution !== false) {
125
- const deferredLink = await metaIntegration.fetchDeferredDeepLink();
126
- if (deferredLink) {
127
- await this.handleDeferredDeepLink(deferredLink);
153
+ platformInitPromises.push(metaIntegration.initialize(config.meta, config.debug).then(async () => {
154
+ // After Meta initializes, fetch deferred deep link
155
+ if (config.enableAttribution !== false) {
156
+ const deferredLink = await metaIntegration.fetchDeferredDeepLink();
157
+ if (deferredLink) {
158
+ await this.handleDeferredDeepLink(deferredLink);
159
+ }
128
160
  }
129
- }
161
+ }));
130
162
  }
131
- // Initialize TikTok SDK if configured
163
+ // Add TikTok initialization if configured
132
164
  if (((_c = config.tiktok) === null || _c === void 0 ? void 0 : _c.appId) && ((_d = config.tiktok) === null || _d === void 0 ? void 0 : _d.tiktokAppId)) {
133
- await tiktokIntegration.initialize(config.tiktok, config.debug);
165
+ platformInitPromises.push(tiktokIntegration.initialize(config.tiktok, config.debug));
134
166
  }
135
- // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
136
- await appleSearchAdsIntegration.initialize(config.debug);
167
+ // Wait for all platform integrations to complete
168
+ await Promise.all(platformInitPromises);
137
169
  debugLog('Platform integrations initialized', {
138
170
  meta: metaIntegration.isAvailable(),
139
171
  tiktok: tiktokIntegration.isAvailable(),
140
172
  appleSearchAds: appleSearchAdsIntegration.isAvailable(),
173
+ playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
141
174
  });
142
175
  // SDK initialized successfully - set state before tracking install event
143
176
  this.state.initialized = true;
@@ -398,6 +431,7 @@ export class DatalyrSDK {
398
431
  currentUserId: this.state.currentUserId,
399
432
  queueStats: this.eventQueue.getStats(),
400
433
  attribution: attributionManager.getAttributionSummary(),
434
+ journey: journeyManager.getJourneySummary(),
401
435
  };
402
436
  }
403
437
  /**
@@ -407,10 +441,28 @@ export class DatalyrSDK {
407
441
  return this.state.anonymousId;
408
442
  }
409
443
  /**
410
- * Get detailed attribution data
444
+ * Get detailed attribution data (includes journey tracking data)
411
445
  */
412
446
  getAttributionData() {
413
- return attributionManager.getAttributionData();
447
+ const attribution = attributionManager.getAttributionData();
448
+ const journeyData = journeyManager.getAttributionData();
449
+ // Merge attribution with journey data
450
+ return {
451
+ ...attribution,
452
+ ...journeyData,
453
+ };
454
+ }
455
+ /**
456
+ * Get journey tracking summary
457
+ */
458
+ getJourneySummary() {
459
+ return journeyManager.getJourneySummary();
460
+ }
461
+ /**
462
+ * Get full customer journey (all touchpoints)
463
+ */
464
+ getJourney() {
465
+ return journeyManager.getJourney();
414
466
  }
415
467
  /**
416
468
  * Set custom attribution data (for testing or manual attribution)
@@ -460,22 +512,25 @@ export class DatalyrSDK {
460
512
  // MARK: - SKAdNetwork Enhanced Methods
461
513
  /**
462
514
  * Track event with automatic SKAdNetwork conversion value encoding
515
+ * Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
463
516
  */
464
517
  async trackWithSKAdNetwork(event, properties) {
465
518
  // Existing tracking (keep exactly as-is)
466
519
  await this.track(event, properties);
467
- // NEW: Automatic SKAdNetwork encoding
520
+ // Automatic SKAdNetwork encoding with SKAN 4.0 support
468
521
  if (!DatalyrSDK.conversionEncoder) {
469
522
  if (DatalyrSDK.debugEnabled) {
470
523
  errorLog('SKAdNetwork encoder not initialized. Pass skadTemplate in initialize()');
471
524
  }
472
525
  return;
473
526
  }
474
- const conversionValue = DatalyrSDK.conversionEncoder.encode(event, properties);
475
- if (conversionValue > 0) {
476
- const success = await SKAdNetworkBridge.updateConversionValue(conversionValue);
527
+ // Use SKAN 4.0 encoding (includes coarse value and lock window)
528
+ const result = DatalyrSDK.conversionEncoder.encodeWithSKAN4(event, properties);
529
+ if (result.fineValue > 0 || result.priority > 0) {
530
+ // Use SKAN 4.0 method (automatically falls back to SKAN 3.0 on older iOS)
531
+ const success = await SKAdNetworkBridge.updatePostbackConversionValue(result);
477
532
  if (DatalyrSDK.debugEnabled) {
478
- debugLog(`Event: ${event}, Conversion Value: ${conversionValue}, Success: ${success}`, properties);
533
+ debugLog(`SKAN: event=${event}, fine=${result.fineValue}, coarse=${result.coarseValue}, lock=${result.lockWindow}, success=${success}`, properties);
479
534
  }
480
535
  }
481
536
  else if (DatalyrSDK.debugEnabled) {
@@ -676,6 +731,7 @@ export class DatalyrSDK {
676
731
  meta: metaIntegration.isAvailable(),
677
732
  tiktok: tiktokIntegration.isAvailable(),
678
733
  appleSearchAds: appleSearchAdsIntegration.isAvailable(),
734
+ playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
679
735
  };
680
736
  }
681
737
  /**
@@ -685,6 +741,14 @@ export class DatalyrSDK {
685
741
  getAppleSearchAdsAttribution() {
686
742
  return appleSearchAdsIntegration.getAttributionData();
687
743
  }
744
+ /**
745
+ * Get Google Play Install Referrer attribution data (Android only)
746
+ * Returns referrer data if available, null otherwise
747
+ */
748
+ getPlayInstallReferrer() {
749
+ const data = playInstallReferrerIntegration.getReferrerData();
750
+ return data ? playInstallReferrerIntegration.getAttributionData() : null;
751
+ }
688
752
  /**
689
753
  * Update tracking authorization status on all platform SDKs
690
754
  * Call this AFTER the user responds to the ATT permission dialog
@@ -849,6 +913,41 @@ export class DatalyrSDK {
849
913
  errorLog('Error persisting user data:', error);
850
914
  }
851
915
  }
916
+ /**
917
+ * Initialize network status monitoring
918
+ * Automatically updates event queue when network status changes
919
+ */
920
+ async initializeNetworkMonitoring() {
921
+ try {
922
+ await networkStatusManager.initialize();
923
+ // Update event queue with current network status
924
+ this.state.isOnline = networkStatusManager.isOnline();
925
+ this.eventQueue.setOnlineStatus(this.state.isOnline);
926
+ // Subscribe to network changes
927
+ this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
928
+ const isOnline = state.isConnected && (state.isInternetReachable !== false);
929
+ this.state.isOnline = isOnline;
930
+ this.eventQueue.setOnlineStatus(isOnline);
931
+ // Track network status change event (only if SDK is fully initialized)
932
+ if (this.state.initialized) {
933
+ this.track('$network_status_change', {
934
+ is_online: isOnline,
935
+ network_type: state.type,
936
+ is_internet_reachable: state.isInternetReachable,
937
+ }).catch(() => {
938
+ // Ignore errors for network status events
939
+ });
940
+ }
941
+ });
942
+ debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
943
+ }
944
+ catch (error) {
945
+ errorLog('Error initializing network monitoring (non-blocking):', error);
946
+ // Default to online if monitoring fails
947
+ this.state.isOnline = true;
948
+ this.eventQueue.setOnlineStatus(true);
949
+ }
950
+ }
852
951
  /**
853
952
  * Set up app state monitoring for lifecycle events (optimized)
854
953
  */
@@ -869,6 +968,8 @@ export class DatalyrSDK {
869
968
  else if (nextAppState === 'active') {
870
969
  // App became active, ensure we have fresh session if needed
871
970
  this.refreshSession();
971
+ // Refresh network status when coming back from background
972
+ networkStatusManager.refresh();
872
973
  // Notify auto-events manager for session handling
873
974
  if (this.autoEventsManager) {
874
975
  this.autoEventsManager.handleAppForeground();
@@ -906,6 +1007,13 @@ export class DatalyrSDK {
906
1007
  this.appStateSubscription.remove();
907
1008
  this.appStateSubscription = null;
908
1009
  }
1010
+ // Remove network status listener
1011
+ if (this.networkStatusUnsubscribe) {
1012
+ this.networkStatusUnsubscribe();
1013
+ this.networkStatusUnsubscribe = null;
1014
+ }
1015
+ // Destroy network status manager
1016
+ networkStatusManager.destroy();
909
1017
  // Destroy event queue
910
1018
  this.eventQueue.destroy();
911
1019
  // Reset state
package/lib/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export declare const datalyr: DatalyrSDK;
3
3
  export { Datalyr };
4
4
  export * from './types';
5
5
  export { attributionManager } from './attribution';
6
+ export { journeyManager } from './journey';
7
+ export type { TouchAttribution, TouchPoint } from './journey';
6
8
  export { createAutoEventsManager, AutoEventsManager } from './auto-events';
7
9
  export * from './utils';
8
10
  export * from './http-client';
@@ -10,6 +12,8 @@ export * from './event-queue';
10
12
  export { DatalyrSDK };
11
13
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
12
14
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
13
- export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
15
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
16
+ export { networkStatusManager } from './network-status';
17
+ export type { NetworkState, NetworkStateListener } from './network-status';
14
18
  export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
15
19
  export default DatalyrSDK;
package/lib/index.js CHANGED
@@ -8,6 +8,7 @@ export { Datalyr };
8
8
  // Export types and utilities
9
9
  export * from './types';
10
10
  export { attributionManager } from './attribution';
11
+ export { journeyManager } from './journey';
11
12
  export { createAutoEventsManager, AutoEventsManager } from './auto-events';
12
13
  // Re-export utilities for advanced usage
13
14
  export * from './utils';
@@ -19,6 +20,8 @@ export { DatalyrSDK };
19
20
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
20
21
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
21
22
  // Export platform integrations
22
- export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
23
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
24
+ // Export network status manager
25
+ export { networkStatusManager } from './network-status';
23
26
  // Default export for compatibility
24
27
  export default DatalyrSDK;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Platform SDK Integrations
3
- * Meta (Facebook), TikTok, and Apple Search Ads SDK wrappers
3
+ * Meta (Facebook), TikTok, Apple Search Ads, and Google Play Install Referrer SDK wrappers
4
4
  */
5
5
  export { MetaIntegration, metaIntegration } from './meta-integration';
6
6
  export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
7
7
  export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
8
+ export { playInstallReferrerIntegration } from './play-install-referrer';
9
+ export type { PlayInstallReferrer } from './play-install-referrer';
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Platform SDK Integrations
3
- * Meta (Facebook), TikTok, and Apple Search Ads SDK wrappers
3
+ * Meta (Facebook), TikTok, Apple Search Ads, and Google Play Install Referrer SDK wrappers
4
4
  */
5
5
  export { MetaIntegration, metaIntegration } from './meta-integration';
6
6
  export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
7
7
  export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
8
+ export { playInstallReferrerIntegration } from './play-install-referrer';
@@ -15,6 +15,7 @@ export declare class MetaIntegration {
15
15
  private deferredDeepLinkData;
16
16
  /**
17
17
  * Initialize Meta SDK with configuration
18
+ * Supported on both iOS and Android via native modules
18
19
  */
19
20
  initialize(config: MetaConfig, debug?: boolean): Promise<void>;
20
21
  /**