Files
purchases-ios/Sources/Networking/Backend.swift
T
NachoSoto 4b2138367b Refactored TransactionPoster: removed 2 dependencies and abstracted parameters (#2542)
Follow up to #2540.

This was necessary to ensure that we can implement #2533 without
reference cycles.
This was the hierarchy before this change:

![IMG_B25ECE86C2F9-1](https://github.com/RevenueCat/purchases-ios/assets/685609/70e1d939-8fa5-424c-8673-bb519f386118)

The red arrows are what this PR removes.

## Changes:
- `TransactionPoster` no longer has references to `IdentityManager` and
`Attribution`
- `markSyncedIfNeeded` is no longer in `TransactionPoster`, moved back
to `PurchasesOrchestrator`
- Changed `TransactionPoster`'s callback to return `Result<CustomerInfo,
BackendError>`, providing greater flexibility for users to be able to
read the specific errors.
- Extracted `PurchasedTransactionData` to be reduce the number of
parameters passed around. This removed the need for 2
`swiftlint:disable:next function_parameter_count`. This is the majority
of this diff. It was helpful to combine in this PR because I needed to
add another parameter to the flow: `unsyncedAttributes`, since
`TransactionPoster` could no longer read these directly.
2023-05-30 12:16:24 -07:00

193 lines
6.6 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// Backend.swift
//
// Created by Joshua Liebowitz on 8/2/21.
import Foundation
class Backend {
let identity: IdentityAPI
let offerings: OfferingsAPI
let offlineEntitlements: OfflineEntitlementsAPI
let customer: CustomerAPI
let internalAPI: InternalAPI
private let config: BackendConfiguration
convenience init(
apiKey: String,
systemInfo: SystemInfo,
httpClientTimeout: TimeInterval = Configuration.networkTimeoutDefault,
eTagManager: ETagManager,
operationDispatcher: OperationDispatcher,
attributionFetcher: AttributionFetcher,
offlineCustomerInfoCreator: OfflineCustomerInfoCreator?,
dateProvider: DateProvider = DateProvider()
) {
let httpClient = HTTPClient(apiKey: apiKey,
systemInfo: systemInfo,
eTagManager: eTagManager,
requestTimeout: httpClientTimeout)
let config = BackendConfiguration(httpClient: httpClient,
operationDispatcher: operationDispatcher,
operationQueue: QueueProvider.createBackendQueue(),
systemInfo: systemInfo,
offlineCustomerInfoCreator: offlineCustomerInfoCreator,
dateProvider: dateProvider)
self.init(backendConfig: config, attributionFetcher: attributionFetcher)
}
convenience init(backendConfig: BackendConfiguration, attributionFetcher: AttributionFetcher) {
let customer = CustomerAPI(backendConfig: backendConfig, attributionFetcher: attributionFetcher)
let identity = IdentityAPI(backendConfig: backendConfig)
let offerings = OfferingsAPI(backendConfig: backendConfig)
let offlineEntitlements = OfflineEntitlementsAPI(backendConfig: backendConfig)
let internalAPI = InternalAPI(backendConfig: backendConfig)
self.init(backendConfig: backendConfig,
customerAPI: customer,
identityAPI: identity,
offeringsAPI: offerings,
offlineEntitlements: offlineEntitlements,
internalAPI: internalAPI)
}
required init(backendConfig: BackendConfiguration,
customerAPI: CustomerAPI,
identityAPI: IdentityAPI,
offeringsAPI: OfferingsAPI,
offlineEntitlements: OfflineEntitlementsAPI,
internalAPI: InternalAPI) {
self.config = backendConfig
self.customer = customerAPI
self.identity = identityAPI
self.offerings = offeringsAPI
self.offlineEntitlements = offlineEntitlements
self.internalAPI = internalAPI
}
func clearHTTPClientCaches() {
self.config.clearCache()
}
func post(attributionData: [String: Any],
network: AttributionNetwork,
appUserID: String,
completion: CustomerAPI.SimpleResponseHandler?) {
self.customer.post(attributionData: attributionData,
network: network,
appUserID: appUserID,
completion: completion)
}
func post(adServicesToken: String,
appUserID: String,
completion: CustomerAPI.SimpleResponseHandler?) {
self.customer.post(adServicesToken: adServicesToken,
appUserID: appUserID,
completion: completion)
}
func getCustomerInfo(appUserID: String,
withRandomDelay randomDelay: Bool,
allowComputingOffline: Bool = true,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler) {
self.customer.getCustomerInfo(appUserID: appUserID,
withRandomDelay: randomDelay,
allowComputingOffline: allowComputingOffline,
completion: completion)
}
func post(receiptData: Data,
productData: ProductRequestData?,
transactionData: PurchasedTransactionData,
observerMode: Bool,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler) {
self.customer.post(receiptData: receiptData,
productData: productData,
transactionData: transactionData,
observerMode: observerMode,
completion: completion)
}
func post(subscriberAttributes: SubscriberAttribute.Dictionary,
appUserID: String,
completion: CustomerAPI.SimpleResponseHandler?) {
self.customer.post(subscriberAttributes: subscriberAttributes, appUserID: appUserID, completion: completion)
}
}
extension Backend {
/// - Throws: `NetworkError`
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
func healthRequest(signatureVerification: Bool) async throws {
try await Async.call { completion in
self.internalAPI.healthRequest(signatureVerification: signatureVerification) { error in
completion(.init(error))
}
}
}
}
// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension Backend: @unchecked Sendable {}
// MARK: - Internal
extension Backend {
typealias ResponseHandler<Response> = @Sendable (Swift.Result<Response, BackendError>) -> Void
}
extension Backend {
@objc var signatureVerificationEnabled: Bool {
return self.config.httpClient.signatureVerificationEnabled
}
}
extension Backend {
enum QueueProvider {
static func createBackendQueue() -> OperationQueue {
let operationQueue = OperationQueue()
operationQueue.name = "Backend Queue"
operationQueue.maxConcurrentOperationCount = 1
return operationQueue
}
}
}
// MARK: - Testing extensions
extension Backend {
var networkTimeout: TimeInterval {
return self.config.httpClient.timeout
}
var offlineCustomerInfoEnabled: Bool {
return self.config.offlineCustomerInfoCreator != nil
}
}