Commit Graph
83 Commits
Author SHA1 Message Date
c4d824d68c feat(remote-config): source API base host from remote-config sources (#7123)
* feat(remote-config): wire manager behind feature flag

* fix(remote-config): only mark refresh fresh after cache write

* feat(remote-config): source API base host from remote-config sources

Resolve the API base host from the shared RemoteConfigSourceProvider (with
the embedded defaults as a floor) instead of the hardcoded apiBaseURL, so
requests can fail over across API hosts. proxyURL and an overridden
apiBaseURL still pin the host and bypass API sources, and per-endpoint
fallback URLs are unchanged.

The provider is created once in the composition root (gated on
remoteConfigEnabled) and injected explicitly into Backend/HTTPClient; nil
preserves the existing SystemInfo.apiBaseURL behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): derive nullable API source provider and manager from remoteConfigEnabled

Create the RemoteConfigSourceProvider (and its shared on-disk cache) up front,
gated on remoteConfigEnabled, and build the RemoteConfigManager from the
provider's nullability after Backend exists so it can reuse backend.remoteConfigAPI.
Keeps a single shared source provider + disk cache across the networking layer
and the manager, and avoids touching the disk cache directory when remote config
is disabled.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): drop redundant remote-config wiring comment

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): drop redundant remote-config manager comment

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): simplify APISourceProviding doc comment

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): always wire API source provider, gate only disk cache

Create the RemoteConfigSourceProvider unconditionally and inject it into
Backend/HTTPClient so main-API host resolution runs even when remote config
is disabled (embedded default sources as the floor). Only the on-disk cache
and the real RemoteConfigManager stay gated on remoteConfigEnabled; the
provider's topic store is now optional (nil means defaults-only).

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): rename APISourceProviding to APISourceProviderType

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(remote-config): resolve web billing host from API sources

The web billing paths are hosted on the same api.revenuecat.com host as the
main API, so they should resolve their base host from the API source provider
too (with failover) instead of the static serverHostURL.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): drop redundant web billing API-sources comments

The comments restated what the code and surrounding context already convey.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(remote-config): assert ETag retry stays on the API source host

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(remote-config): drop APISourceProviderType, use RemoteConfigSourceProviderType directly

The networking layer depended on the narrow APISourceProviderType protocol, an extra
abstraction over RemoteConfigSourceProviderType that added little. Fold currentAPISource()
into RemoteConfigSourceProviderType and have HTTPClient/Backend depend on it directly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(remote-config): wire apiSourceProvider into dedicated remote-config lane

The main-into-branch merge left `HTTPClient.dedicatedRemoteConfig` (and its
call site) without the new `apiSourceProvider` argument, breaking compilation.
Thread the provider through so the dedicated `/config` lane also resolves its
base host from remote-config API sources, matching the main lane.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: gate remote-config API sources behind usesRemoteConfigAPISources

Resolve main-API request hosts from the remote-config API sources only when
the `usesRemoteConfigAPISources` dangerous setting is enabled. When disabled
(the default), requests keep targeting the static `SystemInfo.apiBaseURL`.

Enable the setting in the API-source `HTTPClient` tests and add a test
asserting an injected provider is ignored while the setting is off.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(test): pass apiSourceProvider in BackendRemoteConfigLaneTests HTTPClient init

