@datalyr/react-native 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/datalyr-react-native.podspec +1 -1
- package/expo-module.config.json +6 -0
- package/ios/DatalyrNativeModule.swift +222 -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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { NativeModules, Platform } from 'react-native';
|
|
11
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Apple Search Ads attribution data returned from AdServices API (iOS only)
|
|
@@ -107,11 +108,23 @@ interface PlayInstallReferrerModule {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// Native modules - available on both iOS and Android
|
|
110
|
-
|
|
111
|
+
// iOS uses Expo Modules (new arch compatible), Android uses NativeModules (interop layer)
|
|
112
|
+
let DatalyrNative: DatalyrNativeModule | null = null;
|
|
113
|
+
if (Platform.OS === 'ios') {
|
|
114
|
+
try {
|
|
115
|
+
DatalyrNative = requireNativeModule<DatalyrNativeModule>('DatalyrNative');
|
|
116
|
+
} catch {
|
|
117
|
+
// Native module not available
|
|
118
|
+
}
|
|
119
|
+
} else if (Platform.OS === 'android') {
|
|
120
|
+
DatalyrNative = NativeModules.DatalyrNative ?? null;
|
|
121
|
+
}
|
|
111
122
|
|
|
112
|
-
// Play Install Referrer - Android only
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
// Play Install Referrer - Android only (stays on NativeModules)
|
|
124
|
+
let DatalyrPlayInstallReferrer: PlayInstallReferrerModule | null = null;
|
|
125
|
+
if (Platform.OS === 'android') {
|
|
126
|
+
DatalyrPlayInstallReferrer = NativeModules.DatalyrPlayInstallReferrer ?? null;
|
|
127
|
+
}
|
|
115
128
|
|
|
116
129
|
/**
|
|
117
130
|
* Check if native module is available
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
3
|
|
|
3
4
|
// SKAN 4.0 / AdAttributionKit coarse value type
|
|
4
5
|
export type SKANCoarseValue = 'low' | 'medium' | 'high';
|
|
@@ -87,9 +88,15 @@ interface SKAdNetworkModule {
|
|
|
87
88
|
): Promise<OverlappingWindowPostbackResponse>;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// SKAdNetwork is iOS-only, use Expo Modules for new arch compatibility
|
|
92
|
+
let DatalyrSKAdNetwork: SKAdNetworkModule | undefined;
|
|
93
|
+
if (Platform.OS === 'ios') {
|
|
94
|
+
try {
|
|
95
|
+
DatalyrSKAdNetwork = requireNativeModule<SKAdNetworkModule>('DatalyrSKAdNetwork');
|
|
96
|
+
} catch {
|
|
97
|
+
// Module not available
|
|
98
|
+
}
|
|
99
|
+
}
|
|
93
100
|
|
|
94
101
|
export class SKAdNetworkBridge {
|
|
95
102
|
private static _isSKAN4Available: boolean | null = null;
|
package/ios/DatalyrNative.m
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#import <React/RCTBridgeModule.h>
|
|
2
|
-
|
|
3
|
-
@interface RCT_EXTERN_MODULE(DatalyrNative, NSObject)
|
|
4
|
-
|
|
5
|
-
// Meta SDK Methods
|
|
6
|
-
RCT_EXTERN_METHOD(initializeMetaSDK:(NSString *)appId
|
|
7
|
-
clientToken:(NSString *)clientToken
|
|
8
|
-
advertiserTrackingEnabled:(BOOL)advertiserTrackingEnabled
|
|
9
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
10
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
11
|
-
|
|
12
|
-
RCT_EXTERN_METHOD(fetchDeferredAppLink:(RCTPromiseResolveBlock)resolve
|
|
13
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
14
|
-
|
|
15
|
-
RCT_EXTERN_METHOD(logMetaEvent:(NSString *)eventName
|
|
16
|
-
valueToSum:(NSNumber *)valueToSum
|
|
17
|
-
parameters:(NSDictionary *)parameters
|
|
18
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
19
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
20
|
-
|
|
21
|
-
RCT_EXTERN_METHOD(logMetaPurchase:(double)amount
|
|
22
|
-
currency:(NSString *)currency
|
|
23
|
-
parameters:(NSDictionary *)parameters
|
|
24
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
25
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
26
|
-
|
|
27
|
-
RCT_EXTERN_METHOD(setMetaUserData:(NSDictionary *)userData
|
|
28
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
29
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
30
|
-
|
|
31
|
-
RCT_EXTERN_METHOD(clearMetaUserData:(RCTPromiseResolveBlock)resolve
|
|
32
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
33
|
-
|
|
34
|
-
RCT_EXTERN_METHOD(updateMetaTrackingAuthorization:(BOOL)enabled
|
|
35
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
36
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
37
|
-
|
|
38
|
-
// TikTok SDK Methods
|
|
39
|
-
RCT_EXTERN_METHOD(initializeTikTokSDK:(NSString *)appId
|
|
40
|
-
tiktokAppId:(NSString *)tiktokAppId
|
|
41
|
-
accessToken:(NSString *)accessToken
|
|
42
|
-
debug:(BOOL)debug
|
|
43
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
44
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
45
|
-
|
|
46
|
-
RCT_EXTERN_METHOD(trackTikTokEvent:(NSString *)eventName
|
|
47
|
-
eventId:(NSString *)eventId
|
|
48
|
-
properties:(NSDictionary *)properties
|
|
49
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
50
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
51
|
-
|
|
52
|
-
RCT_EXTERN_METHOD(identifyTikTokUser:(NSString *)externalId
|
|
53
|
-
externalUserName:(NSString *)externalUserName
|
|
54
|
-
phoneNumber:(NSString *)phoneNumber
|
|
55
|
-
email:(NSString *)email
|
|
56
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
57
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
58
|
-
|
|
59
|
-
RCT_EXTERN_METHOD(logoutTikTok:(RCTPromiseResolveBlock)resolve
|
|
60
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
61
|
-
|
|
62
|
-
RCT_EXTERN_METHOD(updateTikTokTrackingAuthorization:(BOOL)enabled
|
|
63
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
64
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
65
|
-
|
|
66
|
-
// SDK Availability Check
|
|
67
|
-
RCT_EXTERN_METHOD(getSDKAvailability:(RCTPromiseResolveBlock)resolve
|
|
68
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
69
|
-
|
|
70
|
-
// Apple Search Ads Attribution
|
|
71
|
-
RCT_EXTERN_METHOD(getAppleSearchAdsAttribution:(RCTPromiseResolveBlock)resolve
|
|
72
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
73
|
-
|
|
74
|
-
@end
|
package/ios/DatalyrNative.swift
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import FBSDKCoreKit
|
|
3
|
-
import TikTokBusinessSDK
|
|
4
|
-
import AdServices
|
|
5
|
-
|
|
6
|
-
@objc(DatalyrNative)
|
|
7
|
-
class DatalyrNative: NSObject {
|
|
8
|
-
|
|
9
|
-
@objc static func requiresMainQueueSetup() -> Bool {
|
|
10
|
-
return false
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// MARK: - Meta (Facebook) SDK Methods
|
|
14
|
-
|
|
15
|
-
@objc func initializeMetaSDK(
|
|
16
|
-
_ appId: String,
|
|
17
|
-
clientToken: String?,
|
|
18
|
-
advertiserTrackingEnabled: Bool,
|
|
19
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
20
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
21
|
-
) {
|
|
22
|
-
DispatchQueue.main.async {
|
|
23
|
-
Settings.shared.appID = appId
|
|
24
|
-
|
|
25
|
-
if let token = clientToken, !token.isEmpty {
|
|
26
|
-
Settings.shared.clientToken = token
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
Settings.shared.isAdvertiserTrackingEnabled = advertiserTrackingEnabled
|
|
30
|
-
Settings.shared.isAdvertiserIDCollectionEnabled = advertiserTrackingEnabled
|
|
31
|
-
|
|
32
|
-
ApplicationDelegate.shared.application(
|
|
33
|
-
UIApplication.shared,
|
|
34
|
-
didFinishLaunchingWithOptions: nil
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
resolve(true)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@objc func fetchDeferredAppLink(
|
|
42
|
-
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
43
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
44
|
-
) {
|
|
45
|
-
AppLinkUtility.fetchDeferredAppLink { url, error in
|
|
46
|
-
if error != nil {
|
|
47
|
-
// Don't reject - deferred deep link not available is expected in many cases
|
|
48
|
-
// Error is normal when no deferred link exists
|
|
49
|
-
resolve(nil)
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if let url = url {
|
|
54
|
-
resolve(url.absoluteString)
|
|
55
|
-
} else {
|
|
56
|
-
resolve(nil)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@objc func logMetaEvent(
|
|
62
|
-
_ eventName: String,
|
|
63
|
-
valueToSum: NSNumber?,
|
|
64
|
-
parameters: NSDictionary?,
|
|
65
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
66
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
67
|
-
) {
|
|
68
|
-
var params: [AppEvents.ParameterName: Any] = [:]
|
|
69
|
-
|
|
70
|
-
if let dict = parameters as? [String: Any] {
|
|
71
|
-
for (key, value) in dict {
|
|
72
|
-
params[AppEvents.ParameterName(key)] = value
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if let value = valueToSum?.doubleValue {
|
|
77
|
-
AppEvents.shared.logEvent(AppEvents.Name(eventName), valueToSum: value, parameters: params)
|
|
78
|
-
} else if params.isEmpty {
|
|
79
|
-
AppEvents.shared.logEvent(AppEvents.Name(eventName))
|
|
80
|
-
} else {
|
|
81
|
-
AppEvents.shared.logEvent(AppEvents.Name(eventName), parameters: params)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
resolve(true)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@objc func logMetaPurchase(
|
|
88
|
-
_ amount: Double,
|
|
89
|
-
currency: String,
|
|
90
|
-
parameters: NSDictionary?,
|
|
91
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
92
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
93
|
-
) {
|
|
94
|
-
var params: [AppEvents.ParameterName: Any] = [:]
|
|
95
|
-
|
|
96
|
-
if let dict = parameters as? [String: Any] {
|
|
97
|
-
for (key, value) in dict {
|
|
98
|
-
params[AppEvents.ParameterName(key)] = value
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
|
|
103
|
-
resolve(true)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@objc func setMetaUserData(
|
|
107
|
-
_ userData: NSDictionary,
|
|
108
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
109
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
110
|
-
) {
|
|
111
|
-
AppEvents.shared.setUserData(
|
|
112
|
-
userData["email"] as? String,
|
|
113
|
-
forType: .email
|
|
114
|
-
)
|
|
115
|
-
AppEvents.shared.setUserData(
|
|
116
|
-
userData["firstName"] as? String,
|
|
117
|
-
forType: .firstName
|
|
118
|
-
)
|
|
119
|
-
AppEvents.shared.setUserData(
|
|
120
|
-
userData["lastName"] as? String,
|
|
121
|
-
forType: .lastName
|
|
122
|
-
)
|
|
123
|
-
AppEvents.shared.setUserData(
|
|
124
|
-
userData["phone"] as? String,
|
|
125
|
-
forType: .phone
|
|
126
|
-
)
|
|
127
|
-
AppEvents.shared.setUserData(
|
|
128
|
-
userData["dateOfBirth"] as? String,
|
|
129
|
-
forType: .dateOfBirth
|
|
130
|
-
)
|
|
131
|
-
AppEvents.shared.setUserData(
|
|
132
|
-
userData["gender"] as? String,
|
|
133
|
-
forType: .gender
|
|
134
|
-
)
|
|
135
|
-
AppEvents.shared.setUserData(
|
|
136
|
-
userData["city"] as? String,
|
|
137
|
-
forType: .city
|
|
138
|
-
)
|
|
139
|
-
AppEvents.shared.setUserData(
|
|
140
|
-
userData["state"] as? String,
|
|
141
|
-
forType: .state
|
|
142
|
-
)
|
|
143
|
-
AppEvents.shared.setUserData(
|
|
144
|
-
userData["zip"] as? String,
|
|
145
|
-
forType: .zip
|
|
146
|
-
)
|
|
147
|
-
AppEvents.shared.setUserData(
|
|
148
|
-
userData["country"] as? String,
|
|
149
|
-
forType: .country
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
resolve(true)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@objc func clearMetaUserData(
|
|
156
|
-
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
157
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
158
|
-
) {
|
|
159
|
-
AppEvents.shared.clearUserData()
|
|
160
|
-
resolve(true)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
@objc func updateMetaTrackingAuthorization(
|
|
164
|
-
_ enabled: Bool,
|
|
165
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
166
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
167
|
-
) {
|
|
168
|
-
Settings.shared.isAdvertiserTrackingEnabled = enabled
|
|
169
|
-
Settings.shared.isAdvertiserIDCollectionEnabled = enabled
|
|
170
|
-
resolve(true)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// MARK: - TikTok SDK Methods
|
|
174
|
-
|
|
175
|
-
@objc func initializeTikTokSDK(
|
|
176
|
-
_ appId: String,
|
|
177
|
-
tiktokAppId: String,
|
|
178
|
-
accessToken: String?,
|
|
179
|
-
debug: Bool,
|
|
180
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
181
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
182
|
-
) {
|
|
183
|
-
DispatchQueue.main.async {
|
|
184
|
-
let config = TikTokConfig(appId: appId, tiktokAppId: tiktokAppId)
|
|
185
|
-
|
|
186
|
-
if let token = accessToken, !token.isEmpty {
|
|
187
|
-
config?.accessToken = token
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if debug {
|
|
191
|
-
config?.setLogLevel(.debug)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if let validConfig = config {
|
|
195
|
-
TikTokBusiness.initializeSdk(validConfig)
|
|
196
|
-
resolve(true)
|
|
197
|
-
} else {
|
|
198
|
-
reject("tiktok_init_error", "Failed to create TikTok config", nil)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@objc func trackTikTokEvent(
|
|
204
|
-
_ eventName: String,
|
|
205
|
-
eventId: String?,
|
|
206
|
-
properties: NSDictionary?,
|
|
207
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
208
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
209
|
-
) {
|
|
210
|
-
// Use TikTokBaseEvent for modern API (trackEvent methods are deprecated)
|
|
211
|
-
let event: TikTokBaseEvent
|
|
212
|
-
|
|
213
|
-
if let eid = eventId, !eid.isEmpty {
|
|
214
|
-
event = TikTokBaseEvent(eventName: eventName, eventId: eid)
|
|
215
|
-
} else {
|
|
216
|
-
event = TikTokBaseEvent(eventName: eventName)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Add properties to the event
|
|
220
|
-
if let dict = properties as? [String: Any] {
|
|
221
|
-
for (key, value) in dict {
|
|
222
|
-
event.addProperty(withKey: key, value: value)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
TikTokBusiness.trackTTEvent(event)
|
|
227
|
-
resolve(true)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
@objc func identifyTikTokUser(
|
|
231
|
-
_ externalId: String,
|
|
232
|
-
externalUserName: String,
|
|
233
|
-
phoneNumber: String,
|
|
234
|
-
email: String,
|
|
235
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
236
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
237
|
-
) {
|
|
238
|
-
// Method signature: identifyWithExternalID:externalUserName:phoneNumber:email:
|
|
239
|
-
TikTokBusiness.identify(
|
|
240
|
-
withExternalID: externalId.isEmpty ? nil : externalId,
|
|
241
|
-
externalUserName: externalUserName.isEmpty ? nil : externalUserName,
|
|
242
|
-
phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
|
|
243
|
-
email: email.isEmpty ? nil : email
|
|
244
|
-
)
|
|
245
|
-
resolve(true)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
@objc func logoutTikTok(
|
|
249
|
-
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
250
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
251
|
-
) {
|
|
252
|
-
TikTokBusiness.logout()
|
|
253
|
-
resolve(true)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
@objc func updateTikTokTrackingAuthorization(
|
|
257
|
-
_ enabled: Bool,
|
|
258
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
259
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
260
|
-
) {
|
|
261
|
-
// TikTok SDK handles ATT automatically, but we track the change
|
|
262
|
-
resolve(true)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// MARK: - SDK Availability Check
|
|
266
|
-
|
|
267
|
-
@objc func getSDKAvailability(
|
|
268
|
-
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
269
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
270
|
-
) {
|
|
271
|
-
resolve([
|
|
272
|
-
"meta": true,
|
|
273
|
-
"tiktok": true,
|
|
274
|
-
"appleSearchAds": true
|
|
275
|
-
])
|
|
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
|
-
}
|
|
332
|
-
}
|