Files
purchases-ios/Sources/Misc/SandboxEnvironmentDetector.swift
T
Andy Boedo 3120e66a78 fix mac os sandbox check slowness (#3879)
Alternative approach to #3875, also meant to address
https://github.com/RevenueCat/purchases-ios/issues/3871

We're currently looking at a field called Environment in the local
receipt to check whether the app is running in sandbox on macOS.

However, since it's an undocumented field, we can't trust that it will
always be there, so if the value is production then we check the
bundle's signature, which can be slow to run.

This PR changes that behavior so that we only perform that check if the
value for that variable isn't available, which would cause a receipt
parsing failure since it's marked as non-optional.

Still need to write tests for this, but it should be significantly
easier than for the other PR.
2024-08-23 10:10:17 -03:00

102 lines
3.0 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
//
// SandboxEnvironmentDetector.swift
//
// Created by Nacho Soto on 6/2/22.
import Foundation
/// A type that can determine if the current environment is sandbox.
protocol SandboxEnvironmentDetector: Sendable {
var isSandbox: Bool { get }
}
/// ``SandboxEnvironmentDetector`` that uses a `Bundle` to detect the environment
final class BundleSandboxEnvironmentDetector: SandboxEnvironmentDetector {
private let bundle: Atomic<Bundle>
private let isRunningInSimulator: Bool
private let receiptFetcher: LocalReceiptFetcherType
private let macAppStoreDetector: MacAppStoreDetector?
init(
bundle: Bundle = .main,
isRunningInSimulator: Bool = SystemInfo.isRunningInSimulator,
receiptFetcher: LocalReceiptFetcherType = LocalReceiptFetcher(),
macAppStoreDetector: MacAppStoreDetector? = nil
) {
self.bundle = .init(bundle)
self.isRunningInSimulator = isRunningInSimulator
self.receiptFetcher = receiptFetcher
self.macAppStoreDetector = macAppStoreDetector
}
var isSandbox: Bool {
guard !self.isRunningInSimulator else {
return true
}
guard let path = self.bundle.value.appStoreReceiptURL?.path else {
return false
}
#if os(macOS) || targetEnvironment(macCatalyst)
// this relies on an undocumented field in the receipt that provides the Environment.
// if it's not present, we go to a secondary check.
if let isProductionReceipt = self.isProductionReceipt {
return !isProductionReceipt
} else {
return !self.isMacAppStore
}
#else
return path.contains("sandboxReceipt")
#endif
}
#if DEBUG
// Mutable in tests so it can be overriden
static var `default`: SandboxEnvironmentDetector = BundleSandboxEnvironmentDetector()
#else
static let `default`: SandboxEnvironmentDetector = BundleSandboxEnvironmentDetector()
#endif
}
extension BundleSandboxEnvironmentDetector: Sendable {}
// MARK: -
#if os(macOS) || targetEnvironment(macCatalyst)
private extension BundleSandboxEnvironmentDetector {
var isProductionReceipt: Bool? {
do {
let receiptEnvironment = try self.receiptFetcher.fetchAndParseLocalReceipt().environment
guard receiptEnvironment != .unknown else { return nil } // don't make assumptions if we're not sure
return receiptEnvironment == .production
} catch {
Logger.error(Strings.receipt.parse_receipt_locally_error(error: error))
return nil
}
}
var isMacAppStore: Bool {
let detector = self.macAppStoreDetector ?? DefaultMacAppStoreDetector()
return detector.isMacAppStore
}
}
#endif