@datalyr/react-native 1.2.0 → 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.
- package/CHANGELOG.md +8 -0
- package/README.md +30 -1
- 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/DatalyrNative.m +4 -0
- package/ios/DatalyrNative.swift +58 -1
- package/ios/DatalyrSKAdNetwork.m +52 -1
- package/lib/ConversionValueEncoder.d.ts +13 -1
- package/lib/ConversionValueEncoder.js +57 -23
- package/lib/datalyr-sdk.d.ts +34 -2
- package/lib/datalyr-sdk.js +90 -8
- package/lib/index.d.ts +4 -1
- package/lib/index.js +2 -1
- package/lib/integrations/apple-search-ads-integration.d.ts +43 -0
- package/lib/integrations/apple-search-ads-integration.js +106 -0
- package/lib/integrations/index.d.ts +4 -1
- package/lib/integrations/index.js +3 -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 +74 -0
- package/lib/integrations/play-install-referrer.js +156 -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 +67 -2
- package/lib/native/DatalyrNativeBridge.js +80 -7
- package/lib/native/SKAdNetworkBridge.d.ts +21 -0
- package/lib/native/SKAdNetworkBridge.js +54 -0
- package/package.json +9 -3
- package/src/ConversionValueEncoder.ts +67 -26
- package/src/datalyr-sdk-expo.ts +98 -9
- package/src/datalyr-sdk.ts +109 -14
- package/src/expo.ts +8 -0
- package/src/index.ts +6 -1
- package/src/integrations/apple-search-ads-integration.ts +119 -0
- package/src/integrations/index.ts +4 -1
- package/src/integrations/meta-integration.ts +4 -3
- package/src/integrations/play-install-referrer.ts +203 -0
- package/src/integrations/tiktok-integration.ts +4 -3
- package/src/journey.ts +338 -0
- package/src/native/DatalyrNativeBridge.ts +137 -9
- 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", "~>
|
|
24
|
-
s.dependency "TikTokBusinessSDK", "~> 1.
|
|
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 = {
|
package/ios/DatalyrNative.m
CHANGED
|
@@ -67,4 +67,8 @@ RCT_EXTERN_METHOD(updateTikTokTrackingAuthorization:(BOOL)enabled
|
|
|
67
67
|
RCT_EXTERN_METHOD(getSDKAvailability:(RCTPromiseResolveBlock)resolve
|
|
68
68
|
reject:(RCTPromiseRejectBlock)reject)
|
|
69
69
|
|
|
70
|
+
// Apple Search Ads Attribution
|
|
71
|
+
RCT_EXTERN_METHOD(getAppleSearchAdsAttribution:(RCTPromiseResolveBlock)resolve
|
|
72
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
73
|
+
|
|
70
74
|
@end
|
package/ios/DatalyrNative.swift
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import FBSDKCoreKit
|
|
3
3
|
import TikTokBusinessSDK
|
|
4
|
+
import AdServices
|
|
4
5
|
|
|
5
6
|
@objc(DatalyrNative)
|
|
6
7
|
class DatalyrNative: NSObject {
|
|
@@ -269,7 +270,63 @@ class DatalyrNative: NSObject {
|
|
|
269
270
|
) {
|
|
270
271
|
resolve([
|
|
271
272
|
"meta": true,
|
|
272
|
-
"tiktok": true
|
|
273
|
+
"tiktok": true,
|
|
274
|
+
"appleSearchAds": true
|
|
273
275
|
])
|
|
274
276
|
}
|
|
277
|
+
|
|
278
|
+
// MARK: - Apple Search Ads Attribution
|
|
279
|
+
|
|
280
|
+
@objc func getAppleSearchAdsAttribution(
|
|
281
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
282
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
283
|
+
) {
|
|
284
|
+
// AdServices is available on iOS 14.3+
|
|
285
|
+
if #available(iOS 14.3, *) {
|
|
286
|
+
do {
|
|
287
|
+
// Get the attribution token from AdServices
|
|
288
|
+
let token = try AAAttribution.attributionToken()
|
|
289
|
+
|
|
290
|
+
// Send token to Apple's API to get attribution data
|
|
291
|
+
var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
|
|
292
|
+
request.httpMethod = "POST"
|
|
293
|
+
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
|
|
294
|
+
request.httpBody = token.data(using: .utf8)
|
|
295
|
+
|
|
296
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
297
|
+
if let error = error {
|
|
298
|
+
// Network error - resolve with nil instead of rejecting
|
|
299
|
+
resolve(nil)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
guard let data = data else {
|
|
304
|
+
resolve(nil)
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
do {
|
|
309
|
+
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
310
|
+
// Return attribution data
|
|
311
|
+
// Includes: attribution, orgId, orgName, campaignId, campaignName,
|
|
312
|
+
// adGroupId, adGroupName, clickDate, conversionType, etc.
|
|
313
|
+
resolve(json)
|
|
314
|
+
} else {
|
|
315
|
+
resolve(nil)
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
resolve(nil)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
task.resume()
|
|
322
|
+
|
|
323
|
+
} catch {
|
|
324
|
+
// Attribution token not available (user didn't come from Apple Search Ads)
|
|
325
|
+
resolve(nil)
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
// iOS version too old
|
|
329
|
+
resolve(nil)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
275
332
|
}
|
package/ios/DatalyrSKAdNetwork.m
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DatalyrConfig, EventData, UserProperties, AutoEventConfig, DeferredDeepLinkResult } from './types';
|
|
2
2
|
import { AttributionData } from './attribution';
|
|
3
3
|
import { SessionData } from './auto-events';
|
|
4
|
+
import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
4
5
|
export declare class DatalyrSDK {
|
|
5
6
|
private state;
|
|
6
7
|
private httpClient;
|
|
@@ -55,15 +56,30 @@ export declare class DatalyrSDK {
|
|
|
55
56
|
currentUserId?: string;
|
|
56
57
|
queueStats: any;
|
|
57
58
|
attribution: any;
|
|
59
|
+
journey: any;
|
|
58
60
|
};
|
|
59
61
|
/**
|
|
60
62
|
* Get the persistent anonymous ID
|
|
61
63
|
*/
|
|
62
64
|
getAnonymousId(): string;
|
|
63
65
|
/**
|
|
64
|
-
* Get detailed attribution data
|
|
66
|
+
* Get detailed attribution data (includes journey tracking data)
|
|
65
67
|
*/
|
|
66
|
-
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[];
|
|
67
83
|
/**
|
|
68
84
|
* Set custom attribution data (for testing or manual attribution)
|
|
69
85
|
*/
|
|
@@ -90,6 +106,7 @@ export declare class DatalyrSDK {
|
|
|
90
106
|
updateAutoEventsConfig(config: Partial<AutoEventConfig>): void;
|
|
91
107
|
/**
|
|
92
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
|
|
93
110
|
*/
|
|
94
111
|
trackWithSKAdNetwork(event: string, properties?: EventData): Promise<void>;
|
|
95
112
|
/**
|
|
@@ -138,7 +155,19 @@ export declare class DatalyrSDK {
|
|
|
138
155
|
getPlatformIntegrationStatus(): {
|
|
139
156
|
meta: boolean;
|
|
140
157
|
tiktok: boolean;
|
|
158
|
+
appleSearchAds: boolean;
|
|
159
|
+
playInstallReferrer: boolean;
|
|
141
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Get Apple Search Ads attribution data
|
|
163
|
+
* Returns attribution if user installed via Apple Search Ads, null otherwise
|
|
164
|
+
*/
|
|
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;
|
|
142
171
|
/**
|
|
143
172
|
* Update tracking authorization status on all platform SDKs
|
|
144
173
|
* Call this AFTER the user responds to the ATT permission dialog
|
|
@@ -214,6 +243,7 @@ export declare class Datalyr {
|
|
|
214
243
|
currentUserId?: string;
|
|
215
244
|
queueStats: any;
|
|
216
245
|
attribution: any;
|
|
246
|
+
journey: any;
|
|
217
247
|
};
|
|
218
248
|
static getAnonymousId(): string;
|
|
219
249
|
static getAttributionData(): AttributionData;
|
|
@@ -234,7 +264,9 @@ export declare class Datalyr {
|
|
|
234
264
|
static getPlatformIntegrationStatus(): {
|
|
235
265
|
meta: boolean;
|
|
236
266
|
tiktok: boolean;
|
|
267
|
+
appleSearchAds: boolean;
|
|
237
268
|
};
|
|
269
|
+
static getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
|
|
238
270
|
static updateTrackingAuthorization(enabled: boolean): Promise<void>;
|
|
239
271
|
}
|
|
240
272
|
export default datalyr;
|