When compiling with Xcode 16, we started throwing a bunch of new warnings. Here's a list and explanation of each:
- The most common one was needing to explicitly restate the @unchecked Sendable conformance from a parent class.
- There were a couple switches where a new entry was added in tvOS but only for iOS 18, and needed to add it to make it exhaustive.
- There we a couple of places where we were adding a protocol conformance to a class from a different module e.g. extension DispatchTimeInterval: Comparable. This is considered unsafe now because the owners of the module could add the conformance later. Solved by removing the conformance which wasn't being used widely in the codebase, only in tests.
- Finally there was a class where we were capturing SKRequest and SKProductsRequest instances in a sendable block, but only to either log something, or use them as dictionary key, so I just made the classes Sendable (with the new @retroactice attribute because they belong to a different module)
`ProductFetcherSK1` had 2 public methods for fetching products:
`products(withIdentifiers:completion:)` and
`sk1Products(withIdentifiers:completion:)`.
Only one of them was using `TimingUtil` to log slow requests, and that
one happened to not be used by `ProductsManager`, which means that we
weren't getting these logs.
This is evident in
https://github.com/RevenueCat/react-native-purchases/issues/755. With
this change, we'll now be able to see how long the product request took.
### Context:
`#if os(iOS)` no longer evaluates to `true` when compiling for
`visionOS`
Which means we need `|| os(xrOS)`.
But to support older Xcode we need `#if swift(>=5.9) && os(xrOS)`.
But, fun fact, short-circuiting doesn’t work in `Xcode 13`.
And we still need that to run iOS <15 tests (`FB12223404` /
`FB12805891`)
💩
This works around that by defining our own `VISION_OS` flag.
### Changes:
- Added `VISION_OS` compile time flag to determine SDK
- Replaced `#if os(xrOS)` with `#if VISION_OS`
- Added flag to `Package.swift` (thanks @neonichu for helping with this)
I noticed this was being logged twice when requesting SK1 products,
which makes it seem like we're fetching them twice:
```
[store_kit] DEBUG: ℹ️ No existing products cached, starting store products request for: ["com.revenuecat.monthly_4.99.1_week_intro"]
```
`CachingProductsManager` was introduced in #1907. Explained there is why
`ProductsFetcherSK1` keeps some caching behavior.
But the main caching layer is now `CachingProductsManager`, so it makes
sense to keep the log only there.
This adds 4 basic integration tests. We had independent coverage of
`ProductsManager` and `CachingProductsManager` with a mock
`ProductsManager`, but no unit tests covering both together. This serves
that purpose too.
Fixes [CSDK-504].
`SKPaymentQueue.finishTransaction` is asynchronous, but we were assuming
it wasn't.
This meant that when performing multiple purchases (like consumables) in
quick succession, before the first transaction was finished, the second
purchase was reusing the first transaction.
Example log:
```
[Purchases] - INFO: 💰 Finishing transaction ‘0’ for product ‘consumable.10_coins’
[Purchases] - INFO: 😻💰 Purchased product - ‘consumable.10_coins’
[Purchases] - INFO: 💰 Purchasing Product ‘consumable.10_coins’ from package in Offering ‘coins’
[Purchases] - DEBUG: ℹ️ StoreKit1Wrapper (0x00006000008a9530) removedTransaction: consumable.10_coins 0 1
```
Notice how the "removed transaction" was notified _after_ we had already
started the second purchase.
See also #2148 for an earlier fix for finishing transactions.
[CSDK-504]:
https://revenuecats.atlassian.net/browse/CSDK-504?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
See #2107. We still don't know what caused that, but it leads to
degraded performance (mildly reduced by #2115). To advice of that, in
case users are wondering why the app might potentially take excessive
CPU usage, I added this warning.
This is a follow up to #1901 and #1882. Depends on #1967.
Essentially before this change, `Purchases` had both `StoreKit1Wrapper?`
and a `PaymentQueueWrapper`. The later wasn't actually needed when the
first was not-nil, so it was essentially storing `(a?, b?)`. What we
really wanted to express is that it could have one OR the other.
This introduces `Either` to express this requirement in the type system,
as well as `PaymentQueueWrapperType` that will be used to fix#1964.
Follow up to #1901. That fix was incomplete. I missed it because the
protocols were mixed up (fixed in #1961). Now `PaymentQueueWrapper`,
when set up as the delegate (only when SK2 is enabled and SK1 is
disabled) will be added as a transaction observer.
Follow up to #1882. After that PR, `StoreKit1Wrapper` was no longer
listening to
`SKPaymentQueueDelegate.paymentQueue(:shouldAddStorePayment:for:)`,
which means that purchases initiated from the App Store were being
ignored by the SDK if SK2 is enabled.
To fix this, `PaymentQueueWrapper` now becomes the
`SKPaymentQueueDelegate` only when `StoreKit1Wrapper` is not
initialized, and `PurchasesOrchestrator` initiates an SK2 purchase
instead.
## Other changes:
- `PurchasesOrchestrator` purchase methods no longer need the whole
`PromotionalOffer`, only the `SignedData`. This allows the new
implementation to create that data from only the `SKPaymentDiscount`
provided from the `SKPayment`.
- Added tests for `PromotionalOffer.SignedData`
Follow up to #1882. Now that `StoreKit1Wrapper` is not initialized for SK2, it makes sense to clarify that this class is only for SK1.
This is just a simple find-replace.
When SK2 is enabled, `StoreKit2TransactionListener` is enough to handle transactions, so it's no longer necessary to have that and `StoreKitWrapper` both listening to them.
This is the last change from what was started with #1437. That PR introduced a few private error types, but for the errors returned in the public `APIs` we were still passing around `Error`s everywhere, which didn't provide any compile-time guarantees.
### Goals:
- Ensure that public APIs *only* return `ErrorCode`s wrapped with the additional `userInfo`, and not accidentally return other types like `SKError`, `BackendError`, `ErrorCode` directly, etc.
- Guarantee type-safety when passing errors around within our internal implementations instead of assuming certain thrown errors are of a particular type.
### Changes:
- Created `PurchasesError` for wrapping an `ErrorCode` with the additional `userInfo`.
- Changed returned errors from `Error` to `NSError`. This is a change in the API, but the new type is a more specific `Error` so it shouldn't lead to any changes in client apps.
- Because `PurchasesError` isn't implicitly convertible to `NSError`, this ensures that we don't return those values directly, and instead use `PurchasesError.asPublicError`.
- Changed `ErrorCodeConvertible` to a now more precise `PurchasesErrorConvertible`.
Thanks to this new type-safety this PR fixes at least 3 bugs where we were returning private errors instead of `ErrorCode`s.
### Testing:
Thanks to #1871 we know that the returned errors are still convertible to `ErrorCode` so users can `switch` over them.
Follow up to #1583. Subtask of #1382.
### Description
This PR aims to invalidate and re-fetch if appropriate the stuff in the cache when the Storefront changes. I've used both `OfferingsManager` and `ProductsManager` to manage the logic of invalidate and re-fetch the data.
Changes:
- Add `invalidateAndReFetchCachedOfferingsIfAppropiate` method to `OfferingsManager` class.
- Rename `clearCachedProducts` method to `invalidateAndReFetchCachedProductsIfAppropiate` in `ProductsManager` class.
- Add `OfferingsManager` as a dependency of `PurchasesOrchestrator`.
- Clear cache methods returns the identifiers of the elements that have been removed or an empty list if there was no stuff in the cache.
- Add tests to cover some scenarios.
Follow up to #1557. Subtask of #1382
### Description
This PR aims to clear the stuff in the cache when the Storefront changes.
Changes:
- Add `clearCachedOfferings` method to `DeviceCache`.
- Add `clearCache` method to `ProductFetcherSK1`.
- Make `internal` the `clearCache` method in `ProductsFetcherSK2`.
- Handle the **Storefront** changes in the `PurchasesOrchestator`.
- Add tests to cover some scenarios.
* Listen changes via `paymentQueueDidChangeStorefront` method
* Create `StoreKit2StorefrontListener.swift` to listen to Storefront changes for SK2
* Update `PurchasesOrchestrator` to listen to the Storefront changes
* Include `StoreKit2StorefrontListenerTests` in StoreKitTests
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
* Remove force unwrap
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
* Add required spaces
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
* Remove useless cancel method when the listener is released
* Notify Storefront changes in the main thread
* Add cancel task when the Listener is released
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
* Resolves [CF-443]
* Adds `configureWithConfiguration` to `RCPurchasesAPI.m`
* Create Configuration object and API to configure Purchases
* Update API doc for new Purchases API
* Add example code to Configuration documentation
* Adds deprecations for various Purchases.configure methods
Fixes#1521.
Despite `SKProductDiscount.priceLocale` being non-optional, StoreKit might return `nil` `NSLocale`s.
This works around that to make sure the SDK doesn't crash when bridging to `Locale`.
Expose `shouldShowPriceConsent` and `showPriceConsentIfNeeded` on PurchasesDelegate. Now you can manually handle showing the price consent if `shouldShowPriceConsent` is set to `false`
Resolved [CF-69]
Also updated some project settings for the APITesters to produce a couple less warnings
Updated PromotionalOfferAPI.Swift to remove a couple warnings