mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* Small cleanup * add `LocalTransactionDetailsStorage` * Add models for local transaction metadata caching. Reset PurchasesOrchestrator * Complete `Codable`, lossless implementation of `LocalTransactionMetadata` * Rename `LocalTransactionMetadataCache` to `LocalTransactionMetadataStore` * Comment * lint * Remove unnecessary import * Remove unused method * Replace `Storefront` with `storeCountry` in both `ProductRequestData` and `PurchasedTransactionData` This removes the need of a `StorefrontEncodedWrapper` object * Disambiguate `asDictionary()` method Now that `SubscriberAttribute` is `Codable`, `Encodable`'s `asDictionary()` and `SubscriberAttribute`'s `asDictionary()` were clashing * Undo `Codable` conformance of `PresentedOfferingContext` and `TargetingContext` * lint * `LocalTransactionMetadata` now refers to a single transaction. `LocalTransactionMetadataStore` now stores one file per transaction and does not use an in-memory cache * Temporarily remove `LocalTransactionMetadata` and `LocalTransactionMetadataStore` for easier PR review * Revert "Temporarily remove `LocalTransactionMetadata` and `LocalTransactionMetadataStore` for easier PR review" This reverts commitd958601711. * Small code cleanup * Remove `EncodedAppleReceipt` from `LocalTransactionMetadata` * Undo `Codable` conformance of `EncodedAppleReceipt` Not needed for now * Remove `self.appTransactionJWS` from `LocalTransactionMetadata` * Adapt to API changes in `SynchronizedLargeItemCache` * Plug in `LocalTransactionMetadataStore` and store + get stored `LocalTransactionMetadata` * Removed unused parameter * Remove `schemaVersion` property from `LocalTransactionMetadata` * Add `MockLocalTransactionMetadataStore` for tests * Remove `appUserID` from `PurchasedTransactionData` * Update tests * Add `transaction_id` field to POST receipt * adapt tests * Add transaction ID parameter to cache key for `PostReceiptDataOperation`'s factory * [skip ci] Generating new test snapshots (#6024) * [skip ci] Generating new test snapshots (#6025) * [skip ci] Generating new test snapshots (#6027) * [skip ci] Generating new test snapshots (#6026) * [skip ci] Generating new test snapshots (#6028) * [skip ci] Generating new test snapshots (#6029) * [skip ci] Generating new test snapshots (#6033) * [skip ci] Generating new test snapshots (#6032) * Fix lint * Only send transaction ID when there's associated metadata * Fix tests compilation * Add unit test * Add tests * Add tests for `LocalTransactionMetadataStore` * Have `PurchasesOrchestrator` post transaction with syncing purchases in SK2 go through `TransactionPoster` to include the `LocalTransactionMetadata` logic there * Correct values for `purchaseCompletedBy` field in `PostReceiptDataOperation` * Fix finish transaction logic * Add more tests and fix test compilation * More tests * [skip ci] Generating new test snapshots (#6057) * [skip ci] Generating new test snapshots (#6050) * [skip ci] Generating new test snapshots (#6051) * [skip ci] Generating new test snapshots (#6052) * [skip ci] Generating new test snapshots (#6053) * [skip ci] Generating new test snapshots (#6054) * [skip ci] Generating new test snapshots (#6055) * [skip ci] Generating new test snapshots (#6056) * Fix lint * Remove TODO * Remove redundancy * Fix tests and add test * [skip ci] Generating new test snapshots (#6059) * [skip ci] Generating new test snapshots (#6060) * [skip ci] Generating new test snapshots (#6061) * [skip ci] Generating new test snapshots (#6062) * [skip ci] Generating new test snapshots (#6063) * [skip ci] Generating new test snapshots (#6064) * [skip ci] Generating new test snapshots (#6068) * [skip ci] Generating new test snapshots (#6069) * Add `transactionId` to `LocalTransactionMetadata` * Add `getAllStoredMetadata()` method to `LocalTransactionMetadataStore` * Cleanup stored metadata file if it fails to be read or decode * Fix lint * Simplify parameters in `handlePostReceiptResult` method in `PurchasesOrchestrator` The same subscriber attributes were already included in the `PurchasedTransactionData` object * Fix recovering `PresentedOfferingContext` from `LocalTransactionMetadata` * Add `EncodedAppleReceipt` to `LocalTransactionMetadata` * Take `source: PurchaseSource` out from `PurchasedTransactionData` * Rename `PurchaseSource` to `PostReceiptSource` and `ProductRequestData.InitiationSource` to `PostReceiptSource.InitiationSource` * Pass receipt as parameter directly * Fix lint * Remove unused variable * Add internal interface to sync cached local transaction metadata * Adapt unit tests * Trigger cached transaction metadata sync on `applicationWillEnterForeground` * fix test with previous value in snapshot * Fix compilation of tests * Introduce `sdk_originated` parameter in POST /receipt * Adapt tests to new `sdkOriginated` property * TEMP: Remove sdkOriginated field (to split into 2 PRs) * Revert "TEMP: Remove sdkOriginated field (to split into 2 PRs)" This reverts commit6d361fde30. * Remove sdkOriginated field as we won't be adding it for now This reverts commite4989a3d33. * Store transaction metadata when offering context or paywall is present * Fix PaywallEvent initialization in tests * Use variable * Cache CustomerInfo when receiving responses from cached transaction metadata syncs * Add more tests * Fix tests * Move syncing of cached local metadata to `applicationDidBecomeActive`, which does happen on cold app launches * Prevent concurrent cached transaction metadata syncs * Add tests for applicationDidBecomeActive syncing cached transaction metadata * Small improvements * Perform sync in `applicationWillEnterForeground` + at `configure` time * Fix tests after merge from main * Fix and improved tests * Fix tests * test improvements * Use `transactionId` in `PostData` as cacheKey only if it `containsAttributionData` This means that POST /receipt for renewals are deduplicated, while purchases with attribution data are guaranteed to always be sent (regardless of whether there's an in-flight POST /receipt request for a renewal of that purchase) * Adapt tests to new behavior + new tests * Update Sources/Networking/Operations/PostReceiptDataOperation.swift Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com> * Do not clear `LocalTransactionMetadata` when receiving offline-computed CustomerInfo * Add tests * Fix test + add test * Fix test * Fix lint * Fix test compilation * Improve comment * Fix codable `CodingKeys` to not use snake case in rawValues As the `JSONEncoder.default` uses `convertToSnakeCase` `keyEncodingStrategy` This allows decoding using the `JSONDecoder.default (which uses `convertFromSnakeCase` `keyDecodingStrategy` to correctly decode the JSONs resulting from encoding with `JSONEncoder.default`. * Add tests * Improve comment * Add `Codable` idempotency tests for `EncodedAppleReceipt` * Add tests for encoding/decoding `LocalTransactionMetadata` * Have `LocalTransactionMetadataStore` use a `.persistence` `DirectoryType` instead of a `.cache` one * Add E2E tests for `LocalTransactionMetadataStore` * Fix test * Add some logs and reduce some flakiness of tests * Remove test (will be added in a future PR after a small rewrite) * Reduce test flakiness * Change directory name * Perform cached transaction metadata sync without delay on tests * Remove unnecessary helper in tests * Fix deadlock * Inject `applicationSupportDirectory` to `LocalTransactionMetadataStore` for more reliable tests * Fix lint --------- Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
76 lines
2.8 KiB
Swift
76 lines
2.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
|
|
//
|
|
// CallbackCache.swift
|
|
//
|
|
// Created by Joshua Liebowitz on 11/18/21.
|
|
|
|
import Foundation
|
|
|
|
/**
|
|
Generic callback cache whose primary usage is to help ensure API calls in flight are not duplicated.
|
|
Users of this class will store a completion block for any Cacheable API call that is running. If the same request is
|
|
made while a request is in-flight, the completion block will be added to the list and the API call will not be
|
|
performed. Once the first API call has finished, the user is required to call `performOnAllItemsAndRemoveFromCache`.
|
|
This way the results from the initial API call will be surfaced to the waiting completion blocks from the duplicate
|
|
API calls that were not sent. After being called these blocks are removed from the cache.
|
|
*/
|
|
final class CallbackCache<T> where T: CacheKeyProviding {
|
|
|
|
private let _cachedCallbacksByKey: Atomic<[String: [T]]> = .init([:])
|
|
|
|
var cachedCallbacksByKey: [String: [T]] { return self._cachedCallbacksByKey.value }
|
|
|
|
func add(_ callback: T) -> CallbackCacheStatus {
|
|
return self._cachedCallbacksByKey.modify { cachedCallbacksByKey in
|
|
var values = cachedCallbacksByKey[callback.cacheKey] ?? []
|
|
let cacheStatus: CallbackCacheStatus = !values.isEmpty ?
|
|
.addedToExistingInFlightList :
|
|
.firstCallbackAddedToList
|
|
|
|
values.append(callback)
|
|
cachedCallbacksByKey[callback.cacheKey] = values
|
|
return cacheStatus
|
|
}
|
|
}
|
|
|
|
func performOnAllItemsAndRemoveFromCache(withCacheable cacheable: CacheKeyProviding, _ block: (T) -> Void) {
|
|
// Remove items from cache while holding the lock, then invoke callbacks AFTER releasing it.
|
|
// This prevents deadlock when callbacks synchronously add new items to the cache.
|
|
let items: [T]? = self._cachedCallbacksByKey.modify { cachedCallbacksByKey in
|
|
return cachedCallbacksByKey.removeValue(forKey: cacheable.cacheKey)
|
|
}
|
|
items?.forEach(block)
|
|
}
|
|
|
|
deinit {
|
|
#if DEBUG
|
|
if ProcessInfo.isRunningRevenueCatTests {
|
|
precondition(
|
|
self.cachedCallbacksByKey.isEmpty,
|
|
"\(type(of: self)) was deallocated with callbacks still stored."
|
|
)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
extension CallbackCache: Sendable where T: Sendable {}
|
|
|
|
/**
|
|
For use with `CallbackCache`. We store a list of callback objects in the cache and the key used for the list of
|
|
callbacks is provided by an object that conforms to `CacheKeyProviding`.
|
|
*/
|
|
protocol CacheKeyProviding {
|
|
|
|
var cacheKey: String { get }
|
|
|
|
}
|