diff --git a/Gemfile.lock b/Gemfile.lock index d38083ddfe..eb69684e91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,20 +17,20 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.573.0) - aws-sdk-core (3.130.0) + aws-partitions (1.579.0) + aws-sdk-core (3.130.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.55.0) + aws-sdk-kms (1.56.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.113.0) + aws-sdk-s3 (1.113.1) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -116,7 +116,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.205.1) + fastlane (2.205.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -160,7 +160,7 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.18.0) + google-apis-androidpublisher_v3 (0.19.0) google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.2) addressable (~> 2.5, >= 2.5.1) @@ -175,7 +175,7 @@ GEM google-apis-core (>= 0.4, < 2.a) google-apis-playcustomapp_v1 (0.7.0) google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.12.0) + google-apis-storage_v1 (0.13.0) google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) @@ -183,7 +183,7 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.36.1) + google-cloud-storage (1.36.2) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) @@ -191,7 +191,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.2) + googleauth (1.1.3) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -234,7 +234,7 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.6.0) - public_suffix (4.0.6) + public_suffix (4.0.7) rake (13.0.6) redcarpet (3.5.1) representable (3.1.1) diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index c1ae0f772f..bf29ba7823 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ @@ -249,7 +249,6 @@ 5796A39427D6BD6900653165 /* BackendGetOfferingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A39327D6BD6900653165 /* BackendGetOfferingsTests.swift */; }; 5796A39627D6BDAB00653165 /* BackendPostOfferForSigningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A39527D6BDAB00653165 /* BackendPostOfferForSigningTests.swift */; }; 5796A39927D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A39827D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift */; }; - 5796A39B27D6C20A00653165 /* BackendPostAttributionDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A39A27D6C20A00653165 /* BackendPostAttributionDataTests.swift */; }; 5796A3A927D7C43500653165 /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A3A827D7C43500653165 /* Deprecations.swift */; }; 5796A3C027D7D64500653165 /* ResultExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A3BF27D7D64500653165 /* ResultExtensionsTests.swift */; }; 57A0FBF02749C0C2009E2FC3 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A0FBEF2749C0C2009E2FC3 /* Atomic.swift */; }; @@ -268,18 +267,18 @@ 57CFB96D27FE0E79002A6730 /* MockCurrentUserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CFB96B27FE0E79002A6730 /* MockCurrentUserProvider.swift */; }; 57CFB98427FE2258002A6730 /* StoreKit2Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CFB98327FE2258002A6730 /* StoreKit2Setting.swift */; }; 57D04BB827D947C6006DAC06 /* HTTPResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D04BB727D947C6006DAC06 /* HTTPResponseTests.swift */; }; - 57D5414227F656D9004CC35C /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D5414127F656D9004CC35C /* NetworkError.swift */; }; 57D5412E27F6311C004CC35C /* OfferingsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D5412D27F6311C004CC35C /* OfferingsResponse.swift */; }; + 57D5414227F656D9004CC35C /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D5414127F656D9004CC35C /* NetworkError.swift */; }; 57DC9F4627CC2E4900DA6AF9 /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DC9F4527CC2E4900DA6AF9 /* HTTPRequest.swift */; }; 57DC9F4A27CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DC9F4927CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift */; }; 57DE806D28074976008D6C6F /* Storefront.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE806C28074976008D6C6F /* Storefront.swift */; }; 57DE807128074C23008D6C6F /* SK1Storefront.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE807028074C23008D6C6F /* SK1Storefront.swift */; }; 57DE807328074C76008D6C6F /* SK2Storefront.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE807228074C76008D6C6F /* SK2Storefront.swift */; }; - 57DE80BE28077010008D6C6F /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D22BF6626F3CBFB001AE2F9 /* XCTestCase+Extensions.swift */; }; - 57DE80BF2807705F008D6C6F /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D22BF6626F3CBFB001AE2F9 /* XCTestCase+Extensions.swift */; }; 57DE80892807540D008D6C6F /* StorefrontTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE80882807540D008D6C6F /* StorefrontTests.swift */; }; 57DE80AE28075D77008D6C6F /* OSVersionEquivalent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE80AD28075D77008D6C6F /* OSVersionEquivalent.swift */; }; 57DE80AF28075D77008D6C6F /* OSVersionEquivalent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE80AD28075D77008D6C6F /* OSVersionEquivalent.swift */; }; + 57DE80BE28077010008D6C6F /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D22BF6626F3CBFB001AE2F9 /* XCTestCase+Extensions.swift */; }; + 57DE80BF2807705F008D6C6F /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D22BF6626F3CBFB001AE2F9 /* XCTestCase+Extensions.swift */; }; 57E0473B277260DE0082FE91 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 57E0473A277260DE0082FE91 /* SnapshotTesting */; }; 57E2230727500BB1002DB06E /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E2230627500BB1002DB06E /* AtomicTests.swift */; }; 57EAE527274324C60060EB74 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAE526274324C60060EB74 /* Lock.swift */; }; @@ -305,6 +304,7 @@ A563F586271E072B00246E0C /* MockBeginRefundRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A563F585271E072B00246E0C /* MockBeginRefundRequestHelper.swift */; }; A563F589271E1DAD00246E0C /* MockBeginRefundRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A563F585271E072B00246E0C /* MockBeginRefundRequestHelper.swift */; }; A56F9AB126990E9200AFC48F /* CustomerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56F9AB026990E9200AFC48F /* CustomerInfo.swift */; }; + A5B6CDD8280F3843007629D5 /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5B6CDD5280F3843007629D5 /* AdServices.framework */; platformFilters = (ios, maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; A5F0104E2717B3150090732D /* BeginRefundRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F0104D2717B3150090732D /* BeginRefundRequestHelper.swift */; }; B300E4BF26D436F900B22262 /* LogIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */; }; B300E4C026D4371200B22262 /* SKPaymentTransactionExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F591492526B994B400D32E58 /* SKPaymentTransactionExtensionsTests.swift */; }; @@ -333,7 +333,6 @@ B34605CA279A6E380031CA74 /* GetCustomerInfoOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605B5279A6E380031CA74 /* GetCustomerInfoOperation.swift */; }; B34605CB279A6E380031CA74 /* PostReceiptDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605B6279A6E380031CA74 /* PostReceiptDataOperation.swift */; }; B34605CC279A6E380031CA74 /* LogInOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605B7279A6E380031CA74 /* LogInOperation.swift */; }; - B34605CD279A6E380031CA74 /* PostAttributionDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605B8279A6E380031CA74 /* PostAttributionDataOperation.swift */; }; B34605CE279A6E380031CA74 /* PostSubscriberAttributesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605B9279A6E380031CA74 /* PostSubscriberAttributesOperation.swift */; }; B34605CF279A6E380031CA74 /* GetOfferingsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605BA279A6E380031CA74 /* GetOfferingsOperation.swift */; }; B34605D1279A6E600031CA74 /* SubscribersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34605D0279A6E600031CA74 /* SubscribersAPI.swift */; }; @@ -393,7 +392,6 @@ F5BE424226965F9F00254A30 /* ProductRequestData+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BE424126965F9F00254A30 /* ProductRequestData+Initialization.swift */; }; F5BE44432698581100254A30 /* AttributionTypeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BE44422698581100254A30 /* AttributionTypeFactory.swift */; }; F5BE444726985E7B00254A30 /* AttributionTypeFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E350420D54B99BB39448E0 /* AttributionTypeFactoryTests.swift */; }; - F5BE4479269E4A4C00254A30 /* AfficheClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BE4478269E4A4C00254A30 /* AfficheClientProxy.swift */; }; F5BE447B269E4A7500254A30 /* TrackingManagerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BE447A269E4A7500254A30 /* TrackingManagerProxy.swift */; }; F5BE447D269E4ADB00254A30 /* ASIdManagerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BE447C269E4ADB00254A30 /* ASIdManagerProxy.swift */; }; F5C0196926E880800005D61E /* StoreKitStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C0196826E880800005D61E /* StoreKitStrings.swift */; }; @@ -687,7 +685,6 @@ 5796A39527D6BDAB00653165 /* BackendPostOfferForSigningTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendPostOfferForSigningTests.swift; sourceTree = ""; }; 5796A39727D6C07D00653165 /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; 5796A39827D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendPostSubscriberAttributesTests.swift; sourceTree = ""; }; - 5796A39A27D6C20A00653165 /* BackendPostAttributionDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendPostAttributionDataTests.swift; sourceTree = ""; }; 5796A3A827D7C43500653165 /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecations.swift; sourceTree = ""; }; 5796A3BF27D7D64500653165 /* ResultExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtensionsTests.swift; sourceTree = ""; }; 57A0FBEF2749C0C2009E2FC3 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; @@ -704,8 +701,8 @@ 57CFB96B27FE0E79002A6730 /* MockCurrentUserProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCurrentUserProvider.swift; sourceTree = ""; }; 57CFB98327FE2258002A6730 /* StoreKit2Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2Setting.swift; sourceTree = ""; }; 57D04BB727D947C6006DAC06 /* HTTPResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponseTests.swift; sourceTree = ""; }; - 57D5414127F656D9004CC35C /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 57D5412D27F6311C004CC35C /* OfferingsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferingsResponse.swift; sourceTree = ""; }; + 57D5414127F656D9004CC35C /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 57DC9F4527CC2E4900DA6AF9 /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; 57DC9F4927CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatusCodeTests.swift; sourceTree = ""; }; 57DE806C28074976008D6C6F /* Storefront.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storefront.swift; sourceTree = ""; }; @@ -736,6 +733,7 @@ A563F585271E072B00246E0C /* MockBeginRefundRequestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBeginRefundRequestHelper.swift; sourceTree = ""; }; A563F587271E076800246E0C /* BeginRefundRequestHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeginRefundRequestHelperTests.swift; sourceTree = ""; }; A56F9AB026990E9200AFC48F /* CustomerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerInfo.swift; sourceTree = ""; }; + A5B6CDD5280F3843007629D5 /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/AdServices.framework; sourceTree = DEVELOPER_DIR; }; A5F0104D2717B3150090732D /* BeginRefundRequestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeginRefundRequestHelper.swift; sourceTree = ""; }; B302206927271BCB008F1A0D /* Decoder+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decoder+Extensions.swift"; sourceTree = ""; }; B302206D2728B798008F1A0D /* BackendErrorStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendErrorStrings.swift; sourceTree = ""; }; @@ -759,7 +757,6 @@ B34605B5279A6E380031CA74 /* GetCustomerInfoOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetCustomerInfoOperation.swift; sourceTree = ""; }; B34605B6279A6E380031CA74 /* PostReceiptDataOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostReceiptDataOperation.swift; sourceTree = ""; }; B34605B7279A6E380031CA74 /* LogInOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogInOperation.swift; sourceTree = ""; }; - B34605B8279A6E380031CA74 /* PostAttributionDataOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostAttributionDataOperation.swift; sourceTree = ""; }; B34605B9279A6E380031CA74 /* PostSubscriberAttributesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostSubscriberAttributesOperation.swift; sourceTree = ""; }; B34605BA279A6E380031CA74 /* GetOfferingsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetOfferingsOperation.swift; sourceTree = ""; }; B34605D0279A6E600031CA74 /* SubscribersAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscribersAPI.swift; sourceTree = ""; }; @@ -818,7 +815,6 @@ F5BE424126965F9F00254A30 /* ProductRequestData+Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductRequestData+Initialization.swift"; sourceTree = ""; }; F5BE4244269676E200254A30 /* StoreKitRequestFetcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreKitRequestFetcherTests.swift; sourceTree = ""; }; F5BE44422698581100254A30 /* AttributionTypeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributionTypeFactory.swift; sourceTree = ""; }; - F5BE4478269E4A4C00254A30 /* AfficheClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AfficheClientProxy.swift; sourceTree = ""; }; F5BE447A269E4A7500254A30 /* TrackingManagerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingManagerProxy.swift; sourceTree = ""; }; F5BE447C269E4ADB00254A30 /* ASIdManagerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASIdManagerProxy.swift; sourceTree = ""; }; F5C0196826E880800005D61E /* StoreKitStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitStrings.swift; sourceTree = ""; }; @@ -844,6 +840,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A5B6CDD8280F3843007629D5 /* AdServices.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1328,6 +1325,7 @@ 3530C18722653E8F00D6DF52 /* Frameworks */ = { isa = PBXGroup; children = ( + A5B6CDD5280F3843007629D5 /* AdServices.framework */, B36824BD268FBC5B00957E4C /* XCTest.framework */, 2DE20B9126409ECF004C597D /* StoreKit.framework */, 2DE20B7526408806004C597D /* StoreKitTest.framework */, @@ -1560,7 +1558,6 @@ 5796A38F27D6BCD100653165 /* BackendGetIntroEligibilityTests.swift */, 5796A39327D6BD6900653165 /* BackendGetOfferingsTests.swift */, 5796A38B27D6BA1600653165 /* BackendLoginTests.swift */, - 5796A39A27D6C20A00653165 /* BackendPostAttributionDataTests.swift */, 5796A39527D6BDAB00653165 /* BackendPostOfferForSigningTests.swift */, 5796A39827D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift */, 5796A38727D6B85900653165 /* BackendPostReceiptDataTests.swift */, @@ -1627,7 +1624,6 @@ B34605B4279A6E380031CA74 /* GetIntroEligibilityOperation.swift */, B34605BA279A6E380031CA74 /* GetOfferingsOperation.swift */, B34605B7279A6E380031CA74 /* LogInOperation.swift */, - B34605B8279A6E380031CA74 /* PostAttributionDataOperation.swift */, B34605AD279A6E380031CA74 /* PostOfferForSigningOperation.swift */, B34605B6279A6E380031CA74 /* PostReceiptDataOperation.swift */, B34605B9279A6E380031CA74 /* PostSubscriberAttributesOperation.swift */, @@ -1704,7 +1700,6 @@ isa = PBXGroup; children = ( B39E8119268E849900D31189 /* AttributionNetwork.swift */, - F5BE4478269E4A4C00254A30 /* AfficheClientProxy.swift */, F5BE447C269E4ADB00254A30 /* ASIdManagerProxy.swift */, 37E35CD16BB73BB091E64D9A /* AttributionData.swift */, 37E3521731D8DC16873F55F3 /* AttributionFetcher.swift */, @@ -2168,7 +2163,6 @@ B39E811D268E887500D31189 /* SubscriberAttribute.swift in Sources */, A5F0104E2717B3150090732D /* BeginRefundRequestHelper.swift in Sources */, B34605C0279A6E380031CA74 /* CustomerInfoCallback.swift in Sources */, - F5BE4479269E4A4C00254A30 /* AfficheClientProxy.swift in Sources */, B33CEAA0268CDCC9008A3144 /* ISOPeriodFormatter.swift in Sources */, 2DDF41A324F6F331005BC22D /* ReceiptParser.swift in Sources */, 2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */, @@ -2273,7 +2267,6 @@ F5714EA526D6C24D00635477 /* JSONDecoder+Extensions.swift in Sources */, B302206A27271BCB008F1A0D /* Decoder+Extensions.swift in Sources */, B3766F1E26BDA95100141450 /* IntroEligibilityResponse.swift in Sources */, - B34605CD279A6E380031CA74 /* PostAttributionDataOperation.swift in Sources */, B34605C1279A6E380031CA74 /* NetworkOperation.swift in Sources */, 35E840CC270FB70D00899AE2 /* ManageSubscriptionsHelper.swift in Sources */, 6E38843A0CAFD551013D0A3F /* StoreProduct.swift in Sources */, @@ -2303,7 +2296,6 @@ 2D4D6AF524F717B800B656BE /* ContainerFactory.swift in Sources */, 351B514726D44A0D00BD2BD7 /* MockSystemInfo.swift in Sources */, B300E4C226D439B700B22262 /* IntroEligibilityCalculatorTests.swift in Sources */, - 5796A39B27D6C20A00653165 /* BackendPostAttributionDataTests.swift in Sources */, 2DDF41CA24F6F4C3005BC22D /* ArraySlice_UInt8+ExtensionsTests.swift in Sources */, 2DDF41E124F6F527005BC22D /* MockReceiptParser.swift in Sources */, 351B514D26D44A8600BD2BD7 /* MockHTTPClient.swift in Sources */, diff --git a/Sources/Attribution/AfficheClientProxy.swift b/Sources/Attribution/AfficheClientProxy.swift deleted file mode 100644 index 81aff940d5..0000000000 --- a/Sources/Attribution/AfficheClientProxy.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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 -// -// AfficheClientProxy.swift -// -// Created by Juanpe Catalán on 14/7/21. -// - -import Foundation - -typealias AttributionDetailsBlock = ([String: NSObject]?, Error?) -> Void - -// We need this class to avoid Kid apps being rejected for getting idfa. It seems like App -// Review uses some grep to find the class names, so we ended up creating a fake class that -// exposes the same methods we're looking for in ADClient to call the same methods and mangling -// the class names. So that Apple can't find them during the review, but we can still access them on runtime. -// You can see the class here: https://rev.cat/fake-affiche-client -class FakeAfficheClient: NSObject { - - // We need this method to be available as an optional implicitly unwrapped method for `AnyClass`. - @objc static func sharedClient() -> FakeAfficheClient { - FakeAfficheClient() - } - - // We need this method to be available as an optional implicitly unwrapped method for `AnyClass`. - @objc(requestAttributionDetailsWithBlock:) - func requestAttributionDetails(_ completionHandler: @escaping AttributionDetailsBlock) { - Logger.warn(Strings.attribution.apple_affiche_framework_present_but_couldnt_call_request_attribution_details) - } - -} - -class AfficheClientProxy { - - private static let mangledClassName = "NQPyvrag" - - static var afficheClientClass: AnyClass? { - NSClassFromString(Self.mangledClassName.rot13()) - } - - func requestAttributionDetails(_ completionHandler: @escaping AttributionDetailsBlock) { - let client: AnyObject - if let klass = Self.afficheClientClass, let clientClass = klass as AnyObject as? NSObjectProtocol { - // This looks strange, but #selector() does fun things to create a selector. If the selector for the given - // function matches the selector on another class, it can be used in place. Results: - // If ADClient class is instantiated above, then +sharedClient selector is performed even though you can see - // that we're using #selector(FakeAfficheClient.sharedClient) to instantiate a Selector object. - client = clientClass.perform(#selector(FakeAfficheClient.sharedClient)).takeUnretainedValue() - } else { - client = FakeAfficheClient.sharedClient() - } - - client.requestAttributionDetails(completionHandler) - } - -} diff --git a/Sources/Attribution/AttributionFetcher.swift b/Sources/Attribution/AttributionFetcher.swift index 155166c333..2385e5615e 100644 --- a/Sources/Attribution/AttributionFetcher.swift +++ b/Sources/Attribution/AttributionFetcher.swift @@ -19,10 +19,16 @@ import UIKit import WatchKit #endif +#if canImport(AdServices) +import AdServices +#endif + enum AttributionFetcherError: Error { case identifierForAdvertiserUnavailableForPlatform case identifierForAdvertiserFrameworksUnavailable + case adServicesNotAvailable + case adServicesTokenFetchError } @@ -67,35 +73,23 @@ class AttributionFetcher { return nil } - func afficheClientAttributionDetails(completion: @escaping ([String: NSObject]?, Error?) -> Void) { - // Should match available platforms in - // https://developer.apple.com/documentation/iad/adclient?language=swift -#if os(iOS) - guard let afficheClientProxy = attributionFactory.afficheClientProxy() else { - Logger.warn(Strings.attribution.search_ads_attribution_cancelled_missing_ad_framework) - completion(nil, AttributionFetcherError.identifierForAdvertiserFrameworksUnavailable) - return + // should match OS availability in https://developer.apple.com/documentation/ad_services + @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) + var adServicesToken: String? { +#if canImport(AdServices) + do { + return try AAAttribution.attributionToken() + } catch { + let message = Strings.attribution.adservices_token_fetch_failed(error: error) + Logger.appleWarning(message) + return nil } - afficheClientProxy.requestAttributionDetails(completion) #else - completion(nil, AttributionFetcherError.identifierForAdvertiserUnavailableForPlatform) + Logger.warn(Strings.attribution.adservices_not_supported) + return nil #endif } - var isAuthorizedToPostSearchAds: Bool { - // Should match platforms that require permissions detailed in - // https://developer.apple.com/app-store/user-privacy-and-data-use/ - if !appTrackingTransparencyRequired { - return true - } - - if #available(iOS 14.0.0, tvOS 14.0.0, *) { - return isAuthorizedToPostSearchAdsInATTRequiredOS - } - - return true - } - var authorizationStatus: FakeTrackingManagerAuthorizationStatus { // should match OS availability here: https://rev.cat/app-tracking-transparency guard #available(iOS 14.0.0, tvOS 14.0.0, macOS 11.0.0, *) else { diff --git a/Sources/Attribution/AttributionNetwork.swift b/Sources/Attribution/AttributionNetwork.swift index 04ccd0d52e..dadb146d71 100644 --- a/Sources/Attribution/AttributionNetwork.swift +++ b/Sources/Attribution/AttributionNetwork.swift @@ -22,37 +22,43 @@ import Foundation /** Apple's search ads */ - case appleSearchAds + @available(*, deprecated, message: "use adServices") + case appleSearchAds = 0 /** Adjust https://www.adjust.com/ */ - case adjust + case adjust = 1 /** AppsFlyer https://www.appsflyer.com/ */ - case appsFlyer + case appsFlyer = 2 /** Branch https://www.branch.io/ */ - case branch + case branch = 3 /** Tenjin https://www.tenjin.io/ */ - case tenjin + case tenjin = 4 /** Facebook https://developers.facebook.com/ */ - case facebook + case facebook = 5 /** mParticle https://www.mparticle.com/ */ - case mParticle + case mParticle = 6 + + /** + AdServices token + */ + case adServices = 7 } diff --git a/Sources/Attribution/AttributionPoster.swift b/Sources/Attribution/AttributionPoster.swift index 40df8469b5..48cb037783 100644 --- a/Sources/Attribution/AttributionPoster.swift +++ b/Sources/Attribution/AttributionPoster.swift @@ -35,7 +35,6 @@ class AttributionPoster { self.subscriberAttributesManager = subscriberAttributesManager } - // swiftlint:disable:next function_body_length func post(attributionData data: [String: Any], fromNetwork network: AttributionNetwork, networkUserId: String?) { @@ -88,46 +87,27 @@ class AttributionPoster { } if !newData.isEmpty { - if network == .appleSearchAds { - postSearchAds(newData: newData, - network: network, - appUserID: currentAppUserID, - newDictToCache: newDictToCache) - } else { - postSubscriberAttributes(newData: newData, - network: network, - appUserID: currentAppUserID, - newDictToCache: newDictToCache) - } + postSubscriberAttributes(newData: newData, + network: network, + appUserID: currentAppUserID, + newDictToCache: newDictToCache) } } - func postAppleSearchAdsAttributionIfNeeded() { - guard attributionFetcher.isAuthorizedToPostSearchAds else { + // should match OS availability in https://developer.apple.com/documentation/ad_services + @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) + func postAdServicesTokenIfNeeded() { + let latestTokenSent = latestNetworkIdAndAdvertisingIdentifierSent(network: .adServices) + guard latestTokenSent == nil else { return } - let latestIdsSent = latestNetworkIdAndAdvertisingIdentifierSent(network: .appleSearchAds) - guard latestIdsSent == nil else { + guard let attributionToken = attributionFetcher.adServicesToken else { return } - attributionFetcher.afficheClientAttributionDetails { attributionDetails, error in - guard let attributionDetails = attributionDetails, - error == nil else { - return - } - - let attributionDetailsValues = Array(attributionDetails.values) - let firstAttributionDict = attributionDetailsValues.first as? [String: NSObject] - - guard let hasIad = firstAttributionDict?["iad-attribution"] as? NSNumber, - hasIad.boolValue == true else { - return - } - - self.post(attributionData: attributionDetails, fromNetwork: .appleSearchAds, networkUserId: nil) - } + Logger.debug("Logging attribution token for now to avoid lint warning: \(attributionToken)") + // post } func postPostponedAttributionDataIfNeeded() { @@ -162,19 +142,6 @@ class AttributionPoster { return cachedDict[networkID] } - private func postSearchAds(newData: [String: Any], - network: AttributionNetwork, - appUserID: String, - newDictToCache: [String: String]) { - backend.post(attributionData: newData, network: network, appUserID: appUserID) { error in - guard error == nil else { - return - } - - self.deviceCache.set(latestNetworkAndAdvertisingIdsSent: newDictToCache, appUserID: appUserID) - } - } - private func postSubscriberAttributes(newData: [String: Any], network: AttributionNetwork, appUserID: String, diff --git a/Sources/Attribution/AttributionTypeFactory.swift b/Sources/Attribution/AttributionTypeFactory.swift index 70717f241a..ceb0697559 100644 --- a/Sources/Attribution/AttributionTypeFactory.swift +++ b/Sources/Attribution/AttributionTypeFactory.swift @@ -16,10 +16,6 @@ import Foundation class AttributionTypeFactory { - func afficheClientProxy() -> AfficheClientProxy? { - return AfficheClientProxy.afficheClientClass == nil ? nil : AfficheClientProxy() - } - func atFollowingProxy() -> TrackingManagerProxy? { return TrackingManagerProxy.trackingClass == nil ? nil : TrackingManagerProxy() } diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md b/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md index ceb1c236af..6aadacb578 100644 --- a/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md +++ b/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md @@ -110,7 +110,7 @@ Most features require configuring the SDK before using it. - ``Purchases/finishTransactions`` - ``Purchases/invalidateCustomerInfoCache()`` - ``Purchases/forceUniversalAppStore`` -- ``Purchases/automaticAppleSearchAdsAttributionCollection`` +- ``Purchases/automaticAdServicesAttributionTokenCollection`` - ``Purchases/proxyURL`` - ``Purchases/verboseLogs`` - ``Purchases/verboseLogHandler`` diff --git a/Sources/Identity/CustomerInfo.swift b/Sources/Identity/CustomerInfo.swift index 63c578404d..b9c81dacdc 100644 --- a/Sources/Identity/CustomerInfo.swift +++ b/Sources/Identity/CustomerInfo.swift @@ -43,12 +43,6 @@ import Foundation return mostRecentDate } - /// Returns all product IDs of the non-subscription purchases a user has made. - @available(*, deprecated, message: "use nonSubscriptionTransactions") - @objc public var nonConsumablePurchases: Set { - Set(self.nonSubscriptionTransactions.map { $0.productIdentifier }) - } - /** * Returns all the non-subscription purchases a user has made. * The purchases are ordered by purchase date in ascending order. diff --git a/Sources/Logging/Strings/AttributionStrings.swift b/Sources/Logging/Strings/AttributionStrings.swift index 66fb130bc0..b1e511427d 100644 --- a/Sources/Logging/Strings/AttributionStrings.swift +++ b/Sources/Logging/Strings/AttributionStrings.swift @@ -38,7 +38,8 @@ enum AttributionStrings { case unsynced_attributes(unsyncedAttributes: SubscriberAttributeDict) case attribute_set_locally(attribute: String) case missing_advertiser_identifiers - case unknown_sk2_product_discount_type(rawValue: String) + case adservices_not_supported + case adservices_token_fetch_failed(error: Error) } @@ -115,9 +116,12 @@ extension AttributionStrings: CustomStringConvertible { case .missing_advertiser_identifiers: return "Attribution error: identifierForAdvertisers is missing" - case .unknown_sk2_product_discount_type(let rawValue): - return "Failed to create StoreProductDiscount.DiscountType with unknown value: \(rawValue)" + case .adservices_not_supported: + return "Tried to fetch AdServices attribution token on device without " + + "AdServices support." + case .adservices_token_fetch_failed(let error): + return "Fetching AdServices attribution token failed with error: \(error.localizedDescription)" } } diff --git a/Sources/Logging/Strings/StoreKitStrings.swift b/Sources/Logging/Strings/StoreKitStrings.swift index 38a9bc927f..7ab0b6a53e 100644 --- a/Sources/Logging/Strings/StoreKitStrings.swift +++ b/Sources/Logging/Strings/StoreKitStrings.swift @@ -34,6 +34,8 @@ enum StoreKitStrings { case sk1_no_known_product_type + case unknown_sk2_product_discount_type(rawValue: String) + } extension StoreKitStrings: CustomStringConvertible { @@ -71,6 +73,10 @@ extension StoreKitStrings: CustomStringConvertible { case .sk1_no_known_product_type: return "This StoreProduct represents an SK1 product, the type of product cannot be determined, " + "the value will be undefined. Use `StoreProduct.productCategory` instead." + + case .unknown_sk2_product_discount_type(let rawValue): + return "Failed to create StoreProductDiscount.DiscountType with unknown value: \(rawValue)" + } } diff --git a/Sources/Misc/Deprecations.swift b/Sources/Misc/Deprecations.swift index 6014cc6614..f039b80c05 100644 --- a/Sources/Misc/Deprecations.swift +++ b/Sources/Misc/Deprecations.swift @@ -56,9 +56,16 @@ public extension Purchases { return await eligiblePromotionalOffers(forProduct: product) } + /** + * Enable automatic collection of Apple Search Ads attribution. Defaults to `false`. + */ + @available(*, deprecated, message: "Use Purchases.automaticAdServicesAttributionTokenCollection instead") + @objc static var automaticAppleSearchAdsAttributionCollection: Bool = false + } public extension StoreProduct { + @available(iOS, introduced: 13.0, deprecated, renamed: "eligiblePromotionalOffers()") @available(tvOS, introduced: 13.0, deprecated, renamed: "eligiblePromotionalOffers()") @available(watchOS, introduced: 6.2, deprecated, renamed: "eligiblePromotionalOffers()") @@ -67,4 +74,15 @@ public extension StoreProduct { func getEligiblePromotionalOffers() async -> [PromotionalOffer] { return await self.eligiblePromotionalOffers() } + +} + +extension CustomerInfo { + + /// Returns all product IDs of the non-subscription purchases a user has made. + @available(*, deprecated, message: "use nonSubscriptionTransactions") + @objc public var nonConsumablePurchases: Set { + return Set(self.nonSubscriptionTransactions.map { $0.productIdentifier }) + } + } diff --git a/Sources/Networking/Backend.swift b/Sources/Networking/Backend.swift index ba816e3c25..7598c2357b 100644 --- a/Sources/Networking/Backend.swift +++ b/Sources/Networking/Backend.swift @@ -131,20 +131,6 @@ class Backend { self.operationQueue.addOperation(postOfferForSigningOperation) } - func post(attributionData: [String: Any], - network: AttributionNetwork, - appUserID: String, - completion: SimpleResponseHandler?) { - let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.httpClient, - authHeaders: self.authHeaders, - appUserID: appUserID) - let postAttributionDataOperation = PostAttributionDataOperation(configuration: config, - attributionData: attributionData, - network: network, - responseHandler: completion) - self.operationQueue.addOperation(postAttributionDataOperation) - } - func logIn(currentAppUserID: String, newAppUserID: String, completion: @escaping LogInResponseHandler) { diff --git a/Sources/Networking/Operations/PostAttributionDataOperation.swift b/Sources/Networking/Operations/PostAttributionDataOperation.swift deleted file mode 100644 index 70468ea88a..0000000000 --- a/Sources/Networking/Operations/PostAttributionDataOperation.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// 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 -// -// PostAttributionDataOperation.swift -// -// Created by Joshua Liebowitz on 11/19/21. - -import Foundation - -class PostAttributionDataOperation: NetworkOperation { - - private let configuration: UserSpecificConfiguration - private let attributionData: [String: Any] - private let network: AttributionNetwork - private let responseHandler: Backend.SimpleResponseHandler? - - init(configuration: UserSpecificConfiguration, - attributionData: [String: Any], - network: AttributionNetwork, - responseHandler: Backend.SimpleResponseHandler?) { - self.attributionData = attributionData - self.network = network - self.configuration = configuration - self.responseHandler = responseHandler - - super.init(configuration: configuration) - } - - override func begin(completion: @escaping () -> Void) { - self.post(completion: completion) - } - - private func post(completion: @escaping () -> Void) { - guard let appUserID = try? self.configuration.appUserID.escapedOrError() else { - self.responseHandler?(.missingAppUserID()) - completion() - - return - } - - let request = HTTPRequest(method: .post(Body(network: self.network, attributionData: self.attributionData)), - path: .postAttributionData(appUserID: appUserID)) - - self.httpClient.perform( - request, - authHeaders: self.authHeaders - ) { (response: HTTPResponse.Result) in - defer { - completion() - } - - self.responseHandler?(response.error.map(BackendError.networkError)) - } - } - -} - -private extension PostAttributionDataOperation { - - struct Body: Encodable { - - let network: AttributionNetwork - let data: AnyEncodable - - init(network: AttributionNetwork, attributionData: [String: Any]) { - self.network = network - self.data = AnyEncodable(attributionData) - } - - } - -} diff --git a/Sources/Purchasing/Purchases.swift b/Sources/Purchasing/Purchases.swift index d921d078b6..9141bf33f1 100644 --- a/Sources/Purchasing/Purchases.swift +++ b/Sources/Purchasing/Purchases.swift @@ -91,9 +91,12 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void private let operationDispatcher: OperationDispatcher /** - * Enable automatic collection of Apple Search Ads attribution. Defaults to `false`. + * Enable automatic collection of AdServices attribution token. Defaults to `false`. + * + * Should match OS availability in https://developer.apple.com/documentation/ad_services */ - @objc public static var automaticAppleSearchAdsAttributionCollection: Bool = false + @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) + @objc public static var automaticAdServicesAttributionTokenCollection: Bool = false /** * Used to set the log level. Useful for debugging issues with the lovely team @RevenueCat. @@ -415,7 +418,11 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void } subscribeToAppStateNotifications() attributionPoster.postPostponedAttributionDataIfNeeded() - postAppleSearchAddsAttributionCollectionIfNeeded() + + // should match OS availability in https://developer.apple.com/documentation/ad_services + if #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) { + postAdServicesTokenIfNeeded() + } self.customerInfoObservationDisposable = customerInfoManager.monitorChanges { [weak self] customerInfo in guard let self = self else { return } @@ -438,7 +445,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void storeKitWrapper.delegate = nil customerInfoObservationDisposable?() privateDelegate = nil - Self.automaticAppleSearchAdsAttributionCollection = false + Self.proxyURL = nil } @@ -745,11 +752,13 @@ extension Purchases { attributionPoster.post(attributionData: data, fromNetwork: network, networkUserId: networkUserId) } - private func postAppleSearchAddsAttributionCollectionIfNeeded() { - guard Self.automaticAppleSearchAdsAttributionCollection else { + // should match OS availability in https://developer.apple.com/documentation/ad_services + @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) + private func postAdServicesTokenIfNeeded() { + guard Self.automaticAdServicesAttributionTokenCollection else { return } - attributionPoster.postAppleSearchAdsAttributionIfNeeded() + attributionPoster.postAdServicesTokenIfNeeded() } } @@ -1967,7 +1976,11 @@ private extension Purchases { Logger.debug(Strings.configure.application_active) updateAllCachesIfNeeded() dispatchSyncSubscriberAttributesIfNeeded() - postAppleSearchAddsAttributionCollectionIfNeeded() + + // should match OS availability in https://developer.apple.com/documentation/ad_services + if #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) { + postAdServicesTokenIfNeeded() + } } @objc func applicationWillResignActive(notification: Notification) { diff --git a/Sources/Purchasing/StoreKitAbstractions/StoreProductDiscount.swift b/Sources/Purchasing/StoreKitAbstractions/StoreProductDiscount.swift index 0b4f58a2d3..043ddcfa9c 100644 --- a/Sources/Purchasing/StoreKitAbstractions/StoreProductDiscount.swift +++ b/Sources/Purchasing/StoreKitAbstractions/StoreProductDiscount.swift @@ -259,7 +259,7 @@ extension StoreProductDiscount.DiscountType { case SK2ProductDiscount.OfferType.promotional: return .promotional default: - Logger.warn(Strings.attribution.unknown_sk2_product_discount_type(rawValue: sk2Discount.type.rawValue)) + Logger.warn(Strings.storeKit.unknown_sk2_product_discount_type(rawValue: sk2Discount.type.rawValue)) return nil } } diff --git a/Sources/SubscriberAttributes/AttributionDataMigrator.swift b/Sources/SubscriberAttributes/AttributionDataMigrator.swift index 1dfec879fb..d8eba5b47c 100644 --- a/Sources/SubscriberAttributes/AttributionDataMigrator.swift +++ b/Sources/SubscriberAttributes/AttributionDataMigrator.swift @@ -61,8 +61,8 @@ private extension AttributionDataMigrator { networkSpecificSubscriberAttributes = [:] case .mParticle: networkSpecificSubscriberAttributes = convertMParticleAttribution(attributionData) - case .none, .appleSearchAds: - // Apple Search Ads uses standard attribution system + case .none, .appleSearchAds, .adServices: + // Apple Search Ads & AdServices use standard attribution system networkSpecificSubscriberAttributes = [:] } diff --git a/Tests/APITesters/ObjCAPITester/ObjCAPITester.xcodeproj/project.pbxproj b/Tests/APITesters/ObjCAPITester/ObjCAPITester.xcodeproj/project.pbxproj index e817a1e119..f1f1c0598a 100644 --- a/Tests/APITesters/ObjCAPITester/ObjCAPITester.xcodeproj/project.pbxproj +++ b/Tests/APITesters/ObjCAPITester/ObjCAPITester.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2C396F5E281C64B700669657 /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C396F5D281C64B700669657 /* AdServices.framework */; }; 2DD77909270E23870079CBD4 /* RCAttributionNetworkAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = A5D614EC26EBE84F007DDB75 /* RCAttributionNetworkAPI.m */; }; 2DD7790B270E23870079CBD4 /* RCCustomerInfoAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = A5D614F026EBE84F007DDB75 /* RCCustomerInfoAPI.m */; }; 2DD7790C270E23870079CBD4 /* RCPurchasesErrorCodeAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = A5D614EE26EBE84F007DDB75 /* RCPurchasesErrorCodeAPI.m */; }; @@ -45,6 +46,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2C396F5D281C64B700669657 /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = System/Library/Frameworks/AdServices.framework; sourceTree = SDKROOT; }; 2DD778F5270E235B0079CBD4 /* ObjCAPITester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ObjCAPITester.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5738F426278672070096D623 /* RCStoreProductDiscountAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCStoreProductDiscountAPI.h; sourceTree = ""; }; 5738F427278672070096D623 /* RCStoreProductDiscountAPI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCStoreProductDiscountAPI.m; sourceTree = ""; }; @@ -95,6 +97,7 @@ buildActionMask = 2147483647; files = ( 575885A42748274E00CA2169 /* RevenueCat.framework in Frameworks */, + 2C396F5E281C64B700669657 /* AdServices.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -165,6 +168,7 @@ A52A8A3A26EC2AEC00F06846 /* Frameworks */ = { isa = PBXGroup; children = ( + 2C396F5D281C64B700669657 /* AdServices.framework */, 575885A32748274E00CA2169 /* RevenueCat.framework */, 5758859F2748274600CA2169 /* RevenueCat.framework */, 57588593274826DF00CA2169 /* RevenueCat.framework */, diff --git a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCAttributionNetworkAPI.m b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCAttributionNetworkAPI.m index 9a846d310f..4c31ce1ba2 100644 --- a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCAttributionNetworkAPI.m +++ b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCAttributionNetworkAPI.m @@ -15,6 +15,7 @@ @implementation RCAttributionNetworkAPI + (void)checkEnums { RCAttributionNetwork network = RCAttributionNetworkAdjust; switch(network) { + case RCAttributionNetworkAdServices: case RCAttributionNetworkAppleSearchAds: case RCAttributionNetworkAdjust: case RCAttributionNetworkAppsFlyer: diff --git a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCPurchasesAPI.m b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCPurchasesAPI.m index ca9733f93b..d35a48c4c8 100644 --- a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCPurchasesAPI.m +++ b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCPurchasesAPI.m @@ -16,6 +16,7 @@ @implementation RCPurchasesAPI NSString *version; BOOL automaticAppleSearchAdsAttributionCollection; +BOOL automaticAdServicesAttributionTokenCollection; BOOL debugLogsEnabled; RCLogLevel logLevel; NSURL *proxyURL; @@ -72,9 +73,13 @@ + (void)checkAPI { [RCPurchases addAttributionData:@{} fromNetwork:RCAttributionNetworkBranch]; [RCPurchases addAttributionData:@{} fromNetwork:RCAttributionNetworkBranch forNetworkUserId:@""]; [RCPurchases addAttributionData:@{} fromNetwork:RCAttributionNetworkBranch forNetworkUserId:nil]; - + + // should have deprecation warning: + // 'automaticAppleSearchAdsAttributionCollection' is deprecated: Use Purchases.automaticAdServicesAttributionTokenCollection instead automaticAppleSearchAdsAttributionCollection = [RCPurchases automaticAppleSearchAdsAttributionCollection]; + automaticAdServicesAttributionTokenCollection = [RCPurchases automaticAdServicesAttributionTokenCollection]; + // should have deprecation warning 'debugLogsEnabled' is deprecated: use logLevel instead debugLogsEnabled = [RCPurchases debugLogsEnabled]; diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj b/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj index 52fbce1227..4b83f198c2 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2C396F5C281C64AF00669657 /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C396F5B281C64AF00669657 /* AdServices.framework */; }; 2DD778E4270E23460079CBD4 /* AttributionNetworkAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614CA26EBE7EA007DDB75 /* AttributionNetworkAPI.swift */; }; 2DD778E5270E23460079CBD4 /* IntroEligibilityAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614CD26EBE7EA007DDB75 /* IntroEligibilityAPI.swift */; }; 2DD778E6270E23460079CBD4 /* PurchasesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614CE26EBE7EA007DDB75 /* PurchasesAPI.swift */; }; @@ -45,6 +46,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2C396F5B281C64AF00669657 /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = System/Library/Frameworks/AdServices.framework; sourceTree = SDKROOT; }; 2DD778D0270E233F0079CBD4 /* SwiftAPITester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAPITester.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5738F40B27866DD00096D623 /* StoreProductDiscountAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProductDiscountAPI.swift; sourceTree = ""; }; 5738F429278673A80096D623 /* SubscriptionPeriodAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPeriodAPI.swift; sourceTree = ""; }; @@ -75,6 +77,7 @@ buildActionMask = 2147483647; files = ( 5758859C2748272A00CA2169 /* RevenueCat.framework in Frameworks */, + 2C396F5C281C64AF00669657 /* AdServices.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,6 +87,7 @@ 2D6F3871270E58DB002C9987 /* Frameworks */ = { isa = PBXGroup; children = ( + 2C396F5B281C64AF00669657 /* AdServices.framework */, 5758859B2748272A00CA2169 /* RevenueCat.framework */, 575885972748271100CA2169 /* RevenueCat.framework */, ); diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/AttributionNetworkAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/AttributionNetworkAPI.swift index 3852873be2..b948ed9a93 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/AttributionNetworkAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/AttributionNetworkAPI.swift @@ -23,7 +23,8 @@ func checkAttributionNetworkEnums() { .branch, .tenjin, .facebook, - .mParticle: + .mParticle, + .adServices: print(aNetwork!) @unknown default: fatalError() diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/PurchasesAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/PurchasesAPI.swift index ad10bbdbdf..ec10e9b55e 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/PurchasesAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/PurchasesAPI.swift @@ -83,6 +83,8 @@ private func checkStaticMethods() { Purchases.addAttributionData([String: Any](), from: AttributionNetwork.adjust, forNetworkUserId: "") Purchases.addAttributionData([String: Any](), from: AttributionNetwork.adjust, forNetworkUserId: nil) + // should have deprecation warning 'automaticAppleSearchAdsAttributionCollection' is deprecated: Use + // Purchases.automaticAdServicesAttributionTokenCollection instead let automaticAppleSearchAdsAttributionCollection: Bool = Purchases.automaticAppleSearchAdsAttributionCollection // should have deprecation warning 'debugLogsEnabled' is deprecated: use logLevel instead let debugLogsEnabled: Bool = Purchases.debugLogsEnabled @@ -92,9 +94,11 @@ private func checkStaticMethods() { let simulatesAskToBuyInSandbox: Bool = Purchases.simulatesAskToBuyInSandbox let sharedPurchases: Purchases = Purchases.shared let isPurchasesConfigured: Bool = Purchases.isConfigured + let automaticAdServicesAttributionTokenCollection: Bool = Purchases.automaticAdServicesAttributionTokenCollection print(canI, version, automaticAppleSearchAdsAttributionCollection, debugLogsEnabled, logLevel, proxyUrl!, - forceUniversalAppStore, simulatesAskToBuyInSandbox, sharedPurchases, isPurchasesConfigured) + forceUniversalAppStore, simulatesAskToBuyInSandbox, sharedPurchases, isPurchasesConfigured, + automaticAdServicesAttributionTokenCollection) } private func checkPurchasesPurchasingAPI(purchases: Purchases) { diff --git a/Tests/UnitTests/Attribution/AttributionPosterTests.swift b/Tests/UnitTests/Attribution/AttributionPosterTests.swift index d5a03355cf..1241bfac01 100644 --- a/Tests/UnitTests/Attribution/AttributionPosterTests.swift +++ b/Tests/UnitTests/Attribution/AttributionPosterTests.swift @@ -57,7 +57,6 @@ class AttributionPosterTests: XCTestCase { attributionFetcher: attributionFetcher, subscriberAttributesManager: subscriberAttributesManager) resetAttributionStaticProperties() - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) } private func resetAttributionStaticProperties() { @@ -65,9 +64,7 @@ class AttributionPosterTests: XCTestCase { MockTrackingManagerProxy.mockAuthorizationStatus = .authorized } - MockAttributionTypeFactory.shouldReturnAdClientProxy = true MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - MockAdClientProxy.requestAttributionDetailsCallCount = 0 } override func tearDown() { @@ -79,223 +76,43 @@ class AttributionPosterTests: XCTestCase { func testPostAttributionDataSkipsIfAlreadySent() { let userID = "userID" - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) attributionPoster.post(attributionData: ["something": "here"], fromNetwork: .adjust, networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 1 attributionPoster.post(attributionData: ["something": "else"], fromNetwork: .adjust, networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 1 } - func testPostAppleSearchAdsAttributionDataSkipsIfAlreadySent() { - let userID = "userID" - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) - - attributionPoster.post(attributionData: ["something": "here"], - fromNetwork: .appleSearchAds, - networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 1 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 0 - - attributionPoster.post(attributionData: ["something": "else"], - fromNetwork: .appleSearchAds, - networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 1 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 0 - - } - func testPostAttributionDataDoesntSkipIfNetworkChanged() { let userID = "userID" - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) attributionPoster.post(attributionData: ["something": "here"], fromNetwork: .adjust, networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 1 attributionPoster.post(attributionData: ["something": "else"], fromNetwork: .facebook, networkUserId: userID) - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 2 } func testPostAttributionDataDoesntSkipIfDifferentUserIdButSameNetwork() { - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) - attributionPoster.post(attributionData: ["something": "here"], fromNetwork: .adjust, networkUserId: "attributionUser1") - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 1 attributionPoster.post(attributionData: ["something": "else"], fromNetwork: .adjust, networkUserId: "attributionUser2") - - expect(self.backend.invokedPostAttributionDataCount) == 0 expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 2 } - func testPostAppleSearchAdsAttributionDataDoesntSkipIfDifferentUserIdButSameNetwork() { - backend.stubbedPostAttributionDataCompletionResult = (nil, ()) - - attributionPoster.post(attributionData: ["something": "here"], - fromNetwork: .appleSearchAds, - networkUserId: "attributionUser1") - expect(self.backend.invokedPostAttributionDataCount) == 1 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 0 - - attributionPoster.post(attributionData: ["something": "else"], - fromNetwork: .appleSearchAds, - networkUserId: "attributionUser2") - - expect(self.backend.invokedPostAttributionDataCount) == 2 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 0 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfATTFrameworkNotIncludedOnNewOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = true - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = false - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 0 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetCount) == 0 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfIAdFrameworkNotIncluded() { - MockAttributionTypeFactory.shouldReturnAdClientProxy = false - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 0 - } - - // `MockTrackingManagerProxy.mockAuthorizationStatus isn't available on tvOS - #if os(iOS) - - func testPostAppleSearchAdsAttributionIfNeededPostsIfATTFrameworkNotIncludedOnOldOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = false - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = false - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - } - - func testPostAppleSearchAdsAttributionIfNeededPostsIfAuthorizedOnNewOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = true - - MockTrackingManagerProxy.mockAuthorizationStatus = .authorized - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - } - - func testPostAppleSearchAdsAttributionIfNeededPostsIfAuthorizedOnOldOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = false - MockTrackingManagerProxy.mockAuthorizationStatus = .authorized - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - } - - func testPostAppleSearchAdsAttributionIfNeededPostsIfAuthNotDeterminedOnOldOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = false - MockTrackingManagerProxy.mockAuthorizationStatus = .notDetermined - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfAuthNotDeterminedOnNewOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = true - - MockTrackingManagerProxy.mockAuthorizationStatus = .notDetermined - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 0 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfNotAuthorizedOnOldOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = false - MockTrackingManagerProxy.mockAuthorizationStatus = .denied - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 0 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfNotAuthorizedOnNewOS() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - systemInfo.stubbedIsOperatingSystemAtLeastVersion = true - MockTrackingManagerProxy.mockAuthorizationStatus = .denied - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 0 - } - - func testPostAppleSearchAdsAttributionIfNeededSkipsIfAlreadySent() throws { - guard #available(iOS 14, *) else { throw XCTSkip() } - - MockTrackingManagerProxy.mockAuthorizationStatus = .authorized - MockAttributionTypeFactory.shouldReturnAdClientProxy = true - MockAttributionTypeFactory.shouldReturnTrackingManagerProxy = true - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - - self.attributionPoster.postAppleSearchAdsAttributionIfNeeded() - - expect(MockAdClientProxy.requestAttributionDetailsCallCount) == 1 - } - - #endif } diff --git a/Tests/UnitTests/Mocks/MockAttributionFetcher.swift b/Tests/UnitTests/Mocks/MockAttributionFetcher.swift index 636ee45921..3276ac4d08 100644 --- a/Tests/UnitTests/Mocks/MockAttributionFetcher.swift +++ b/Tests/UnitTests/Mocks/MockAttributionFetcher.swift @@ -15,9 +15,9 @@ class MockAttributionFetcher: AttributionFetcher { return "rc_idfv" } - override func afficheClientAttributionDetails( - completion completionHandler: @escaping ([String: NSObject]?, Error?) -> Void - ) { - completionHandler(["Version3.1": ["iad-campaign-id": 15292426, "iad-attribution": true] as NSObject], nil) + var adServicesTokenCollectionCalled = false + override var adServicesToken: String? { + adServicesTokenCollectionCalled = true + return nil } } diff --git a/Tests/UnitTests/Mocks/MockAttributionTypeFactory.swift b/Tests/UnitTests/Mocks/MockAttributionTypeFactory.swift index 0f6042e599..d2ade9cc59 100644 --- a/Tests/UnitTests/Mocks/MockAttributionTypeFactory.swift +++ b/Tests/UnitTests/Mocks/MockAttributionTypeFactory.swift @@ -16,25 +16,6 @@ import Foundation #endif @testable import RevenueCat -class MockAdClientProxy: AfficheClientProxy { - - static var mockAttributionDetails: [String: NSObject] = [ - "Version3.1": - [ - "iad-campaign-id": 15292426, - "iad-attribution": true - ] as NSObject - ] - static var mockError: Error? - static var requestAttributionDetailsCallCount = 0 - - override func requestAttributionDetails(_ completionHandler: @escaping AttributionDetailsBlock) { - Self.requestAttributionDetailsCallCount += 1 - completionHandler(Self.mockAttributionDetails, Self.mockError) - } - -} - @available(iOS 14, macOS 11, tvOS 14, *) class MockTrackingManagerProxy: TrackingManagerProxy { @@ -48,12 +29,6 @@ class MockTrackingManagerProxy: TrackingManagerProxy { class MockAttributionTypeFactory: AttributionTypeFactory { - static var shouldReturnAdClientProxy = true - - override func afficheClientProxy() -> AfficheClientProxy? { - Self.shouldReturnAdClientProxy ? MockAdClientProxy() : nil - } - static var shouldReturnTrackingManagerProxy = true override func atFollowingProxy() -> TrackingManagerProxy? { diff --git a/Tests/UnitTests/Mocks/MockBackend.swift b/Tests/UnitTests/Mocks/MockBackend.swift index cd38ecb74f..7e6e05d98f 100644 --- a/Tests/UnitTests/Mocks/MockBackend.swift +++ b/Tests/UnitTests/Mocks/MockBackend.swift @@ -121,27 +121,6 @@ class MockBackend: Backend { completion(stubbedGetOfferingsCompletionResult!) } - var invokedPostAttributionData = false - var invokedPostAttributionDataCount = 0 - var invokedPostAttributionDataParameters: (data: [String: Any]?, network: AttributionNetwork, appUserID: String?)? - var invokedPostAttributionDataParametersList = [(data: [String: Any]?, - network: AttributionNetwork, - appUserID: String?)]() - var stubbedPostAttributionDataCompletionResult: (BackendError?, Void)? - - override func post(attributionData: [String: Any], - network: AttributionNetwork, - appUserID: String, - completion: ((BackendError?) -> Void)?) { - invokedPostAttributionData = true - invokedPostAttributionDataCount += 1 - invokedPostAttributionDataParameters = (attributionData, network, appUserID) - invokedPostAttributionDataParametersList.append((attributionData, network, appUserID)) - if let result = stubbedPostAttributionDataCompletionResult { - completion?(result.0) - } - } - var invokedCreateAlias = false var invokedCreateAliasCount = 0 var invokedCreateAliasParameters: (appUserID: String?, newAppUserID: String?)? diff --git a/Tests/UnitTests/Networking/Backend/BackendPostAttributionDataTests.swift b/Tests/UnitTests/Networking/Backend/BackendPostAttributionDataTests.swift deleted file mode 100644 index 14616130f2..0000000000 --- a/Tests/UnitTests/Networking/Backend/BackendPostAttributionDataTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// 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 -// -// BackendPostAttributionDataTests.swift -// -// Created by Nacho Soto on 3/7/22. - -import Foundation -import Nimble -import XCTest - -@testable import RevenueCat - -class BackendPostAttributionDataTests: BaseBackendTests { - - override func createClient() -> MockHTTPClient { - super.createClient(#file) - } - - func testPostAttributesPutsDataInDataKey() throws { - self.httpClient.mock( - requestPath: .postAttributionData(appUserID: Self.userID), - response: .init(statusCode: .success) - ) - - let data: [String: AnyObject] = ["a": "b" as NSString, "c": "d" as NSString] - - backend.post(attributionData: data, - network: AttributionNetwork.appleSearchAds, - appUserID: Self.userID, - completion: nil) - - expect(self.httpClient.calls).toEventually(haveCount(1)) - } - -} diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS12-testPostAttributesPutsDataInDataKey.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS12-testPostAttributesPutsDataInDataKey.1.json deleted file mode 100644 index 8372d04da1..0000000000 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS12-testPostAttributesPutsDataInDataKey.1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "headers" : { - "Authorization" : "Bearer asharedsecret" - }, - "request" : { - "body" : { - "data" : { - "a" : "b", - "c" : "d" - }, - "network" : 0 - }, - "method" : "POST", - "url" : "https:\/\/api.revenuecat.com\/v1\/subscribers\/user\/attribution" - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS13-testPostAttributesPutsDataInDataKey.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS13-testPostAttributesPutsDataInDataKey.1.json deleted file mode 100644 index 8372d04da1..0000000000 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS13-testPostAttributesPutsDataInDataKey.1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "headers" : { - "Authorization" : "Bearer asharedsecret" - }, - "request" : { - "body" : { - "data" : { - "a" : "b", - "c" : "d" - }, - "network" : 0 - }, - "method" : "POST", - "url" : "https:\/\/api.revenuecat.com\/v1\/subscribers\/user\/attribution" - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS14-testPostAttributesPutsDataInDataKey.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS14-testPostAttributesPutsDataInDataKey.1.json deleted file mode 100644 index 8372d04da1..0000000000 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS14-testPostAttributesPutsDataInDataKey.1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "headers" : { - "Authorization" : "Bearer asharedsecret" - }, - "request" : { - "body" : { - "data" : { - "a" : "b", - "c" : "d" - }, - "network" : 0 - }, - "method" : "POST", - "url" : "https:\/\/api.revenuecat.com\/v1\/subscribers\/user\/attribution" - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS15-testPostAttributesPutsDataInDataKey.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS15-testPostAttributesPutsDataInDataKey.1.json deleted file mode 100644 index 8372d04da1..0000000000 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostAttributionDataTests/iOS15-testPostAttributesPutsDataInDataKey.1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "headers" : { - "Authorization" : "Bearer asharedsecret" - }, - "request" : { - "body" : { - "data" : { - "a" : "b", - "c" : "d" - }, - "network" : 0 - }, - "method" : "POST", - "url" : "https:\/\/api.revenuecat.com\/v1\/subscribers\/user\/attribution" - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Purchasing/PurchasesTests.swift b/Tests/UnitTests/Purchasing/PurchasesTests.swift index 61e409a031..599a8195d7 100644 --- a/Tests/UnitTests/Purchasing/PurchasesTests.swift +++ b/Tests/UnitTests/Purchasing/PurchasesTests.swift @@ -210,32 +210,6 @@ class PurchasesTests: XCTestCase { } } - var invokedPostAttributionData = false - var invokedPostAttributionDataCount = 0 - // swiftlint:disable:next large_tuple - var invokedPostAttributionDataParameters: ( - data: [String: Any]?, - network: AttributionNetwork, - appUserID: String? - )? - var invokedPostAttributionDataParametersList = [(data: [String: Any]?, - network: AttributionNetwork, - appUserID: String?)]() - var stubbedPostAttributionDataCompletionResult: (BackendError?, Void)? - - override func post(attributionData: [String: Any], - network: AttributionNetwork, - appUserID: String, - completion: ((BackendError?) -> Void)? = nil) { - invokedPostAttributionData = true - invokedPostAttributionDataCount += 1 - invokedPostAttributionDataParameters = (attributionData, network, appUserID) - invokedPostAttributionDataParametersList.append((attributionData, network, appUserID)) - if let result = stubbedPostAttributionDataCompletionResult { - completion?(result.0) - } - } - var postOfferForSigningCalled = false var postOfferForSigningPaymentDiscountResponse: Result<[String: Any], BackendError> = .success([:]) @@ -289,14 +263,18 @@ class PurchasesTests: XCTestCase { var purchases: Purchases! func setupPurchases(automaticCollection: Bool = false) { - Purchases.automaticAppleSearchAdsAttributionCollection = automaticCollection + if #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) { + Purchases.automaticAdServicesAttributionTokenCollection = automaticCollection + } self.identityManager.mockIsAnonymous = false initializePurchasesInstance(appUserId: identityManager.currentAppUserID) } func setupAnonPurchases() { - Purchases.automaticAppleSearchAdsAttributionCollection = false + if #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) { + Purchases.automaticAdServicesAttributionTokenCollection = false + } self.identityManager.mockIsAnonymous = true initializePurchasesInstance(appUserId: nil) } @@ -1863,22 +1841,6 @@ class PurchasesTests: XCTestCase { expect(attributionData?["rc_idfv"] as? String) == "rc_idfv" } - func testPassesTheArrayForAllNetworks() { - setupPurchases() - let data = ["yo": "dog", "what": 45, "is": ["up"]] as [String: Any] - - Purchases.deprecated.addAttributionData(data, fromNetwork: AttributionNetwork.appleSearchAds) - - for key in data.keys { - expect(self.backend.invokedPostAttributionDataParametersList[0].data?.keys.contains(key)) - .toEventually(beTrue()) - } - expect(self.backend.invokedPostAttributionDataParametersList[0].data?.keys.contains("rc_idfa")) == true - expect(self.backend.invokedPostAttributionDataParametersList[0].data?.keys.contains("rc_idfv")) == true - expect(self.backend.invokedPostAttributionDataParametersList[0].network) == AttributionNetwork.appleSearchAds - expect(self.backend.invokedPostAttributionDataParametersList[0].appUserID) == self.purchases?.appUserID - } - func testSharedInstanceIsSetWhenConfiguring() { let purchases = Purchases.configure(withAPIKey: "") expect(Purchases.shared) === purchases @@ -2158,74 +2120,24 @@ class PurchasesTests: XCTestCase { expect(invokedParameters?.appUserID) == self.purchases?.appUserID } - func testAttributionDataSendsNetworkAppUserId() throws { - let data = ["yo": "dog", "what": 45, "is": ["up"]] as [String: Any] - - Purchases.deprecated.addAttributionData(data, - from: AttributionNetwork.appleSearchAds, - forNetworkUserId: "newuser") - - setupPurchases() - - expect(self.backend.invokedPostAttributionData).toEventually(beTrue()) - - let invokedMethodParams = try XCTUnwrap(self.backend.invokedPostAttributionDataParameters) - for key in data.keys { - expect(invokedMethodParams.data?.keys.contains(key)).to(beTrue()) + @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) + func testAdServicesAttributionTokenIsAutomaticallyCollected() throws { + guard #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) else { + throw XCTSkip("Required API is not available for this test.") } - expect(invokedMethodParams.data?.keys.contains("rc_idfa")) == true - expect(invokedMethodParams.data?.keys.contains("rc_idfv")) == true - expect(invokedMethodParams.data?.keys.contains("rc_attribution_network_id")) == true - expect(invokedMethodParams.data?["rc_attribution_network_id"] as? String) == "newuser" - expect(invokedMethodParams.network) == AttributionNetwork.appleSearchAds - expect(invokedMethodParams.appUserID) == identityManager.currentAppUserID + setupPurchases(automaticCollection: true) + expect(self.attributionFetcher.adServicesTokenCollectionCalled) == true } - func testAttributionDataDontSendNetworkAppUserIdIfNotProvided() throws { - let data = ["yo": "dog", "what": 45, "is": ["up"]] as [String: Any] - - Purchases.deprecated.addAttributionData(data, fromNetwork: AttributionNetwork.appleSearchAds) - - setupPurchases() - - let invokedMethodParams = try XCTUnwrap(self.backend.invokedPostAttributionDataParameters) - for key in data.keys { - expect(invokedMethodParams.data?.keys.contains(key)) == true + @available(iOS 14.3, *) + func testAdServicesAttributionTokenIsNotAutomaticallyCollectedIfDisabled() throws { + guard #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) else { + throw XCTSkip("Required API is not available for this test.") } - expect(invokedMethodParams.data?.keys.contains("rc_idfa")) == true - expect(invokedMethodParams.data?.keys.contains("rc_idfv")) == true - expect(invokedMethodParams.data?.keys.contains("rc_attribution_network_id")) == false - expect(invokedMethodParams.network) == AttributionNetwork.appleSearchAds - expect(invokedMethodParams.appUserID) == identityManager.currentAppUserID - } - - func testAdClientAttributionDataIsAutomaticallyCollected() throws { - setupPurchases(automaticCollection: true) - - let invokedMethodParams = try XCTUnwrap(self.backend.invokedPostAttributionDataParameters) - - expect(invokedMethodParams).toNot(beNil()) - expect(invokedMethodParams.network) == AttributionNetwork.appleSearchAds - - let obtainedVersionData = try XCTUnwrap(invokedMethodParams.data?["Version3.1"] as? NSDictionary) - expect(obtainedVersionData["iad-campaign-id"]).toNot(beNil()) - } - - func testAdClientAttributionDataIsNotAutomaticallyCollectedIfDisabled() { setupPurchases(automaticCollection: false) - expect(self.backend.invokedPostAttributionDataParameters).to(beNil()) - } - - func testAttributionDataPostponesMultiple() { - let data = ["yo": "dog", "what": 45, "is": ["up"]] as [String: Any] - - Purchases.deprecated.addAttributionData(data, from: AttributionNetwork.adjust, forNetworkUserId: "newuser") - - setupPurchases(automaticCollection: true) - expect(self.backend.invokedPostAttributionDataParametersList.count) == 1 - expect(self.subscriberAttributesManager.invokedConvertAttributionDataAndSetParametersList.count) == 1 + expect(self.attributionFetcher.adServicesTokenCollectionCalled) == false } func testObserverModeSetToFalseSetFinishTransactions() throws { diff --git a/Tests/UnitTests/SubscriberAttributes/PurchasesSubscriberAttributesTests.swift b/Tests/UnitTests/SubscriberAttributes/PurchasesSubscriberAttributesTests.swift index f1845d9812..3770759cdb 100644 --- a/Tests/UnitTests/SubscriberAttributes/PurchasesSubscriberAttributesTests.swift +++ b/Tests/UnitTests/SubscriberAttributes/PurchasesSubscriberAttributesTests.swift @@ -126,8 +126,7 @@ class PurchasesSubscriberAttributesTests: XCTestCase { UserDefaults().removePersistentDomain(forName: "TestDefaults") } - func setupPurchases(automaticCollection: Bool = false) { - Purchases.automaticAppleSearchAdsAttributionCollection = automaticCollection + func setupPurchases() { self.mockIdentityManager.mockIsAnonymous = false let purchasesOrchestrator = PurchasesOrchestrator(productsManager: mockProductsManager, storeKitWrapper: mockStoreKitWrapper,