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)
## Motivation
- Compile errors in Xcode 15 with iOS 16
- Flaky tests
## Description
### iOS 16 fixes
The following things aren't available:
- `@available(visionOS)`
- `.topBarTrailing` (need to use `.navigationBarTrailing`)
- Fix issues with `Locale(identifier: "")` now returning `en` but ONLY
on iOS 16 in Xcode 15
- iOS 17 returns the expected `nil` in Xcode 15
- There was a test using `#if swift` that was now being run by iOS 16
tests that needed an additional
`AvailabilityChecks.iOS17APIAvailableOrSkipTest()`
### Flaky tests fixes
Using new `scan_with_flaky_test_retries` action from in
`fastlane-plugin-revenuecat_internal`
- Replaces the `retry_failed_tests` lane that did a similar thing
- Removed a bunch of duplicated steps in the `.circle/config.yml` that
needed to run the `retry_failed_tests`
- `scan_with_flaky_test_retries` is now used in:
- `test_ios`
- `test_tvos`
- `test_watchos`
- `backend_integration_tests`
### Carthage tests timing out
The carthage installation tests timed out pretty regularly. They will
now use the large resource class (where everything else uses the medium)
This was a regression introduced by #3605.
### Example:
Consider a paywall configuration with these locales: `en_US`, `de_DE`.
Running on a device with locale `en_IN` for example.
Before #3605, `PaywallData.localizedConfiguration` would have returned
the `en_US` localization. However, that change made it so that it could
no longer find a matching localization with a different region.
This fix improves the locale lookup so that if it doesn't find a
matching localization with both language AND region, it then tries to
look it up with only the language, before falling back to the default
localization.
This allows decorating a `String` property in a `Decodable` type so it
ensures it gets converted to `nil` if it's empty.
It will be used for `PaywallData`.
We were escaping users for every (hopefully) request. This refactor
moves that responsibility to the path creation in `HTTPRequest.Path` to
ensure it's done consistently.
Additionally, this is required so that `SigningTests` produce a
consistent path (see #2746).
This also refactors the implementation to be able to share the same
code.
This algorithm will be used for some of the components in the new
signatures.
See also #2397.
Xcode 14.3 introduced the following error:
> Heterogeneous collection literal could only be inferred to '[String :
Any]'; add explicit type annotation if this is intentional
### Changes:
- Added explicit types to fix this new error
- Removed unnecessary `@preconcurrency` imports
- Added new `CustomerInfo.emptyInfo` to reduce duplication of same fake
`CustomerInfo` creation
## Changes:
- `DeviceCache` support for caching `ProductsEntitlementsResponse` as
well as "last updated" date
- Added `JSONEncoder.encode` helper matching `JSONDecoder.decode`
version, with an optional `logErrors` parameter
- `OfferingsManager. updateProductsEntitlementsCacheIfStale`
- `Purchases.updateAllCaches` also updates this new cache
- Added `OfferingsManager` / `DeviceCache` / `Purchases` tests
- Integration test for fetching mapping
### Purpose:
This is a prerequisite for #2288. The new grace period means that if we
didn't update the date from the cached responses, entitlements would
become stale and expired after 3 days.
### Changes:
- Added `HTTPResponseBody.copy(with newRequestDate:)` to be able to
modify responses from the header request date
- `CustomerInfo` implements this method to modify its request date
- Added `HTTPResponse.requestDate` to be able to keep track of the
request date from the server
- Added `VerificationResult.from(cache:response:)` to determine the most
restrictive verification result based on what's cached or checked from a
response
- Added `HTTPResponse.copy(with:)` to modify the verification result of
a response using the previous method
- `HTTPClient` now uses the most restrictive verification result
- `HTTPClient` updates request date from server responses or cached
responses (unless verification failed). This was the missing piece for
#2288.
### Tests:
- Verify that `ETagManager` does not use an ETag if verification was
previously not enabled
- Verify that `ETagManager` returns the request date from the server
when returning a cached response
- Verify that `HTTPClient` updates the request date from the server or
from a cached response
- Verify that `HTTPClient` does not update request date if verification
failed
- Tests for `HTTPResponse` request date parsing
- Test for `CustomerInfoResponseHandler` updating request date
- Tests for `CustomerInfo.copy(with newRequestDate:)`
### Other smaller changes:
- Moved `CustomerInfo.asData()` into `Encodable.asJSONEncodedData()`
- Renamed `requestTime` to `requestDate` everywhere for consistency
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 `base64` and 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.testDoesntCacheForDifferentReceipts`
ensures that different receipts have different cache keys, and therefore
this new implementation still avoids collisions (other than the
extremely unlikely hash collisions).
[SDKONCALL-200]:
https://revenuecats.atlassian.net/browse/SDKONCALL-200?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This simplifies the `+=` implementation by avoiding an intermediate
dictionary, and allows a simpler way of merging dictionaries.
Will be used in #2174.
Co-authored-by: Cesar de la Vega <cesarvegaro@gmail.com>
I was adding a temporary test to verify a receipt, and made these better
to help debugging.
Mainly because I had to look at this failing code to realize that the
new receipt needed to be `.txt` (since Xcode doesn't show extensions
anymore), so I added that to the error messages.
Created `Async` with some shared methods that make this conversion
simpler in a type-safe way.
Helps turn this:
```swift
_ = Task<Void, Never> {
do {
completion(.success(try await f()))
} catch {
completion(.failure(ErrorUtils.error())
}
}
```
Into this:
```swift
Async.call(with: completion) {
try await f()
}
```
Simplifies the common pattern in tests:
```swift
expect(array).to(haveCount(1))
let element = try XCTUnwrap(array.first)
```
Into simply:
```swift
let element = try XCTUnwrap(array.onlyElement)
```
Fixes#1685.
### Description
Swift's `lazy` and `static` property initialization is always thread-safe. However, `key` was only created as an empty dictionary, and the evaluation of `ROT13.string`, which was the one initializing that data, wasn't isolated.
By creating `ROT13.key` inline, this ensures that the initialization is only done once in a thread-safe way.
See also #1658.
This updates the behavior of `CustomerInfoResponse` decoding so that having invalid content inside of one of the structures fails more loudly instead of getting ignored.
For that, `@DefaultValue` no longer ignores errors, and instead `@IgnoreDecodeErrors` must be used.
For some of the values in `CustomerInfoResponse` this makes sense (like `Store`), but not for things like subscriptions where we would be losing data.
See #1651.
Since that regression fix, we're no longer using it because it's not possible to make it decode dictionary keys without changing their case.
Therefore we can't ensure data isn't changed in the process.
Both `return` statements where the same, the `else` was just providing a default value to `customerInfoIfSynced`.
Because `??` doesn't support an `async` function, we couldn't use that. By using this new operator we can simplify the implementation to a single `return` statement.
This allows having properties in an `Encodable` type that won't be encoded.
This will be used to storing the `rawData` when decoding our types (see #1496).
Turns out `Data(string.utf8)` is a much simpler way of converting `String` to `Data` that doesn't lead to an `Optional`.
UTF-8 can represent all valid Unicode code points, so this was always safe, but now we know at compile time.
The reason the old variant returned an `Optional` is because it accepts other encodings like `.ascii` which might not be able to represent everything in the `String`.
This simplifies several parts of the code that were dealing with an error that was impossible.