@datalyr/react-native 1.3.1 → 1.4.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/datalyr-react-native.podspec +1 -1
- package/expo-module.config.json +6 -0
- package/ios/DatalyrNativeModule.swift +221 -0
- package/ios/DatalyrSKAdNetworkModule.swift +333 -0
- package/lib/native/DatalyrNativeBridge.js +20 -4
- package/lib/native/SKAdNetworkBridge.js +12 -2
- package/package.json +10 -4
- package/src/native/DatalyrNativeBridge.ts +17 -4
- package/src/native/SKAdNetworkBridge.ts +11 -4
- package/ios/DatalyrNative.m +0 -74
- package/ios/DatalyrNative.swift +0 -332
- package/ios/DatalyrSKAdNetwork.m +0 -425
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import FBSDKCoreKit
|
|
3
|
+
import TikTokBusinessSDK
|
|
4
|
+
import AdServices
|
|
5
|
+
|
|
6
|
+
public class DatalyrNativeModule: Module {
|
|
7
|
+
public func definition() -> ModuleDefinition {
|
|
8
|
+
Name("DatalyrNative")
|
|
9
|
+
|
|
10
|
+
// MARK: - Meta (Facebook) SDK Methods
|
|
11
|
+
|
|
12
|
+
AsyncFunction("initializeMetaSDK") { (appId: String, clientToken: String?, advertiserTrackingEnabled: Bool, promise: Promise) in
|
|
13
|
+
DispatchQueue.main.async {
|
|
14
|
+
Settings.shared.appID = appId
|
|
15
|
+
|
|
16
|
+
if let token = clientToken, !token.isEmpty {
|
|
17
|
+
Settings.shared.clientToken = token
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Settings.shared.isAdvertiserTrackingEnabled = advertiserTrackingEnabled
|
|
21
|
+
Settings.shared.isAdvertiserIDCollectionEnabled = advertiserTrackingEnabled
|
|
22
|
+
|
|
23
|
+
ApplicationDelegate.shared.application(
|
|
24
|
+
UIApplication.shared,
|
|
25
|
+
didFinishLaunchingWithOptions: nil
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
promise.resolve(true)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
AsyncFunction("fetchDeferredAppLink") { (promise: Promise) in
|
|
33
|
+
AppLinkUtility.fetchDeferredAppLink { url, error in
|
|
34
|
+
if error != nil {
|
|
35
|
+
promise.resolve(nil)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if let url = url {
|
|
40
|
+
promise.resolve(url.absoluteString)
|
|
41
|
+
} else {
|
|
42
|
+
promise.resolve(nil)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
AsyncFunction("logMetaEvent") { (eventName: String, valueToSum: Double?, parameters: [String: Any]?, promise: Promise) in
|
|
48
|
+
var params: [AppEvents.ParameterName: Any] = [:]
|
|
49
|
+
|
|
50
|
+
if let dict = parameters {
|
|
51
|
+
for (key, value) in dict {
|
|
52
|
+
params[AppEvents.ParameterName(key)] = value
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if let value = valueToSum {
|
|
57
|
+
AppEvents.shared.logEvent(AppEvents.Name(eventName), valueToSum: value, parameters: params)
|
|
58
|
+
} else if params.isEmpty {
|
|
59
|
+
AppEvents.shared.logEvent(AppEvents.Name(eventName))
|
|
60
|
+
} else {
|
|
61
|
+
AppEvents.shared.logEvent(AppEvents.Name(eventName), parameters: params)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
promise.resolve(true)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
AsyncFunction("logMetaPurchase") { (amount: Double, currency: String, parameters: [String: Any]?, promise: Promise) in
|
|
68
|
+
var params: [AppEvents.ParameterName: Any] = [:]
|
|
69
|
+
|
|
70
|
+
if let dict = parameters {
|
|
71
|
+
for (key, value) in dict {
|
|
72
|
+
params[AppEvents.ParameterName(key)] = value
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
|
|
77
|
+
promise.resolve(true)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
AsyncFunction("setMetaUserData") { (userData: [String: Any], promise: Promise) in
|
|
81
|
+
AppEvents.shared.setUserData(userData["email"] as? String, forType: .email)
|
|
82
|
+
AppEvents.shared.setUserData(userData["firstName"] as? String, forType: .firstName)
|
|
83
|
+
AppEvents.shared.setUserData(userData["lastName"] as? String, forType: .lastName)
|
|
84
|
+
AppEvents.shared.setUserData(userData["phone"] as? String, forType: .phone)
|
|
85
|
+
AppEvents.shared.setUserData(userData["dateOfBirth"] as? String, forType: .dateOfBirth)
|
|
86
|
+
AppEvents.shared.setUserData(userData["gender"] as? String, forType: .gender)
|
|
87
|
+
AppEvents.shared.setUserData(userData["city"] as? String, forType: .city)
|
|
88
|
+
AppEvents.shared.setUserData(userData["state"] as? String, forType: .state)
|
|
89
|
+
AppEvents.shared.setUserData(userData["zip"] as? String, forType: .zip)
|
|
90
|
+
AppEvents.shared.setUserData(userData["country"] as? String, forType: .country)
|
|
91
|
+
|
|
92
|
+
promise.resolve(true)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
AsyncFunction("clearMetaUserData") { (promise: Promise) in
|
|
96
|
+
AppEvents.shared.clearUserData()
|
|
97
|
+
promise.resolve(true)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
AsyncFunction("updateMetaTrackingAuthorization") { (enabled: Bool, promise: Promise) in
|
|
101
|
+
Settings.shared.isAdvertiserTrackingEnabled = enabled
|
|
102
|
+
Settings.shared.isAdvertiserIDCollectionEnabled = enabled
|
|
103
|
+
promise.resolve(true)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// MARK: - TikTok SDK Methods
|
|
107
|
+
|
|
108
|
+
AsyncFunction("initializeTikTokSDK") { (appId: String, tiktokAppId: String, accessToken: String?, debug: Bool, promise: Promise) in
|
|
109
|
+
DispatchQueue.main.async {
|
|
110
|
+
let config = TikTokConfig(appId: appId, tiktokAppId: tiktokAppId)
|
|
111
|
+
|
|
112
|
+
if let token = accessToken, !token.isEmpty {
|
|
113
|
+
config?.accessToken = token
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if debug {
|
|
117
|
+
config?.setLogLevel(.debug)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if let validConfig = config {
|
|
121
|
+
TikTokBusiness.initializeSdk(validConfig)
|
|
122
|
+
promise.resolve(true)
|
|
123
|
+
} else {
|
|
124
|
+
promise.reject("tiktok_init_error", "Failed to create TikTok config")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
AsyncFunction("trackTikTokEvent") { (eventName: String, eventId: String?, properties: [String: Any]?, promise: Promise) in
|
|
130
|
+
let event: TikTokBaseEvent
|
|
131
|
+
|
|
132
|
+
if let eid = eventId, !eid.isEmpty {
|
|
133
|
+
event = TikTokBaseEvent(eventName: eventName, eventId: eid)
|
|
134
|
+
} else {
|
|
135
|
+
event = TikTokBaseEvent(eventName: eventName)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if let dict = properties {
|
|
139
|
+
for (key, value) in dict {
|
|
140
|
+
event.addProperty(withKey: key, value: value)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
TikTokBusiness.trackTTEvent(event)
|
|
145
|
+
promise.resolve(true)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
AsyncFunction("identifyTikTokUser") { (externalId: String, externalUserName: String, phoneNumber: String, email: String, promise: Promise) in
|
|
149
|
+
TikTokBusiness.identify(
|
|
150
|
+
withExternalID: externalId.isEmpty ? nil : externalId,
|
|
151
|
+
externalUserName: externalUserName.isEmpty ? nil : externalUserName,
|
|
152
|
+
phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
|
|
153
|
+
email: email.isEmpty ? nil : email
|
|
154
|
+
)
|
|
155
|
+
promise.resolve(true)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
AsyncFunction("logoutTikTok") { (promise: Promise) in
|
|
159
|
+
TikTokBusiness.logout()
|
|
160
|
+
promise.resolve(true)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
AsyncFunction("updateTikTokTrackingAuthorization") { (enabled: Bool, promise: Promise) in
|
|
164
|
+
// TikTok SDK handles ATT automatically, but we track the change
|
|
165
|
+
promise.resolve(true)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// MARK: - SDK Availability Check
|
|
169
|
+
|
|
170
|
+
AsyncFunction("getSDKAvailability") { (promise: Promise) in
|
|
171
|
+
promise.resolve([
|
|
172
|
+
"meta": true,
|
|
173
|
+
"tiktok": true,
|
|
174
|
+
"appleSearchAds": true
|
|
175
|
+
])
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// MARK: - Apple Search Ads Attribution
|
|
179
|
+
|
|
180
|
+
AsyncFunction("getAppleSearchAdsAttribution") { (promise: Promise) in
|
|
181
|
+
if #available(iOS 14.3, *) {
|
|
182
|
+
do {
|
|
183
|
+
let token = try AAAttribution.attributionToken()
|
|
184
|
+
|
|
185
|
+
var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
|
|
186
|
+
request.httpMethod = "POST"
|
|
187
|
+
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
|
|
188
|
+
request.httpBody = token.data(using: .utf8)
|
|
189
|
+
|
|
190
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
191
|
+
if error != nil {
|
|
192
|
+
promise.resolve(nil)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
guard let data = data else {
|
|
197
|
+
promise.resolve(nil)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
do {
|
|
202
|
+
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
203
|
+
promise.resolve(json)
|
|
204
|
+
} else {
|
|
205
|
+
promise.resolve(nil)
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
promise.resolve(nil)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
task.resume()
|
|
212
|
+
|
|
213
|
+
} catch {
|
|
214
|
+
promise.resolve(nil)
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
promise.resolve(nil)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
public class DatalyrSKAdNetworkModule: Module {
|
|
5
|
+
public func definition() -> ModuleDefinition {
|
|
6
|
+
Name("DatalyrSKAdNetwork")
|
|
7
|
+
|
|
8
|
+
// SKAN 3.0 - Legacy method for iOS 14.0-16.0
|
|
9
|
+
AsyncFunction("updateConversionValue") { (value: Int, promise: Promise) in
|
|
10
|
+
if #available(iOS 14.0, *) {
|
|
11
|
+
SKAdNetwork.updateConversionValue(value)
|
|
12
|
+
promise.resolve(true)
|
|
13
|
+
} else {
|
|
14
|
+
promise.reject("ios_version_error", "SKAdNetwork requires iOS 14.0+")
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// SKAN 4.0 / AdAttributionKit - Method for iOS 16.1+ with coarse value and lock window support
|
|
19
|
+
// On iOS 17.4+, this uses AdAttributionKit under the hood
|
|
20
|
+
AsyncFunction("updatePostbackConversionValue") { (fineValue: Int, coarseValue: String, lockWindow: Bool, promise: Promise) in
|
|
21
|
+
guard fineValue >= 0 && fineValue <= 63 else {
|
|
22
|
+
promise.reject("invalid_value", "Conversion value must be between 0 and 63")
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if #available(iOS 16.1, *) {
|
|
27
|
+
let coarse = Self.mapCoarseValue(coarseValue)
|
|
28
|
+
|
|
29
|
+
SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
|
|
30
|
+
if let error = error {
|
|
31
|
+
promise.reject("skadnetwork_error", error.localizedDescription)
|
|
32
|
+
} else {
|
|
33
|
+
var framework = "SKAdNetwork"
|
|
34
|
+
if #available(iOS 17.4, *) {
|
|
35
|
+
framework = "AdAttributionKit"
|
|
36
|
+
}
|
|
37
|
+
promise.resolve([
|
|
38
|
+
"success": true,
|
|
39
|
+
"framework": framework,
|
|
40
|
+
"fineValue": fineValue,
|
|
41
|
+
"coarseValue": coarseValue,
|
|
42
|
+
"lockWindow": lockWindow
|
|
43
|
+
] as [String: Any])
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if #available(iOS 14.0, *) {
|
|
47
|
+
// Fallback to SKAN 3.0 for iOS 14.0-16.0
|
|
48
|
+
SKAdNetwork.updateConversionValue(fineValue)
|
|
49
|
+
promise.resolve([
|
|
50
|
+
"success": true,
|
|
51
|
+
"framework": "SKAdNetwork",
|
|
52
|
+
"fineValue": fineValue,
|
|
53
|
+
"coarseValue": "n/a",
|
|
54
|
+
"lockWindow": false
|
|
55
|
+
] as [String: Any])
|
|
56
|
+
} else {
|
|
57
|
+
promise.reject("ios_version_error", "SKAdNetwork requires iOS 14.0+")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if SKAN 4.0 is available (iOS 16.1+)
|
|
62
|
+
AsyncFunction("isSKAN4Available") { (promise: Promise) in
|
|
63
|
+
if #available(iOS 16.1, *) {
|
|
64
|
+
promise.resolve(true)
|
|
65
|
+
} else {
|
|
66
|
+
promise.resolve(false)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if AdAttributionKit is available (iOS 17.4+)
|
|
71
|
+
AsyncFunction("isAdAttributionKitAvailable") { (promise: Promise) in
|
|
72
|
+
if #available(iOS 17.4, *) {
|
|
73
|
+
promise.resolve(true)
|
|
74
|
+
} else {
|
|
75
|
+
promise.resolve(false)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if overlapping windows are available (iOS 18.4+)
|
|
80
|
+
AsyncFunction("isOverlappingWindowsAvailable") { (promise: Promise) in
|
|
81
|
+
if #available(iOS 18.4, *) {
|
|
82
|
+
promise.resolve(true)
|
|
83
|
+
} else {
|
|
84
|
+
promise.resolve(false)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Register for ad network attribution (supports both AdAttributionKit and SKAdNetwork)
|
|
89
|
+
AsyncFunction("registerForAttribution") { (promise: Promise) in
|
|
90
|
+
if #available(iOS 17.4, *) {
|
|
91
|
+
SKAdNetwork.updatePostbackConversionValue(0, coarseValue: .low, lockWindow: false) { error in
|
|
92
|
+
if let error = error {
|
|
93
|
+
promise.reject("attribution_error", error.localizedDescription)
|
|
94
|
+
} else {
|
|
95
|
+
promise.resolve(["framework": "AdAttributionKit", "registered": true] as [String: Any])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else if #available(iOS 14.0, *) {
|
|
99
|
+
SKAdNetwork.registerAppForAdNetworkAttribution()
|
|
100
|
+
promise.resolve(["framework": "SKAdNetwork", "registered": true] as [String: Any])
|
|
101
|
+
} else {
|
|
102
|
+
promise.reject("ios_version_error", "Attribution requires iOS 14.0+")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get attribution framework info
|
|
107
|
+
AsyncFunction("getAttributionInfo") { (promise: Promise) in
|
|
108
|
+
promise.resolve(Self.buildAttributionInfo())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Update conversion value for re-engagement (AdAttributionKit iOS 17.4+ only)
|
|
112
|
+
AsyncFunction("updateReengagementConversionValue") { (fineValue: Int, coarseValue: String, lockWindow: Bool, promise: Promise) in
|
|
113
|
+
if #available(iOS 17.4, *) {
|
|
114
|
+
guard fineValue >= 0 && fineValue <= 63 else {
|
|
115
|
+
promise.reject("invalid_value", "Conversion value must be between 0 and 63")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let coarse = Self.mapCoarseValue(coarseValue)
|
|
120
|
+
|
|
121
|
+
SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
|
|
122
|
+
if let error = error {
|
|
123
|
+
promise.reject("reengagement_error", error.localizedDescription)
|
|
124
|
+
} else {
|
|
125
|
+
promise.resolve([
|
|
126
|
+
"success": true,
|
|
127
|
+
"type": "reengagement",
|
|
128
|
+
"framework": "AdAttributionKit",
|
|
129
|
+
"fineValue": fineValue,
|
|
130
|
+
"coarseValue": coarseValue,
|
|
131
|
+
"lockWindow": lockWindow
|
|
132
|
+
] as [String: Any])
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
promise.reject("unsupported", "Re-engagement attribution requires iOS 17.4+ (AdAttributionKit)")
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// iOS 18.4+ - Check if geo-level postback data is available
|
|
141
|
+
AsyncFunction("isGeoPostbackAvailable") { (promise: Promise) in
|
|
142
|
+
if #available(iOS 18.4, *) {
|
|
143
|
+
promise.resolve(true)
|
|
144
|
+
} else {
|
|
145
|
+
promise.resolve(false)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// iOS 18.4+ - Set postback environment for testing
|
|
150
|
+
AsyncFunction("setPostbackEnvironment") { (environment: String, promise: Promise) in
|
|
151
|
+
if #available(iOS 18.4, *) {
|
|
152
|
+
let isSandbox = environment == "sandbox"
|
|
153
|
+
NSLog("[Datalyr] Postback environment set to: %@ (note: actual sandbox mode is controlled via device Developer Mode)", environment)
|
|
154
|
+
promise.resolve([
|
|
155
|
+
"environment": environment,
|
|
156
|
+
"isSandbox": isSandbox,
|
|
157
|
+
"note": "Enable Developer Mode in iOS Settings for sandbox postbacks"
|
|
158
|
+
] as [String: Any])
|
|
159
|
+
} else {
|
|
160
|
+
promise.reject("unsupported", "Development postbacks require iOS 18.4+")
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// iOS 18.4+ - Get enhanced attribution info including geo availability
|
|
165
|
+
AsyncFunction("getEnhancedAttributionInfo") { (promise: Promise) in
|
|
166
|
+
promise.resolve(Self.buildEnhancedAttributionInfo())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// iOS 18.4+ - Update postback with overlapping window support
|
|
170
|
+
AsyncFunction("updatePostbackWithWindow") { (fineValue: Int, coarseValue: String, lockWindow: Bool, windowIndex: Int, promise: Promise) in
|
|
171
|
+
guard fineValue >= 0 && fineValue <= 63 else {
|
|
172
|
+
promise.reject("invalid_value", "Conversion value must be between 0 and 63")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
guard windowIndex >= 0 && windowIndex <= 2 else {
|
|
177
|
+
promise.reject("invalid_window", "Window index must be 0, 1, or 2")
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if #available(iOS 16.1, *) {
|
|
182
|
+
let coarse = Self.mapCoarseValue(coarseValue)
|
|
183
|
+
|
|
184
|
+
SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
|
|
185
|
+
if let error = error {
|
|
186
|
+
promise.reject("postback_error", error.localizedDescription)
|
|
187
|
+
} else {
|
|
188
|
+
var framework = "SKAdNetwork"
|
|
189
|
+
var version = "4.0"
|
|
190
|
+
var overlapping = false
|
|
191
|
+
|
|
192
|
+
if #available(iOS 18.4, *) {
|
|
193
|
+
framework = "AdAttributionKit"
|
|
194
|
+
version = "2.0"
|
|
195
|
+
overlapping = true
|
|
196
|
+
} else if #available(iOS 17.4, *) {
|
|
197
|
+
framework = "AdAttributionKit"
|
|
198
|
+
version = "1.0"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
promise.resolve([
|
|
202
|
+
"success": true,
|
|
203
|
+
"framework": framework,
|
|
204
|
+
"version": version,
|
|
205
|
+
"fineValue": fineValue,
|
|
206
|
+
"coarseValue": coarseValue,
|
|
207
|
+
"lockWindow": lockWindow,
|
|
208
|
+
"windowIndex": windowIndex,
|
|
209
|
+
"overlappingWindows": overlapping,
|
|
210
|
+
] as [String: Any])
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
promise.reject("unsupported", "This method requires iOS 16.1+")
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// MARK: - Helper Methods
|
|
220
|
+
|
|
221
|
+
@available(iOS 16.1, *)
|
|
222
|
+
private static func mapCoarseValue(_ value: String) -> SKAdNetwork.CoarseConversionValue {
|
|
223
|
+
switch value {
|
|
224
|
+
case "high": return .high
|
|
225
|
+
case "medium": return .medium
|
|
226
|
+
default: return .low
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private static func buildAttributionInfo() -> [String: Any] {
|
|
231
|
+
var info: [String: Any] = [:]
|
|
232
|
+
|
|
233
|
+
if #available(iOS 17.4, *) {
|
|
234
|
+
info["framework"] = "AdAttributionKit"
|
|
235
|
+
info["version"] = "1.0"
|
|
236
|
+
info["reengagement_available"] = true
|
|
237
|
+
info["fine_value_range"] = ["min": 0, "max": 63]
|
|
238
|
+
info["coarse_values"] = ["low", "medium", "high"]
|
|
239
|
+
if #available(iOS 18.4, *) {
|
|
240
|
+
info["overlapping_windows"] = true
|
|
241
|
+
} else {
|
|
242
|
+
info["overlapping_windows"] = false
|
|
243
|
+
}
|
|
244
|
+
} else if #available(iOS 16.1, *) {
|
|
245
|
+
info["framework"] = "SKAdNetwork"
|
|
246
|
+
info["version"] = "4.0"
|
|
247
|
+
info["reengagement_available"] = false
|
|
248
|
+
info["overlapping_windows"] = false
|
|
249
|
+
info["fine_value_range"] = ["min": 0, "max": 63]
|
|
250
|
+
info["coarse_values"] = ["low", "medium", "high"]
|
|
251
|
+
} else if #available(iOS 14.0, *) {
|
|
252
|
+
info["framework"] = "SKAdNetwork"
|
|
253
|
+
info["version"] = "3.0"
|
|
254
|
+
info["reengagement_available"] = false
|
|
255
|
+
info["overlapping_windows"] = false
|
|
256
|
+
info["fine_value_range"] = ["min": 0, "max": 63]
|
|
257
|
+
info["coarse_values"] = [] as [String]
|
|
258
|
+
} else {
|
|
259
|
+
info["framework"] = "none"
|
|
260
|
+
info["version"] = "0"
|
|
261
|
+
info["reengagement_available"] = false
|
|
262
|
+
info["overlapping_windows"] = false
|
|
263
|
+
info["fine_value_range"] = ["min": 0, "max": 0]
|
|
264
|
+
info["coarse_values"] = [] as [String]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return info
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private static func buildEnhancedAttributionInfo() -> [String: Any] {
|
|
271
|
+
if #available(iOS 18.4, *) {
|
|
272
|
+
return [
|
|
273
|
+
"framework": "AdAttributionKit",
|
|
274
|
+
"version": "2.0",
|
|
275
|
+
"reengagement_available": true,
|
|
276
|
+
"overlapping_windows": true,
|
|
277
|
+
"geo_postback_available": true,
|
|
278
|
+
"development_postbacks": true,
|
|
279
|
+
"fine_value_range": ["min": 0, "max": 63],
|
|
280
|
+
"coarse_values": ["low", "medium", "high"],
|
|
281
|
+
"features": ["overlapping_windows", "geo_level_postbacks", "development_postbacks", "reengagement"]
|
|
282
|
+
] as [String: Any]
|
|
283
|
+
} else if #available(iOS 17.4, *) {
|
|
284
|
+
return [
|
|
285
|
+
"framework": "AdAttributionKit",
|
|
286
|
+
"version": "1.0",
|
|
287
|
+
"reengagement_available": true,
|
|
288
|
+
"overlapping_windows": false,
|
|
289
|
+
"geo_postback_available": false,
|
|
290
|
+
"development_postbacks": false,
|
|
291
|
+
"fine_value_range": ["min": 0, "max": 63],
|
|
292
|
+
"coarse_values": ["low", "medium", "high"],
|
|
293
|
+
"features": ["reengagement"]
|
|
294
|
+
] as [String: Any]
|
|
295
|
+
} else if #available(iOS 16.1, *) {
|
|
296
|
+
return [
|
|
297
|
+
"framework": "SKAdNetwork",
|
|
298
|
+
"version": "4.0",
|
|
299
|
+
"reengagement_available": false,
|
|
300
|
+
"overlapping_windows": false,
|
|
301
|
+
"geo_postback_available": false,
|
|
302
|
+
"development_postbacks": false,
|
|
303
|
+
"fine_value_range": ["min": 0, "max": 63],
|
|
304
|
+
"coarse_values": ["low", "medium", "high"],
|
|
305
|
+
"features": [] as [String]
|
|
306
|
+
] as [String: Any]
|
|
307
|
+
} else if #available(iOS 14.0, *) {
|
|
308
|
+
return [
|
|
309
|
+
"framework": "SKAdNetwork",
|
|
310
|
+
"version": "3.0",
|
|
311
|
+
"reengagement_available": false,
|
|
312
|
+
"overlapping_windows": false,
|
|
313
|
+
"geo_postback_available": false,
|
|
314
|
+
"development_postbacks": false,
|
|
315
|
+
"fine_value_range": ["min": 0, "max": 63],
|
|
316
|
+
"coarse_values": [] as [String],
|
|
317
|
+
"features": [] as [String]
|
|
318
|
+
] as [String: Any]
|
|
319
|
+
} else {
|
|
320
|
+
return [
|
|
321
|
+
"framework": "none",
|
|
322
|
+
"version": "0",
|
|
323
|
+
"reengagement_available": false,
|
|
324
|
+
"overlapping_windows": false,
|
|
325
|
+
"geo_postback_available": false,
|
|
326
|
+
"development_postbacks": false,
|
|
327
|
+
"fine_value_range": ["min": 0, "max": 0],
|
|
328
|
+
"coarse_values": [] as [String],
|
|
329
|
+
"features": [] as [String]
|
|
330
|
+
] as [String: Any]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -6,12 +6,28 @@
|
|
|
6
6
|
* - iOS: Meta SDK, TikTok SDK, Apple Search Ads (AdServices)
|
|
7
7
|
* - Android: Meta SDK, TikTok SDK, Play Install Referrer
|
|
8
8
|
*/
|
|
9
|
-
var _a;
|
|
9
|
+
var _a, _b;
|
|
10
10
|
import { NativeModules, Platform } from 'react-native';
|
|
11
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
11
12
|
// Native modules - available on both iOS and Android
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// iOS uses Expo Modules (new arch compatible), Android uses NativeModules (interop layer)
|
|
14
|
+
let DatalyrNative = null;
|
|
15
|
+
if (Platform.OS === 'ios') {
|
|
16
|
+
try {
|
|
17
|
+
DatalyrNative = requireNativeModule('DatalyrNative');
|
|
18
|
+
}
|
|
19
|
+
catch (_c) {
|
|
20
|
+
// Native module not available
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (Platform.OS === 'android') {
|
|
24
|
+
DatalyrNative = (_a = NativeModules.DatalyrNative) !== null && _a !== void 0 ? _a : null;
|
|
25
|
+
}
|
|
26
|
+
// Play Install Referrer - Android only (stays on NativeModules)
|
|
27
|
+
let DatalyrPlayInstallReferrer = null;
|
|
28
|
+
if (Platform.OS === 'android') {
|
|
29
|
+
DatalyrPlayInstallReferrer = (_b = NativeModules.DatalyrPlayInstallReferrer) !== null && _b !== void 0 ? _b : null;
|
|
30
|
+
}
|
|
15
31
|
/**
|
|
16
32
|
* Check if native module is available
|
|
17
33
|
*/
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
3
|
+
// SKAdNetwork is iOS-only, use Expo Modules for new arch compatibility
|
|
4
|
+
let DatalyrSKAdNetwork;
|
|
5
|
+
if (Platform.OS === 'ios') {
|
|
6
|
+
try {
|
|
7
|
+
DatalyrSKAdNetwork = requireNativeModule('DatalyrSKAdNetwork');
|
|
8
|
+
}
|
|
9
|
+
catch (_a) {
|
|
10
|
+
// Module not available
|
|
11
|
+
}
|
|
12
|
+
}
|
|
3
13
|
export class SKAdNetworkBridge {
|
|
4
14
|
/**
|
|
5
15
|
* SKAN 3.0 - Update conversion value (0-63)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalyr/react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs for iOS and Android",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"README.md",
|
|
20
20
|
"CHANGELOG.md",
|
|
21
21
|
"LICENSE",
|
|
22
|
-
"*.podspec"
|
|
22
|
+
"*.podspec",
|
|
23
|
+
"expo-module.config.json"
|
|
23
24
|
],
|
|
24
25
|
"keywords": [
|
|
25
26
|
"react-native",
|
|
@@ -57,10 +58,11 @@
|
|
|
57
58
|
"url": "https://github.com/datalyr/react-native.git"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
61
|
+
"@react-native-community/netinfo": ">=11.0.0",
|
|
62
|
+
"expo-modules-core": ">=2.0.0",
|
|
60
63
|
"react": ">=18.0.0",
|
|
61
64
|
"react-native": ">=0.72.0",
|
|
62
|
-
"react-native-device-info": ">=12.0.0"
|
|
63
|
-
"@react-native-community/netinfo": ">=11.0.0"
|
|
65
|
+
"react-native-device-info": ">=12.0.0"
|
|
64
66
|
},
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
76
78
|
"@typescript-eslint/parser": "^8.0.0",
|
|
77
79
|
"eslint": "^9.0.0",
|
|
80
|
+
"expo-modules-core": "^3.0.29",
|
|
78
81
|
"jest": "^30.0.0",
|
|
79
82
|
"typescript": "^5.7.0"
|
|
80
83
|
},
|
|
@@ -90,6 +93,9 @@
|
|
|
90
93
|
}
|
|
91
94
|
},
|
|
92
95
|
"peerDependenciesMeta": {
|
|
96
|
+
"expo-modules-core": {
|
|
97
|
+
"optional": false
|
|
98
|
+
},
|
|
93
99
|
"expo": {
|
|
94
100
|
"optional": true
|
|
95
101
|
},
|