Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created InternalAPI for "health" request #1971

Merged
merged 3 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@
572247D127BEC28E00C524A7 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572247D027BEC28E00C524A7 /* Array+Extensions.swift */; };
572247F727BF1ADF00C524A7 /* ArrayExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572247F627BF1ADF00C524A7 /* ArrayExtensionsTests.swift */; };
5722482727C2BD3200C524A7 /* LockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722482627C2BD3200C524A7 /* LockTests.swift */; };
5723FBBE28EDE1360003BA16 /* InternalAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5723FBBD28EDE1360003BA16 /* InternalAPI.swift */; };
5733B18E27FF586A00EC2045 /* BackendError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5733B18D27FF586A00EC2045 /* BackendError.swift */; };
5733B1A427FF9F8300EC2045 /* NetworkErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5733B1A327FF9F8300EC2045 /* NetworkErrorTests.swift */; };
5733B1A827FFBCC800EC2045 /* BackendErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5733B1A727FFBCC800EC2045 /* BackendErrorTests.swift */; };
Expand Down Expand Up @@ -292,6 +293,7 @@
5796A3A927D7C43500653165 /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A3A827D7C43500653165 /* Deprecations.swift */; };
5796A3C027D7D64500653165 /* ResultExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A3BF27D7D64500653165 /* ResultExtensionsTests.swift */; };
579B67F428C5326A0094F7E8 /* PaymentQueueWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579B67F328C5326A0094F7E8 /* PaymentQueueWrapper.swift */; };
579D2E3A28F0BF5A0094B36F /* BackendInternalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579D2E3928F0BF5A0094B36F /* BackendInternalTests.swift */; };
57A0FBF02749C0C2009E2FC3 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A0FBEF2749C0C2009E2FC3 /* Atomic.swift */; };
57A0FBF22749CF66009E2FC3 /* SynchronizedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A0FBF12749CF66009E2FC3 /* SynchronizedUserDefaults.swift */; };
57A17727276A721D0052D3A8 /* Set+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A17726276A721D0052D3A8 /* Set+Extensions.swift */; };
Expand Down Expand Up @@ -734,6 +736,7 @@
572247D027BEC28E00C524A7 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
572247F627BF1ADF00C524A7 /* ArrayExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensionsTests.swift; sourceTree = "<group>"; };
5722482627C2BD3200C524A7 /* LockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockTests.swift; sourceTree = "<group>"; };
5723FBBD28EDE1360003BA16 /* InternalAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalAPI.swift; sourceTree = "<group>"; };
5733B18D27FF586A00EC2045 /* BackendError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendError.swift; sourceTree = "<group>"; };
5733B1A327FF9F8300EC2045 /* NetworkErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkErrorTests.swift; sourceTree = "<group>"; };
5733B1A727FFBCC800EC2045 /* BackendErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendErrorTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -807,6 +810,7 @@
5796A3A827D7C43500653165 /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecations.swift; sourceTree = "<group>"; };
5796A3BF27D7D64500653165 /* ResultExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtensionsTests.swift; sourceTree = "<group>"; };
579B67F328C5326A0094F7E8 /* PaymentQueueWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentQueueWrapper.swift; sourceTree = "<group>"; };
579D2E3928F0BF5A0094B36F /* BackendInternalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendInternalTests.swift; sourceTree = "<group>"; };
57A0FBEF2749C0C2009E2FC3 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
57A0FBF12749CF66009E2FC3 /* SynchronizedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizedUserDefaults.swift; sourceTree = "<group>"; };
57A17726276A721D0052D3A8 /* Set+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Extensions.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1614,6 +1618,7 @@
B34605D0279A6E600031CA74 /* CustomerAPI.swift */,
B3781567285A79FC000A7B93 /* IdentityAPI.swift */,
B378156B285A9729000A7B93 /* OfferingsAPI.swift */,
5723FBBD28EDE1360003BA16 /* InternalAPI.swift */,
);
path = Networking;
sourceTree = "<group>";
Expand Down Expand Up @@ -1785,16 +1790,17 @@
isa = PBXGroup;
children = (
5796A39727D6C07D00653165 /* __Snapshots__ */,
A56C2E002819C33500995421 /* BackendPostAdServicesTokenTests.swift */,
5796A38927D6B96300653165 /* BackendGetCustomerInfoTests.swift */,
5796A38F27D6BCD100653165 /* BackendGetIntroEligibilityTests.swift */,
5796A39327D6BD6900653165 /* BackendGetOfferingsTests.swift */,
579D2E3928F0BF5A0094B36F /* BackendInternalTests.swift */,
5796A38B27D6BA1600653165 /* BackendLoginTests.swift */,
A56C2E002819C33500995421 /* BackendPostAdServicesTokenTests.swift */,
A56DFDF1286665E600EF2E32 /* BackendPostAttributionDataTests.swift */,
5796A39527D6BDAB00653165 /* BackendPostOfferForSigningTests.swift */,
5796A39827D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift */,
5796A38727D6B85900653165 /* BackendPostReceiptDataTests.swift */,
5796A39827D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift */,
5796A38027D6B78500653165 /* BaseBackendTest.swift */,
A56DFDF1286665E600EF2E32 /* BackendPostAttributionDataTests.swift */,
);
path = Backend;
sourceTree = "<group>";
Expand Down Expand Up @@ -2389,6 +2395,7 @@
57DE806D28074976008D6C6F /* Storefront.swift in Sources */,
B3B5FBB6269CED6400104A0C /* ErrorDetails.swift in Sources */,
2D991ACA268BA56900085481 /* StoreKitRequestFetcher.swift in Sources */,
5723FBBE28EDE1360003BA16 /* InternalAPI.swift in Sources */,
A55D5D66282ECCC100FA7623 /* PostAdServicesTokenOperation.swift in Sources */,
B3B5FBB4269CED4B00104A0C /* BackendErrorCode.swift in Sources */,
2DDF41B624F6F387005BC22D /* ASN1ObjectIdentifierBuilder.swift in Sources */,
Expand Down Expand Up @@ -2686,6 +2693,7 @@
57ACB13728184CF1000DCC9F /* DecoderExtensionTests.swift in Sources */,
351B516126D44BEB00BD2BD7 /* IdentityManagerTests.swift in Sources */,
57032ABF28C13CE4004FF47A /* StoreKit2SettingTests.swift in Sources */,
579D2E3A28F0BF5A0094B36F /* BackendInternalTests.swift in Sources */,
351B51C126D450E800BD2BD7 /* OfferingsManagerTests.swift in Sources */,
5796A39927D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift in Sources */,
35D8330A262FBA9A00E60AC5 /* MockUserDefaults.swift in Sources */,
Expand Down
13 changes: 13 additions & 0 deletions Sources/FoundationExtensions/Result+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ extension Result {

}

