Skip to content

Commit

Permalink
GetCustomerInfoAPI: avoid making a request if there's any `PostRece…
Browse files Browse the repository at this point in the history
…iptDataOperation` in progress (#1911)

Fixes [CSDK-419].

This is an improvement over the fix in #1292. In there, we avoided
appending on an existing `CustomerInfoOperation` if there were any
`PostReceiptDataOperation`s in progress, because those might come back
with outdated entitlements.
This fix removes that cache key hack, and instead *reuses* the entire
response to that `PostReceiptDataOperation` by "stealing" that request's
cache key.

This is perfectly captured by the existing test
`BackendPostReceiptDataTests.testGetsUpdatedSubscriberInfoAfterPost`,
added in #1292. Amazingly enough, that test still passes with one minor
change: the entire process requires one fewer API call 🎉 🐼

[CSDK-419]:
https://revenuecats.atlassian.net/browse/CSDK-419?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
NachoSoto authored Sep 21, 2022
1 parent 8a8a8d0 commit 43d474d
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 39 deletions.
40 changes: 37 additions & 3 deletions Sources/Networking/Caching/CustomerInfoCallback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ struct CustomerInfoCallback: CacheKeyProviding {

typealias Completion = (Result<CustomerInfo, BackendError>) -> Void

let cacheKey: String
let source: NetworkOperation.Type
let completion: Completion
var cacheKey: String
var source: NetworkOperation.Type
var completion: Completion

init(operation: CacheableNetworkOperation, completion: @escaping Completion) {
self.cacheKey = operation.cacheKey
Expand All @@ -28,3 +28,37 @@ struct CustomerInfoCallback: CacheKeyProviding {
}

}

// MARK: - CallbackCache helpers

extension CallbackCache where T == CustomerInfoCallback {

func addOrAppendToPostReceiptDataOperation(callback: CustomerInfoCallback) -> CallbackCacheStatus {
if let existing = self.callbacks(ofType: PostReceiptDataOperation.self).last {
return self.add(callback: callback.withNewCacheKey(existing.cacheKey))
} else {
return self.add(callback: callback)
}
}

private func callbacks(ofType type: NetworkOperation.Type) -> [T] {
return self
.cachedCallbacksByKey
.value
.lazy
.flatMap(\.value)
.filter { $0.source == type }
}

}

private extension CustomerInfoCallback {

func withNewCacheKey(_ newKey: String) -> Self {
var copy = self
copy.cacheKey = newKey

return copy
}

}
2 changes: 1 addition & 1 deletion Sources/Networking/CustomerAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class CustomerAPI {
customerInfoCallbackCache: self.customerInfoCallbackCache)

let callback = CustomerInfoCallback(operation: operation, completion: completion)
let cacheStatus = self.customerInfoCallbackCache.add(callback: callback)
let cacheStatus = self.customerInfoCallbackCache.addOrAppendToPostReceiptDataOperation(callback: callback)
self.backendConfig.addCacheableOperation(operation,
withRandomDelay: randomDelay,
cacheStatus: cacheStatus)
Expand Down
34 changes: 1 addition & 33 deletions Sources/Networking/Operations/GetCustomerInfoOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,8 @@ class GetCustomerInfoOperation: CacheableNetworkOperation {
self.customerInfoResponseHandler = customerInfoResponseHandler
self.customerInfoCallbackCache = customerInfoCallbackCache

var individualizedCacheKeyPart = configuration.appUserID

// If there is any enqueued `PostReceiptDataOperation` we don't want this new
// `GetCustomerInfoOperation` to share the same cache key.
// If it did, future `GetCustomerInfoOperation` would receive a cached value
// instead of an up-to-date `CustomerInfo` after those post receipt operations finish.
if customerInfoCallbackCache.hasPostReceiptOperations {
individualizedCacheKeyPart += "-\(customerInfoCallbackCache.numberOfGetCustomerInfoOperations)"
}

super.init(configuration: configuration,
individualizedCacheKeyPart: individualizedCacheKeyPart)
individualizedCacheKeyPart: configuration.appUserID)
}

override func begin(completion: @escaping () -> Void) {
Expand Down Expand Up @@ -72,25 +62,3 @@ private extension GetCustomerInfoOperation {
}

}

private extension CallbackCache where T == CustomerInfoCallback {

var numberOfGetCustomerInfoOperations: Int {
return self.callbacks(ofType: GetCustomerInfoOperation.self)
}

var hasPostReceiptOperations: Bool {
return self.callbacks(ofType: PostReceiptDataOperation.self) > 0
}

private func callbacks(ofType type: NetworkOperation.Type) -> Int {
return self
.cachedCallbacksByKey
.value
.lazy
.flatMap(\.value)
.filter { $0.source == type }
.count
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,7 @@ class BackendPostReceiptDataTests: BaseBackendTests {

expect(self.httpClient.calls.map { $0.request.path }) == [
getCustomerInfoPath,
.postReceiptData,
getCustomerInfoPath
.postReceiptData
]
}

Expand Down

0 comments on commit 43d474d

Please sign in to comment.