From d1d7383dcf81775737dfcbf0b4a48b1894b54ca6 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Tue, 26 Nov 2024 13:44:17 -0500 Subject: [PATCH] refactor: refactor MPSearchAdsAttribution --- UnitTests/MPStateMachineTests.m | 14 ++++ mParticle-Apple-SDK.xcodeproj/project.pbxproj | 12 --- mParticle-Apple-SDK/MPBackendController.m | 5 +- .../Utils/MPSearchAdsAttribution.h | 9 --- .../Utils/MPSearchAdsAttribution.m | 81 ------------------- mParticle-Apple-SDK/Utils/MPStateMachine.h | 5 +- mParticle-Apple-SDK/Utils/MPStateMachine.m | 69 ++++++++++++++-- 7 files changed, 79 insertions(+), 116 deletions(-) delete mode 100644 mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.h delete mode 100644 mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.m diff --git a/UnitTests/MPStateMachineTests.m b/UnitTests/MPStateMachineTests.m index cfb341ed0..fdf93b707 100644 --- a/UnitTests/MPStateMachineTests.m +++ b/UnitTests/MPStateMachineTests.m @@ -219,4 +219,18 @@ - (void)testSetLocation { #endif } +#if TARGET_OS_IOS == 1 +- (void)testRequestAttribution { + XCTestExpectation *expectation = [self expectationWithDescription:@"Request Attribution"]; + void (^searchAdsCompletion)(void) = ^{ + [expectation fulfill]; + }; + + MPStateMachine *stateMachine = [MParticle sharedInstance].stateMachine; + + [stateMachine requestAttributionDetailsWithBlock:searchAdsCompletion requestsCompleted:0]; + [self waitForExpectationsWithTimeout:DEFAULT_TIMEOUT handler:nil]; +} +#endif + @end diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index 36aa424f5..4e60afae0 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -152,7 +152,6 @@ 53A79BCE29CDFB2000E7489F /* NSDictionary+MPCaseInsensitive.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B0929CDFB1F00E7489F /* NSDictionary+MPCaseInsensitive.m */; }; 53A79BD129CDFB2000E7489F /* MPResponseConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B0C29CDFB1F00E7489F /* MPResponseConfig.h */; }; 53A79BD229CDFB2000E7489F /* MPApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B0D29CDFB1F00E7489F /* MPApplication.m */; }; - 53A79BD629CDFB2000E7489F /* MPSearchAdsAttribution.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1129CDFB1F00E7489F /* MPSearchAdsAttribution.h */; }; 53A79BD729CDFB2000E7489F /* MPUploadBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */; }; 53A79BD929CDFB2000E7489F /* MPUserIdentityChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1429CDFB1F00E7489F /* MPUserIdentityChange.m */; }; 53A79BDA29CDFB2000E7489F /* MPDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1529CDFB1F00E7489F /* MPDevice.m */; }; @@ -163,7 +162,6 @@ 53A79BE229CDFB2000E7489F /* MPIUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */; }; 53A79BE329CDFB2000E7489F /* MPBracket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1E29CDFB1F00E7489F /* MPBracket.cpp */; }; 53A79BE529CDFB2000E7489F /* MPResponseConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2029CDFB1F00E7489F /* MPResponseConfig.m */; }; - 53A79BE729CDFB2000E7489F /* MPSearchAdsAttribution.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2229CDFB1F00E7489F /* MPSearchAdsAttribution.m */; }; 53A79BE829CDFB2000E7489F /* MPUploadBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2329CDFB1F00E7489F /* MPUploadBuilder.h */; }; 53A79BE929CDFB2000E7489F /* MPApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2429CDFB1F00E7489F /* MPApplication.h */; }; 53A79BEB29CDFB2000E7489F /* NSNumber+MPFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */; }; @@ -352,7 +350,6 @@ 53A79D2E29CE23F700E7489F /* MPBackendController.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AA329CDFB1E00E7489F /* MPBackendController.h */; }; 53A79D2F29CE23F700E7489F /* MPKitConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B5F29CDFB1F00E7489F /* MPKitConfiguration.h */; }; 53A79D3029CE23F700E7489F /* MPConnectorResponseProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AAD29CDFB1E00E7489F /* MPConnectorResponseProtocol.h */; }; - 53A79D3129CE23F700E7489F /* MPSearchAdsAttribution.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1129CDFB1F00E7489F /* MPSearchAdsAttribution.h */; }; 53A79D3229CE23F700E7489F /* MPConnectorFactoryProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AB029CDFB1E00E7489F /* MPConnectorFactoryProtocol.h */; }; 53A79D3329CE23F700E7489F /* MPNetworkCommunication.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AB129CDFB1E00E7489F /* MPNetworkCommunication.h */; }; 53A79D3429CE23F700E7489F /* MPURL.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AB329CDFB1E00E7489F /* MPURL.h */; }; @@ -422,7 +419,6 @@ 53A79D7B29CE23F700E7489F /* MPIConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B6429CDFB1F00E7489F /* MPIConstants.m */; }; 53A79D7C29CE23F700E7489F /* MPLaunchInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2929CDFB1F00E7489F /* MPLaunchInfo.m */; }; 53A79D7D29CE23F700E7489F /* MPPromotion.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B4129CDFB1F00E7489F /* MPPromotion.m */; }; - 53A79D7E29CE23F700E7489F /* MPSearchAdsAttribution.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2229CDFB1F00E7489F /* MPSearchAdsAttribution.m */; }; 53A79D8129CE23F700E7489F /* MPIdentityApiRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79A9829CDFB1E00E7489F /* MPIdentityApiRequest.m */; }; 53A79D8229CE23F700E7489F /* MPCommerceEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B4029CDFB1F00E7489F /* MPCommerceEvent.mm */; }; 53A79D8329CE23F700E7489F /* MPConsentSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79AF729CDFB1F00E7489F /* MPConsentSerialization.m */; }; @@ -601,7 +597,6 @@ 53A79B0929CDFB1F00E7489F /* NSDictionary+MPCaseInsensitive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+MPCaseInsensitive.m"; sourceTree = ""; }; 53A79B0C29CDFB1F00E7489F /* MPResponseConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPResponseConfig.h; sourceTree = ""; }; 53A79B0D29CDFB1F00E7489F /* MPApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPApplication.m; sourceTree = ""; }; - 53A79B1129CDFB1F00E7489F /* MPSearchAdsAttribution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSearchAdsAttribution.h; sourceTree = ""; }; 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUploadBuilder.m; sourceTree = ""; }; 53A79B1429CDFB1F00E7489F /* MPUserIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserIdentityChange.m; sourceTree = ""; }; 53A79B1529CDFB1F00E7489F /* MPDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPDevice.m; sourceTree = ""; }; @@ -612,7 +607,6 @@ 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPIUserDefaults.m; sourceTree = ""; }; 53A79B1E29CDFB1F00E7489F /* MPBracket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MPBracket.cpp; sourceTree = ""; }; 53A79B2029CDFB1F00E7489F /* MPResponseConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPResponseConfig.m; sourceTree = ""; }; - 53A79B2229CDFB1F00E7489F /* MPSearchAdsAttribution.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSearchAdsAttribution.m; sourceTree = ""; }; 53A79B2329CDFB1F00E7489F /* MPUploadBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUploadBuilder.h; sourceTree = ""; }; 53A79B2429CDFB1F00E7489F /* MPApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPApplication.h; sourceTree = ""; }; 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSNumber+MPFormatter.swift"; sourceTree = ""; }; @@ -1018,7 +1012,6 @@ D3CEDAC22C9DAC25001B32DF /* MPDateFormatter.swift */, 53A79B0D29CDFB1F00E7489F /* MPApplication.m */, D3961CDD2CC0B7E4003B3194 /* NSString+MPPercentEscape.swift */, - 53A79B1129CDFB1F00E7489F /* MPSearchAdsAttribution.h */, 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */, 53A79B1429CDFB1F00E7489F /* MPUserIdentityChange.m */, 53A79B1529CDFB1F00E7489F /* MPDevice.m */, @@ -1029,7 +1022,6 @@ 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */, 53A79B1E29CDFB1F00E7489F /* MPBracket.cpp */, 53A79B2029CDFB1F00E7489F /* MPResponseConfig.m */, - 53A79B2229CDFB1F00E7489F /* MPSearchAdsAttribution.m */, 53A79B2329CDFB1F00E7489F /* MPUploadBuilder.h */, 53A79B2429CDFB1F00E7489F /* MPApplication.h */, 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */, @@ -1339,7 +1331,6 @@ 53A79B7129CDFB2000E7489F /* MPBackendController.h in Headers */, 53A79C1D29CDFB2100E7489F /* MPKitConfiguration.h in Headers */, 53A79B7929CDFB2000E7489F /* MPConnectorResponseProtocol.h in Headers */, - 53A79BD629CDFB2000E7489F /* MPSearchAdsAttribution.h in Headers */, 53A79B7C29CDFB2000E7489F /* MPConnectorFactoryProtocol.h in Headers */, 53A79B7D29CDFB2000E7489F /* MPNetworkCommunication.h in Headers */, 53A79B7F29CDFB2000E7489F /* MPURL.h in Headers */, @@ -1436,7 +1427,6 @@ 53A79D2E29CE23F700E7489F /* MPBackendController.h in Headers */, 53A79D2F29CE23F700E7489F /* MPKitConfiguration.h in Headers */, 53A79D3029CE23F700E7489F /* MPConnectorResponseProtocol.h in Headers */, - 53A79D3129CE23F700E7489F /* MPSearchAdsAttribution.h in Headers */, 53A79D3229CE23F700E7489F /* MPConnectorFactoryProtocol.h in Headers */, 53A79D3329CE23F700E7489F /* MPNetworkCommunication.h in Headers */, 53A79D3429CE23F700E7489F /* MPURL.h in Headers */, @@ -1745,7 +1735,6 @@ 53A79C2129CDFB2100E7489F /* MPIConstants.m in Sources */, 53A79BEE29CDFB2000E7489F /* MPLaunchInfo.m in Sources */, 53A79C0229CDFB2100E7489F /* MPPromotion.m in Sources */, - 53A79BE729CDFB2000E7489F /* MPSearchAdsAttribution.m in Sources */, 53A79B6629CDFB2000E7489F /* MPIdentityApiRequest.m in Sources */, 53A79C0129CDFB2100E7489F /* MPCommerceEvent.mm in Sources */, 53A79BBF29CDFB2000E7489F /* MPConsentSerialization.m in Sources */, @@ -1914,7 +1903,6 @@ 53A79D7C29CE23F700E7489F /* MPLaunchInfo.m in Sources */, 53A79D7D29CE23F700E7489F /* MPPromotion.m in Sources */, 534CD25C29CE2877008452B3 /* NSNumber+MPFormatter.swift in Sources */, - 53A79D7E29CE23F700E7489F /* MPSearchAdsAttribution.m in Sources */, 53A79D8129CE23F700E7489F /* MPIdentityApiRequest.m in Sources */, 53A79D8229CE23F700E7489F /* MPCommerceEvent.mm in Sources */, 53A79D8329CE23F700E7489F /* MPConsentSerialization.m in Sources */, diff --git a/mParticle-Apple-SDK/MPBackendController.m b/mParticle-Apple-SDK/MPBackendController.m index 4777289d7..b4fe3a6f4 100644 --- a/mParticle-Apple-SDK/MPBackendController.m +++ b/mParticle-Apple-SDK/MPBackendController.m @@ -24,9 +24,6 @@ #import "MPKitContainer.h" #import "MPUserAttributeChange.h" #import "MPUserIdentityChange.h" -#if TARGET_OS_IOS == 1 -#import "MPSearchAdsAttribution.h" -#endif #import "MPURLRequestBuilder.h" #import "MPListenerController.h" #import "MParticleWebView.h" @@ -1538,7 +1535,7 @@ - (void)startWithKey:(NSString *)apiKey secret:(NSString *)secret networkOptions #if TARGET_OS_IOS == 1 if (MParticle.sharedInstance.collectSearchAdsAttribution) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(SEARCH_ADS_ATTRIBUTION_GLOBAL_TIMEOUT_SECONDS * NSEC_PER_SEC)), [MParticle messageQueue], searchAdsCompletion); - [stateMachine.searchAttribution requestAttributionDetailsWithBlock:searchAdsCompletion requestsCompleted:0]; + [stateMachine requestAttributionDetailsWithBlock:searchAdsCompletion requestsCompleted:0]; } else { searchAdsCompletion(); } diff --git a/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.h b/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.h deleted file mode 100644 index 16529b7c9..000000000 --- a/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -#if TARGET_OS_IOS == 1 -@interface MPSearchAdsAttribution : NSObject - -- (void)requestAttributionDetailsWithBlock:(void (^ _Nonnull)(void))completionHandler requestsCompleted:(int)requestsCompleted; - -@end -#endif diff --git a/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.m b/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.m deleted file mode 100644 index b984628dc..000000000 --- a/mParticle-Apple-SDK/Utils/MPSearchAdsAttribution.m +++ /dev/null @@ -1,81 +0,0 @@ -#import - -#if TARGET_OS_IOS == 1 -#import "MPSearchAdsAttribution.h" -#import "mParticle.h" -#import "MPStateMachine.h" -#import "MPIConstants.h" -#import "MPILogger.h" -#import - -@interface MParticle () - -+ (dispatch_queue_t)messageQueue; -@property (nonatomic, strong) MPStateMachine *stateMachine; - -@end - -@implementation MPSearchAdsAttribution - -- (void)requestAttributionDetailsWithBlock:(void (^ _Nonnull)(void))completionHandler requestsCompleted:(int)requestsCompleted { - NSError *error; - if (@available(iOS 14.3, *)) { - NSString *attributionToken = [AAAttribution attributionTokenWithError:&error]; - if (!attributionToken) { - completionHandler(); - return; - } - - if (attributionToken) { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api-adservices.apple.com/api/v1/"]]; - [request setHTTPMethod:@"POST"]; - [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:[attributionToken dataUsingEncoding:NSUTF8StringEncoding]]; - - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - sessionConfiguration.timeoutIntervalForRequest = 30; - sessionConfiguration.timeoutIntervalForResource = 30; - NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration - delegate:nil - delegateQueue:nil]; - dispatch_async([MParticle messageQueue], ^{ - [urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *urlResponse, NSError *error) { - if (error) { - MPILogError(@"Failed requesting Ads Attribution with error: %@.", [error localizedDescription]); - if (error.code == 1 /* ADClientErrorLimitAdTracking */) { - completionHandler(); - } else if ((requestsCompleted + 1) > SEARCH_ADS_ATTRIBUTION_MAX_RETRIES) { - completionHandler(); - } else { - // Per Apple docs, "Handle any errors you receive and re-poll for data, if required" - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(SEARCH_ADS_ATTRIBUTION_DELAY_BEFORE_RETRY * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self requestAttributionDetailsWithBlock:completionHandler requestsCompleted:(requestsCompleted + 1)]; - }); - } - } else { - NSDictionary *adAttributionDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - // Convert the dictionary to the prior format expected by our backend - NSDictionary *finalAttributionDictionary = @{ - @"Version4.0": @{ - @"iad-attribution": adAttributionDictionary[@"attribution"], - @"iad-org-id": [adAttributionDictionary[@"orgId"] stringValue], - @"iad-campaign-id": [adAttributionDictionary[@"campaignId"] stringValue], - @"iad-conversion-type": adAttributionDictionary[@"conversionType"], - @"iad-click-date": adAttributionDictionary[@"clickDate"], - @"iad-adgroup-id": [adAttributionDictionary[@"adGroupId"] stringValue], - @"iad-country-or-region": adAttributionDictionary[@"countryOrRegion"], - @"iad-keyword-id": [adAttributionDictionary[@"keywordId"] stringValue], - @"iad-ad-id": [adAttributionDictionary[@"adId"] stringValue], - } - }; - [MParticle sharedInstance].stateMachine.searchAdsInfo = [[finalAttributionDictionary mutableCopy] copy]; - completionHandler(); - } - }]; - }); - } - } -} - -@end -#endif diff --git a/mParticle-Apple-SDK/Utils/MPStateMachine.h b/mParticle-Apple-SDK/Utils/MPStateMachine.h index debbd762a..1b73e4262 100644 --- a/mParticle-Apple-SDK/Utils/MPStateMachine.h +++ b/mParticle-Apple-SDK/Utils/MPStateMachine.h @@ -13,7 +13,6 @@ #endif #endif @class MPCustomModule; -@class MPSearchAdsAttribution; @class MPDataPlanOptions; @interface MPStateMachine : NSObject @@ -52,9 +51,6 @@ @property (nonatomic) NSNumber * _Nullable attAuthorizationStatus; @property (nonatomic) NSNumber * _Nullable attAuthorizationTimestamp; @property (nonatomic, strong, nonnull) NSNumber *aliasMaxWindow; -#if TARGET_OS_IOS == 1 -@property (nonatomic, strong, nonnull) MPSearchAdsAttribution *searchAttribution; -#endif @property (nonatomic, strong, nonnull) NSDictionary *searchAdsInfo; @property (nonatomic) BOOL automaticSessionTracking; @property (nonatomic) BOOL allowASR; @@ -75,5 +71,6 @@ - (void)configureDataBlocking:(nullable NSDictionary *)blockSettings; - (void)setMinUploadDate:(nullable NSDate *)date uploadType:(MPUploadType)uploadType; - (nonnull NSDate *)minUploadDateForUploadType:(MPUploadType)uploadType; +- (void)requestAttributionDetailsWithBlock:(void (^ _Nonnull)(void))completionHandler requestsCompleted:(int)requestsCompleted; @end diff --git a/mParticle-Apple-SDK/Utils/MPStateMachine.m b/mParticle-Apple-SDK/Utils/MPStateMachine.m index adb58fa40..71b18af61 100644 --- a/mParticle-Apple-SDK/Utils/MPStateMachine.m +++ b/mParticle-Apple-SDK/Utils/MPStateMachine.m @@ -10,9 +10,6 @@ #import "MPConsumerInfo.h" #import "MPPersistenceController.h" #import "MPKitContainer.h" -#if TARGET_OS_IOS == 1 -#import "MPSearchAdsAttribution.h" -#endif #import #import "MPForwardQueueParameters.h" #import "MPDataPlanFilter.h" @@ -22,6 +19,7 @@ #ifndef MPARTICLE_LOCATION_DISABLE #import #endif +#import #endif static NSString *const kCookieDateKey = @"e"; @@ -100,9 +98,6 @@ - (instancetype)init { _launchDate = [NSDate date]; _launchOptions = nil; _logLevel = MPILogLevelNone; -#if TARGET_OS_IOS == 1 - _searchAttribution = [[MPSearchAdsAttribution alloc] init]; -#endif NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; @@ -765,4 +760,66 @@ - (void)configureDataBlocking:(nullable NSDictionary *)blockSettings { } } +- (void)requestAttributionDetailsWithBlock:(void (^ _Nonnull)(void))completionHandler requestsCompleted:(int)requestsCompleted { +#if TARGET_OS_IOS == 1 + NSError *error; + if (@available(iOS 14.3, *)) { + NSString *attributionToken = [AAAttribution attributionTokenWithError:&error]; + if (!attributionToken) { + completionHandler(); + return; + } + + if (attributionToken) { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api-adservices.apple.com/api/v1/"]]; + [request setHTTPMethod:@"POST"]; + [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[attributionToken dataUsingEncoding:NSUTF8StringEncoding]]; + + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + sessionConfiguration.timeoutIntervalForRequest = 30; + sessionConfiguration.timeoutIntervalForResource = 30; + NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration + delegate:nil + delegateQueue:nil]; + dispatch_async([MParticle messageQueue], ^{ + [urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *urlResponse, NSError *error) { + if (error) { + MPILogError(@"Failed requesting Ads Attribution with error: %@.", [error localizedDescription]); + if (error.code == 1 /* ADClientErrorLimitAdTracking */) { + completionHandler(); + } else if ((requestsCompleted + 1) > SEARCH_ADS_ATTRIBUTION_MAX_RETRIES) { + completionHandler(); + } else { + // Per Apple docs, "Handle any errors you receive and re-poll for data, if required" + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(SEARCH_ADS_ATTRIBUTION_DELAY_BEFORE_RETRY * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self requestAttributionDetailsWithBlock:completionHandler requestsCompleted:(requestsCompleted + 1)]; + }); + } + } else { + NSDictionary *adAttributionDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + // Convert the dictionary to the prior format expected by our backend + NSDictionary *finalAttributionDictionary = @{ + @"Version4.0": @{ + @"iad-attribution": adAttributionDictionary[@"attribution"], + @"iad-org-id": [adAttributionDictionary[@"orgId"] stringValue], + @"iad-campaign-id": [adAttributionDictionary[@"campaignId"] stringValue], + @"iad-conversion-type": adAttributionDictionary[@"conversionType"], + @"iad-click-date": adAttributionDictionary[@"clickDate"], + @"iad-adgroup-id": [adAttributionDictionary[@"adGroupId"] stringValue], + @"iad-country-or-region": adAttributionDictionary[@"countryOrRegion"], + @"iad-keyword-id": [adAttributionDictionary[@"keywordId"] stringValue], + @"iad-ad-id": [adAttributionDictionary[@"adId"] stringValue], + } + }; + self.searchAdsInfo = [[finalAttributionDictionary mutableCopy] copy]; + completionHandler(); + } + }]; + }); + } + } +#endif +} + @end