extension Result where Success == Void {

/// Creates a `Result<Void, Error>` with an optional `Error`.
init(_ error: Failure?) {
if let error = error {
self = .failure(error)
} else {
self = .success(())
}
}

}

extension Result where Success: OptionalType {

/// Converts a `Result<Success?, Error>` into `Result<Success, Error>?`
Expand Down
16 changes: 13 additions & 3 deletions Sources/Networking/Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class Backend {

let identity: IdentityAPI
let offerings: OfferingsAPI
let customer: CustomerAPI
let internalAPI: InternalAPI

private let config: BackendConfiguration
private let customer: CustomerAPI

convenience init(apiKey: String,
systemInfo: SystemInfo,
Expand All @@ -43,17 +44,26 @@ class Backend {
let customer = CustomerAPI(backendConfig: backendConfig, attributionFetcher: attributionFetcher)
let identity = IdentityAPI(backendConfig: backendConfig)
let offerings = OfferingsAPI(backendConfig: backendConfig)
self.init(backendConfig: backendConfig, customerAPI: customer, identityAPI: identity, offeringsAPI: offerings)
let internalAPI = InternalAPI(backendConfig: backendConfig)

self.init(backendConfig: backendConfig,
customerAPI: customer,
identityAPI: identity,
offeringsAPI: offerings,
internalAPI: internalAPI)
}

required init(backendConfig: BackendConfiguration,
customerAPI: CustomerAPI,
identityAPI: IdentityAPI,
offeringsAPI: OfferingsAPI) {
offeringsAPI: OfferingsAPI,
internalAPI: InternalAPI) {
self.config = backendConfig

self.customer = customerAPI
self.identity = identityAPI
self.offerings = offeringsAPI
self.internalAPI = internalAPI
}

func clearHTTPClientCaches() {
Expand Down
10 changes: 5 additions & 5 deletions Sources/Networking/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ class HTTPClient {
self.dnsChecker = dnsChecker
self.timeout = requestTimeout
self.apiKey = apiKey
self.authHeaders = HTTPClient.authorizationHeader(withAPIKey: apiKey)
self.authHeaders = HTTPClient.authorizationHeader(withAPIKey: apiKey)
}

func perform<Value: HTTPResponseBody>(_ request: HTTPRequest, completionHandler: Completion<Value>?) {
perform(request: .init(httpRequest: request,
headers: self.authHeaders,
completionHandler: completionHandler))
self.perform(request: .init(httpRequest: request,
headers: request.path.authenticated ? self.authHeaders : [:],
completionHandler: completionHandler))
}

func clearCaches() {
eTagManager.clearCaches()
self.eTagManager.clearCaches()
}

}
Expand Down
27 changes: 27 additions & 0 deletions Sources/Networking/HTTPClient/HTTPRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,35 @@ extension HTTPRequest {
case postReceiptData
case postSubscriberAttributes(appUserID: String)
case postAdServicesToken(appUserID: String)
case health

}

}

extension HTTPRequest.Path {

/// Whether requests to this path are authenticated.
var authenticated: Bool {
switch self {
case .getCustomerInfo,
.getOfferings,
.getIntroEligibility,
.logIn,
.postAttributionData,
.postOfferForSigning,
.postReceiptData,
.postSubscriberAttributes,
.postAdServicesToken:
return true

case .health:
return false
}
}

}

extension HTTPRequest.Path: Hashable {}

extension HTTPRequest.Path: CustomStringConvertible {
Expand Down Expand Up @@ -106,6 +130,9 @@ extension HTTPRequest.Path: CustomStringConvertible {

case let .postSubscriberAttributes(appUserID):
return "subscribers/\(appUserID)/attributes"

case .health:
return "health"
}
}

Expand Down
85 changes: 85 additions & 0 deletions Sources/Networking/InternalAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// 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(completion: @escaping ResponseHandler) {
let operation = HealthOperation(httpClient: self.backendConfig.httpClient,
callbackCache: self.callbackCache)

let callback = HealthOperation.Callback(cacheKey: operation.cacheKey, completion: completion)
let cacheStatus = self.callbackCache.add(callback)

self.backendConfig.addCacheableOperation(operation,
withRandomDelay: false,
cacheStatus: cacheStatus)
}

}

