Commit Graph
83 Commits
Author SHA1 Message Date
Cesar de la Vega 29250a4f11 Add X-Preferred-Locales header (#4008)
Added a new `X-Supported-Locales` header to be used in the new customer
center feature
2024-07-02 17:32:50 +02:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
Josh Holtz c6e185ff07 Only install swiftlint on Xcode 15 jobs (#3913)
### Motivation

Better implementation of
https://github.com/RevenueCat/purchases-ios/pull/3909 and what was just
commited to `5.0-dev`

### Description

- Only install `swiftlint` on Xcode 15.3 jobs
- Also moved Ruby 3.2.2 install into `install-bundle-dependencies` which
was also done in `5.0-dev`
2024-05-18 08:21:02 -05:00
Toni Rico bb8066d9b3 Add http_request_performed diagnostics event (#3897)
### Description
This adds a new diagnostics event `http_request_performed` with some
useful information we would like to track about the http requests
performed to our servers.
2024-05-16 15:13:40 +02:00
Cesar de la Vega becc15243b Add origin to HTTPResponseType (#3793)
This is needed for Diagnostics
2024-04-10 13:38:15 +02:00
Cesar de la Vega a956dbec71 Add name property to HTTPRequestPath (#3790)
This will be used for diagnostics
2024-04-10 13:06:05 +02:00
Cesar de la Vega 03d93bb061 Add HTTPRequest.DiagnosticsPath (#3791)
Create DiagnosticsHTTPRequestPath for Diagnostics

Depends on #3790 which adds the name
2024-04-10 13:00:48 +02:00
NachoSoto 1cea9978aa Trusted Entitlements: tests for signature verification without header hash (#3505)
Follow up to #3424. This adds coverage to ensure that the backend
continues to sign correctly for old SDK versions that don't support
this.
2023-12-21 12:47:34 -08:00
NachoSoto fb76e662e8 Trusted Entitlements: add support for signing request headers (#3424)
This adds support for including arbitrary headers in the signature
verification, therefore preventing tampering of them.
2023-12-21 11:53:10 -08:00
NachoSoto e31a531ca1 HTTPClient: test all request headers (#3425)
This allows us to verify exactly what headers are included in all
requests.

It also simplifies the implementation for #3424.
2023-11-17 12:19:49 -08:00
Josh HoltzandNachoSoto 7d78633fd8 Adds X-Storefront request header for App Store Storefront (#3405)
### Motivation

Allow backend to determine Storefront of customer

### Description

- Adds `X-Storefront` as a default header
- Uses `StoreKit 1` implementation since its not `async`
- `StoreKit 2` implementation is `async` and would require a some
restructuring that we don't need right now

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
2023-11-14 14:04:07 -08:00
NachoSoto ea952f0108 ErrorUtils.purchasesError(withSKError:): handle URLErrors (#3346)
Fixes #3343.

`SKPaymentTransaction.error` docs say:
> The error property is undefined except when `transactionState` is set
to `SKPaymentTransactionState.failed`. Your application can read the
error property to determine why the transaction failed. For a list of
error constants, see `SKErrorDomain` in StoreKit Constants.

Which is why [`PurchasesOrchestrator` only considered that
domain](https://github.com/RevenueCat/purchases-ios/blob/4.28.1/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift#L792).

This expands this handling to consider any other network error,
therefore providing more precise errors.

Since offline connection error detection was only in `HTTPClient`, I
also moved that out and removed `NetworkError.offlineConnection`, so
that we detect this when converting `NetworkError.networkError` instead.
2023-10-25 20:25:09 -07:00
NachoSoto ed80980d08 HTTPClient: verbose logs for request IDs (#3320)
See https://github.com/RevenueCat/khepri/pull/6798.
This can help debug issues as it helps us cross-reference requests on
our end.

### Changes:
- Logging `X-Request-ID` and `X-Amzn-Trace-ID` headers if present (only
in verbose mode)
- Logging these even for failed requests
- Also logging `request_handled_by_load_shedder` for failed requests
- To accomplish that. `HTTPURLResponse` now conforms to our
`HTTPResponseType`, this allows us to extract headers in an uniform way

### Example:
```
[network] DEBUG: ℹ️ API request completed: GET '/v1/subscribers/$RCAnonymousID%3Ad9dc4f3b33054cf28c0f2ec9da0f48d5' (304)
Request-ID: 'be534c50-5231-403b-be2f-7c077861751d'; Amzn-Trace-ID: 'Root=1-6532c6ca-7ef80f961ec9c4145484df13'
```
2023-10-25 20:24:23 -07:00
NachoSoto 5e968ad268 Paywalls: implemented PostPaywallEventsOperation (#3158)
This adds the operation + `PaywallEventsRequest` and associated tests.
Also added method to `InternalAPI` for posting events.
2023-09-14 18:28:11 -07:00
NachoSoto 341f0d340c Integration Tests: improve flaky tests (#3163)
We get a lot of flaky failures on `OfflineStoreKitIntegrationTests`
(example:
https://app.circleci.com/pipelines/github/RevenueCat/purchases-ios/14843/workflows/a3cffa7c-da25-4811-931d-fc241befee0f/jobs/118214/tests).
I managed to reproduce locally running some of these 100+ times on a
loop until they failed.

I traced the issue to this sequence:
- SK2 purchase
- `StoreKit.Transaction.unfinished` is empty

This is likely due to a race condition. In practice, when computing
offline `CustomerInfo`, we never check it right away, since we need to
wait for the server to return a 500.

I filed a Radar for this: `FB13133387`.

To make tests more realistic, I introduced a delay in `HTTPClient`
returning fake 5xx errors. I haven't been able to reproduce this issue
again, so this should make `OfflineStoreKitIntegrationTests` more
reliable.

I've also added a new debug log that could help debug similar issues,
because `Strings.offline.computed_offline_customer_info` was not very
complete.
2023-09-07 12:25:19 -07:00
NachoSoto 75c0d03ce6 HTTPClient: also parse errors with application/json;charset=utf8 (#3041)
See #2529.
2023-08-17 09:45:22 -07:00
NachoSoto 833524314d HTTPClient: extracted HTTPRequestPath protocol (#2986)
This paves the way to add a second set of paths with a separate server
URL to be able to shed requests to offerings and product entitlement
mapping to a separate server.

### Changes:
- New `HTTPRequestPath` with the same properties as `HTTPRequest.Path` +
a new `serverHostURL`
- Moved proxy logic from `SystemInfo`/`HTTPClient` to `HTTPRequestPath`
- Simplified `HTTPRequest` implementation
2023-08-10 15:23:13 -07:00
NachoSoto bb3a974eb8 Codable: debug log entire JSON when decoding fails (#2864)
This is a simple example, but on a larger JSON it becomes easier for us
to debug decoding the entire JSON rather than seeing only the part that
failed:
```
[codable] DEBUG: ℹ️ Encountered error when decoding JSON: {"e": "e3"}
[codable] ERROR: 😿‼️ Couldn't decode data from json, it was corrupted: Context(codingPath: [CodingKeys(stringValue: "e", intValue: nil)], debugDescription: "Cannot initialize E from invalid String value e3", underlyingError: nil)
```
2023-07-24 13:39:08 +02:00
NachoSoto f7e36fb993 Trusted Entitlements: add support for signing POST body (#2753)
### Changes:
- `HTTPClient` has a new `X-Post-Params-Hash` request header
- Replaced `HTTPRequest.Method.post`'s `Encodable` value with a more
specific `HTTPRequestBody` (like `HTTPResponseBody`)
- `HTTPRequestBody` allows overriding `contentForSignature` (it defaults
to `[]`) which then `Signing` uses for the hash
- Fixed `LogInOperation` not passing the `VerificationResult` to the
result `CustomerInfo`. This is captured in unit and integration tests
now.
- Add support for signing `POST` requests in `Path.logIn` and
`Path.postReceiptData`

Depends on https://github.com/RevenueCat/khepri/pull/6246
2023-07-13 07:45:16 -07:00
NachoSoto c5552fa453 IgnoreDecodeErrors: log decoding error (#2778) 2023-07-12 06:35:54 -07:00
NachoSoto 48e9560c09 Trusted Entitlements: log signature errors on requests with .informational mode (#2751)
Example (the first `warn` log is `debug` and when running RC tests
only):
```
[signing] WARN: ⚠️ INVALID SIGNATURE DETECTED:
Request: GET /v1/subscribers/identify
Response: 200
Headers: ["X-Signature: signature", "X-RevenueCat-Request-Time: 1658749988564", "Content-Length: 0", "X-RevenueCat-ETag: etag"]
Body (length: 0): e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
[signing] ERROR: 😿‼️ Request to subscribers/identify failed verification. This is likely due to a malicious user intercepting and modifying requests.
```
2023-07-07 14:05:55 -07:00
NachoSoto bd3ac5faf6 Trusted Entitlements: produce verification failures for static endpoints with no signature (#2752)
Equivalent to https://github.com/RevenueCat/purchases-android/pull/1119
2023-07-06 10:08:45 -07:00
NachoSoto 95a5a97c1c Trusted Entitlements: add API key and HTTPRequest.Path to signature (#2746)
Depends on #2747.

### Changes:
- Changed `Signing` to a `final class`, so it can be instantiated with
the api key.
-  Updated all `SigningTests` to include the correct signatures
2023-07-05 16:39:04 -07:00
NachoSoto 3da1c50925 HTTPRequest.Path: escape appUserID (#2747)
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).
2023-07-05 15:41:51 -07:00
NachoSoto 3f538fc6f9 Trusted Entitlements: update handling of 304 responses (#2698)
### Changes:
- `ETagManager` no longer stores the verification mode
- Created new `VerifiedHTTPResponse`, which has the
`VerificationResult`, extracted from `HTTPResponse`
- Removed `VerificationResult.from(cache:response:)`
- Changed `SignatureVerificationHTTPClientTests` to test using the real
`ETagManager`, so they become more "end to end" unit tests. A lot of
them were relying on implementation details of the mock instead of the
real `ETagManager`.
- Removed several `HTTPClient` tests that were checking a behavior that
was impossible (for example, no verification result despite it being
enabled), or that checked behavior based on the cached
`VerificationResult`
2023-06-30 12:24:53 -07:00
NachoSoto d457cb01d0 Trusted Entitlements: new Signature format (#2679)
### New format:
 - 32 bytes: intermediate public key
 - 4 bytes: Expiration (in days since epoch)
- 64 bytes: intermediate public key signature, signed with the root
private key
 - 16 bytes: salt
 - 64 bytes: payload signature:
   - salt
   - nonce (if present)
   - request time (as int string)
   - etag (if present)
   - payload

This also adds support for optional nonces for "static" signatures,
which is required for #2667.
2023-06-30 11:50:52 -07:00
NachoSoto 984b231d92 Refactor: extract ErrorResponse into its own file (#2697) 2023-06-23 07:53:53 -07:00
NachoSoto 7298dfa69f ETagManager: refactored e-tag creation and tests (#2671)
We had lots of different parts of the code doing
`request.url?.absoluteString` and also using different values for
e-tags.
This refactor combines all that to use a single `eTag(for:)` method.

I did this as part of #2665, which is getting closed, but it will still
be useful if we ever decide to change the content of e-tags.
2023-06-21 11:02:45 -07:00
NachoSoto f9660fbd1f LoadShedderIntegrationTests: verify requests are actually handled by load shedder (#2663) 2023-06-19 08:16:57 -07:00
NachoSoto 5c35df27d9 ETagManager.httpResultFromCacheOrBackend: return response headers (#2666) 2023-06-19 08:16:16 -07:00
NachoSoto ed35ab2da9 HTTPClient: disable URLSession cache (#2668)
Turns out this was breaking an HTTP 304 integration test in #2659. I'm
very confused why we hadn't seen it before.

Possibly it didn't matter for normal requests, but it was breaking
signature verification because the `nonce` was different on the new
request, and therefore it wouldn't match the cached signature.
2023-06-16 10:26:39 -07:00
NachoSoto 6d1cf97a41 ErrorResponse: drastically improved error messages, no more "unknown error"s (#2660)
## Description:

We've gotten several reports with error messages that aren't very
helpful, like #2390.

Turns out that whenever the API returned a non-successful response but
without an error code, we always produced _"unknown error"_.
This improves the behavior to default to showing the status code. One
example that was very obvious is setting up the SDK with an invalid API
key.

## Examples:

### `OfferingsManager.Error`:

- Before:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: The operation couldn’t be completed. (RevenueCat.NetworkError error 6.)
```

- After:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: Unknown error. API request failed with status code 401
```

### `BackendError`:
- Before:
```
[Purchases] - ERROR: 😿‼️ Unknown error.
```

- After:
```
[Purchases] - ERROR: 😿‼️ Unknown error. API request failed with status code 401
```

### Request debug logs:
- Before:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error.
```
- After:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error. API request failed with status code 401
```

### `CustomerInfoManager`:
- Before:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error
```

- After:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error. API request failed with status code 401
```
2023-06-16 09:19:16 -07:00
NachoSoto 20dcc86bdd Documented HTTPStatusCode.isSuccessfullySynced (#2661) 2023-06-16 07:55:58 -07:00
NachoSoto 6986a2f95e NetworkError.signatureVerificationFailed: added status code to error userInfo (#2657) 2023-06-16 07:55:21 -07:00
NachoSoto 208bf53386 HTTPClient: improved log for failed requests (#2669) 2023-06-16 07:49:52 -07:00
NachoSoto 869642b6bd ETagManager: added new verbose logs (#2656) 2023-06-16 07:45:24 -07:00
NachoSoto 4fff1dc54e Fixed HTTPResponse.description (#2664)
Probably a bad conversion to multiline string. It had a leftover `" +`
2023-06-16 09:33:45 +02:00
NachoSoto ea173c0f7e copy(with: VerificationResult): optimization to avoid copies (#2639) 2023-06-14 07:05:37 -07:00
NachoSoto 06b0f3d723 Add InternalDangerousSettings.forceSignatureFailures (#2635)
Similar to #2486. This will be used for SDK-3181.

```
- WARN: ⚠️ Returning fake signature verification failure for 'GET /v1/subscribers/$RCAnonymousID:fc0508bea4a74a41bfc9a69db30d925f'
- ERROR: 😿‼️ Request failed signature verification. Request to /v1/subscribers/$RCAnonymousID%3Afc0508bea4a74a41bfc9a69db30d925f failed verification
```
2023-06-13 13:43:16 -07:00
NachoSoto 2276d5c2ce AppleReceipt.debugDescription: don't pretty-print JSON (#2564) 2023-05-31 07:11:23 -07:00
NachoSoto 94537a39af HTTPClient: don't assume error responses are JSON (#2529)
When receiving 500 responses, `HTTPClient` was always calling
`ErrorResponse.from(_ data: Data)`, which tried to decode the content as
if it was JSON.
However, if the response had something completely different, it would
fail to decode.

This wasn't really a bug because `HTTPClient` was still forwarding the
right result, but now it will do so without even trying to parse it.
2023-05-24 09:51:50 -07:00
NachoSoto 50ffbfb3e9 Performance: new check to ensure serialization / deserialization is done from background thread (#2496) 2023-05-19 11:58:12 -07:00
NachoSoto e9e6d887c2 Offline Entitlements: integration tests (#2501)
## Example tests:
```swift
func testOfferingsAreCachedInMemory() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings) === onlineOfferings
}

func testOfferingsAreCachedOnDisk() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()
    await self.resetPurchases()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings.response) == onlineOfferings.response

    let offering = try XCTUnwrap(offlineOfferings.current)
    expect(offering.availablePackages.count) == onlineOfferings.current?.availablePackages.count
    expect(offering.monthly?.storeProduct.productIdentifier) == "com.revenuecat.monthly_4.99.1_week_intro"
}
```
2023-05-19 10:54:42 -07:00
NachoSoto b04d894ccb DangerousSettings: debug-only forceServerErrors (#2486) 2023-05-16 09:59:51 -04:00
NachoSoto d8985986af ETagManager: don't send validation time if not present (#2490) 2023-05-16 09:59:00 -04:00
NachoSoto d68ca889e9 HTTPClient: log actual response status code (#2487)
We were currently logging the status code retrieved from `ETagManager`,
which made it seem like etags weren't working.

Example:
> API request completed: GET /v1/subscribers/identify (200)

With this change, we now log the actual response status code so we can
verify the server is returning the correct status code:
> API request completed: GET /v1/subscribers/identify (304)
2023-05-15 19:28:25 -04:00
Andy BoedoandNachoSoto 618ff121d2 Custom Entitlements Computation mode (#2439)
### Changes:

- Added `Purchases.configureInCustomEntitlementsComputationMode`
- Added `Purchases.switchUser(to:)`
- Disabled parts of the SDK in this new mode
- New example app for testing `customEntitlementsComputation` mode

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
2023-04-25 13:21:46 -07:00
NachoSoto 9c1bb9dbaf ProductEntitlementMapping: enabled entitlement mapping fetching (#2425) 2023-04-24 06:04:30 -07:00
NachoSotoandAndy Boedo 8b4c2eb706 ETagManager/HTTPClient: sending new X-RC-Last-Refresh-Time header (#2373)
Fixes SDK-3024 and SDK-3026.

---------

Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2023-04-14 14:32:24 -07:00
NachoSoto 06f80c30a1 HTTPClient: debug log when performing redirects (#2371)
This will be used to detect the load balancer redirecting to the load
shedder. See also #2362.
2023-03-23 14:34:14 -07:00