Files
purchases-ios/Sources/Networking/InternalAPI.swift
T
NachoSoto 457ba021d9 HTTPClient: automatically add nonce based on HTTPRequest.Path (#2286)
Instead of manually choosing when to include `nonce` in requests, this
data-driven approach ensures that all requests to endpoints that support
signature verification will make `HTTPClient` do the right thing.

See also #2277 for how `CustomerInfo` and `EntitlementInfo` will use
this.
2023-02-22 09:20:25 -08:00

118 lines
3.8 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
//
// InternalAPI.swift
//
// Created by Nacho Soto on 10/5/22.
import Foundation
final class InternalAPI {
typealias ResponseHandler = (BackendError?) -> Void
private let backendConfig: BackendConfiguration
private let callbackCache: CallbackCache<HealthOperation.Callback>
init(backendConfig: BackendConfiguration) {
self.backendConfig = backendConfig
self.callbackCache = .init()
}
func healthRequest(signatureVerification: Bool, completion: @escaping ResponseHandler) {
let factory = HealthOperation.createFactory(httpClient: self.backendConfig.httpClient,
callbackCache: self.callbackCache,
signatureVerification: signatureVerification)
let callback = HealthOperation.Callback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.callbackCache.add(callback)
self.backendConfig.addCacheableOperation(with: factory,
withRandomDelay: false,
cacheStatus: cacheStatus)
}
}
// MARK: - Health
private final class HealthOperation: CacheableNetworkOperation {
struct Callback: CacheKeyProviding {
let cacheKey: String
let completion: InternalAPI.ResponseHandler
}
struct Configuration: NetworkConfiguration {
let httpClient: HTTPClient
}
private let callbackCache: CallbackCache<Callback>
private let signatureVerification: Bool
static func createFactory(
httpClient: HTTPClient,
callbackCache: CallbackCache<Callback>,
signatureVerification: Bool
) -> CacheableNetworkOperationFactory<HealthOperation> {
return .init({ .init(httpClient: httpClient,
callbackCache: callbackCache,
cacheKey: $0,
signatureVerification: signatureVerification) },
individualizedCacheKeyPart: "")
}
private init(httpClient: HTTPClient,
callbackCache: CallbackCache<Callback>,
cacheKey: String,
signatureVerification: Bool) {
self.callbackCache = callbackCache
self.signatureVerification = signatureVerification
super.init(configuration: Configuration(httpClient: httpClient), cacheKey: cacheKey)
}
override func begin(completion: @escaping () -> Void) {
let request: HTTPRequest = .init(method: .get, path: .health)
self.httpClient.perform(
request,
with: self.verificationMode
) { (response: HTTPResponse<HTTPEmptyResponseBody>.Result) in
self.finish(with: response, completion: completion)
}
}
private func finish(with response: HTTPResponse<HTTPEmptyResponseBody>.Result,
completion: () -> Void) {
self.callbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callback in
callback.completion(
response
.mapError(BackendError.networkError)
.error
)
}
completion()
}
private var verificationMode: Signing.ResponseVerificationMode {
if self.signatureVerification, #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) {
return Signing.verificationMode(with: .enforced)
} else {
return .disabled
}
}
}