// MARK: - Health

private class HealthOperation: CacheableNetworkOperation {

struct Callback: CacheKeyProviding {

let cacheKey: String
let completion: InternalAPI.ResponseHandler

}

struct Configuration: NetworkConfiguration {

let httpClient: HTTPClient

}

private let callbackCache: CallbackCache<Callback>

init(httpClient: HTTPClient,
callbackCache: CallbackCache<Callback>) {
self.callbackCache = callbackCache

super.init(configuration: Configuration(httpClient: httpClient),
individualizedCacheKeyPart: "")
}

override func begin(completion: @escaping () -> Void) {
self.httpClient.perform(
.init(method: .get, path: .health)
) { (response: HTTPResponse<HTTPEmptyResponseBody>.Result) in
self.callbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callback in
callback.completion(
response
.mapError(BackendError.networkError)
.error
)
}

completion()
}
}

}
10 changes: 10 additions & 0 deletions Tests/UnitTests/FoundationExtensions/ResultExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ class ResultExtensionsTests: TestCase {
}
}

func testVoidValueInitWithNoError() {
expect(Result<Void, Error>(nil)).to(beSuccess())
}

func testVoidValueInitWithError() {
expect(Result<Void, Error>(.error1)).to(beFailure {
expect($0).to(matchError(Error.error1))
})
}

}

class ResultAsOptionalResultTest: TestCase {
Expand Down
8 changes: 7 additions & 1 deletion Tests/UnitTests/Mocks/MockBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ class MockBackend: Backend {
let identity = MockIdentityAPI(backendConfig: backendConfig)
let offerings = MockOfferingsAPI(backendConfig: backendConfig)
let customer = CustomerAPI(backendConfig: backendConfig, attributionFetcher: attributionFetcher)
self.init(backendConfig: backendConfig, customerAPI: customer, identityAPI: identity, offeringsAPI: offerings)
let internalAPI = InternalAPI(backendConfig: backendConfig)

self.init(backendConfig: backendConfig,
customerAPI: customer,
identityAPI: identity,
offeringsAPI: offerings,
internalAPI: internalAPI)
}

override func post(receiptData: Data,
Expand Down
3 changes: 2 additions & 1 deletion Tests/UnitTests/Mocks/MockHTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class MockHTTPClient: HTTPClient {
private let sourceTestFile: StaticString

override func perform<Value: HTTPResponseBody>(_ request: HTTPRequest, completionHandler: Completion<Value>?) {
let call = Call(request: request, headers: authHeaders)
let call = Call(request: request,
headers: request.path.authenticated ? self.authHeaders : [:])

DispatchQueue.main.async {
self.calls.append(call)
Expand Down
Loading