Files
purchases-ios/Sources/Networking/InternalAPI.swift
T
Pol Miro c565837758 Remove compiler flag (#5943)
* 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.
2025-12-23 19:06:32 +01:00

174 lines
6.8 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// InternalAPI.swift
//
// Created by Nacho Soto on 10/5/22.
import Foundation
class InternalAPI {
typealias ResponseHandler = (BackendError?) -> Void
#if DEBUG
typealias HealthReportResponseHandler = (Result<HealthReport, BackendError>) -> Void
typealias HealthReportAvailabilityResponseHandler = (Result<HealthReportAvailability, BackendError>) -> Void
private let healthReportCallbackCache: CallbackCache<HealthReportOperation.Callback>
private let healthReportAvailabilityCallbackCache: CallbackCache<HealthReportAvailabilityOperation.Callback>
#endif
private let backendConfig: BackendConfiguration
private let healthCallbackCache: CallbackCache<HealthOperation.Callback>
init(backendConfig: BackendConfiguration) {
self.backendConfig = backendConfig
self.healthCallbackCache = .init()
#if DEBUG
self.healthReportCallbackCache = .init()
self.healthReportAvailabilityCallbackCache = .init()
#endif
}
func healthRequest(signatureVerification: Bool, completion: @escaping ResponseHandler) {
let factory = HealthOperation.createFactory(httpClient: self.backendConfig.httpClient,
callbackCache: self.healthCallbackCache,
signatureVerification: signatureVerification)
let callback = HealthOperation.Callback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.healthCallbackCache.add(callback)
self.backendConfig.addCacheableOperation(with: factory,
delay: .none,
cacheStatus: cacheStatus)
}
#if DEBUG
func healthReportRequest(appUserID: String, completion: @escaping HealthReportResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let factory = HealthReportOperation.createFactory(configuration: config,
callbackCache: self.healthReportCallbackCache)
let callback = HealthReportOperation.Callback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.healthReportCallbackCache.add(callback)
self.backendConfig.addCacheableOperation(with: factory,
delay: .none,
cacheStatus: cacheStatus)
}
func healthReportAvailabilityRequest(
appUserID: String,
completion: @escaping HealthReportAvailabilityResponseHandler
) {
let config = NetworkOperation.UserSpecificConfiguration(
httpClient: self.backendConfig.httpClient,
appUserID: appUserID
)
let factory = HealthReportAvailabilityOperation.createFactory(
configuration: config,
callbackCache: self.healthReportAvailabilityCallbackCache
)
let callback = HealthReportAvailabilityOperation.Callback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.healthReportAvailabilityCallbackCache.add(callback)
self.backendConfig.addCacheableOperation(with: factory,
delay: .none,
cacheStatus: cacheStatus)
}
#endif
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postFeatureEvents(events: [StoredFeatureEvent], completion: @escaping ResponseHandler) {
guard !events.isEmpty else {
completion(nil)
return
}
let request = FeatureEventsRequest(events: events)
let operation = PostFeatureEventsOperation(
configuration: .init(httpClient: self.backendConfig.httpClient),
request: request,
path: HTTPRequest.FeatureEventsPath.postEvents,
responseHandler: completion
)
self.backendConfig.operationQueue.addOperation(operation)
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postDiagnosticsEvents(events: [DiagnosticsEvent], completion: @escaping ResponseHandler) {
guard !events.isEmpty else {
completion(nil)
return
}
let operation = DiagnosticsPostOperation(configuration: .init(httpClient: self.backendConfig.httpClient),
request: .init(events: events),
responseHandler: completion)
self.backendConfig.addDiagnosticsOperation(operation, delay: .long)
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postAdEvents(events: [StoredAdEvent], completion: @escaping ResponseHandler) {
guard !events.isEmpty else {
completion(nil)
return
}
let request = AdEventsRequest(events: events)
let operation = PostAdEventsOperation(
configuration: .init(httpClient: self.backendConfig.httpClient),
request: request,
path: HTTPRequest.AdPath.postEvents,
responseHandler: completion
)
self.backendConfig.operationQueue.addOperation(operation)
}
}
extension InternalAPI {
/// - Throws: `BackendError`
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postFeatureEvents(events: [StoredFeatureEvent]) async throws {
let error = await Async.call { completion in
self.postFeatureEvents(events: events, completion: completion)
}
if let error { throw error }
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postDiagnosticsEvents(events: [DiagnosticsEvent]) async throws {
let error = await Async.call { completion in
self.postDiagnosticsEvents(events: events, completion: completion)
}
if let error { throw error }
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func postAdEvents(events: [StoredAdEvent]) async throws {
let error = await Async.call { completion in
self.postAdEvents(events: events, completion: completion)
}
if let error { throw error }
}
}
// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension InternalAPI: @unchecked Sendable {}