@datalyr/react-native 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.
Files changed (40) hide show
  1. package/android/build.gradle +54 -0
  2. package/android/src/main/AndroidManifest.xml +14 -0
  3. package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
  4. package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
  5. package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
  6. package/datalyr-react-native.podspec +2 -2
  7. package/ios/DatalyrSKAdNetwork.m +52 -1
  8. package/lib/ConversionValueEncoder.d.ts +13 -1
  9. package/lib/ConversionValueEncoder.js +57 -23
  10. package/lib/datalyr-sdk.d.ts +25 -2
  11. package/lib/datalyr-sdk.js +59 -8
  12. package/lib/index.d.ts +2 -0
  13. package/lib/index.js +1 -0
  14. package/lib/integrations/index.d.ts +3 -1
  15. package/lib/integrations/index.js +2 -1
  16. package/lib/integrations/meta-integration.d.ts +1 -0
  17. package/lib/integrations/meta-integration.js +4 -3
  18. package/lib/integrations/play-install-referrer.d.ts +74 -0
  19. package/lib/integrations/play-install-referrer.js +156 -0
  20. package/lib/integrations/tiktok-integration.d.ts +1 -0
  21. package/lib/integrations/tiktok-integration.js +4 -3
  22. package/lib/journey.d.ts +106 -0
  23. package/lib/journey.js +258 -0
  24. package/lib/native/DatalyrNativeBridge.d.ts +42 -3
  25. package/lib/native/DatalyrNativeBridge.js +63 -9
  26. package/lib/native/SKAdNetworkBridge.d.ts +21 -0
  27. package/lib/native/SKAdNetworkBridge.js +54 -0
  28. package/package.json +8 -3
  29. package/src/ConversionValueEncoder.ts +67 -26
  30. package/src/datalyr-sdk-expo.ts +55 -6
  31. package/src/datalyr-sdk.ts +72 -13
  32. package/src/expo.ts +4 -0
  33. package/src/index.ts +2 -0
  34. package/src/integrations/index.ts +3 -1
  35. package/src/integrations/meta-integration.ts +4 -3
  36. package/src/integrations/play-install-referrer.ts +203 -0
  37. package/src/integrations/tiktok-integration.ts +4 -3
  38. package/src/journey.ts +338 -0
  39. package/src/native/DatalyrNativeBridge.ts +99 -13
  40. package/src/native/SKAdNetworkBridge.ts +86 -2
