Skip to content

Commit 8b0a05f

Browse files
authored
Feature/3.4.0 (#118)
* various bug fixes and internal improvements
1 parent eb19a5d commit 8b0a05f

18 files changed

+145
-72
lines changed

ApphudSDK.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'ApphudSDK'
3-
s.version = '3.3.8'
3+
s.version = '3.4.0'
44
s.summary = 'Build and Measure In-App Subscriptions on iOS.'
55
s.description = 'Apphud covers every aspect when it comes to In-App Subscriptions from integration to analytics on iOS and Android.'
66
s.homepage = 'https://github.com/apphud/ApphudSDK'

Examples/ApphudDemoSwift/ApphudSDKDemo/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
2222

2323
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2424

25-
Apphud.start(apiKey: "app_4sY9cLggXpMDDQMmvc5wXUPGReMp8G")
26-
// Apphud.setDeviceIdentifiers(idfa: nil, idfv: UIDevice.current.identifierForVendor?.uuidString)
25+
Apphud.start(apiKey: "YOUR_API_KEY")
26+
Apphud.setDeviceIdentifiers(idfa: nil, idfv: UIDevice.current.identifierForVendor?.uuidString)
2727
fetchIDFA()
2828

2929
/** Custom User Properties Examples */

Sources/Internal/ApphudAsyncStoreKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ internal class ApphudAsyncStoreKit {
9090

9191
do {
9292

93-
ApphudLoggerService.shared.paywallCheckoutInitiated(paywallId: apphudProduct?.paywallId, placementId: apphudProduct?.placementId, productId: product.id)
93+
ApphudLoggerService.shared.paywallCheckoutInitiated(apphudProduct: apphudProduct, productId: product.id)
9494
#if os(iOS) || os(tvOS) || os(macOS) || os(watchOS)
9595
let result = try await product.purchase(options: options)
9696
#else

Sources/Internal/ApphudHttpClient.swift

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ internal struct ApphudAPIArrayResponse<T: Decodable>: Decodable {
2525
}
2626

2727
typealias ApphudParsedResponse = (Bool, [String: Any]?, Data?, Error?, Int)
28-
typealias ApphudHTTPResponse = (Bool, [String: Any]?, Data?, Error?, Int, Double)
29-
typealias ApphudHTTPResponseCallback = (Bool, [String: Any]?, Data?, Error?, Int, Double) -> Void
28+
typealias ApphudHTTPResponse = (Bool, [String: Any]?, Data?, Error?, Int, Double, Int)
29+
typealias ApphudHTTPResponseCallback = (Bool, [String: Any]?, Data?, Error?, Int, Double, Int) -> Void
3030
typealias ApphudStringCallback = (String?, Error?) -> Void
3131
/**
3232
This is Apphud's internal class.
@@ -111,8 +111,8 @@ public class ApphudHttpClient {
111111
return URLSession.init(configuration: config)
112112
}()
113113

114-
private let GET_TIMEOUT: TimeInterval = 10.0
115-
public var POST_CUSTOMERS_TIMEOUT: TimeInterval = 10.0
114+
private let GET_TIMEOUT: TimeInterval = 7.0
115+
public var POST_CUSTOMERS_TIMEOUT: TimeInterval = 7.0
116116
private let POST_TIMEOUT: TimeInterval = 20.0
117117

118118
internal func requestInstance(url: URL) -> URLRequest? {
@@ -129,11 +129,11 @@ public class ApphudHttpClient {
129129
}
130130
}
131131

132-
internal func startRequest(path: ApphudEndpoint, apiVersion: ApphudApiVersion = .APIV1, params: [String: Any]?, method: ApphudHttpMethod, useDecoder: Bool = false, retry: Bool = false, callback: ApphudHTTPResponseCallback?) {
132+
internal func startRequest(path: ApphudEndpoint, apiVersion: ApphudApiVersion = .APIV1, params: [String: Any]?, method: ApphudHttpMethod, useDecoder: Bool = false, retry: Bool = false, requestID: String? = nil, callback: ApphudHTTPResponseCallback?) {
133133

134134
let timeout = path == .customers ? POST_CUSTOMERS_TIMEOUT : nil
135135

136-
if let request = makeRequest(path: path.value, apiVersion: apiVersion, params: params, method: method, defaultTimeout: timeout), !suspended {
136+
if let request = makeRequest(path: path.value, apiVersion: apiVersion, params: params, method: method, defaultTimeout: timeout, requestID: requestID), !suspended {
137137
Task(priority: .userInitiated) {
138138

139139
let retries: Int
@@ -150,7 +150,7 @@ public class ApphudHttpClient {
150150
let response = await start(request: request, useDecoder: useDecoder, retries: retries, delay: retryDelay)
151151

152152
Task { @MainActor in
153-
callback?(response.0, response.1, response.2, response.3, response.4, response.5)
153+
callback?(response.0, response.1, response.2, response.3, response.4, response.5, response.6)
154154
}
155155
}
156156
} else {
@@ -226,11 +226,7 @@ public class ApphudHttpClient {
226226
return nil
227227
}
228228

229-
private var requestID: String {
230-
UUID().uuidString.lowercased().replacingOccurrences(of: "-", with: "") + "_" + String(Date().timeIntervalSince1970)
231-
}
232-
233-
private func makeRequest(path: String, apiVersion: ApphudApiVersion, params: [String: Any]?, method: ApphudHttpMethod, defaultTimeout: TimeInterval? = nil) -> URLRequest? {
229+
private func makeRequest(path: String, apiVersion: ApphudApiVersion, params: [String: Any]?, method: ApphudHttpMethod, defaultTimeout: TimeInterval? = nil, requestID: String? = nil) -> URLRequest? {
234230

235231
var request: URLRequest?
236232

@@ -240,7 +236,7 @@ public class ApphudHttpClient {
240236

241237
if method == .get {
242238
var components = URLComponents(string: urlString)
243-
var items: [URLQueryItem] = [URLQueryItem(name: "api_key", value: apiKey), URLQueryItem(name: "request_id", value: requestID)]
239+
var items: [URLQueryItem] = [URLQueryItem(name: "api_key", value: apiKey)]
244240
if let requestParams = params {
245241
for key in requestParams.keys {
246242
items.append(URLQueryItem(name: key, value: (requestParams[key] as? LosslessStringConvertible)?.description))
@@ -266,13 +262,14 @@ public class ApphudHttpClient {
266262
request?.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
267263
request?.setValue(platform, forHTTPHeaderField: "X-Platform")
268264
request?.setValue(self.sdkType, forHTTPHeaderField: "X-SDK")
265+
request?.setValue(requestID ?? UUID().uuidString, forHTTPHeaderField: "Idempotency-Key")
269266
request?.setValue(sdkVersion, forHTTPHeaderField: "X-SDK-VERSION")
270267
request?.setValue("Apphud \(platform) (\(self.sdkType) \(sdkVersion))", forHTTPHeaderField: "User-Agent")
271268

272269
request?.timeoutInterval = defaultTimeout ?? (method == .get ? GET_TIMEOUT : POST_TIMEOUT)
273270

274271
if method != .get {
275-
var finalParams: [String: Any] = ["api_key": apiKey, "request_id": requestID]
272+
var finalParams: [String: Any] = ["api_key": apiKey]
276273
if params != nil {
277274
finalParams.merge(params!, uniquingKeysWith: {$1})
278275
}
@@ -313,32 +310,40 @@ public class ApphudHttpClient {
313310
let method = request.httpMethod ?? ""
314311

315312
do {
316-
let (data, response) = retries > 0 ? try await URLSession.shared.data(for: request, retries: retries, delay: delay) : try await URLSession.shared.data(for: request)
313+
let result: (Data, URLResponse, Int)
314+
if retries > 0 {
315+
result = try await URLSession.shared.data(for: request, retries: retries, delay: delay)
316+
} else {
317+
let resp = try await URLSession.shared.data(for: request)
318+
result = (resp.0, resp.1, 1)
319+
}
317320

318-
guard let httpResponse = response as? HTTPURLResponse else {
319-
return (false, nil, nil, nil, NSURLErrorUnknown, 0)
321+
guard let httpResponse = result.1 as? HTTPURLResponse else {
322+
return (false, nil, nil, nil, NSURLErrorUnknown, 0, 1)
320323
}
321324

322-
let apphudResponse: ApphudParsedResponse = parseResponse(request: request, httpResponse: httpResponse, data: data, parseJson: !useDecoder || apphudIsSandbox())
325+
let apphudResponse: ApphudParsedResponse = parseResponse(request: request, httpResponse: httpResponse, data: result.0, parseJson: !useDecoder || apphudIsSandbox())
323326

324327
let requestDuration = Date().timeIntervalSince(startDate)
325328

326-
let finalHttpResponse: ApphudHTTPResponse = (apphudResponse.0, apphudResponse.1, apphudResponse.2, apphudResponse.3, apphudResponse.4, requestDuration)
329+
let finalHttpResponse: ApphudHTTPResponse = (apphudResponse.0, apphudResponse.1, apphudResponse.2, apphudResponse.3, apphudResponse.4, requestDuration, result.2)
327330

328331
return finalHttpResponse
329332

330333
} catch {
334+
let apphudError = error as? ApphudError
335+
let attempts = apphudError?.attempts ?? retries
331336

332337
let code = (error as NSError?)?.code ?? NSURLErrorUnknown
333338

334339
if ApphudUtils.shared.logLevel == .all {
335-
apphudLog("Request \(method) \(request.url?.absoluteString ?? "") failed with code: \(code) after: \(retries) retries error: \(error.localizedDescription)", logLevel: .all)
340+
apphudLog("Request \(method) \(request.url?.absoluteString ?? "") failed with code: \(code) after: \(attempts) attempts error: \(error.localizedDescription)", logLevel: .all)
336341
} else {
337-
apphudLog("Request \(method) \(request.url?.absoluteString ?? "") failed with code: \(code) after: \(retries) retries error: \(error.localizedDescription)")
342+
apphudLog("Request \(method) \(request.url?.absoluteString ?? "") failed with code: \(code) after: \(attempts) attempts error: \(error.localizedDescription)")
338343
}
339344

340345
// Handle any errors that occurred during the request
341-
return (false, nil, nil, error, code, 0)
346+
return (false, nil, nil, error, code, 0, attempts)
342347
}
343348
}
344349

Sources/Internal/ApphudInternal+Attribution.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ extension ApphudInternal {
134134
}
135135

136136
func startAttributionRequest(params: [String: Any], provider: ApphudAttributionProvider, identifer: String?, callback: ((Bool) -> Void)?) {
137-
self.httpClient?.startRequest(path: .attribution, params: params, method: .post, retry: true) { (result, _, _, _, _, _) in
137+
self.httpClient?.startRequest(path: .attribution, params: params, method: .post, retry: true) { (result, _, _, _, _, _, _) in
138138
switch provider {
139139
case .adjust:
140140
// to avoid sending the same data several times in a row

Sources/Internal/ApphudInternal+Currency.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,19 @@ extension ApphudInternal {
4141
private func fetchCurrencyWithMaxTimeout(_ completion: @escaping () -> Void) {
4242

4343
Task {
44-
let result = await Storefront.current
44+
let result: Storefront? = nil //await Storefront.current
4545
if let store = result, await currentUser?.currency?.countryCodeAlpha3 != store.countryCode {
4646

4747
storefrontCurrency = ApphudCurrency(countryCode: store.countryCode,
4848
code: nil,
4949
storeId: store.id,
5050
countryCodeAlpha3: store.countryCode)
5151
setNeedsToUpdateUser = true
52+
} else if result == nil {
53+
apphudLog("Failed to get Storefront, fetching currency from SKProducts")
54+
Task.detached {
55+
await self.fetchCurrencyLegacy()
56+
}
5257
}
5358
if !currencyTaskFinished {
5459
completion()
@@ -66,7 +71,7 @@ extension ApphudInternal {
6671

6772
private func fetchCurrencyLegacy() async {
6873

69-
let skProducts: [SKProduct] = ApphudStoreKitWrapper.shared.products
74+
var skProducts: [SKProduct] = ApphudStoreKitWrapper.shared.products
7075

7176
if skProducts.isEmpty {
7277
let groups: [ApphudGroup]?
@@ -83,6 +88,13 @@ extension ApphudInternal {
8388
})
8489

8590
await continueToFetchStoreKitProducts(maxAttempts: APPHUD_DEFAULT_RETRIES)
91+
skProducts = await withCheckedContinuation { continuation in
92+
Task { @MainActor in
93+
performWhenStoreKitProductFetched(maxAttempts: 3) { _ in
94+
continuation.resume(returning: ApphudStoreKitWrapper.shared.products)
95+
}
96+
}
97+
}
8698
}
8799

88100
let priceLocale = skProducts.first?.priceLocale
@@ -104,5 +116,6 @@ extension ApphudInternal {
104116
countryCodeAlpha3: nil)
105117

106118
setNeedsToUpdateUser = true
119+
apphudLog("Did prepare legacy currency \(countryCode)/\(currencyCode)")
107120
}
108121
}

Sources/Internal/ApphudInternal+Product.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ extension ApphudInternal {
6666
paywalls.forEach { p in
6767
productIDs.append(contentsOf: p.products.map { $0.productId })
6868
}
69+
70+
placements.forEach { pl in
71+
productIDs.append(contentsOf: pl.paywall?.products.map{ $0.productId } ?? [])
72+
}
6973

7074
permissionGroups?.forEach({ group in
7175
productIDs.append(contentsOf: group.products.map { $0.productId })
@@ -177,7 +181,7 @@ extension ApphudInternal {
177181
return
178182
}
179183

180-
httpClient?.startRequest(path: .products, apiVersion: .APIV3, params: ["observer_mode": ApphudUtils.shared.storeKitObserverMode, "device_id": currentDeviceID], method: .get, useDecoder: true, retry: true) { _, _, data, error, code, duration in
184+
httpClient?.startRequest(path: .products, apiVersion: .APIV3, params: ["observer_mode": ApphudUtils.shared.storeKitObserverMode, "device_id": currentDeviceID], method: .get, useDecoder: true, retry: true) { _, _, data, error, code, duration, attempts in
181185

182186
if error == nil {
183187
ApphudLoggerService.shared.add(key: .products, value: duration, retryLog: self.productsFetchRetries)

Sources/Internal/ApphudInternal+Purchase.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,12 @@ extension ApphudInternal {
299299
params["product_bundle_id"] = purchasedApphudProduct.id
300300
params["paywall_id"] = purchasedApphudProduct.paywallId
301301
params["placement_id"] = purchasedApphudProduct.placementId
302+
if let varID = purchasedApphudProduct.variationIdentifier {
303+
params["variation_identifier"] = varID
304+
}
305+
if let expID = purchasedApphudProduct.experimentId {
306+
params["experiment_id"] = expID
307+
}
302308
}
303309

304310
purchasingProduct = nil
@@ -318,6 +324,13 @@ extension ApphudInternal {
318324
}
319325

320326
params["paywall_id"] = paywall?.id
327+
if let varID = paywall?.variationIdentifier {
328+
params["variation_identifier"] = varID
329+
}
330+
if let expID = paywall?.experimentId {
331+
params["experiment_id"] = expID
332+
}
333+
321334
let apphudP = paywall?.products.first(where: { $0.productId == transactionProductIdentifier })
322335
apphudP?.id.map { params["product_bundle_id"] = $0 }
323336
}
@@ -340,7 +353,7 @@ extension ApphudInternal {
340353

341354
apphudLog("Uploading App Store Receipt...")
342355

343-
httpClient?.startRequest(path: .subscriptions, params: params, method: .post, useDecoder: true, retry: (hasMadePurchase && !fallbackMode)) { (result, _, data, error, errorCode, duration) in
356+
httpClient?.startRequest(path: .subscriptions, params: params, method: .post, useDecoder: true, retry: (hasMadePurchase && !fallbackMode)) { (result, _, data, error, errorCode, duration, attempts) in
344357
Task { @MainActor in
345358
if !result && hasMadePurchase && self.fallbackMode {
346359
self.requiresReceiptSubmission = true
@@ -463,7 +476,7 @@ extension ApphudInternal {
463476
// MARK: - Private purchase methods
464477

465478
private func purchase(product: SKProduct, apphudProduct: ApphudProduct?, validate: Bool, value: Double? = nil, callback: ((ApphudPurchaseResult) -> Void)?) {
466-
ApphudLoggerService.shared.paywallCheckoutInitiated(paywallId: apphudProduct?.paywallId, placementId: apphudProduct?.placementId, productId: product.productIdentifier)
479+
ApphudLoggerService.shared.paywallCheckoutInitiated(apphudProduct: apphudProduct, productId: product.productIdentifier)
467480

468481
purchasingProduct = apphudProduct
469482

@@ -565,7 +578,7 @@ extension ApphudInternal {
565578

566579
private func signPromoOffer(productID: String, discountID: String, callback: ((SKPaymentDiscount?, Error?) -> Void)?) {
567580
let params: [String: Any] = ["product_id": productID, "offer_id": discountID, "application_username": ApphudStoreKitWrapper.shared.appropriateApplicationUsername() ?? "", "device_id": currentDeviceID, "user_id": currentUserID ]
568-
httpClient?.startRequest(path: .signOffer, params: params, method: .post) { (result, dict, _, error, _, _) in
581+
httpClient?.startRequest(path: .signOffer, params: params, method: .post) { (result, dict, _, error, _, _, _) in
569582
if result, let responseDict = dict, let dataDict = responseDict["data"] as? [String: Any], let resultsDict = dataDict["results"] as? [String: Any] {
570583

571584
let signatureData = resultsDict["data"] as? [String: Any]

Sources/Internal/ApphudInternal+UserUpdate.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ extension ApphudInternal {
9292
let fields = initialCall ? ["user_id": self.currentUserID, "initial_call": true] : [:]
9393

9494
return await withCheckedContinuation { continuation in
95-
updateUser(fields: fields, delay: delay) { (result, _, data, error, code, duration) in
95+
updateUser(fields: fields, delay: delay) { (result, _, data, error, code, duration, attempts) in
9696

9797
Task {
9898
let hasChanges = await self.parseUser(data: data)
@@ -132,7 +132,7 @@ extension ApphudInternal {
132132
return
133133
}
134134

135-
self.updateUser(fields: ["user_id": userID]) { (result, _, data, _, _, _) in
135+
self.updateUser(fields: ["user_id": userID]) { (result, _, data, _, _, _, attempts) in
136136
if result {
137137
Task {
138138
await self.parseUser(data: data)
@@ -144,7 +144,7 @@ extension ApphudInternal {
144144

145145
internal func grantPromotional(_ duration: Int, _ permissionGroup: ApphudGroup?, productId: String?, callback: ApphudBoolCallback?) {
146146
performWhenUserRegistered {
147-
self.grantPromotional(duration, permissionGroup, productId: productId) { (result, _, data, _, _, _) in
147+
self.grantPromotional(duration, permissionGroup, productId: productId) { (result, _, data, _, _, _, _) in
148148
if result {
149149
Task {
150150
let hasChanges = await self.parseUser(data: data)
@@ -212,15 +212,15 @@ extension ApphudInternal {
212212
}
213213

214214
// retry on http level only if setNeedsToUpdateUser = true, otherwise let retry on initial launch level
215-
let needsHTTPLevelRetries = params["initial_call"] == nil
215+
let initialCall = params["initial_call"] != nil
216216
setNeedsToUpdateUser = false
217217

218218
appInstallationDate.map { params["first_seen"] = $0 }
219219
Bundle.main.bundleIdentifier.map { params["bundle_id"] = $0 }
220220
// do not automatically pass currentUserID here,because we have separate method updateUserID
221221

222222
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [self] in
223-
httpClient?.startRequest(path: .customers, params: params, method: .post, useDecoder: true, retry: needsHTTPLevelRetries) { done, response, data, error, errorCode, duration in
223+
httpClient?.startRequest(path: .customers, params: params, method: .post, useDecoder: true, retry: !initialCall, requestID: initialCall ? initialRequestID : nil) { done, response, data, error, errorCode, duration, attempts in
224224
if errorCode == 403 {
225225
apphudLog("Unable to perform API requests, because your account has been suspended.", forceDisplay: true)
226226
ApphudHttpClient.shared.unauthorized = true
@@ -230,7 +230,7 @@ extension ApphudInternal {
230230
apphudLog("Unable to perform API requests, because your API Key is invalid.", forceDisplay: true)
231231
ApphudHttpClient.shared.invalidAPiKey = true
232232
}
233-
callback(done, response, data, error, errorCode, duration)
233+
callback(done, response, data, error, errorCode, duration, attempts)
234234
}
235235
}
236236
}
@@ -331,7 +331,7 @@ extension ApphudInternal {
331331

332332
let canSaveToCache = values.2
333333

334-
httpClient?.startRequest(path: .properties, params: params, method: .post, retry: true) { (result, _, _, error, code, _) in
334+
httpClient?.startRequest(path: .properties, params: params, method: .post, retry: true) { (result, _, _, error, code, _, _) in
335335
if result {
336336
if canSaveToCache {
337337
Task {

0 commit comments

Comments
 (0)