* Remove compiler flag ENABLE_AD_EVENTS_TRACKING
* Fix: Use trackedAdEvents instead of trackedEvents in tests
The PurchasesAdEventsTests were accessing the wrong property on
MockEventsManager. They should use trackedAdEvents (which stores
AdEvent objects) instead of trackedEvents (which stores FeatureEvent
objects).
* Fix test compilation errors
- Add 'try' to actor property access in PurchasesAdEventsTests
- Remove unnecessary 'as? AdEvent' casts since trackedAdEvents already returns [AdEvent]
- Fix unused variable warnings in EventsManagerAdEventsTests by using _ instead of named variables
* Temporarily enable snapshot recording for AdEventsRequestTests
These tests were previously behind the ENABLE_AD_EVENTS_TRACKING compiler
flag and were not running in CI. Now that we've removed the flag, the tests
run but need their snapshots re-recorded in the CI environment.
This commit temporarily enables recording mode. After CI records the
snapshots, we'll revert this change and commit the updated snapshots.
* Update ad events request test snapshots
- Re-record snapshot tests that were previously wrapped in compiler flag
- Snapshots now use alphabetically sorted JSON keys
* Add Sendable conformance to ad event data classes
Fix pod-lib-lint warnings by marking ad event data classes as @unchecked Sendable.
These classes have immutable properties and are safe for concurrent access.
* Refactor: Extract flushEvents logic into flushFeatureEvents
Separates feature event flushing into its own method:
- Add flushFeatureEvents to EventsManagerType protocol
- Implement flushFeatureEvents with existing flush logic
- Update flushEvents to delegate to flushFeatureEvents
- Update Purchases.flushPaywallEvents to call flushFeatureEvents
- Add flushFeatureEvents to MockEventsManager
This prepares EventsManager for supporting multiple event types.
* Add ad event storage and serialization
Adds core infrastructure for storing and serializing ad events:
- StoredAdEvent: Model for ad events with user context
- StoredAdEventSerializer: JSON serialization/deserialization
- AdEventStore: File-based persistent storage with fetch/clear operations
- AdEventStoreTests: Comprehensive test coverage
Clean implementation without legacy migration code since feature is behind compiler flag.
* Add ad events integration to EventsManager
Implements complete ad events lifecycle in EventsManager:
- Add flushAdEvents to EventsManagerType protocol
- AdEventStore integration with new initialization parameter
- Implement track(adEvent:) with proper serialization and storage
- Update flushEvents to flush both feature and ad events
- Network request handling via InternalAPI.postAdEvents
- Comprehensive error handling and logging (EventsManagerStrings)
- Full test coverage (EventsManagerAdEventsTests)
- Mock support for ad events in test infrastructure
Network layer additions:
- AdEventsRequest: HTTP request body for ad events endpoint
- PostAdEventsOperation: Network operation for posting ad events
- InternalAPI.postAdEvents: Async method for posting ad events
* Rename adInstanceId to impressionId to match backend schema
The backend expects `impression_id` but we were sending `ad_instance_id`.
This renames the field throughout the codebase:
- AdEvent.swift: Update AdEventData protocol and all event classes
- AdEventsRequest.swift: Update serialization field name
- All test files: Update to use impressionId parameter
- Objective-C API: Update parameter names
The field will be serialized as `impression_id` via snake_case conversion.
* Add AdEventsIntegrationTests and internal flush method
- Add AdEventsIntegrationTests.swift following BaseBackendIntegrationTests pattern
- Add internal flushAdEvents() method in Purchases for test access
- Add 4 test cases: tracking/flushing, empty flush, clearing after flush, persistence
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Clarify side-effects of removing the file
Co-authored-by: JayShortway <29483617+JayShortway@users.noreply.github.com>
* Remove test-only flushAdEvents method from Purchases
Refactor to expose eventsManager as internal instead of having a test-only wrapper method.
Changes:
- Make eventsManager internal instead of private in Purchases
- Remove flushAdEvents wrapper method from Purchases
- Update integration tests to access eventsManager.flushAdEvents() directly
This keeps test-only code out of the Purchases API while maintaining test access via @testable import.
* Simplify StoredAdEvent equality with sorted keys
Use JSONEncoder with sortedKeys to ensure consistent JSON encoding, allowing
StoredAdEvent to rely on automatic Equatable synthesis instead of complex
JSON decoding and NSDictionary comparison.
Benefits:
- Cleaner code: removed 18 lines of custom equality logic
- Better performance: direct string comparison vs JSON decoding
- More maintainable: automatic Equatable synthesis
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor ad events integration tests to use app lifecycle events instead of internal API
- Remove dependency on internal eventsManager property
- Revert eventsManager back to private access level
- Tests now simulate app backgrounding to trigger automatic event flushing
- Aligns with production behavior where events flush on app lifecycle events
- No public API needed for testing
* Improve testSizeLimitTriggersCleanup with stronger assertions
- Add event identity verification to ensure the new event is retained after cleanup
- Replace count-only assertions with exact event equality checks
- Add logging verification to confirm cleanup was triggered
- Remove unused mock reset code
- Improve variable naming for clarity (overflowedEvents -> remainingEvents)
* Replace Task.sleep with deterministic message waiting in ad events tests
Test improvements:
- Replace arbitrary Task.sleep() delays with await logger.waitForMessage()
- Wait for specific log messages instead of fixed time delays
- Add allowNoMessages parameter where needed for empty flush scenarios
TestLogHandler enhancements:
- Add waitForMessage() async method for deterministic waiting
- Support task cancellation with proper cleanup
- Resume waiting continuations when matching messages arrive
- Change EntryCondition from private to fileprivate for internal usage
This eliminates race conditions and makes tests more reliable by waiting
for actual events rather than guessing at timing delays.
* Replace waitForMessage with verifyMessageIsEventuallyLogged in ad events tests
Test changes:
- Replace custom event-driven waitForMessage() with Nimble's verifyMessageIsEventuallyLogged()
- Use established polling-based assertion pattern consistent with rest of test suite
- All 5 call sites in AdEventsIntegrationTests updated to use try await
TestLogHandler cleanup:
- Remove waitForMessage() method and continuation-based waiting infrastructure
- Remove PendingWait struct, pendingWaits property, and related helper methods
- Remove continuation resumption logic from didReceive()
- Change EntryCondition back to private
This simplifies the test helper while using the existing, proven polling approach.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: JayShortway <29483617+JayShortway@users.noreply.github.com>
* Rename paywallsEventsManager to eventsManager
* Rename PaywallEventsManagerType to EventsManagerType
* Rename PaywallEventsManager to EventsManager
* Rename PaywallEventStore to FeatureEventStore
* Rename InternalAPI.postPaywallEvents to postFeatureEvents
* Rename StoredEventSerializer to StoredFeatureEventSerializer
* Rename createStoredEvent to createFeatureStoredEvent
* Rename StoredEvent to StoredFeatureEvent
* Rename HTTPRequest.PaywallPath to HTTPRequest.FeatureEventsPath
* Rearrange events files
* Rename event related files
* Extract shared events endpoint configuration to EventsHTTPRequestPath
Both FeatureEventsPath and AdPath share the same /v1/events endpoint
configuration, differing only in domain (api-paywalls.revenuecat.com vs
a.revenue.cat) and name. Extract common logic to EventsHTTPRequestPath
protocol in Sources/Events/Networking.
* Move Sources/FeatureEvents to Sources/Events/FeatureEvents
* Rename EventsRequest to FeatureEventsRequest
- Rename EventsRequest.swift to FeatureEventsRequest.swift
- Move file to Sources/Events/FeatureEvents/Networking/
- Update all references in source and test files
- Update Xcode project file references
This makes it clear that this request type is specifically for the
feature events endpoint, not a generic events request.
* Remove ad events from feature events system
Ad events should not be sent through the feature events system as they
require their own separate infrastructure with a different API endpoint.
Changes:
- Remove FeatureEvent conformance from AdEvent
- Remove .ads case from Feature enum
- Remove ad events handling from FeatureEventsRequest
- Add no-op track(adEvent:) method to EventsManager with comment
- Update AdTracker to call the new track(adEvent:) method
Ad events tracking now becomes a no-op until proper implementation with:
- StoredAdEvent structure
- AdEventStore for persistence
- InternalAPI.postAdEvents() using HTTPRequest.AdPath.postEvents
* Rename PostEventsOperation to PostFeatureEventsOperation
- Rename PostEventsOperation.swift to PostFeatureEventsOperation.swift
- Move file to Sources/Events/FeatureEvents/Networking/
- Update class name and remove misleading "generic" comment
- Update references in InternalAPI.swift
- Update Xcode project file references
This makes it clear that this operation is specifically for posting
feature events, not a generic operation for various event types.
* Add RC Ads event tracking infrastructure
Implements event tracking for RevenueCat Ads following the Paywalls Events pattern.
- Adds three event types: ad displayed, ad opened, and ad revenue
- Uses actor-based concurrency with file-based event storage
- Posts events to e.revenue.cat/v1/events endpoint
- Marks track(adEvent:) method as @_spi(Internal) for internal use
- Includes comprehensive unit tests with snapshot testing
- Available on iOS 15.0+, tvOS 15.0+, macOS 12.0+, watchOS 8.0+
Event schema follows khepri RC Ads input events specification with
app_session_id field matching backend expectations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Avoid mapping in EventsRequest+Ad
* Consolidate ad events into PaywallEventsManager
Eliminates duplication between AdEventsManager and PaywallEventsManager by:
- Making PaywallEventsManager handle all event types (ads, paywalls, customerCenter)
- Adding feature-aware routing in flushEvents() to send events to correct domains
- Ad events → https://e.revenue.cat/v1/events
- Other events → https://api-paywalls.revenuecat.com/v1/events
- Removing AdEventsManager, AdEventStore, and their tests (~500 lines)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove AdsStrings and use PaywallsStrings instead
AdsStrings only had 2 strings in use, both of which already exist in PaywallsStrings. Consolidates logging strings to eliminate duplication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Namespace Precision enum inside AdEvent.RevenueData
Moves Precision from a top-level public enum to AdEvent.RevenueData.Precision to avoid polluting the global namespace and reduce autocomplete noise.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Move Precision conformances to end of file
Follows the pattern of other types by declaring protocol conformances at the end of the file rather than inline with the type definition.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove redundant sessionIdentifier from AdImpressionData
The sessionIdentifier field in AdImpressionData was redundant since
StoredEvent already tracks appSessionID. This change:
- Removes sessionIdentifier from AdImpressionData struct
- Uses storedEvent.appSessionID directly when building requests
- Renames sessionId to appSessionId to match backend field app_session_id
- Updates documentation examples to remove sessionIdentifier parameter
- Adds guard statement for optional appSessionID
- Updates all test fixtures and regenerates snapshots
- Removes iOS26 snapshot references from project
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove unneeded SessionId
* Add proper assertions to PurchasesAdEventsTests
Enhanced test coverage for public ad tracking APIs:
- Verify events are tracked via mockPaywallEventsManager
- Assert correct event types (displayed, opened, revenue)
- Validate all impression data fields
- Verify revenue-specific fields (revenueMicros, currency, precision)
All 3 tests now pass with proper verification.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add ENABLE_AD_EVENTS_TRACKING compiler flag
* Simplify track ad events APIs
* Fix ENABLE_AD_EVENTS_TRACKING scope and refactor Precision to struct
Compiler flag fixes:
- Move ENABLE_AD_EVENTS_TRACKING #endif to wrap all ad event code including internal enum
- Add compiler flag guards to all three test files
Precision API refactor:
- Change AdRevenue.Precision from enum to struct following Apple's API patterns (like Message.Reason)
- Use internal init(rawValue:) to prevent custom precision values
- Convert enum cases to static properties maintaining same public API
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor mediatorName from String to MediatorName struct
Replace string-based mediator names with a type-safe struct following Apple's API patterns:
- Add MediatorName struct with public init(rawValue:) to support custom values
- Include predefined static properties for .adMob and .appLovin
- Update AdEventData protocol to require MediatorName type
- Update all event types (AdDisplayed, AdOpened, AdRevenue) to use MediatorName
- Use .rawValue when serializing to network requests
- Update all tests and documentation examples to use .appLovin
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove RawRepresentable conformance from Precision and update test snapshots
- Remove RawRepresentable conformance from AdRevenue.Precision to allow internal init
- Update test assertions to use .appLovin instead of string comparison
- Update all snapshot test files to use "AppLovin" instead of "MAX"
- All ad event tests now passing (11/11)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix PaywallDataDecodingTests locale check for region overrides
The test was failing when the system locale included region overrides
(e.g., "en_US@rg=eszzzz" instead of just "en_US").
Changes:
- Use beginWith("en_US") instead of exact equality check
- Compare against enConfig directly instead of fetching via Locale.current
This makes the test more robust while maintaining the same behavior validation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Rename timestampMs to timestamp but keep serialized timestamp_ms
* Use different serverHostURL
* Simplify and paralelize postEvents
* Adjust usage of ENABLE_AD_EVENTS_TRACKING flag
* Move track ad event methods to AdTracker
* Remove PaywallEventsManager implementation
* Improving naming overlapping naming AdEvent->AdEventRequest
* Simplify compiler flag usage
* Use Experimental
* Remove duplicate with PostPaywallEventsOperation and PostAdEventsOperation
* Remove postAdEvents methods assuming reuse of event storage
* Make Precision rawValue internal
* Remove runtime checks
* Trigger build
* Move Public Types MARK comment after internal protocol
* Add documentation for AdDisplayed, AdOpened, and AdRevenue properties
* Remove redundant smoke tests
The "DoesNotCrash" tests were completely redundant with the
comprehensive "StoresEvent" tests. The comprehensive tests already:
1. Verify methods don't crash (implicitly)
2. Verify events are stored
3. Verify event data is correct
Removed 3 redundant tests, keeping only the 3 comprehensive tests.
* Fix bug not tracking initialized events
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Revert "Temporary revert of automatic health reporting in the SDK (#5353)"
This reverts commit dd7086fd98.
* Do not run health check when running integration tests
* Revert "fix `Purchases` temporary leak when running SDK health check (#5350)"
This reverts commit 26de54308c.
* Revert "[DX-457] Log the SDK configuration report on every `#DEBUG` run (#5317)"
This reverts commit 21f226de00.
* Creates a brand new SDK Manager to reuse logic across different classes
* Updates to use the new manager
* Adds a new method to log the health report to the console only for debug builds
* Run health check for debug builds
* Fixes sendable warnings that got introduced with new manager
* Fixes swiftlint violations
* Adds unit tests for the new SDK Health manager
* Creates a new authorization provider
* Injects the new authorization provider
* Tidies and cleans up unit tests
* Adds a new configuration value
* Updates configuration unit tests
* Makes sure that values are only set on DEBUG configuration
* Reverts state of default value
* Updates wording of output message
* Fixes the wrong wording of error message
* Logs the word RevenueCat along with report title
* Fixes wording issues in error messages
* Addresses PR comments
* Adds extra checks
* Renames configuration value
* Removes unnecessary `Task`
* Renames parameter
* Calls the new endpoint in the BE to check if the app is available or not
* Do not log or perform the report operations if it is not available
* Missing xcodeproj changes
* Adds unit test coverage
* Turns validation on by default
* Creates a new path for the report availability endpoint
* We need to bypass auth but make sure that requests are still signed
* Updates the name of the response field
* Updates unit tests target with new response structure
* Handles no credentials case more gracefully
* Adds new unit tests for the new No ASC credentials warning
* Updates the file's header comment
* Adds documentation
* Wraps calls in necessary compiler directives
* Hide all report-related content behind compiler directives
* Makes more health-related functionality DEBUG only
* Addresses PR comments
* Updates all unit test build failures
* Gets it back to compile on `CUSTOM_ENTITLEMENT_COMPUTATION`
* Removes configuration value
* Updates logging to only include issues
* Adds a mock method for the health report availability request
* Initial app creation
* Initial screen
* Installs and configures the SDK
* Creates a full Swift 6 compliant ViewModel
* Adds a demo customer center button
* Fixes icon name
* Updates a bit of the logic to fetch offerings
* Updates initial layout by showing offerings
* Makes more progress with a VERY DRAFT and VERY WIP screen
* Creates a new network path and requests
* Creates a new health API and adds it to the BE class
* Creates a `checkOfferings` method
* Pushes local changes
* Moved viewmodels to separate files
* Prepared some basic UI structure
* Initial shell of the new method for testing
* Initial implementation of `HealthReportOperation` from the SDK
* Passes `appUserId` through to the internal API
* Adds a new path called `appUserID`
* Removes 'old' `appHealth` endpoint
* Creates a new model for the network response
* Report should be the response type
* Updates backend request
* Updates types and documentation for the new internal request
* Moves validation into its own file
* Replaces method for DebugViewModel
* Updates error messages
* Updates the `Status` section
* Adds logic to show failure reason
* Removes Sample App from PR
* Resets `Package.resolved`
* Availability checks
* Check if device is authorized to make payments
* Fixes all SwiftLint errors
* Updates `ProductStatus` to not have a raw value of String
* Minor style fixes
* Removes warning
* Adds more availability checks
* Fixes comments
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
* Simplifies logic
* Updates comment
* Updates the comments to denote that this method is not intended for production use
* Removes android-only invalid SDK version error
* Adds an action button that the user can use to open the dashboard
* Updates copy about non-actionable item
* Prefers `Your RevenueCat Configuration` to `The Dashboard`
* Removes `Sendable`-related warnings
* Fixes unit tests on CI/CD
* Fixes tests
* Adds unit test for offering configuration error
* Adds unit test for no packages in default offering
* Adds unit test for generic offering configuration
* Removes periods from the end of the error messages
* Adds unit tests for all new errors
* Fixes linter errors
* Wrap in a `DEBUG` compiler directive
* Includes all products in the healthy check status to be used in the Sample App
* Updates report to include product and offering information
* Moves logic over to the report rather than being exclusive to the Debug View
* Fixes CI/CD failures
* Updates the asset for the health check status
---------
Co-authored-by: Hidde van der Ploeg <hiddevanderploeg@me.com>
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
### Changes:
- `InternalAPI` is now `Sendable` to deal with new compilation error
- `PaywallEventStoreType` is now `Sendable`
- Extracted `SK2BeginRefundRequestHelperType` to make
`SK2BeginRefundRequestHelper` `Sendable`
- Updated `MockSK2BeginRefundRequestHelper` to be thread-safe and
`Sendable`
- Fixed compilation error in `DebugContentViews`
- Fixed compilation error in `DebugViewModel`
- Fixed compilation error in `StoreMessagesHelper`
### Changes:
- Added `PurchasesOrchestrator` tests for paywall data sent through post
receipt
- Fixed and tested state issue with cached paywall data and failed
purchases
- Added `Integration Tests` for tracking and flushing events
- Configured `Integration Tests` with a custom documents directory to
ensure it's empty on every test invocation
- Changed deployment target on `Integration Tests` to iOS 16 to simplify
code
- Setting `Purchases.logLevel` before configuring purchases on
`Integration Tests`
- Added assertion to ensure `FileHandler` never runs on the main thread
### 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`
Depends on #2326 and #2328.
This allows us to keep all the code, except users won't be able to
configure the SDK with this option.
Note that `Signing.ResponseVerificationMode.enforced` _is_ available, so
we can still test all that behavior, including from integration tests.
Instead of manually choosing when to include `nonce` in requests, this
data-driven approach ensures that all requests to endpoints that support
signature verification will make `HTTPClient` do the right thing.
See also #2277 for how `CustomerInfo` and `EntitlementInfo` will use
this.
Fixes [CSDK-629].
Depends on #2274 and #2267.
Entitlement verification is accomplished by preventing MiTM attacks
between the SDK and the RevenueCat server.
With verification enabled, the SDK ensures that the response created by
the server was not modified by a third-party, and the entitlements
received are exactly what was sent.
Entitlements are only verified if enabled using
`Configuration.Builder.with(entitlementVerificationMode:)`, which is
disabled by default.
### Changes:
- Added new `VerificationResult`, which replaces
`HTTPResponseVerificationResult`
- Renamed `Configuration.EntitlementVerificationLevel` to
`Configuration.EntitlementVerificationMode`
- `Configuration.with(entitlementVerificationMode:)` is now `public`
- Added `CustomerInfo.entitlementVerification`
- Added `EntitlementInfos.verification` and
`EntitlementInfo.verification`
- Added tests to cover serialization of these new values
- `CustomerInfoResponseHandler` copies the `ValidationResult` to
`CustomerInfo` (all operations that fetch `CustomerInfo` use this)
- Renamed "validation" to "verification" everywhere for consistency
- Finalized documentation
### TODO:
- [x] Figure out whether to use a separate type from
`HTTPResponseValidationResult` or rename that one
- [x] Figure out final API type and property names
- [x] Add tests for `CustomerInfo.copy(with:)`
- [x] Ensure validation result is encoded and decoded (for caching)
- [x] Add missing snapshots
[CSDK-629]:
https://revenuecats.atlassian.net/browse/CSDK-629?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
### Changes:
- Created `CacheableNetworkOperationFactory` to abstract operation and
cache key creation
- Instead of always creating an operation, we create a factory first,
which pre-computes the cache key
- This cache key is used to determine if it's a cache hit, as well as
for the subsequently created operation
I'm still investigating a leak (#2105), and I suspect it's coming from
us creating and storing completion blocks, and those staying in memory
beyond the lifetime of the operation.
This refactor and performance improvement helps us avoid get more things
in memory than we need to.
See #2138 for how this is tested.