@@ -0,0 +1,229 @@
1
+ package com.datalyr.reactnative;
2
+
3
+ import android.os.RemoteException;
4
+ import android.util.Log;
5
+
6
+ import com.facebook.react.bridge.Arguments;
7
+ import com.facebook.react.bridge.Promise;
8
+ import com.facebook.react.bridge.ReactApplicationContext;
9
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
+ import com.facebook.react.bridge.ReactMethod;
11
+ import com.facebook.react.bridge.WritableMap;
12
+
13
+ import com.android.installreferrer.api.InstallReferrerClient;
14
+ import com.android.installreferrer.api.InstallReferrerStateListener;
15
+ import com.android.installreferrer.api.ReferrerDetails;
16
+
17
+ import java.io.UnsupportedEncodingException;
18
+ import java.net.URLDecoder;
19
+ import java.util.HashMap;
20
+ import java.util.Map;
21
+
22
+ /**
23
+ * Google Play Install Referrer Module for Android
24
+ *
25
+ * Captures install attribution data from Google Play Store:
26
+ * - UTM parameters (utm_source, utm_medium, utm_campaign, etc.)
27
+ * - Google Ads click ID (gclid)
28
+ * - Referrer timestamps
29
+ *
30
+ * This data is critical for attributing installs to marketing campaigns.
31
+ */
32
+ public class DatalyrPlayInstallReferrerModule extends ReactContextBaseJavaModule {
33
+ private static final String TAG = "DatalyrPlayReferrer";
34
+ private static final String MODULE_NAME = "DatalyrPlayInstallReferrer";
35
+
36
+ private final ReactApplicationContext reactContext;
37
+ private InstallReferrerClient referrerClient;
38
+
39
+ public DatalyrPlayInstallReferrerModule(ReactApplicationContext context) {
40
+ super(context);
41
+ this.reactContext = context;
42
+ }
43
+
44
+ @Override
45
+ public String getName() {
46
+ return MODULE_NAME;
47
+ }
48
+
49
+ /**
50
+ * Check if Play Install Referrer is available
51
+ */
52
+ @ReactMethod
53
+ public void isAvailable(Promise promise) {
54
+ try {
55
+ // Check if the Play Install Referrer library is available
56
+ Class.forName("com.android.installreferrer.api.InstallReferrerClient");
57
+ promise.resolve(true);
58
+ } catch (ClassNotFoundException e) {
59
+ promise.resolve(false);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get install referrer data from Google Play
65
+ *
66
+ * Returns an object with:
67
+ * - referrerUrl: The full referrer URL
68
+ * - referrerClickTimestamp: When the referrer link was clicked (ms)
69
+ * - installBeginTimestamp: When the install began (ms)
70
+ * - installCompleteTimestamp: When install was completed (ms) - Android 10+
71
+ * - gclid: Google Ads click ID (if present)
72
+ * - utmSource, utmMedium, utmCampaign, etc.
73
+ */
74
+ @ReactMethod
75
+ public void getInstallReferrer(final Promise promise) {
76
+ try {
77
+ referrerClient = InstallReferrerClient.newBuilder(reactContext.getApplicationContext()).build();
78
+
79
+ referrerClient.startConnection(new InstallReferrerStateListener() {
80
+ @Override
81
+ public void onInstallReferrerSetupFinished(int responseCode) {
82
+ switch (responseCode) {
83
+ case InstallReferrerClient.InstallReferrerResponse.OK:
84
+ try {
85
+ ReferrerDetails details = referrerClient.getInstallReferrer();
86
+ WritableMap result = parseReferrerDetails(details);
87
+ promise.resolve(result);
88
+ } catch (RemoteException e) {
89
+ Log.e(TAG, "Failed to get install referrer", e);
90
+ promise.resolve(null);
91
+ } finally {
92
+ referrerClient.endConnection();
93
+ }
94
+ break;
95
+
96
+ case InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED:
97
+ Log.d(TAG, "Install referrer not supported on this device");
98
+ promise.resolve(null);
99
+ referrerClient.endConnection();
100
+ break;
101
+
102
+ case InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE:
103
+ Log.d(TAG, "Install referrer service unavailable");
104
+ promise.resolve(null);
105
+ referrerClient.endConnection();
106
+ break;
107
+
108
+ default:
109
+ Log.d(TAG, "Install referrer unknown response: " + responseCode);
110
+ promise.resolve(null);
111
+ referrerClient.endConnection();
112
+ break;
113
+ }
114
+ }
115
+
116
+ @Override
117
+ public void onInstallReferrerServiceDisconnected() {
118
+ Log.d(TAG, "Install referrer service disconnected");
119
+ // Connection lost - can try to reconnect if needed
120
+ }
121
+ });
122
+ } catch (Exception e) {
123
+ Log.e(TAG, "Failed to start install referrer client", e);
124
+ promise.resolve(null);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Parse ReferrerDetails into a WritableMap with UTM parameters extracted
130
+ */
131
+ private WritableMap parseReferrerDetails(ReferrerDetails details) {
132
+ WritableMap result = Arguments.createMap();
133
+
134
+ try {
135
+ String referrerUrl = details.getInstallReferrer();
136
+ result.putString("referrerUrl", referrerUrl);
137
+ result.putDouble("referrerClickTimestamp", details.getReferrerClickTimestampSeconds() * 1000.0);
138
+ result.putDouble("installBeginTimestamp", details.getInstallBeginTimestampSeconds() * 1000.0);
139
+
140
+ // Android 10+ has install complete timestamp
141
+ try {
142
+ long installCompleteTs = details.getInstallBeginTimestampServerSeconds();
143
+ if (installCompleteTs > 0) {
144
+ result.putDouble("installCompleteTimestamp", installCompleteTs * 1000.0);
145
+ }
146
+ } catch (NoSuchMethodError e) {
147
+ // Method not available on older Android versions
148
+ }
149
+
150
+ // Parse UTM parameters from referrer URL
151
+ if (referrerUrl != null && !referrerUrl.isEmpty()) {
152
+ Map<String, String> params = parseReferrerUrl(referrerUrl);
153
+
154
+ // UTM parameters
155
+ if (params.containsKey("utm_source")) {
156
+ result.putString("utmSource", params.get("utm_source"));
157
+ }
158
+ if (params.containsKey("utm_medium")) {
159
+ result.putString("utmMedium", params.get("utm_medium"));
160
+ }
161
+ if (params.containsKey("utm_campaign")) {
162
+ result.putString("utmCampaign", params.get("utm_campaign"));
163
+ }
164
+ if (params.containsKey("utm_term")) {
165
+ result.putString("utmTerm", params.get("utm_term"));
166
+ }
167
+ if (params.containsKey("utm_content")) {
168
+ result.putString("utmContent", params.get("utm_content"));
169
+ }
170
+
171
+ // Google Ads click ID
172
+ if (params.containsKey("gclid")) {
173
+ result.putString("gclid", params.get("gclid"));
174
+ }
175
+
176
+ // Other potential click IDs
177
+ if (params.containsKey("fbclid")) {
178
+ result.putString("fbclid", params.get("fbclid"));
179
+ }
180
+ if (params.containsKey("ttclid")) {
181
+ result.putString("ttclid", params.get("ttclid"));
182
+ }
183
+
184
+ // App referrer (used by some attribution providers)
185
+ if (params.containsKey("referrer")) {
186
+ result.putString("referrer", params.get("referrer"));
187
+ }
188
+ }
189
+
190
+ Log.d(TAG, "Install referrer parsed successfully");
191
+
192
+ } catch (Exception e) {
193
+ Log.e(TAG, "Failed to parse referrer details", e);
194
+ }
195
+
196
+ return result;
197
+ }
198
+
199
+ /**
200
+ * Parse URL-encoded referrer string into key-value pairs
201
+ */
202
+ private Map<String, String> parseReferrerUrl(String referrerUrl) {
203
+ Map<String, String> params = new HashMap<>();
204
+
205
+ if (referrerUrl == null || referrerUrl.isEmpty()) {
206
+ return params;
207
+ }
208
+
209
+ try {
210
+ // Decode the URL-encoded referrer
211
+ String decoded = URLDecoder.decode(referrerUrl, "UTF-8");
212
+
213
+ // Split by & to get key=value pairs
214
+ String[] pairs = decoded.split("&");
215
+ for (String pair : pairs) {
216
+ int idx = pair.indexOf('=');
217
+ if (idx > 0 && idx < pair.length() - 1) {
218
+ String key = pair.substring(0, idx);
219
+ String value = pair.substring(idx + 1);
220
+ params.put(key, value);
221
+ }
222
+ }
223
+ } catch (UnsupportedEncodingException e) {
224
+ Log.e(TAG, "Failed to decode referrer URL", e);
225
+ }
226
+
227
+ return params;
228
+ }
229
+ }
@@ -20,8 +20,8 @@ Pod::Spec.new do |s|
20
20
  s.swift_version = "5.0"
21
21
 
22
22
  s.dependency "React-Core"
23
- s.dependency "FBSDKCoreKit", "~> 17.0"
24
- s.dependency "TikTokBusinessSDK", "~> 1.4"
23
+ s.dependency "FBSDKCoreKit", "~> 18.0"
24
+ s.dependency "TikTokBusinessSDK", "~> 1.6"
25
25
 
26
26
  # Disable bitcode (required for TikTok SDK)
27
27
  s.pod_target_xcconfig = {
@@ -8,6 +8,7 @@
8
8
 
9
9
  RCT_EXPORT_MODULE();
10
10
 
11
+ // SKAN 3.0 - Legacy method for iOS 14.0-16.0
11
12
  RCT_EXPORT_METHOD(updateConversionValue:(NSInteger)value
12
13
  resolve:(RCTPromiseResolveBlock)resolve
13
14
  reject:(RCTPromiseRejectBlock)reject) {
@@ -23,4 +24,54 @@ RCT_EXPORT_METHOD(updateConversionValue:(NSInteger)value
23
24
  }
24
25
  }
25
26
 
26
- @end
27
+ // SKAN 4.0 - New method for iOS 16.1+ with coarse value and lock window support
28
+ RCT_EXPORT_METHOD(updatePostbackConversionValue:(NSInteger)fineValue
29
+ coarseValue:(NSString *)coarseValue
30
+ lockWindow:(BOOL)lockWindow
31
+ resolve:(RCTPromiseResolveBlock)resolve
32
+ reject:(RCTPromiseRejectBlock)reject) {
33
+ if (@available(iOS 16.1, *)) {
34
+ // Convert string to SKAdNetwork.CoarseConversionValue
35
+ SKAdNetworkCoarseConversionValue coarse;
36
+ if ([coarseValue isEqualToString:@"high"]) {
37
+ coarse = SKAdNetworkCoarseConversionValueHigh;
38
+ } else if ([coarseValue isEqualToString:@"medium"]) {
39
+ coarse = SKAdNetworkCoarseConversionValueMedium;
40
+ } else {
41
+ coarse = SKAdNetworkCoarseConversionValueLow;
42
+ }
43
+
44
+ [SKAdNetwork updatePostbackConversionValue:fineValue
45
+ coarseValue:coarse
46
+ lockWindow:lockWindow
47
+ completionHandler:^(NSError * _Nullable error) {
48
+ if (error) {
49
+ reject(@"skadnetwork_error", error.localizedDescription, error);
50
+ } else {
51
+ resolve(@(YES));
52
+ }
53
+ }];
54
+ } else if (@available(iOS 14.0, *)) {
55
+ // Fallback to SKAN 3.0 for iOS 14.0-16.0
56
+ @try {
57
+ [SKAdNetwork updateConversionValue:fineValue];
58
+ resolve(@(YES));
59
+ } @catch (NSException *exception) {
60
+ reject(@"skadnetwork_error", exception.reason, nil);
61
+ }
62
+ } else {
63
+ reject(@"ios_version_error", @"SKAdNetwork requires iOS 14.0+", nil);
64
+ }
65
+ }
66
+
67
+ // Check if SKAN 4.0 is available (iOS 16.1+)
68
+ RCT_EXPORT_METHOD(isSKAN4Available:(RCTPromiseResolveBlock)resolve
69
+ reject:(RCTPromiseRejectBlock)reject) {
70
+ if (@available(iOS 16.1, *)) {
71
+ resolve(@(YES));
72
+ } else {
73
+ resolve(@(NO));
74
+ }
75
+ }
76
+
77
+ @end
@@ -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
  };
@@ -56,15 +56,30 @@ export declare class DatalyrSDK {
56
56
  currentUserId?: string;
57
57
  queueStats: any;
58
58
  attribution: any;
59
+ journey: any;
59
60
  };
60
61
  /**
61
62
  * Get the persistent anonymous ID
62
63
  */
63
64
  getAnonymousId(): string;
64
65
  /**
65
- * Get detailed attribution data
66
+ * Get detailed attribution data (includes journey tracking data)
66
67
  */
67
- getAttributionData(): AttributionData;
68
+ getAttributionData(): AttributionData & Record<string, any>;
69
+ /**
70
+ * Get journey tracking summary
71
+ */
72
+ getJourneySummary(): {
73
+ hasFirstTouch: boolean;
74
+ hasLastTouch: boolean;
75
+ touchpointCount: number;
76
+ daysSinceFirstTouch: number;
77
+ sources: string[];
78
+ };
79
+ /**
80
+ * Get full customer journey (all touchpoints)
81
+ */
82
+ getJourney(): import("./journey").TouchPoint[];
68
83
  /**
69
84
  * Set custom attribution data (for testing or manual attribution)
70
85
  */
@@ -91,6 +106,7 @@ export declare class DatalyrSDK {
91
106
  updateAutoEventsConfig(config: Partial<AutoEventConfig>): void;
92
107
  /**
93
108
  * Track event with automatic SKAdNetwork conversion value encoding
109
+ * Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
94
110
  */
95
111
  trackWithSKAdNetwork(event: string, properties?: EventData): Promise<void>;
96
112
  /**
@@ -140,12 +156,18 @@ export declare class DatalyrSDK {
140
156
  meta: boolean;
141
157
  tiktok: boolean;
142
158
  appleSearchAds: boolean;
159
+ playInstallReferrer: boolean;
143
160
  };
144
161
  /**
145
162
  * Get Apple Search Ads attribution data
146
163
  * Returns attribution if user installed via Apple Search Ads, null otherwise
147
164
  */
148
165
  getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
166
+ /**
167
+ * Get Google Play Install Referrer attribution data (Android only)
168
+ * Returns referrer data if available, null otherwise
169
+ */
170
+ getPlayInstallReferrer(): Record<string, any> | null;
149
171
  /**
150
172
  * Update tracking authorization status on all platform SDKs
151
173
  * Call this AFTER the user responds to the ATT permission dialog
@@ -221,6 +243,7 @@ export declare class Datalyr {
221
243
  currentUserId?: string;
222
244
  queueStats: any;
223
245
  attribution: any;
246
+ journey: any;
224
247
  };
225
248
  static getAnonymousId(): string;
226
249
  static getAttributionData(): AttributionData;
@@ -3,10 +3,11 @@ 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';
10
11
  export class DatalyrSDK {
11
12
  constructor() {
12
13
  this.autoEventsManager = null;
@@ -82,6 +83,22 @@ export class DatalyrSDK {
82
83
  if (this.state.config.enableAttribution) {
83
84
  await attributionManager.initialize();
84
85
  }
86
+ // Initialize journey tracking (for first-touch, last-touch, touchpoints)
87
+ await journeyManager.initialize();
88
+ // Record initial attribution to journey if this is a new session with attribution
89
+ const initialAttribution = attributionManager.getAttributionData();
90
+ if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
91
+ await journeyManager.recordAttribution(this.state.sessionId, {
92
+ source: initialAttribution.utm_source || initialAttribution.campaign_source,
93
+ medium: initialAttribution.utm_medium || initialAttribution.campaign_medium,
94
+ campaign: initialAttribution.utm_campaign || initialAttribution.campaign_name,
95
+ fbclid: initialAttribution.fbclid,
96
+ gclid: initialAttribution.gclid,
97
+ ttclid: initialAttribution.ttclid,
98
+ clickIdType: initialAttribution.fbclid ? 'fbclid' : initialAttribution.gclid ? 'gclid' : initialAttribution.ttclid ? 'ttclid' : undefined,
99
+ lyr: initialAttribution.lyr,
100
+ });
101
+ }
85
102
  // Initialize auto-events manager (asynchronously to avoid blocking)
86
103
  if (this.state.config.enableAutoEvents) {
87
104
  this.autoEventsManager = new AutoEventsManager(this.track.bind(this), this.state.config.autoEventConfig);
@@ -134,10 +151,13 @@ export class DatalyrSDK {
134
151
  }
135
152
  // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
136
153
  await appleSearchAdsIntegration.initialize(config.debug);
154
+ // Initialize Google Play Install Referrer (Android only)
155
+ await playInstallReferrerIntegration.initialize();
137
156
  debugLog('Platform integrations initialized', {
138
157
  meta: metaIntegration.isAvailable(),
139
158
  tiktok: tiktokIntegration.isAvailable(),
140
159
  appleSearchAds: appleSearchAdsIntegration.isAvailable(),
160
+ playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
141
161
  });
142
162
  // SDK initialized successfully - set state before tracking install event
143
163
  this.state.initialized = true;
@@ -398,6 +418,7 @@ export class DatalyrSDK {
398
418
  currentUserId: this.state.currentUserId,
399
419
  queueStats: this.eventQueue.getStats(),
400
420
  attribution: attributionManager.getAttributionSummary(),
421
+ journey: journeyManager.getJourneySummary(),
401
422
  };
402
423
  }
403
424
  /**
@@ -407,10 +428,28 @@ export class DatalyrSDK {
407
428
  return this.state.anonymousId;
408
429
  }
409
430
  /**
410
- * Get detailed attribution data
431
+ * Get detailed attribution data (includes journey tracking data)
411
432
  */
412
433
  getAttributionData() {
413
- return attributionManager.getAttributionData();
434
+ const attribution = attributionManager.getAttributionData();
435
+ const journeyData = journeyManager.getAttributionData();
436
+ // Merge attribution with journey data
437
+ return {
438
+ ...attribution,
439
+ ...journeyData,
440
+ };
441
+ }
442
+ /**
443
+ * Get journey tracking summary
444
+ */
445
+ getJourneySummary() {
446
+ return journeyManager.getJourneySummary();
447
+ }
448
+ /**
449
+ * Get full customer journey (all touchpoints)
450
+ */
451
+ getJourney() {
452
+ return journeyManager.getJourney();
414
453
  }
415
454
  /**
416
455
  * Set custom attribution data (for testing or manual attribution)
@@ -460,22 +499,25 @@ export class DatalyrSDK {
460
499
  // MARK: - SKAdNetwork Enhanced Methods
461
500
  /**
462
501
  * Track event with automatic SKAdNetwork conversion value encoding
502
+ * Uses SKAN 4.0 on iOS 16.1+ with coarse values and lock window support
463
503
  */
464
504
  async trackWithSKAdNetwork(event, properties) {
465
505
  // Existing tracking (keep exactly as-is)
466
506
  await this.track(event, properties);
467
- // NEW: Automatic SKAdNetwork encoding
507
+ // Automatic SKAdNetwork encoding with SKAN 4.0 support
468
508
  if (!DatalyrSDK.conversionEncoder) {
469
509
  if (DatalyrSDK.debugEnabled) {
470
510
  errorLog('SKAdNetwork encoder not initialized. Pass skadTemplate in initialize()');
471
511
  }
472
512
  return;
473
513
  }
474
- const conversionValue = DatalyrSDK.conversionEncoder.encode(event, properties);
475
- if (conversionValue > 0) {
476
- const success = await SKAdNetworkBridge.updateConversionValue(conversionValue);
514
+ // Use SKAN 4.0 encoding (includes coarse value and lock window)
515
+ const result = DatalyrSDK.conversionEncoder.encodeWithSKAN4(event, properties);
516
+ if (result.fineValue > 0 || result.priority > 0) {
517
+ // Use SKAN 4.0 method (automatically falls back to SKAN 3.0 on older iOS)
518
+ const success = await SKAdNetworkBridge.updatePostbackConversionValue(result);
477
519
  if (DatalyrSDK.debugEnabled) {
478
- debugLog(`Event: ${event}, Conversion Value: ${conversionValue}, Success: ${success}`, properties);
520
+ debugLog(`SKAN: event=${event}, fine=${result.fineValue}, coarse=${result.coarseValue}, lock=${result.lockWindow}, success=${success}`, properties);
479
521
  }
480
522
  }
481
523
  else if (DatalyrSDK.debugEnabled) {
@@ -676,6 +718,7 @@ export class DatalyrSDK {
676
718
  meta: metaIntegration.isAvailable(),
677
719
  tiktok: tiktokIntegration.isAvailable(),
678
720
  appleSearchAds: appleSearchAdsIntegration.isAvailable(),
721
+ playInstallReferrer: playInstallReferrerIntegration.isAvailable(),
679
722
  };
680
723
  }
681
724
  /**
@@ -685,6 +728,14 @@ export class DatalyrSDK {
685
728
  getAppleSearchAdsAttribution() {
686
729
  return appleSearchAdsIntegration.getAttributionData();
687
730
  }
731
+ /**
732
+ * Get Google Play Install Referrer attribution data (Android only)
733
+ * Returns referrer data if available, null otherwise
734
+ */
735
+ getPlayInstallReferrer() {
736
+ const data = playInstallReferrerIntegration.getReferrerData();
737
+ return data ? playInstallReferrerIntegration.getAttributionData() : null;
738
+ }
688
739
  /**
689
740
  * Update tracking authorization status on all platform SDKs
690
741
  * Call this AFTER the user responds to the ATT permission dialog