diff --git a/Sources/Diagnostics/DiagnosticsTracker.swift b/Sources/Diagnostics/DiagnosticsTracker.swift index 5d7ae438eb..25160d9e88 100644 --- a/Sources/Diagnostics/DiagnosticsTracker.swift +++ b/Sources/Diagnostics/DiagnosticsTracker.swift @@ -16,10 +16,10 @@ import Foundation protocol DiagnosticsTrackerType { @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) - func track(_ event: DiagnosticsEvent) async + func track(_ event: DiagnosticsEvent) @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) - func trackCustomerInfoVerificationResultIfNeeded(_ customerInfo: CustomerInfo) async + func trackCustomerInfoVerificationResultIfNeeded(_ customerInfo: CustomerInfo) @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) // swiftlint:disable:next function_parameter_count @@ -28,7 +28,7 @@ protocol DiagnosticsTrackerType { errorMessage: String?, errorCode: Int?, storeKitErrorDescription: String?, - responseTime: TimeInterval) async + responseTime: TimeInterval) @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) // swiftlint:disable:next function_parameter_count @@ -38,37 +38,42 @@ protocol DiagnosticsTrackerType { responseCode: Int, backendErrorCode: Int?, resultOrigin: HTTPResponseOrigin?, - verificationResult: VerificationResult) async + verificationResult: VerificationResult) @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) func trackPurchaseRequest(wasSuccessful: Bool, storeKitVersion: StoreKitVersion, errorMessage: String?, errorCode: Int?, - storeKitErrorDescription: String?) async + storeKitErrorDescription: String?) } @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) -final class DiagnosticsTracker: DiagnosticsTrackerType { +final class DiagnosticsTracker: DiagnosticsTrackerType, Sendable { private let diagnosticsFileHandler: DiagnosticsFileHandlerType + private let diagnosticsDispatcher: OperationDispatcher private let dateProvider: DateProvider init(diagnosticsFileHandler: DiagnosticsFileHandlerType, + diagnosticsDispatcher: OperationDispatcher = .default, dateProvider: DateProvider = DateProvider()) { self.diagnosticsFileHandler = diagnosticsFileHandler + self.diagnosticsDispatcher = diagnosticsDispatcher self.dateProvider = dateProvider } - func track(_ event: DiagnosticsEvent) async { - await self.clearDiagnosticsFileIfTooBig() - await self.diagnosticsFileHandler.appendEvent(diagnosticsEvent: event) + func track(_ event: DiagnosticsEvent) { + self.diagnosticsDispatcher.dispatchOnWorkerThread { + await self.clearDiagnosticsFileIfTooBig() + await self.diagnosticsFileHandler.appendEvent(diagnosticsEvent: event) + } } func trackCustomerInfoVerificationResultIfNeeded( _ customerInfo: CustomerInfo - ) async { + ) { let verificationResult = customerInfo.entitlements.verification if verificationResult == .notRequested { return @@ -79,7 +84,7 @@ final class DiagnosticsTracker: DiagnosticsTrackerType { properties: [.verificationResultKey: AnyEncodable(verificationResult.name)], timestamp: self.dateProvider.now() ) - await track(event) + self.track(event) } // swiftlint:disable:next function_parameter_count @@ -88,8 +93,8 @@ final class DiagnosticsTracker: DiagnosticsTrackerType { errorMessage: String?, errorCode: Int?, storeKitErrorDescription: String?, - responseTime: TimeInterval) async { - await track( + responseTime: TimeInterval) { + self.track( DiagnosticsEvent(eventType: .appleProductsRequest, properties: [ .successfulKey: AnyEncodable(wasSuccessful), @@ -110,8 +115,8 @@ final class DiagnosticsTracker: DiagnosticsTrackerType { responseCode: Int, backendErrorCode: Int?, resultOrigin: HTTPResponseOrigin?, - verificationResult: VerificationResult) async { - await track( + verificationResult: VerificationResult) { + self.track( DiagnosticsEvent( eventType: DiagnosticsEvent.EventType.httpRequestPerformed, properties: [ @@ -132,8 +137,8 @@ final class DiagnosticsTracker: DiagnosticsTrackerType { storeKitVersion: StoreKitVersion, errorMessage: String?, errorCode: Int?, - storeKitErrorDescription: String?) async { - await track( + storeKitErrorDescription: String?) { + self.track( DiagnosticsEvent(eventType: .applePurchaseAttempt, properties: [ .successfulKey: AnyEncodable(wasSuccessful), @@ -154,14 +159,14 @@ private extension DiagnosticsTracker { func clearDiagnosticsFileIfTooBig() async { if await self.diagnosticsFileHandler.isDiagnosticsFileTooBig() { await self.diagnosticsFileHandler.emptyDiagnosticsFile() - await self.trackMaxEventsStoredLimitReached() + self.trackMaxEventsStoredLimitReached() } } - func trackMaxEventsStoredLimitReached() async { - await self.track(.init(eventType: .maxEventsStoredLimitReached, - properties: [:], - timestamp: self.dateProvider.now())) + func trackMaxEventsStoredLimitReached() { + self.track(.init(eventType: .maxEventsStoredLimitReached, + properties: [:], + timestamp: self.dateProvider.now())) } } diff --git a/Sources/Identity/CustomerInfoManager.swift b/Sources/Identity/CustomerInfoManager.swift index 4d7029f808..21de047cf4 100644 --- a/Sources/Identity/CustomerInfoManager.swift +++ b/Sources/Identity/CustomerInfoManager.swift @@ -284,9 +284,7 @@ class CustomerInfoManager { if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { if let tracker = self.diagnosticsTracker, lastSentCustomerInfo != customerInfo { - Task(priority: .background) { - await tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) - } + tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) } } diff --git a/Sources/Networking/HTTPClient/HTTPClient.swift b/Sources/Networking/HTTPClient/HTTPClient.swift index 5899e7a295..10ffb72817 100644 --- a/Sources/Networking/HTTPClient/HTTPClient.swift +++ b/Sources/Networking/HTTPClient/HTTPClient.swift @@ -582,33 +582,31 @@ private extension HTTPClient { guard let diagnosticsTracker = self.diagnosticsTracker, let result else { return } let responseTime = self.dateProvider.now().timeIntervalSince(requestStartTime) let requestPathName = request.httpRequest.path.name - Task(priority: .background) { - switch result { - case let .success(response): - let httpStatusCode = response.httpStatusCode.rawValue - let verificationResult = response.verificationResult - await diagnosticsTracker.trackHttpRequestPerformed(endpointName: requestPathName, - responseTime: responseTime, - wasSuccessful: true, - responseCode: httpStatusCode, - backendErrorCode: nil, - resultOrigin: response.origin, - verificationResult: verificationResult) - case let .failure(error): - var responseCode = -1 - var backendErrorCode: Int? - if case let .errorResponse(errorResponse, code, _) = error { - responseCode = code.rawValue - backendErrorCode = errorResponse.code.rawValue - } - await diagnosticsTracker.trackHttpRequestPerformed(endpointName: requestPathName, - responseTime: responseTime, - wasSuccessful: false, - responseCode: responseCode, - backendErrorCode: backendErrorCode, - resultOrigin: nil, - verificationResult: .notRequested) + switch result { + case let .success(response): + let httpStatusCode = response.httpStatusCode.rawValue + let verificationResult = response.verificationResult + diagnosticsTracker.trackHttpRequestPerformed(endpointName: requestPathName, + responseTime: responseTime, + wasSuccessful: true, + responseCode: httpStatusCode, + backendErrorCode: nil, + resultOrigin: response.origin, + verificationResult: verificationResult) + case let .failure(error): + var responseCode = -1 + var backendErrorCode: Int? + if case let .errorResponse(errorResponse, code, _) = error { + responseCode = code.rawValue + backendErrorCode = errorResponse.code.rawValue } + diagnosticsTracker.trackHttpRequestPerformed(endpointName: requestPathName, + responseTime: responseTime, + wasSuccessful: false, + responseCode: responseCode, + backendErrorCode: backendErrorCode, + resultOrigin: nil, + verificationResult: .notRequested) } } } diff --git a/Sources/Purchasing/ProductsManager.swift b/Sources/Purchasing/ProductsManager.swift index ab97723375..0a858e566c 100644 --- a/Sources/Purchasing/ProductsManager.swift +++ b/Sources/Purchasing/ProductsManager.swift @@ -145,14 +145,12 @@ private extension ProductsManager { ?? error?.localizedDescription let errorCode = error?.errorCode let storeKitErrorDescription = StoreKitErrorUtils.extractStoreKitErrorDescription(from: error) - Task(priority: .background) { - await diagnosticsTracker.trackProductsRequest(wasSuccessful: error == nil, - storeKitVersion: storeKitVersion, - errorMessage: errorMessage, - errorCode: errorCode, - storeKitErrorDescription: storeKitErrorDescription, - responseTime: responseTime) - } + diagnosticsTracker.trackProductsRequest(wasSuccessful: error == nil, + storeKitVersion: storeKitVersion, + errorMessage: errorMessage, + errorCode: errorCode, + storeKitErrorDescription: storeKitErrorDescription, + responseTime: responseTime) } } diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 57ccc1d7eb..60231f0a88 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -895,18 +895,15 @@ private extension PurchasesOrchestrator { error: PublicError?) { if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), let diagnosticsTracker = self.diagnosticsTracker { - Task(priority: .background) { - let errorMessage = - (error?.userInfo[NSUnderlyingErrorKey] as? Error)?.localizedDescription ?? error?.localizedDescription - let errorCode = error?.code - let storeKitErrorDescription = StoreKitErrorUtils.extractStoreKitErrorDescription(from: error) - - await diagnosticsTracker.trackPurchaseRequest(wasSuccessful: error == nil, - storeKitVersion: storeKitVersion, - errorMessage: errorMessage, - errorCode: errorCode, - storeKitErrorDescription: storeKitErrorDescription) - } + let errorMessage = (error?.userInfo[NSUnderlyingErrorKey] as? Error)?.localizedDescription + ?? error?.localizedDescription + let errorCode = error?.code + let storeKitErrorDescription = StoreKitErrorUtils.extractStoreKitErrorDescription(from: error) + diagnosticsTracker.trackPurchaseRequest(wasSuccessful: error == nil, + storeKitVersion: storeKitVersion, + errorMessage: errorMessage, + errorCode: errorCode, + storeKitErrorDescription: storeKitErrorDescription) } } diff --git a/Tests/UnitTests/Diagnostics/DiagnosticsTrackerTests.swift b/Tests/UnitTests/Diagnostics/DiagnosticsTrackerTests.swift index 8aac290d92..b2c1c8515e 100644 --- a/Tests/UnitTests/Diagnostics/DiagnosticsTrackerTests.swift +++ b/Tests/UnitTests/Diagnostics/DiagnosticsTrackerTests.swift @@ -22,6 +22,7 @@ class DiagnosticsTrackerTests: TestCase { fileprivate var fileHandler: FileHandler! fileprivate var handler: DiagnosticsFileHandler! fileprivate var tracker: DiagnosticsTracker! + fileprivate var diagnosticsDispatcher: MockOperationDispatcher! fileprivate var dateProvider: MockDateProvider! override func setUpWithError() throws { @@ -31,8 +32,10 @@ class DiagnosticsTrackerTests: TestCase { self.fileHandler = try Self.createWithTemporaryFile() self.handler = .init(self.fileHandler) + self.diagnosticsDispatcher = MockOperationDispatcher() self.dateProvider = .init(stubbedNow: Self.eventTimestamp1) self.tracker = .init(diagnosticsFileHandler: self.handler, + diagnosticsDispatcher: self.diagnosticsDispatcher, dateProvider: self.dateProvider) } @@ -49,7 +52,7 @@ class DiagnosticsTrackerTests: TestCase { properties: [.verificationResultKey: AnyEncodable("FAILED")], timestamp: Self.eventTimestamp1) - await self.tracker.track(event) + self.tracker.track(event) let entries = await self.handler.getEntries() expect(entries) == [ @@ -67,9 +70,9 @@ class DiagnosticsTrackerTests: TestCase { properties: [.verificationResultKey: AnyEncodable("FAILED")], timestamp: Self.eventTimestamp2) - await self.tracker.track(event1) + self.tracker.track(event1) self.dateProvider.stubbedNowResult = Self.eventTimestamp2 - await self.tracker.track(event2) + self.tracker.track(event2) let entries = await self.handler.getEntries() expect(entries) == [ @@ -87,7 +90,7 @@ class DiagnosticsTrackerTests: TestCase { func testDoesNotTrackWhenVerificationIsNotRequested() async { let customerInfo: CustomerInfo = .emptyInfo.copy(with: .notRequested) - await self.tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) + self.tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) let entries = await self.handler.getEntries() expect(entries.count) == 0 @@ -96,7 +99,7 @@ class DiagnosticsTrackerTests: TestCase { func testTracksCustomerInfoVerificationFailed() async { let customerInfo: CustomerInfo = .emptyInfo.copy(with: .failed) - await self.tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) + self.tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo) let entries = await self.handler.getEntries() expect(entries) == [ @@ -109,13 +112,13 @@ class DiagnosticsTrackerTests: TestCase { // MARK: - http request performed func testTracksHttpRequestPerformedWithExpectedParameters() async { - await self.tracker.trackHttpRequestPerformed(endpointName: "mock_endpoint", - responseTime: 50, - wasSuccessful: true, - responseCode: 200, - backendErrorCode: 7121, - resultOrigin: .cache, - verificationResult: .verified) + self.tracker.trackHttpRequestPerformed(endpointName: "mock_endpoint", + responseTime: 50, + wasSuccessful: true, + responseCode: 200, + backendErrorCode: 7121, + resultOrigin: .cache, + verificationResult: .verified) let entries = await self.handler.getEntries() expect(entries) == [ .init(eventType: .httpRequestPerformed, @@ -134,21 +137,22 @@ class DiagnosticsTrackerTests: TestCase { // MARK: - product request func testTracksProductRequestWithExpectedParameters() async { - await self.tracker.trackProductsRequest(wasSuccessful: false, - storeKitVersion: .storeKit2, - errorMessage: "test error message", - errorCode: 1234, - storeKitErrorDescription: "store_kit_error_type", - responseTime: 50) + self.tracker.trackProductsRequest(wasSuccessful: false, + storeKitVersion: .storeKit2, + errorMessage: "test error message", + errorCode: 1234, + storeKitErrorDescription: "store_kit_error_type", + responseTime: 50) let emptyErrorMessage: String? = nil let emptyErrorCode: Int? = nil let emptySkErrorDescription: String? = nil - await self.tracker.trackProductsRequest(wasSuccessful: true, - storeKitVersion: .storeKit1, - errorMessage: emptyErrorMessage, - errorCode: emptyErrorCode, - storeKitErrorDescription: emptySkErrorDescription, - responseTime: 20) + self.tracker.trackProductsRequest(wasSuccessful: true, + storeKitVersion: .storeKit1, + errorMessage: emptyErrorMessage, + errorCode: emptyErrorCode, + storeKitErrorDescription: emptySkErrorDescription, + responseTime: 20) + let entries = await self.handler.getEntries() expect(entries) == [ .init(eventType: .appleProductsRequest, @@ -188,7 +192,7 @@ class DiagnosticsTrackerTests: TestCase { properties: [.verificationResultKey: AnyEncodable("FAILED")], timestamp: Self.eventTimestamp2) - await self.tracker.track(event) + self.tracker.track(event) let entries2 = await self.handler.getEntries() expect(entries2.count) == 2 diff --git a/Tests/UnitTests/Mocks/MockDiagnosticsTracker.swift b/Tests/UnitTests/Mocks/MockDiagnosticsTracker.swift index 392bee0c0e..3c27d7afe8 100644 --- a/Tests/UnitTests/Mocks/MockDiagnosticsTracker.swift +++ b/Tests/UnitTests/Mocks/MockDiagnosticsTracker.swift @@ -20,13 +20,13 @@ final class MockDiagnosticsTracker: DiagnosticsTrackerType, Sendable { let trackedEvents: Atomic<[DiagnosticsEvent]> = .init([]) let trackedCustomerInfo: Atomic<[CustomerInfo]> = .init([]) - func track(_ event: DiagnosticsEvent) async { + func track(_ event: DiagnosticsEvent) { self.trackedEvents.modify { $0.append(event) } } func trackCustomerInfoVerificationResultIfNeeded( _ customerInfo: RevenueCat.CustomerInfo - ) async { + ) { self.trackedCustomerInfo.modify { $0.append(customerInfo) } } @@ -41,7 +41,7 @@ final class MockDiagnosticsTracker: DiagnosticsTrackerType, Sendable { responseCode: Int, backendErrorCode: Int?, resultOrigin: HTTPResponseOrigin?, - verificationResult: VerificationResult) async { + verificationResult: VerificationResult) { self.trackedHttpRequestPerformedParams.modify { $0.append( (endpointName, @@ -67,7 +67,7 @@ final class MockDiagnosticsTracker: DiagnosticsTrackerType, Sendable { storeKitVersion: StoreKitVersion, errorMessage: String?, errorCode: Int?, - storeKitErrorDescription: String?) async { + storeKitErrorDescription: String?) { self.trackedPurchaseRequestParams.modify { $0.append( (wasSuccessful, @@ -94,7 +94,7 @@ final class MockDiagnosticsTracker: DiagnosticsTrackerType, Sendable { errorMessage: String?, errorCode: Int?, storeKitErrorDescription: String?, - responseTime: TimeInterval) async { + responseTime: TimeInterval) { self.trackedProductsRequestParams.modify { $0.append( (wasSuccessful, diff --git a/Tests/UnitTests/Mocks/MockOperationDispatcher.swift b/Tests/UnitTests/Mocks/MockOperationDispatcher.swift index 126a4c19d7..35dfb537fe 100644 --- a/Tests/UnitTests/Mocks/MockOperationDispatcher.swift +++ b/Tests/UnitTests/Mocks/MockOperationDispatcher.swift @@ -90,9 +90,14 @@ class MockOperationDispatcher: OperationDispatcher { if self.forwardToOriginalDispatchOnWorkerThread { super.dispatchOnWorkerThread(jitterableDelay: delay, block: block) } else if self.shouldInvokeDispatchOnWorkerThreadBlock { + let semaphore = DispatchSemaphore(value: 0) + Task { await block() + semaphore.signal() } + + semaphore.wait() } }