The lane test constructed HTTPClient without the now-required apiSourceProvider
argument, breaking compilation of the unit test target after merging main.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Rick van der Linden <rick.vanderlinden@revenuecat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 08:56:01 +02:00
Rick b82223e5a1 feat(remote-config): add static fallback config endpoint (#7182)
* feat(remote-config): add static fallback config endpoint

* refactor(remote-config): clarify static fallback path name

* test(remote-config): cover static fallback signature failures

* fix(remote-config): use static fallback only without cache

* test(remote-config): cover static fallback external blobs

* refactor(remote-config): align static fallback naming

* refactor(remote-config): scope static fallback path

* fix(remote-config): mark fallback no content fresh

* fix(remote-config): align fallback accept headers

* refactor(remote-config): rename fallback endpoint types

* fix(remote-config): enable etags for fallback config

* fix(remote-config): skip fallback when proxy is configured

* fix(remote-config): rely on etag cache for fallback

* refactor(remote-config): reuse fallback retry eligibility

* fix(remote-config): disable after fallback client errors
2026-07-10 14:17:31 +02:00
Antonio PallaresandCursor cbdc1ae216 Don't switch hosts on device-connectivity errors (#7176)
* other(networking): don't switch hosts on device-connectivity errors

Add `NetworkError.isDeviceConnectivityError` (true only for device-side
URLError codes) and narrow the fallback-host retry gate to
`isTransient && !isDeviceConnectivityError`, so a device that is offline
no longer triggers pointless host switching. Host-side and ambiguous
failures stay switch-eligible.

Co-authored-by: Cursor <cursoragent@cursor.com>

* other(networking): make isDeviceConnectivityError doc less usage-specific

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 12:06:50 +02:00
RickandRevenueCat Git Bot f4c06c1f4a feat(remote-config): add rc container compression support (#7107)
* feat(remote-config): add rc container compression support

* fix(remote-config): use rc element encoding header

* test(remote-config): align rc container warning logs

* Update baseline swiftinterface files (#7108)

* fix(remote-config): remove unused accept encoding header case

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2026-07-01 11:58:03 +02:00
Rick 6330937692 feat(remote-config): support RC Container format in network stack and use for remote-config request (#7037)
* feat(remote-config): request RC Container format

* fix(remote-config): handle no content responses

* chore(remote-config): allow snapshot todo

* fix(remote-config): disable response verification

* chore(remote-config): document disabled signing

* refactor(remote-config): parse RC containers in HTTP stack
2026-06-19 14:38:38 +02:00
d4f0fb2686 refactor(ads): reuse SDK retry classification for poll errors (#7009)
* refactor(ads): move rewardverification to coresdk

* refactor(ads): move rewardverification to coresdk

* refactor(ads): move rewardverification to coresdk

* refactor(ads): remove unnecessary comment

* fix(ads): linter and behavior fixes

* fix(ads): gate reward-verification VC cache invalidation out of CustomEntitlementComputation

* refactor(ads): rename reward-verification transactionId → clientTransactionID

* fix(ads): remove duplicated SSV payload logic from AdMob adapter

* chore(ads): cover install via injectable

* chore(ads): fix linter

* refactor(ads): introduce RewardVerificationToken

* refactor(ads): add reward_verification_completed log

* Update AdapterSDKs/RevenueCatAdMob/Tests/RevenueCatAdMobTests/PresentRewardVerificationTests.swift

Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>

* fix(ads): linter

* fix(ads): key reward verification poll retries on HTTP status

Retry network/offline transport errors and any HTTP 5xx during reward
verification polling, keying on the HTTP status code rather than the
mapped ErrorCode. Empty/unparseable 5xx responses are now retried, and
4xx client errors fail fast as .backendError instead of being retried to
a misleading .timeout.

* refactor(ads): drop redundant comments on poll retry classifier

* refactor(ads): read poll HTTP status via typed NSError accessor

* refactor(ads): reuse SDK retry classification for poll errors

Classify reward-verification poll retries from the structured BackendError
instead of re-deriving the HTTP status from a flattened public NSError.

Adds a reusable NetworkError.isTransient (5xx and connection-level failures
are transient, 4xx are terminal) and forwards it from BackendError; the
HTTPClient fallback-host retry check now delegates to it. The poll path keeps
the structured BackendError via an internal fetchRewardVerificationStatus,
letting the poller reuse BackendError.isTransient and dropping the bespoke
NSError.httpStatusCode accessor.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(ads): switch from experimental to internal

* docs(ads): reference BackendError in Poller doc comment

---------

Co-authored-by: Peter Porfy <peter.porfy@revenuecat.com>
Co-authored-by: Pol Miro <polmiro@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 03:45:52 -06:00
0b65086ec2 Add workflows network layer for multipage paywalls (#6557)
* Add workflows network layer for multipage paywalls

- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components

iOS equivalent of https://github.com/RevenueCat/purchases-android/pull/3300

Made-with: Cursor

* fix xcodeproj

* reset Package.resolved

* Update WorkflowStep models to match actual backend response

- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)

Made-with: Cursor

* Update WorkflowResponseTests to match updated models

- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload

Made-with: Cursor

* Disable file_length lint rule in HTTPRequestPath.swift

Made-with: Cursor

* Fix GetWorkflowOperation to compute result once before distributing to callbacks

CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.

Made-with: Cursor

* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly

- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
  in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
  and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
  failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
  instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown

Made-with: Cursor

* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation

- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
  withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
  avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
  called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await

Made-with: Cursor

* Make CDN fetcher and processor completion-handler based for consistency

Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.

- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
  DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
  completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
  distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern

Made-with: Cursor

* Fix ambiguous cache key delimiter in GetWorkflowOperation

Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.

Made-with: Cursor

* PR comments

* remove cdn fetcher

* fix response in BackendGetWorkflowsTests.swift

* fix WorkflowResponseTests

* fix error

* [skip ci] Generating new test snapshots (#6584)

* [skip ci] Generating new test snapshots (#6585)

* [skip ci] Generating new test snapshots (#6586)

* [skip ci] Generating new test snapshots (#6587)

* [skip ci] Generating new test snapshots (#6588)

* [skip ci] Generating new test snapshots (#6589)

* [skip ci] Generating new test snapshots (#6590)

* Test CDN mock is not re-assignable per test

* [skip ci] Generating new test snapshots (#6597)

* [skip ci] Generating new test snapshots (#6598)

* [skip ci] Generating new test snapshots (#6599)

* [skip ci] Generating new test snapshots (#6600)

* [skip ci] Generating new test snapshots (#6601)

* [skip ci] Generating new test snapshots (#6602)

* [skip ci] Generating new test snapshots (#6603)

* getWorkflow is signed

* add type parameter

* add response verification for CDN response

* remove workflows list

* add value to WorkflowTriggerAction

* step id

* hash and filerepo

* missingCdnHash

* linter and project

* fix project

* revert Package.resolved

* @unchecked Sendable

* use hash in generateOrGetCachedFileURL

* fix compilation

* skip responseVerificationMode in cdn

* change basePath of caches

* remove reserialization

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests

Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* remove default FileRepositoryType

* fix project

* fix test

* test preserves camel case

* Generating new test snapshots for `feat/workflows-network-layer` - ios-15 (#6654)

* Generating new test snapshots for `feat/workflows-network-layer` - ios-14 (#6656)

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Facundo Menzella <facumenzella@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Facundo Menzella <facumenzella@users.noreply.github.com>
2026-04-21 12:32:01 +02:00
Pol Miro a59ca8f4ea Expose apiKey on Purchases via @_spi(Internal) (#6635)
Adapter modules that import the SDK with `@_spi(Internal) import RevenueCat`
(e.g. `RevenueCatAdMob`) need to read the configured public API key when
constructing payloads such as the AdMob SSV `customRewardString`.

- Store the API key on `SystemInfo` and wire it through `Purchases` init.
- Add `@_spi(Internal) public var apiKey: String` on `Purchases`.
- Add a Swift API tester case so any future signature change is caught.
- Add a unit test verifying the accessor returns the configured key.

The key is already public plaintext in the app binary, so exposing it as SPI
does not introduce any new security risk.
2026-04-21 08:14:42 +02:00
Antonio PallaresandRevenueCat Git Bot f1d9fb86bb Send X-Installation-Method header in all API requests (#6431)
* Send X-Installation-Method header in all API requests

Detect at compile time which package manager was used to integrate the
SDK (SPM, CocoaPods, or prebuilt XCFramework) and send it as a new
X-Installation-Method header on every HTTP request. This helps the
backend understand how customers integrate the SDK.

Made-with: Cursor

* Add installation method assertions to installation tests

Expose installationMethod via @_spi(Internal) on Purchases and verify
the correct value in each installation test project (SPM, CocoaPods,
Carthage, Xcode direct, and XCFramework).

Made-with: Cursor

* [skip ci] Generating new test snapshots (#6433)

* [skip ci] Generating new test snapshots (#6435)

* [skip ci] Generating new test snapshots (#6434)

* [skip ci] Generating new test snapshots (#6440)

* [skip ci] Generating new test snapshots (#6436)

* [skip ci] Generating new test snapshots (#6437)

* [skip ci] Generating new test snapshots (#6438)

* [skip ci] Generating new test snapshots (#6439)

* [skip ci] Generating new test snapshots (#6443)

* Fix missing COCOAPODS_INSTALLATION flag on CocoaPods test targets

The flag was only set on the app target, not on the test targets, so
the #if COCOAPODS_INSTALLATION branches in InstallationTests.swift
were never compiled for CocoaPods tests. Add OTHER_SWIFT_FLAGS with
-DCOCOAPODS_INSTALLATION to all four CocoaPods test target configs.

Made-with: Cursor

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2026-03-12 12:05:14 +01:00
Rick 73c5534bf7 Fix reduced timeouts being used for HTTP requests when a proxy URL is configured (#6416) 2026-03-09 08:47:13 +01:00
RickandRevenueCat Git Bot 94ac735467 Track connection_error_reason property in diagnostics for HTTP errors (#5860)
* Implemented initial HTTPRequestTimeoutManager + added first couple of tests

* Cleaned up the code and added a bunch more unit tests based on the Android test suite

* Updated doc format

* Add new files to workspace

* Added HTTPClient tests testing interaction with timeout manager

* Swiftlint fixes

* Updated comment

* Add missing file to workspace

* Fix after rebase

* Added extra HTTPClient <-> HTTPRequestTimeoutManager tests asserting on the recorded events

* Implemented PR feedback

* Fixed compile issue

* Fix compile issue

* Implemented PR feedback

* Removed deleted file reference from workspace

* Fixed test after changing reset timeout

* Make sure the default HTTPRequistTimeoutManager is using the default value from the HTTPClient

* Add a sophisticated test testing the full timeout flow against an unroutable IP for a real world scenario

* Updated timeout case name

* Fixed case where .timeoutOnMainBackendForFallbackSupportedEndpoint was also recorded for non timeouts and added tests to prevent regression

* Fixed only stubbing the HTTP response based on path, since that could be the same across hosts, resulting in flaky tests

* Implemented tracking ConnectionErrorReason using DiagnosticsTracker on HTTP errors

* Add new file to workspace

* Updated string value of ConnectionErrorReason cases to match Android implementation

* [skip ci] Generating new test snapshots (#5868)

* [skip ci] Generating new test snapshots (#5869)

* [skip ci] Generating new test snapshots (#5870)

* [skip ci] Generating new test snapshots (#5871)

* [skip ci] Generating new test snapshots (#5872)

* [skip ci] Generating new test snapshots (#5874)

* [skip ci] Generating new test snapshots (#5873)

* Updated the list of URLError.code's that we consider a network error

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-11-26 13:51:54 +01:00
Rick b49a054843 Add client side timeout logic for endpoints that support fallback URLs (#5760)
* Implemented initial HTTPRequestTimeoutManager + added first couple of tests

* Cleaned up the code and added a bunch more unit tests based on the Android test suite

* Updated doc format

* Add new files to workspace

* Added HTTPClient tests testing interaction with timeout manager

* Swiftlint fixes

* Updated comment

* Add missing file to workspace

* Fix after rebase

* Added extra HTTPClient <-> HTTPRequestTimeoutManager tests asserting on the recorded events

* Implemented PR feedback

* Fixed compile issue

* Fix compile issue

* Implemented PR feedback

* Removed deleted file reference from workspace

* Fixed test after changing reset timeout

* Make sure the default HTTPRequistTimeoutManager is using the default value from the HTTPClient

* Add a sophisticated test testing the full timeout flow against an unroutable IP for a real world scenario

* Updated timeout case name

* Fixed case where .timeoutOnMainBackendForFallbackSupportedEndpoint was also recorded for non timeouts and added tests to prevent regression

* Fixed only stubbing the HTTP response based on path, since that could be the same across hosts, resulting in flaky tests
2025-11-26 11:16:04 +01:00
d11c054407 Use cached offerings on network errors (#5707)
* Use offerings cache on (most) network errors

* Add unit tests for `NetworkError`'s `shouldFallBackToCachedOfferings`

* Add unit tests for `shouldFallBackToCachedOfferings`

* Fix lint

* Add some more unit tests

* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`

Keeps the values also when returned by the ETagManager

* lint

* Add some tests for ETagManager

* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues

* Add tests for actual source of original cached response of ETagManager

* Add tests to HTTPClientTests regarding the new booleans

* lint

* Add `originalSource` to `CustomerInfo`

* Adds `isLoadedFromCache` boolean to CustomerInfo

* Add docs

* Unit tests for new customer info properties

* Make `OriginalSource.CustomerInfo` a `String` enum

* Add decoding tests for Customer Info with new original source field

* Add `.loadShedder` check for Customer Info obtained in Load Shedder integration tests

* Update fixture

* improve internal docs

* Add intermediate object `Offerings.Contents` to wrap `OfferingsResponse` + `OriginalSource` and `loadedFromCache`

* Adapt existing unit tests to changes in `Offerings` underlying objects

* Check Offerings original source in existing integration tests

* Fix StoreKit unit tests

* Fix RevenueCatUI tests

* More Offerings.Contents tests

* Fix test

* Add more tests

* Fix lint

* [skip ci] Generating new test snapshots (#5741)

* [skip ci] Generating new test snapshots (#5742)

* [skip ci] Generating new test snapshots (#5743)

* [skip ci] Generating new test snapshots (#5744)

* [skip ci] Generating new test snapshots (#5745)

* [skip ci] Generating new test snapshots (#5746)

* [skip ci] Generating new test snapshots (#5747)

* [skip ci] Generating new test snapshots (#5748)

* Use enum for `HTTPResponseSource`

* Update tests

* Adapt to changes in `VerifiedHTTPResponse`

* Lint

* Adapt to changes in `VerifiedHTTPResponse`

* update tests

* Add log when a request to the fallback URL was handled by load shedder (which should never happen)

* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`

* Adapt to changes

* fix lint

* Adapt to changes

* Fix CustomerInfo's OriginalSource implementation + fix unit test

* Fix tests compilation

* Add Cached offerings integration test

* Fix RCUI compilation

* Move isFromCache boolean from `Offerings.Contents` to `Offerings`

* Expand `ForceServerErrorStrategy` functionality

* Improve log when faking error response

* Correct implementation of fake error response without making a request (ForceServerErrorStrategy)

* Update tests + more tests

* Fix compilation of `OfferingsManagerTests`

* Fix compilation of `DebugViewSwiftUITests`

* Improve tests

* Fix `OfflineWithNoMappingStoreKitIntegrationTests`

* FIx compilation

---------

Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-11-07 10:22:16 +00:00
Antonio Pallares 6335078c13 Add backend source to VerifiedHTTPResponse (#5736)
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`

Keeps the values also when returned by the ETagManager

* lint

* Add some tests for ETagManager

* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues

* Add tests for actual source of original cached response of ETagManager

* Add tests to HTTPClientTests regarding the new booleans

* lint

* Use enum for `HTTPResponseSource`

* Update tests

* Add log when a request to the fallback URL was handled by load shedder (which should never happen)

* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`

* fix lint
2025-11-04 20:17:33 +01:00
Rick 72cf38a0fe Added an internal API for overriding the API base URL (#5739)
* Added an internal API for overriding the API base URL

* Updated and added tests for API baseURL configuration option

* Revert change that removed serverHostURL from HTTPRequestPath since it broke other types implementing this protocol

* swiftlint

* Removed the apiBaseURL property from Purchases since it's only used for internal testing anyways

* Remove unused @_spi(Internal)

* Updated comment on api base URL vs proxy URL

* Fixed linting issue
2025-11-04 13:35:20 +01:00
Antonio Pallares 3ace587482 [SDK-4153] Use static fallback urls (#5709)
* change fallback URL logic to support a different path from the main server

* Adapt unit tests to changes in fallback urls logic

* lint

* Add unit test for fallback urls

* fix switched logic
2025-10-29 18:43:19 +01:00
Rick fae90a3da9 Allowing HTTP requests to be retried with fallback host after netwok / DNS errors as well as 5xx errors (#5703)
* Allowing HTTP requests to be retried with fallback host after network / DNS errors as well as 5xx errors

* Fixed linting issues

* Linting
2025-10-24 15:06:04 +02:00
Antonio Pallares 36daf56d20 Add infrastructure to enable richer server down integration tests (#5700)
* Add infrastructure to enable richer server down integration tests

* Fix lint

* Fix compilation and add tests for ForceServerErrorsStrategy

* Fix lint

* Fix compilation

* Fix compilation of Integration tests

* Fix tests now that server errors are not simulated locally

* Fix import

* Temp: all integration tests enabled

* rename

* Improve log message

* Revert "Temp: all integration tests enabled"

This reverts commit fd0316f267.

* Add `failExceptFallbackUrls` `ForceServerErrorStrategy`
2025-10-24 10:03:39 +02:00
Antonio Pallares f120cb26cc Preferred locale override (#5288)
* Add new public APIs to RevenueCatUI to set the preferred local for the UI components

* fix compliation of RevenueCatUI with new APIs

* add API tests

* implement preferred locale in paywalls

* complete implementation

* adapt unit tests

* adapt paywalls tester

* Add new tests to check preferred locale override

* fix lint

* discard all changes in RevenueCatUI (for separate PR)

* remove trailing comma

* fix tests

* Fix tests

* fix paywalls tester compilation

* remove spoiler from future PR

* Refactor `PreferredLocalesProvider` (#5299)

* refactor: reduce `PreferredLocalesProviderType` into `PreferredLocalesProvider` class

* make `preferredLocaleOverride` private in `PreferredLocalesProvider`

* make `SystemInfo`'s `preferredLocalesProvider` private

* rename `PreferredLocalesProvider`'s mock init parameters

* fix compilation issue
2025-06-30 16:28:32 +02:00
Antonio Pallares e0c94708be [Diagnostics] add host parameter to http_request_performed event (#4982)
* feat: implement fallback host in all URL requests

* cleanup fallback host API implementation

* Adapt existing tests to changes in `HTTPRequestPath`

* fix implementation

* add unit tests for fallback API host

* add extra unit test

* fix tests

* fix lint

* implement new `host` parameter in `http_request_performed` diagnostics event

* add unit tests for new `host` diagnostics parameter

* change the logic from fallback hosts to fallback paths

* fix test

* fix fallback endpoint for get product entitlement mapping

* fix backend integration tests not building after changes

* rewrite fallback host functionality knowing that only some specific requests support a fallback host

For those requests that support a fallback host, the path, headers and response structure remain the same

* fix text name

* fix unit test

* remove extra comma

* fix unit test
2025-04-25 17:05:33 +00:00
Antonio Pallares e991e0708a Use fallback API hosts when receiving server down response (#4970)
* feat: implement fallback host in all URL requests

* cleanup fallback host API implementation

* Adapt existing tests to changes in `HTTPRequestPath`

* fix implementation

* add unit tests for fallback API host

* add extra unit test

* fix tests

* fix lint

* change the logic from fallback hosts to fallback paths

* fix test

* fix fallback endpoint for get product entitlement mapping

* fix backend integration tests not building after changes

* rewrite fallback host functionality knowing that only some specific requests support a fallback host

For those requests that support a fallback host, the path, headers and response structure remain the same

* fix text name
2025-04-25 12:00:13 +00:00
Toni RicoandRevenueCat Git Bot 868f848bae Add X-Is-Backgrounded header (#4938)
* Add X-Is-Backgrounded header

* Add unit test for presence of new header

* [skip ci] Generating new test snapshots (#4939)

* [skip ci] Generating new test snapshots (#4940)

* [skip ci] Generating new test snapshots (#4941)

* [skip ci] Generating new test snapshots (#4942)

* [skip ci] Generating new test snapshots (#4943)

* [skip ci] Generating new test snapshots (#4944)

* [skip ci] Generating new test snapshots (#4945)

* Lock isAppBackgrounded state to false during tests

* one more update

* Revert "Lock isAppBackgrounded state to false during tests"

This reverts commit 6a915a756a.

* Lock isAppBackgrounded state to false during tests

* Update tests

* Fix iOS 15 snapshot tests

* Remove unused isApplicationBackgrounded async method

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-04-03 10:37:02 +02:00
Antonio Pallares b0292f2ee7 [Diagnostics] add missing parameter to http_request_performed event (#4857)
* refactor: create `DiagnosticsEvent.Properties` struct with all possible fields in a diagnostics event

* chore: remove unnecessary `CodingKeys` and values as `JSONEncoder.KeyEncodingStrategy.convertToSnakeCase` is used in the diagnostics requests

* tests: WIP: adapt unit tests to new `DiagnosticsEvent.Properties`

* fix: have values in Diagnostics events be snake case

* tests: adapt unit tests to Diagnostics events changes

* fix: `name` parameter in DiagnosticsEvent

* tests: fix snapshot testing of JSON to use `.iso8601` date format

* tests: have `JSONEncoder` of snapshot tests be a copy of the encoder user in the SDK

* tests: update the dates in the `CustomerInfo` snapshots for decoding tests to have ISO 8601 format

* fix: remove duplicated import

* refactor: centralize some info transformations in `DiagnosticsEvent.Properties`' init

* tests: add snapshot tests for `DiagnosticsEvents` object

* feat: add `is_retry` parameter to Diagnostics `http_request_performed` event

* tests: consider new `isRetry` parameter in existing `DiagnosticsEvent` tests

* fix: make sure that the `http_request_performed` event is always called

The previous implementation would skip sending the event in the case of getting a `networkError`
2025-03-07 11:26:32 +01:00
Antonio Pallares 25d1507411 UI Preview Mode: add extra header to network requests (#4752)
* feat: add new header to network requests when in UI preview mode

* test: add unit tests for header in network requests when in UI Preview mode
2025-01-31 12:33:19 +01:00
JayShortway 57766ce7a2 Adds X-Is-Debug-Build header (#4364) 2024-10-10 17:00:03 +02:00
Toni Rico 4503a837e6 [Diagnostics] Refactor diagnostics track methods to handle background work automatically (#4270)
* Refactor diagnostics track methods to handle background work automaticaly

* fix swiftlint
2024-09-26 16:33:28 +02:00
Toni RicoandRevenueCat Git Bot fe181bd5e9 Add kochava integration (#4274)
* Reapply "Add `$kochavaDeviceId` attribution convenience method (#4238)" (#4265)

This reverts commit f2ce4416da.

* Add userAgent tracking for attribution networks

* Fix tests

* Add X-Platform-Device header

* Rename user agent to device version

* Use correct attribution method

* [skip ci] Generating new test snapshots (#4278)

* [skip ci] Generating new test snapshots (#4279)

* [skip ci] Generating new test snapshots (#4280)

* [skip ci] Generating new test snapshots (#4281)

* [skip ci] Generating new test snapshots (#4282)

* [skip ci] Generating new test snapshots (#4283)

* Generating new test snapshots (#4284)

* Fix ttests

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2024-09-25 19:59:36 +02:00
Toni Rico 597af5f215 [Diagnostics] Add backend_error_code property (#4236)
<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-android` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->

### Description
This adds the `backend_error_code` property to diagnostics
`http_request_performed` events.
2024-09-04 09:03:34 +02:00
Will Taylor 348258a41f only retry post receipt for 429 status codes 2024-07-25 16:46:31 -05:00
Will Taylor a4055375f6 retry requests with HTTP status 429 2024-07-23 12:41:16 -05:00
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
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 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 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 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 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 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