From 5e7f906e531404322797e20e6622d4631db01077 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 23 Aug 2023 12:13:08 -0700 Subject: [PATCH] `Configuration`: log warning if attempting to use observer mode with StoreKit 2 See also https://github.com/RevenueCat/revenuecat-docs/pull/299. Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting. This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated. --- .../Logging/Strings/ConfigureStrings.swift | 4 ++ Sources/Misc/Deprecations.swift | 2 +- Sources/Purchasing/Configuration.swift | 13 +++++- Sources/Purchasing/Purchases/Purchases.swift | 3 ++ .../SwiftAPITester/ConfigurationAPI.swift | 7 ++- .../ObjCAPITester/RCConfigurationAPI.m | 19 ++++---- .../SwiftAPITester/ConfigurationAPI.swift | 8 +++- .../Purchasing/ConfigurationTests.swift | 46 +++++++++++++++++++ 8 files changed, 88 insertions(+), 14 deletions(-) diff --git a/Sources/Logging/Strings/ConfigureStrings.swift b/Sources/Logging/Strings/ConfigureStrings.swift index c3ec5b426a..7a0e25167c 100644 --- a/Sources/Logging/Strings/ConfigureStrings.swift +++ b/Sources/Logging/Strings/ConfigureStrings.swift @@ -33,6 +33,8 @@ enum ConfigureStrings { case observer_mode_enabled + case observer_mode_with_storekit2 + case response_verification_mode(Signing.ResponseVerificationMode) case delegate_set @@ -98,6 +100,8 @@ extension ConfigureStrings: LogMessage { return "StoreKit 2 support enabled" case .observer_mode_enabled: return "Purchases is configured in observer mode" + case .observer_mode_with_storekit2: + return "Observer mode is not currently compatible with StoreKit 2" case let .response_verification_mode(mode): switch mode { case .disabled: diff --git a/Sources/Misc/Deprecations.swift b/Sources/Misc/Deprecations.swift index a8968eeb09..a5f2e51cf4 100644 --- a/Sources/Misc/Deprecations.swift +++ b/Sources/Misc/Deprecations.swift @@ -400,7 +400,7 @@ extension CustomerInfo { public extension Configuration.Builder { - /// Set `usesStoreKit2IfAvailable`. If `true`, the SDK will use StoreKit 2 APIs internally. If disabled, it will use StoreKit 1 APIs instead. + /// Set `storeKit2Setting`. If `true`, the SDK will use StoreKit 2 APIs internally. If disabled, it will use StoreKit 1 APIs instead. /// - Parameter usesStoreKit2IfAvailable: enable StoreKit 2 on devices that support it. /// Defaults to `false`. /// - Important: This configuration flag has been deprecated, and will be replaced by automatic remote configuration in the future. diff --git a/Sources/Purchasing/Configuration.swift b/Sources/Purchasing/Configuration.swift index c84326c29e..7a071424cb 100644 --- a/Sources/Purchasing/Configuration.swift +++ b/Sources/Purchasing/Configuration.swift @@ -53,6 +53,7 @@ import Foundation private init(with builder: Builder) { Self.verify(apiKey: builder.apiKey) + Self.verify(observerMode: builder.observerMode, storeKit2Setting: builder.storeKit2Setting) self.apiKey = builder.apiKey self.appUserID = builder.appUserID @@ -129,12 +130,14 @@ import Foundation * Set `observerMode`. * - Parameter observerMode: Set this to `true` if you have your own IAP implementation and want to use only * RevenueCat's backend. Default is `false`. + * + * - Warning: This assumes your IAP implementation uses StoreKit 1. + * Observer mode is not compatible with StoreKit 2. */ - @objc public func with(observerMode: Bool) -> Builder { + @objc public func with(observerMode: Bool) -> Configuration.Builder { self.observerMode = observerMode return self } - /** * Set `userDefaults`. * - Parameter userDefaults: Custom `UserDefaults` to use @@ -289,6 +292,12 @@ extension Configuration { } } + fileprivate static func verify(observerMode: Bool, storeKit2Setting: StoreKit2Setting) { + if observerMode, storeKit2Setting.usesStoreKit2IfAvailable { + Logger.warn(Strings.configure.observer_mode_with_storekit2) + } + } + private static let applePlatformKeyPrefix: String = "appl_" } diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index cfb7545e88..67468634e8 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -1200,6 +1200,9 @@ public extension Purchases { * RevenueCat's backend. Default is `false`. * * - Returns: An instantiated ``Purchases`` object that has been set as a singleton. + * + * - Warning: This assumes your IAP implementation uses StoreKit 1. + * Observer mode is not compatible with StoreKit 2. */ @objc(configureWithAPIKey:appUserID:observerMode:) @discardableResult static func configure(withAPIKey apiKey: String, diff --git a/Tests/APITesters/CustomEntitlementComputationSwiftAPITester/SwiftAPITester/ConfigurationAPI.swift b/Tests/APITesters/CustomEntitlementComputationSwiftAPITester/SwiftAPITester/ConfigurationAPI.swift index 0a56968001..6ec40d9ea0 100644 --- a/Tests/APITesters/CustomEntitlementComputationSwiftAPITester/SwiftAPITester/ConfigurationAPI.swift +++ b/Tests/APITesters/CustomEntitlementComputationSwiftAPITester/SwiftAPITester/ConfigurationAPI.swift @@ -21,9 +21,14 @@ func checkConfigurationAPI() { .with(networkTimeout: 1) .with(storeKit1Timeout: 1) .with(platformInfo: Purchases.PlatformInfo(flavor: "", version: "")) - // Trusted Entitlements: internal until ready to be made public. // .with(entitlementVerificationMode: .informational) .build() print(configuration) } + +@available(*, deprecated) +func checkDeprecatedConfiguration(_ builder: Configuration.Builder) { + _ = builder + .with(usesStoreKit2IfAvailable: true) +} diff --git a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m index 266b44212a..d2710181c3 100644 --- a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m +++ b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m @@ -14,15 +14,16 @@ @implementation RCConfigurationAPI + (void)checkAPI { RCConfigurationBuilder *builder = [RCConfiguration builderWithAPIKey:@""]; RCConfiguration *config __unused = [[[[[[[[[[[builder withApiKey:@""] - withObserverMode:false] - withUserDefaults:NSUserDefaults.standardUserDefaults] - withAppUserID:@""] - withAppUserID:nil] - withDangerousSettings:[[RCDangerousSettings alloc] init]] - withNetworkTimeout:1] - withStoreKit1Timeout: 1] - withPlatformInfo:[[RCPlatformInfo alloc] initWithFlavor:@"" version:@""]] - withUsesStoreKit2IfAvailable:false] build]; + withObserverMode:false] + withUserDefaults:NSUserDefaults.standardUserDefaults] + withAppUserID:@""] + withAppUserID:nil] + withDangerousSettings:[[RCDangerousSettings alloc] init]] + withNetworkTimeout:1] + withStoreKit1Timeout: 1] + withPlatformInfo:[[RCPlatformInfo alloc] initWithFlavor:@"" version:@""]] + withUsesStoreKit2IfAvailable:false] + build]; if (@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)) { RCConfiguration *config __unused = [[builder diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift index ff9e6c6073..95b52f4457 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift @@ -14,7 +14,7 @@ func checkConfigurationAPI() { .with(apiKey: "") .with(appUserID: "") .with(appUserID: nil) - .with(observerMode: false) + .with(observerMode: true) .with(userDefaults: UserDefaults.standard) .with(dangerousSettings: DangerousSettings()) .with(dangerousSettings: DangerousSettings(autoSyncPurchases: true)) @@ -30,3 +30,9 @@ func checkConfigurationAPI() { .build() } } + +@available(*, deprecated) +func checkDeprecatedConfiguration(_ builder: Configuration.Builder) { + _ = builder + .with(usesStoreKit2IfAvailable: false) +} diff --git a/Tests/UnitTests/Purchasing/ConfigurationTests.swift b/Tests/UnitTests/Purchasing/ConfigurationTests.swift index da006c2e20..ba51cf70ad 100644 --- a/Tests/UnitTests/Purchasing/ConfigurationTests.swift +++ b/Tests/UnitTests/Purchasing/ConfigurationTests.swift @@ -31,4 +31,50 @@ class ConfigurationTests: TestCase { expect(Configuration.validate(apiKey: "swRTCezdEzjnJSxdexDNJfcfiFrMXwqZ")) == .legacy } + func testNoObserverModeWithStoreKit1() { + let configuration = Configuration.Builder(withAPIKey: "test").build() + + expect(configuration.observerMode) == false + expect(configuration.storeKit2Setting) == .enabledOnlyForOptimizations + + self.logger.verifyMessageWasNotLogged(Strings.configure.observer_mode_with_storekit2) + } + + @available(*, deprecated) + func testNoObserverModeWithStoreKit2() { + let configuration = Configuration.Builder(withAPIKey: "test") + .with(usesStoreKit2IfAvailable: true) + .build() + + expect(configuration.observerMode) == false + expect(configuration.storeKit2Setting) == .enabledForCompatibleDevices + + self.logger.verifyMessageWasNotLogged(Strings.configure.observer_mode_with_storekit2) + } + + func testObserverModeWithStoreKit1() { + let configuration = Configuration.Builder(withAPIKey: "test") + .with(observerMode: true) + .build() + + expect(configuration.observerMode) == true + expect(configuration.storeKit2Setting) == .enabledOnlyForOptimizations + + self.logger.verifyMessageWasNotLogged(Strings.configure.observer_mode_with_storekit2) + } + + @available(*, deprecated) + func testObserverModeWithStoreKit2() { + let configuration = Configuration.Builder(withAPIKey: "test") + .with(observerMode: true) + .with(usesStoreKit2IfAvailable: true) + .build() + + expect(configuration.observerMode) == true + expect(configuration.storeKit2Setting) == .enabledForCompatibleDevices + + self.logger.verifyMessageWasLogged(Strings.configure.observer_mode_with_storekit2, + level: .warn) + } + }