rn-linkrunner 2.5.2 → 2.6.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/LinkrunnerSDK.podspec +1 -1
- package/ios/Podfile +1 -1
- package/ios/Pods/LinkrunnerKit/LICENSE +21 -0
- package/ios/Pods/LinkrunnerKit/README.md +15 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/HmacSignatureGenerator.swift +95 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/Linkrunner.swift +1109 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/Models.swift +356 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/RequestSigningInterceptor.swift +110 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/SHA256.swift +12 -0
- package/ios/Pods/LinkrunnerKit/Sources/Linkrunner/SKAdNetworkService.swift +428 -0
- package/ios/Pods/Pods.xcodeproj/project.pbxproj +440 -0
- package/ios/Pods/Pods.xcodeproj/xcuserdata/shofiyabootwala.xcuserdatad/xcschemes/LinkrunnerKit.xcscheme +58 -0
- package/ios/Pods/Pods.xcodeproj/xcuserdata/shofiyabootwala.xcuserdatad/xcschemes/xcschememanagement.plist +16 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit-Info.plist +26 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit-dummy.m +5 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit-prefix.pch +12 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit-umbrella.h +16 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit.debug.xcconfig +17 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit.modulemap +6 -0
- package/ios/Pods/Target Support Files/LinkrunnerKit/LinkrunnerKit.release.xcconfig +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
// Import AdAttributionKit for iOS 17.4+
|
|
5
|
+
#if canImport(AdAttributionKit)
|
|
6
|
+
import AdAttributionKit
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
/// Attribution service that handles both modern AdAttributionKit (iOS 17.4+) and legacy SKAdNetwork (iOS 14.0+)
|
|
10
|
+
/// Automatically selects the appropriate API based on iOS version availability
|
|
11
|
+
@available(iOS 14.0, *)
|
|
12
|
+
public class SKAdNetworkService {
|
|
13
|
+
public static let shared = SKAdNetworkService()
|
|
14
|
+
|
|
15
|
+
// Thread safety
|
|
16
|
+
private let serialQueue = DispatchQueue(label: "com.linkrunner.skan", qos: .utility)
|
|
17
|
+
|
|
18
|
+
// Storage keys
|
|
19
|
+
private static let LAST_CONVERSION_VALUE_KEY = "linkrunner_last_conversion_value"
|
|
20
|
+
private static let LAST_COARSE_VALUE_KEY = "linkrunner_last_coarse_value"
|
|
21
|
+
private static let LAST_LOCK_WINDOW_KEY = "linkrunner_last_lock_window"
|
|
22
|
+
private static let SKAN_REGISTER_TIMESTAMP_KEY = "linkrunner_skan_register_timestamp"
|
|
23
|
+
|
|
24
|
+
private init() {}
|
|
25
|
+
|
|
26
|
+
// MARK: - Public API
|
|
27
|
+
|
|
28
|
+
/// Register initial conversion value (0/low) on first app install
|
|
29
|
+
public func registerInitialConversionValue() async {
|
|
30
|
+
await withCheckedContinuation { continuation in
|
|
31
|
+
serialQueue.async { [weak self] in
|
|
32
|
+
guard let self = self else {
|
|
33
|
+
continuation.resume()
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if we've already registered
|
|
38
|
+
if self.hasRegisteredBefore() {
|
|
39
|
+
continuation.resume()
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#if DEBUG
|
|
44
|
+
print("LinkrunnerKit: Registering initial SKAN conversion value (0/low)")
|
|
45
|
+
#endif
|
|
46
|
+
|
|
47
|
+
self.performSKANRegistration(
|
|
48
|
+
fineValue: 0,
|
|
49
|
+
coarseValue: "low",
|
|
50
|
+
lockWindow: false,
|
|
51
|
+
source: "sdk_init"
|
|
52
|
+
) { result in
|
|
53
|
+
if result.success {
|
|
54
|
+
self.markAsRegistered()
|
|
55
|
+
self.saveLastConversionData(
|
|
56
|
+
fineValue: 0,
|
|
57
|
+
coarseValue: "low",
|
|
58
|
+
lockWindow: false
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
continuation.resume()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Update conversion value from API response
|
|
68
|
+
public func updateConversionValue(
|
|
69
|
+
fineValue: Int,
|
|
70
|
+
coarseValue: String?,
|
|
71
|
+
lockWindow: Bool,
|
|
72
|
+
source: String = "api"
|
|
73
|
+
) async -> Bool {
|
|
74
|
+
return await withCheckedContinuation { continuation in
|
|
75
|
+
serialQueue.async { [weak self] in
|
|
76
|
+
guard let self = self else {
|
|
77
|
+
continuation.resume(returning: false)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#if DEBUG
|
|
82
|
+
print("LinkrunnerKit: Processing SKAN update for \(source): fine=\(fineValue)")
|
|
83
|
+
#endif
|
|
84
|
+
|
|
85
|
+
// Check if new value is lower than last updated value
|
|
86
|
+
let lastFineValue = self.getLastConversionValue()
|
|
87
|
+
if fineValue < lastFineValue {
|
|
88
|
+
#if DEBUG
|
|
89
|
+
print("LinkrunnerKit: New conversion value (\(fineValue)) is lower than last value (\(lastFineValue)), skipping update for \(source)")
|
|
90
|
+
#endif
|
|
91
|
+
continuation.resume(returning: false)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if this exact same value is already the current value
|
|
96
|
+
if fineValue == lastFineValue {
|
|
97
|
+
let lastCoarseValue = self.getLastCoarseValue()
|
|
98
|
+
let lastLockWindow = self.getLastLockWindow()
|
|
99
|
+
if coarseValue == lastCoarseValue && lockWindow == lastLockWindow {
|
|
100
|
+
#if DEBUG
|
|
101
|
+
print("LinkrunnerKit: Same conversion value (\(fineValue)) already set, skipping update for \(source)")
|
|
102
|
+
#endif
|
|
103
|
+
continuation.resume(returning: true) // Return true since it's already at the desired state
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#if DEBUG
|
|
109
|
+
print("LinkrunnerKit: Executing SKAN update for \(source): fine=\(fineValue), coarse=\(coarseValue ?? "nil"), lock=\(lockWindow)")
|
|
110
|
+
#endif
|
|
111
|
+
|
|
112
|
+
self.performSKANUpdate(
|
|
113
|
+
fineValue: fineValue,
|
|
114
|
+
coarseValue: coarseValue,
|
|
115
|
+
lockWindow: lockWindow,
|
|
116
|
+
source: source
|
|
117
|
+
) { result in
|
|
118
|
+
if result.success {
|
|
119
|
+
self.saveLastConversionData(
|
|
120
|
+
fineValue: fineValue,
|
|
121
|
+
coarseValue: coarseValue,
|
|
122
|
+
lockWindow: lockWindow
|
|
123
|
+
)
|
|
124
|
+
#if DEBUG
|
|
125
|
+
print("LinkrunnerKit: Successfully updated SKAN conversion value for \(source)")
|
|
126
|
+
#endif
|
|
127
|
+
} else {
|
|
128
|
+
#if DEBUG
|
|
129
|
+
print("LinkrunnerKit: Failed to update SKAN conversion value for \(source): \(result.error ?? "Unknown error")")
|
|
130
|
+
#endif
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
continuation.resume(returning: result.success)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MARK: - Private Implementation
|
|
140
|
+
|
|
141
|
+
private func hasRegisteredBefore() -> Bool {
|
|
142
|
+
return UserDefaults.standard.object(forKey: SKAdNetworkService.SKAN_REGISTER_TIMESTAMP_KEY) != nil
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private func markAsRegistered() {
|
|
146
|
+
UserDefaults.standard.set(Date(), forKey: SKAdNetworkService.SKAN_REGISTER_TIMESTAMP_KEY)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private func getLastConversionValue() -> Int {
|
|
150
|
+
return UserDefaults.standard.integer(forKey: SKAdNetworkService.LAST_CONVERSION_VALUE_KEY)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func getLastCoarseValue() -> String? {
|
|
154
|
+
return UserDefaults.standard.string(forKey: SKAdNetworkService.LAST_COARSE_VALUE_KEY)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private func getLastLockWindow() -> Bool {
|
|
158
|
+
return UserDefaults.standard.bool(forKey: SKAdNetworkService.LAST_LOCK_WINDOW_KEY)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func saveLastConversionData(fineValue: Int, coarseValue: String?, lockWindow: Bool) {
|
|
162
|
+
UserDefaults.standard.set(fineValue, forKey: SKAdNetworkService.LAST_CONVERSION_VALUE_KEY)
|
|
163
|
+
if let coarseValue = coarseValue {
|
|
164
|
+
UserDefaults.standard.set(coarseValue, forKey: SKAdNetworkService.LAST_COARSE_VALUE_KEY)
|
|
165
|
+
}
|
|
166
|
+
UserDefaults.standard.set(lockWindow, forKey: SKAdNetworkService.LAST_LOCK_WINDOW_KEY)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// MARK: - SKAdNetwork API Calls
|
|
170
|
+
|
|
171
|
+
private func performSKANRegistration(
|
|
172
|
+
fineValue: Int,
|
|
173
|
+
coarseValue: String,
|
|
174
|
+
lockWindow: Bool,
|
|
175
|
+
source: String,
|
|
176
|
+
completion: @escaping (SKANResult) -> Void
|
|
177
|
+
) {
|
|
178
|
+
#if canImport(AdAttributionKit)
|
|
179
|
+
if #available(iOS 17.4, *) {
|
|
180
|
+
// iOS 17.4+ use AdAttributionKit for registration
|
|
181
|
+
self.updateWithAdAttributionKit(
|
|
182
|
+
fineValue: fineValue,
|
|
183
|
+
coarseValue: coarseValue,
|
|
184
|
+
lockWindow: lockWindow,
|
|
185
|
+
completion: completion
|
|
186
|
+
)
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
#endif
|
|
190
|
+
|
|
191
|
+
if #available(iOS 16.1, *) {
|
|
192
|
+
// iOS 16.1+ supports coarse value and lock window in registration
|
|
193
|
+
self.registerWithCoarseValue(
|
|
194
|
+
fineValue: fineValue,
|
|
195
|
+
coarseValue: coarseValue,
|
|
196
|
+
lockWindow: lockWindow,
|
|
197
|
+
completion: completion
|
|
198
|
+
)
|
|
199
|
+
} else if #available(iOS 15.4, *) {
|
|
200
|
+
// iOS 15.4+ supports completion handler
|
|
201
|
+
self.registerWithCompletionHandler(
|
|
202
|
+
fineValue: fineValue,
|
|
203
|
+
completion: completion
|
|
204
|
+
)
|
|
205
|
+
} else {
|
|
206
|
+
// iOS 14.0+ basic registration
|
|
207
|
+
self.registerBasic(completion: completion)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private func performSKANUpdate(
|
|
212
|
+
fineValue: Int,
|
|
213
|
+
coarseValue: String?,
|
|
214
|
+
lockWindow: Bool,
|
|
215
|
+
source: String,
|
|
216
|
+
completion: @escaping (SKANResult) -> Void
|
|
217
|
+
) {
|
|
218
|
+
#if canImport(AdAttributionKit)
|
|
219
|
+
if #available(iOS 17.4, *) {
|
|
220
|
+
// iOS 17.4+ use AdAttributionKit (modern approach)
|
|
221
|
+
self.updateWithAdAttributionKit(
|
|
222
|
+
fineValue: fineValue,
|
|
223
|
+
coarseValue: coarseValue,
|
|
224
|
+
lockWindow: lockWindow,
|
|
225
|
+
completion: completion
|
|
226
|
+
)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
#endif
|
|
230
|
+
|
|
231
|
+
if #available(iOS 16.1, *) {
|
|
232
|
+
// iOS 16.1+ supports all parameters with SKAdNetwork
|
|
233
|
+
if let coarseValue = coarseValue {
|
|
234
|
+
self.updateWithCoarseValue(
|
|
235
|
+
fineValue: fineValue,
|
|
236
|
+
coarseValue: coarseValue,
|
|
237
|
+
lockWindow: lockWindow,
|
|
238
|
+
completion: completion
|
|
239
|
+
)
|
|
240
|
+
} else {
|
|
241
|
+
self.updateWithCompletionHandler(
|
|
242
|
+
fineValue: fineValue,
|
|
243
|
+
completion: completion
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
} else if #available(iOS 15.4, *) {
|
|
247
|
+
// iOS 15.4+ supports completion handler
|
|
248
|
+
self.updateWithCompletionHandler(
|
|
249
|
+
fineValue: fineValue,
|
|
250
|
+
completion: completion
|
|
251
|
+
)
|
|
252
|
+
} else {
|
|
253
|
+
// iOS 14.0+ basic update
|
|
254
|
+
self.updateBasic(fineValue: fineValue, completion: completion)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// MARK: - iOS Version-Specific Implementations
|
|
259
|
+
|
|
260
|
+
@available(iOS 16.1, *)
|
|
261
|
+
private func registerWithCoarseValue(
|
|
262
|
+
fineValue: Int,
|
|
263
|
+
coarseValue: String,
|
|
264
|
+
lockWindow: Bool,
|
|
265
|
+
completion: @escaping (SKANResult) -> Void
|
|
266
|
+
) {
|
|
267
|
+
guard let skanCoarseValue = mapCoarseValue(coarseValue) else {
|
|
268
|
+
completion(SKANResult(success: false, error: "Invalid coarse value: \(coarseValue)"))
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
SKAdNetwork.updatePostbackConversionValue(
|
|
273
|
+
fineValue,
|
|
274
|
+
coarseValue: skanCoarseValue,
|
|
275
|
+
lockWindow: lockWindow
|
|
276
|
+
) { error in
|
|
277
|
+
if let error = error {
|
|
278
|
+
completion(SKANResult(success: false, error: error.localizedDescription))
|
|
279
|
+
} else {
|
|
280
|
+
completion(SKANResult(success: true, error: nil))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@available(iOS 16.1, *)
|
|
286
|
+
private func updateWithCoarseValue(
|
|
287
|
+
fineValue: Int,
|
|
288
|
+
coarseValue: String,
|
|
289
|
+
lockWindow: Bool,
|
|
290
|
+
completion: @escaping (SKANResult) -> Void
|
|
291
|
+
) {
|
|
292
|
+
guard let skanCoarseValue = mapCoarseValue(coarseValue) else {
|
|
293
|
+
completion(SKANResult(success: false, error: "Invalid coarse value: \(coarseValue)"))
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
SKAdNetwork.updatePostbackConversionValue(
|
|
298
|
+
fineValue,
|
|
299
|
+
coarseValue: skanCoarseValue,
|
|
300
|
+
lockWindow: lockWindow
|
|
301
|
+
) { error in
|
|
302
|
+
if let error = error {
|
|
303
|
+
completion(SKANResult(success: false, error: error.localizedDescription))
|
|
304
|
+
} else {
|
|
305
|
+
completion(SKANResult(success: true, error: nil))
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@available(iOS 15.4, *)
|
|
311
|
+
private func registerWithCompletionHandler(
|
|
312
|
+
fineValue: Int,
|
|
313
|
+
completion: @escaping (SKANResult) -> Void
|
|
314
|
+
) {
|
|
315
|
+
SKAdNetwork.updatePostbackConversionValue(fineValue) { error in
|
|
316
|
+
if let error = error {
|
|
317
|
+
completion(SKANResult(success: false, error: error.localizedDescription))
|
|
318
|
+
} else {
|
|
319
|
+
completion(SKANResult(success: true, error: nil))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@available(iOS 15.4, *)
|
|
325
|
+
private func updateWithCompletionHandler(
|
|
326
|
+
fineValue: Int,
|
|
327
|
+
completion: @escaping (SKANResult) -> Void
|
|
328
|
+
) {
|
|
329
|
+
SKAdNetwork.updatePostbackConversionValue(fineValue) { error in
|
|
330
|
+
if let error = error {
|
|
331
|
+
completion(SKANResult(success: false, error: error.localizedDescription))
|
|
332
|
+
} else {
|
|
333
|
+
completion(SKANResult(success: true, error: nil))
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@available(iOS 14.0, *)
|
|
339
|
+
private func registerBasic(completion: @escaping (SKANResult) -> Void) {
|
|
340
|
+
SKAdNetwork.registerAppForAdNetworkAttribution()
|
|
341
|
+
// Basic registration doesn't provide completion callback
|
|
342
|
+
completion(SKANResult(success: true, error: nil))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@available(iOS 14.0, *)
|
|
346
|
+
private func updateBasic(fineValue: Int, completion: @escaping (SKANResult) -> Void) {
|
|
347
|
+
SKAdNetwork.updateConversionValue(fineValue)
|
|
348
|
+
// Basic update doesn't provide completion callback
|
|
349
|
+
completion(SKANResult(success: true, error: nil))
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// MARK: - AdAttributionKit Implementation (iOS 17.4+)
|
|
353
|
+
|
|
354
|
+
#if canImport(AdAttributionKit)
|
|
355
|
+
@available(iOS 17.4, *)
|
|
356
|
+
private func updateWithAdAttributionKit(
|
|
357
|
+
fineValue: Int,
|
|
358
|
+
coarseValue: String?,
|
|
359
|
+
lockWindow: Bool,
|
|
360
|
+
completion: @escaping (SKANResult) -> Void
|
|
361
|
+
) {
|
|
362
|
+
Task {
|
|
363
|
+
do {
|
|
364
|
+
// Map coarse value for AdAttributionKit
|
|
365
|
+
var adKitCoarseValue: AdAttributionKit.CoarseConversionValue?
|
|
366
|
+
if let coarseValue = coarseValue {
|
|
367
|
+
adKitCoarseValue = self.mapAdKitCoarseValue(coarseValue)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#if DEBUG
|
|
371
|
+
print("LinkrunnerKit: Using AdAttributionKit to update conversion value: fine=\(fineValue), coarse=\(coarseValue ?? "nil"), lock=\(lockWindow)")
|
|
372
|
+
#endif
|
|
373
|
+
|
|
374
|
+
// Update conversion value using AdAttributionKit
|
|
375
|
+
try await Postback.updateConversionValue(
|
|
376
|
+
fineValue,
|
|
377
|
+
coarseConversionValue: adKitCoarseValue ?? .low,
|
|
378
|
+
lockPostback: lockWindow
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
completion(SKANResult(success: true, error: nil))
|
|
382
|
+
} catch {
|
|
383
|
+
#if DEBUG
|
|
384
|
+
print("LinkrunnerKit: AdAttributionKit update failed: \(error.localizedDescription)")
|
|
385
|
+
#endif
|
|
386
|
+
completion(SKANResult(success: false, error: error.localizedDescription))
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@available(iOS 17.4, *)
|
|
392
|
+
private func mapAdKitCoarseValue(_ value: String) -> AdAttributionKit.CoarseConversionValue {
|
|
393
|
+
switch value.lowercased() {
|
|
394
|
+
case "low":
|
|
395
|
+
return .low
|
|
396
|
+
case "medium":
|
|
397
|
+
return .medium
|
|
398
|
+
case "high":
|
|
399
|
+
return .high
|
|
400
|
+
default:
|
|
401
|
+
return .low
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
#endif
|
|
405
|
+
|
|
406
|
+
// MARK: - Helper Methods
|
|
407
|
+
|
|
408
|
+
@available(iOS 16.1, *)
|
|
409
|
+
private func mapCoarseValue(_ value: String) -> SKAdNetwork.CoarseConversionValue? {
|
|
410
|
+
switch value.lowercased() {
|
|
411
|
+
case "low":
|
|
412
|
+
return .low
|
|
413
|
+
case "medium":
|
|
414
|
+
return .medium
|
|
415
|
+
case "high":
|
|
416
|
+
return .high
|
|
417
|
+
default:
|
|
418
|
+
return nil
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// MARK: - Result Types
|
|
424
|
+
|
|
425
|
+
struct SKANResult {
|
|
426
|
+
let success: Bool
|
|
427
|
+
let error: String?
|
|
428
|
+
}
|