mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
189 lines
5.6 KiB
Swift
189 lines
5.6 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
|
|
//
|
|
// PurchasesDiagnostics.swift
|
|
//
|
|
// Created by Nacho Soto on 9/21/22.
|
|
|
|
import Foundation
|
|
|
|
/// `PurchasesDiagnostics` allows you to ensure that the SDK is set up correctly by diagnosing configuration errors.
|
|
/// To run the test, simply call ``PurchasesDiagnostics/testSDKHealth()``.
|
|
///
|
|
/// #### Example:
|
|
/// ```swift
|
|
/// let diagnostics = PurchasesDiagnostics.default
|
|
/// do {
|
|
/// try await diagnostics.testSDKHealth()
|
|
/// } catch {
|
|
/// print("Diagnostics failed: \(error.localizedDescription)")
|
|
/// }
|
|
/// ```
|
|
@objc(RCPurchasesDiagnostics)
|
|
public final class PurchasesDiagnostics: NSObject {
|
|
|
|
typealias SDK = PurchasesType & InternalPurchasesType
|
|
|
|
private let purchases: SDK
|
|
|
|
init(purchases: SDK) {
|
|
self.purchases = purchases
|
|
}
|
|
|
|
/// Default instance of `PurchasesDiagnostics`.
|
|
/// Note: you must call ``Purchases/configure(with:)-6oipy`` before using this.
|
|
@objc
|
|
public static let `default`: PurchasesDiagnostics = .init(purchases: Purchases.shared)
|
|
}
|
|
|
|
extension PurchasesDiagnostics {
|
|
|
|
/// An error that represents a failing step in ``PurchasesDiagnostics``
|
|
public enum Error: Swift.Error {
|
|
|
|
/// Connection to the API failed
|
|
case failedConnectingToAPI(Swift.Error)
|
|
|
|
/// API key is invalid
|
|
case invalidAPIKey
|
|
|
|
/// Fetching offerings failed due to the underlying error
|
|
case failedFetchingOfferings(Swift.Error)
|
|
|
|
/// Failure performing a signed request
|
|
case failedMakingSignedRequest(Swift.Error)
|
|
|
|
/// Any other not identifier error. You can check the undelying error for details.
|
|
case unknown(Swift.Error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Implementation
|
|
|
|
extension PurchasesDiagnostics {
|
|
|
|
/// Perform tests to ensure SDK is configured correctly.
|
|
/// - `Throws`: ``PurchasesDiagnostics/Error`` if any step fails
|
|
@objc(testSDKHealthWithCompletion:)
|
|
public func testSDKHealth() async throws {
|
|
do {
|
|
try await self.unauthenticatedRequest()
|
|
#if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION
|
|
try await self.authenticatedRequest()
|
|
#endif
|
|
try await self.offeringsRequest()
|
|
try await self.signatureVerification()
|
|
} catch let error as Error {
|
|
throw error
|
|
} catch let error {
|
|
// Catch every other error to ensure that we only throw `Error`s from here.
|
|
throw Error.unknown(error)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private extension PurchasesDiagnostics {
|
|
|
|
/// Makes a request to the backend, to verify connectivity, firewalls, or anything blocking network traffic.
|
|
func unauthenticatedRequest() async throws {
|
|
do {
|
|
try await self.purchases.healthRequest(signatureVerification: false)
|
|
} catch {
|
|
throw Error.failedConnectingToAPI(error)
|
|
}
|
|
}
|
|
|
|
#if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION
|
|
func authenticatedRequest() async throws {
|
|
do {
|
|
_ = try await self.purchases.customerInfo()
|
|
} catch let error as ErrorCode {
|
|
throw self.convert(error)
|
|
} catch {
|
|
throw Error.unknown(error)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
func offeringsRequest() async throws {
|
|
do {
|
|
_ = try await self.purchases.offerings(fetchPolicy: .failIfProductsAreMissing)
|
|
} catch {
|
|
throw Error.failedFetchingOfferings(error)
|
|
}
|
|
}
|
|
|
|
func signatureVerification() async throws {
|
|
guard self.purchases.responseVerificationMode.isEnabled else { return }
|
|
|
|
do {
|
|
try await self.purchases.healthRequest(signatureVerification: true)
|
|
} catch {
|
|
throw Error.failedMakingSignedRequest(error)
|
|
}
|
|
}
|
|
|
|
func convert(_ error: ErrorCode) -> Error {
|
|
switch error {
|
|
case .unknownError:
|
|
return Error.unknown(error)
|
|
|
|
case .offlineConnectionError:
|
|
return Error.failedConnectingToAPI(error)
|
|
|
|
case .invalidCredentialsError:
|
|
return Error.invalidAPIKey
|
|
|
|
case .signatureVerificationFailed:
|
|
return Error.failedMakingSignedRequest(error)
|
|
|
|
default:
|
|
return Error.unknown(error)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension PurchasesDiagnostics.Error: CustomNSError {
|
|
|
|
// swiftlint:disable:next missing_docs
|
|
public var errorUserInfo: [String: Any] {
|
|
return [
|
|
NSUnderlyingErrorKey: self.underlyingError as NSError? ?? NSNull(),
|
|
NSLocalizedDescriptionKey: self.localizedDescription
|
|
]
|
|
}
|
|
|
|
var localizedDescription: String {
|
|
switch self {
|
|
case let .unknown(error): return "Unknown error: \(error.localizedDescription)"
|
|
case let .failedConnectingToAPI(error): return "Error connecting to API: \(error.localizedDescription)"
|
|
case let .failedFetchingOfferings(error): return "Failed fetching offerings: \(error.localizedDescription)"
|
|
case let .failedMakingSignedRequest(error): return "Failed making signed request: \(error.localizedDescription)"
|
|
case .invalidAPIKey: return "API key is not valid"
|
|
}
|
|
}
|
|
|
|
private var underlyingError: Swift.Error? {
|
|
switch self {
|
|
case let .unknown(error): return error
|
|
case let .failedConnectingToAPI(error): return error
|
|
case let .failedFetchingOfferings(error): return error
|
|
case let .failedMakingSignedRequest(error): return error
|
|
case .invalidAPIKey: return nil
|
|
}
|
|
}
|
|
|
|
}
|