PostReceiptDataOperation: replaced receipt base64 with hash for cache key#2199
Conversation
PostReceiptDataOperation: replaced receipt base64 with hash for cache keyPostReceiptDataOperation: replaced receipt base64 with hash for cache key
0e28264 to
21f7e07
Compare
| expect("sample string".asData.hashString) == "99ad9154f94977dd8913f3b7ea14091d00e52b8931c2bc1cfc7ea62b7c26727b" | ||
| } | ||
|
|
||
| func testDataAsHashStringHashesTheEntireData() { |
There was a problem hiding this comment.
This test failed with the original UUID implementation, which only hashed the first 16 bytes (see https://opensource.apple.com/source/CF/CF-855.17/CFUUID.c.auto.html).
9692281 to
0256794
Compare
| } | ||
|
|
||
| // Returns a string representing a fetch token. | ||
| /// - Returns: `UUID` from the first 16 bytes of the underlying data. |
There was a problem hiding this comment.
I added this documentation to specify that this doesn't encode the entire data!
| return self.withUnsafeBytes { | ||
| guard let baseAddress = $0.bindMemory(to: UInt8.self).baseAddress else { | ||
| return nil | ||
| } | ||
|
|
||
| return NSUUID(uuidBytes: baseAddress) as UUID | ||
| } |
There was a problem hiding this comment.
This is also a small optimization, to ensure that we don't copy self.
d0619cc to
0a14bf9
Compare
| let cacheKey = | ||
| """ | ||
| \(configuration.appUserID)-\(postData.isRestore)-\(postData.receiptData.asFetchToken) | ||
| \(configuration.appUserID)-\(postData.isRestore)-\(postData.receiptData.hashString) |
There was a problem hiding this comment.
✅ This is the core of this change
There was a problem hiding this comment.
this is a fantastic solution :chefskiss:
let's add a comment here explaining that we're explicitly using the hashString since the fetchToken could be huge if there are a lot of transactions in the receipt
There was a problem hiding this comment.
Good idea, I added this comment:
/// Cache key comprises of the following:
/// - `appUserID`
/// - `isRestore`
/// - Receipt (`hashString` instead of `fetchToken` to avoid big receipts leading to a huge cache key)
/// - `ProductRequestData.cacheKey`
/// - `presentedOfferingIdentifier`
/// - `observerMode`
/// - `subscriberAttributesByKey`Fixes [CSDK-622]. Depends on #2199, #2200, #2202, #2203. - Extracted `FileReader` to be able to use it in `ReceiptParser` - Extracted `MockBundle` to be able to use it in `ReceiptParser` - Added new `public` `ReceiptParser.fetchAndParseLocalReceipt()`, **only to `ReceiptParser`**. This is not available through `RevenueCat`. - Added tests for the new API - Added new API to `ReceiptParserAPITester` Initially I thought about moving `ReceiptFetcher` to the `Receipt|Parser` target. Unfortunately that would require moving a whole bunch of other dependencies (`OperationDispatcher`, etc, etc). We have no easy way to create a third "Core" framework in which to place these common dependencies, so that wouldn't be trivial. This solution adds a much simpler version of `ReceiptFetcher` that simply reads from `Bundle.appStoreReceiptURL`. As it's documented there, under normal circumstances `SKReceiptRefreshRequest` is not required, so this implementation should work for 99% of use cases, and so this way we avoid increasing the.codebase complexity unnecessarily.
…he key Should fix #2198 and [SDKONCALL-200]. This is a big performance optimization in time and memory, as we no longer need to convert the entire receipt to base 64 and concatenate it to the string, which is what was likely causing #2198. Now, the implementation of `Data.uuid` avoids any data copies and efficiently creates a `UUID` hash with the receipt content. Apart from the new tests for this property, `BackendPostReceiptDataTests.testDoesntCacheForDifferentReceipts` ensures that different receipts have different cache keys, and therefore this new implementation still avoids collisions (other than the extremely unlikely hash collisions).
0a14bf9 to
1aa7e19
Compare
Fixes [CSDK-622]. Depends on #2199, #2200, #2202, #2203. - Extracted `FileReader` to be able to use it in `ReceiptParser` - Extracted `MockBundle` to be able to use it in `ReceiptParser` - Added new `public` `ReceiptParser.fetchAndParseLocalReceipt()`, **only to `ReceiptParser`**. This is not available through `RevenueCat`. - Added tests for the new API - Added new API to `ReceiptParserAPITester` Initially I thought about moving `ReceiptFetcher` to the `Receipt|Parser` target. Unfortunately that would require moving a whole bunch of other dependencies (`OperationDispatcher`, etc, etc). We have no easy way to create a third "Core" framework in which to place these common dependencies, so that wouldn't be trivial. This solution adds a much simpler version of `ReceiptFetcher` that simply reads from `Bundle.appStoreReceiptURL`. As it's documented there, under normal circumstances `SKReceiptRefreshRequest` is not required, so this implementation should work for 99% of use cases, and so this way we avoid increasing the.codebase complexity unnecessarily.
| let cacheKey = | ||
| """ | ||
| \(configuration.appUserID)-\(postData.isRestore)-\(postData.receiptData.asFetchToken) | ||
| \(configuration.appUserID)-\(postData.isRestore)-\(postData.receiptData.hashString) |
There was a problem hiding this comment.
this is a fantastic solution :chefskiss:
let's add a comment here explaining that we're explicitly using the hashString since the fetchToken could be huge if there are a lot of transactions in the receipt
49e7be5 to
3204749
Compare
**This is an automatic release.** ### New Features * Added new `ReceiptParser.fetchAndParseLocalReceipt` (#2204) via NachoSoto (@NachoSoto) * `PurchasesReceiptParser`: added API to parse receipts from `base64` string (#2200) via NachoSoto (@NachoSoto) ### Bugfixes * `CustomerInfo`: support parsing schema version 2 to restore SDK `v3.x` compatibility (#2213) via NachoSoto (@NachoSoto) ### Other Changes * `JSONDecoder`: added decoding type when logging `DecodingError.keyNotFound` (#2212) via NachoSoto (@NachoSoto) * Added `ReceiptParserTests` (#2203) via NachoSoto (@NachoSoto) * Deploy `PurchaseTester` for `macOS` (#2011) via NachoSoto (@NachoSoto) * `ReceiptFetcher`: refactored implementation to log error when failing to fetch receipt (#2202) via NachoSoto (@NachoSoto) * `PostReceiptDataOperation`: replaced receipt `base64` with `hash` for cache key (#2199) via NachoSoto (@NachoSoto) * `PurchaseTester`: small refactor to simplify `Date` formatting (#2210) via NachoSoto (@NachoSoto) * `PurchasesReceiptParser`: improved documentation to reference `default` (#2197) via NachoSoto (@NachoSoto) * Created `CachingTrialOrIntroPriceEligibilityChecker` (#2007) via NachoSoto (@NachoSoto) * Update Gemfile.lock (#2205) via Cesar de la Vega (@vegaro) * remove stalebot in favor of SLAs in Zendesk (#2196) via Andy Boedo (@aboedo) * Update fastlane-plugin-revenuecat_internal to latest version (#2194) via Cesar de la Vega (@vegaro)
Fixes [CSDK-626]. Depends on #2199, #2200, #2202, #2203, #2204, #2210.  [CSDK-626]: https://revenuecats.atlassian.net/browse/CSDK-626?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Should fix #2198 and SDKONCALL-200. This is a big performance optimization in time and memory, as we no longer need to convert the entire receipt to
base64and concatenate it to the string, which is what was likely causing #2198.This now uses
Data.hashString, which avoids any data copies and efficiently creates a hash with the receipt content.Apart from the new tests for this property,
BackendPostReceiptDataTests.testDoesntCacheForDifferentReceiptsensures that different receipts have different cache keys, and therefore this new implementation still avoids collisions (other than the extremely unlikely hash collisions).