From 2430aa99b3c34db56ea18f7566e7e98c351ba025 Mon Sep 17 00:00:00 2001 From: Aditi3 Date: Thu, 27 Jan 2022 14:49:11 +0530 Subject: [PATCH 01/31] Update `compatibilityVersion` to `Xcode 10.0` From d0b715a0820153fd02057f3923960d547efe50b6 Mon Sep 17 00:00:00 2001 From: uerceg Date: Mon, 7 Aug 2023 13:25:48 +0200 Subject: [PATCH 02/31] refac: use non-deprecated methods in skan registration flow --- Adjust/ADJSKAdNetwork.m | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Adjust/ADJSKAdNetwork.m b/Adjust/ADJSKAdNetwork.m index 2e2f67d78..4ca07d4df 100644 --- a/Adjust/ADJSKAdNetwork.m +++ b/Adjust/ADJSKAdNetwork.m @@ -151,8 +151,27 @@ - (void)adjRegisterWithCompletionHandler:(void (^)(NSError *error))callback { callback(nil); return; } - [self registerAppForAdNetworkAttribution]; - callback(nil); + + if (@available(iOS 16.1, *)) { + [self updatePostbackConversionValue:0 + coarseValue:[self getSkAdNetworkCoarseConversionValue:@"low"] + lockWindow:NO + completionHandler:^(NSError * _Nonnull error) { + callback(error); + }]; + } else if (@available(iOS 15.4, *)) { + [self updatePostbackConversionValue:0 + completionHandler:^(NSError * _Nonnull error) { + callback(error); + }]; + } else if (@available(iOS 14.0, *)) { + [self registerAppForAdNetworkAttribution]; + callback(nil); + } else { + [self.logger error:@"SKAdNetwork API not available on this iOS version"]; + callback(nil); + } + [self writeSkAdNetworkRegisterCallTimestamp]; } From f2300908bac8de55a3d833a2b9516cc5e8544c62 Mon Sep 17 00:00:00 2001 From: uerceg Date: Mon, 14 Aug 2023 14:32:42 +0200 Subject: [PATCH 03/31] refac: update waterfall logic when updating skan conversion value --- Adjust/ADJSKAdNetwork.m | 45 ++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Adjust/ADJSKAdNetwork.m b/Adjust/ADJSKAdNetwork.m index 4ca07d4df..0d9847b2f 100644 --- a/Adjust/ADJSKAdNetwork.m +++ b/Adjust/ADJSKAdNetwork.m @@ -179,22 +179,39 @@ - (void)adjUpdateConversionValue:(NSInteger)conversionValue coarseValue:(NSString *)coarseValue lockWindow:(NSNumber *)lockWindow completionHandler:(void (^)(NSError *error))callback { - if (coarseValue != nil && lockWindow != nil) { - // 4.0 world - [self updatePostbackConversionValue:conversionValue - coarseValue:[self getSkAdNetworkCoarseConversionValue:coarseValue] - lockWindow:[lockWindow boolValue] - completionHandler:^(NSError * _Nonnull error) { - if (error) { - [self.logger error:@"Call to SKAdNetwork's updatePostbackConversionValue:coarseValue:lockWindow:completionHandler: method with conversion value: %d, coarse value: %@, lock window: %d failed\nDescription: %@", conversionValue, coarseValue, [lockWindow boolValue], error.localizedDescription]; + // let's make sure first that the conversionValue makes sense + if (conversionValue < 0) { + callback(nil); + } else { + if (@available(iOS 16.1, *)) { + // let's check if coarseValue and lockWindow make sense + if (coarseValue != nil && lockWindow != nil) { + // they do + [self updatePostbackConversionValue:conversionValue + coarseValue:[self getSkAdNetworkCoarseConversionValue:coarseValue] + lockWindow:[lockWindow boolValue] + completionHandler:^(NSError * _Nonnull error) { + if (error) { + [self.logger error:@"Call to SKAdNetwork's updatePostbackConversionValue:coarseValue:lockWindow:completionHandler: method with conversion value: %d, coarse value: %@, lock window: %d failed\nDescription: %@", conversionValue, coarseValue, [lockWindow boolValue], error.localizedDescription]; + } else { + [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:coarseValue:lockWindow:completionHandler: method with conversion value: %d, coarse value: %@, lock window: %d", conversionValue, coarseValue, [lockWindow boolValue]]; + } + callback(error); + }]; } else { - [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:coarseValue:lockWindow:completionHandler: method with conversion value: %d, coarse value: %@, lock window: %d", conversionValue, coarseValue, [lockWindow boolValue]]; + // they don't, let's make sure to update conversion value with a + // call to updatePostbackConversionValue:completionHandler: method + [self updatePostbackConversionValue:conversionValue + completionHandler:^(NSError * _Nonnull error) { + if (error) { + [self.logger error:@"Call to updatePostbackConversionValue:completionHandler: method with conversion value: %d failed\nDescription: %@", conversionValue, error.localizedDescription]; + } else { + [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d", conversionValue]; + } + callback(error); + }]; } - callback(error); - }]; - } else { - // pre 4.0 world - if (@available(iOS 15.4, *)) { + } else if (@available(iOS 15.4, *)) { [self updatePostbackConversionValue:conversionValue completionHandler:^(NSError * _Nonnull error) { if (error) { From b31b74e735f926ff559d9f3683f7806f5231fd5c Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Mon, 14 Aug 2023 15:24:21 +0200 Subject: [PATCH 04/31] refac: adds additional SKAN update CV case --- Adjust/ADJSKAdNetwork.m | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/Adjust/ADJSKAdNetwork.m b/Adjust/ADJSKAdNetwork.m index 0d9847b2f..b38cc8fc5 100644 --- a/Adjust/ADJSKAdNetwork.m +++ b/Adjust/ADJSKAdNetwork.m @@ -182,11 +182,14 @@ - (void)adjUpdateConversionValue:(NSInteger)conversionValue // let's make sure first that the conversionValue makes sense if (conversionValue < 0) { callback(nil); - } else { - if (@available(iOS 16.1, *)) { - // let's check if coarseValue and lockWindow make sense - if (coarseValue != nil && lockWindow != nil) { - // they do + return; + } + + if (@available(iOS 16.1, *)) { + // let's check if coarseValue and lockWindow make sense + if (coarseValue != nil) { + if (lockWindow != nil) { + // they do both [self updatePostbackConversionValue:conversionValue coarseValue:[self getSkAdNetworkCoarseConversionValue:coarseValue] lockWindow:[lockWindow boolValue] @@ -199,35 +202,47 @@ - (void)adjUpdateConversionValue:(NSInteger)conversionValue callback(error); }]; } else { - // they don't, let's make sure to update conversion value with a - // call to updatePostbackConversionValue:completionHandler: method + // Only coarse value is received [self updatePostbackConversionValue:conversionValue + coarseValue:[self getSkAdNetworkCoarseConversionValue:coarseValue] completionHandler:^(NSError * _Nonnull error) { if (error) { - [self.logger error:@"Call to updatePostbackConversionValue:completionHandler: method with conversion value: %d failed\nDescription: %@", conversionValue, error.localizedDescription]; + [self.logger error:@"Call to SKAdNetwork's updatePostbackConversionValue:coarseValue:completionHandler: method with conversion value: %d, coarse value: %@ failed\nDescription: %@", conversionValue, coarseValue, error.localizedDescription]; } else { - [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d", conversionValue]; + [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:coarseValue:completionHandler: method with conversion value: %d, coarse value: %@", conversionValue, coarseValue]; } callback(error); }]; } - } else if (@available(iOS 15.4, *)) { + } else { + // they don't, let's make sure to update conversion value with a + // call to updatePostbackConversionValue:completionHandler: method [self updatePostbackConversionValue:conversionValue completionHandler:^(NSError * _Nonnull error) { if (error) { - [self.logger error:@"Call to updatePostbackConversionValue:completionHandler: method with conversion value: %d failed\nDescription: %@", conversionValue, error.localizedDescription]; + [self.logger error:@"Call to SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d failed\nDescription: %@", conversionValue, error.localizedDescription]; } else { [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d", conversionValue]; } callback(error); }]; - } else if (@available(iOS 14.0, *)) { - [self updateConversionValue:conversionValue]; - callback(nil); - } else { - [self.logger error:@"SKAdNetwork API not available on this iOS version"]; - callback(nil); } + } else if (@available(iOS 15.4, *)) { + [self updatePostbackConversionValue:conversionValue + completionHandler:^(NSError * _Nonnull error) { + if (error) { + [self.logger error:@"Call to SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d failed\nDescription: %@", conversionValue, error.localizedDescription]; + } else { + [self.logger debug:@"Called SKAdNetwork's updatePostbackConversionValue:completionHandler: method with conversion value: %d", conversionValue]; + } + callback(error); + }]; + } else if (@available(iOS 14.0, *)) { + [self updateConversionValue:conversionValue]; + callback(nil); + } else { + [self.logger error:@"SKAdNetwork API not available on this iOS version"]; + callback(nil); } } From f432521a70f137898790e29d4d2a465a2e543516 Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Wed, 2 Aug 2023 13:02:03 +0200 Subject: [PATCH 05/31] feat: Waiting for ATT consent delay SDK-1737 - iOS SDK - waiting the ATT consent --- Adjust/ADJActivityHandler.h | 14 +- Adjust/ADJActivityHandler.m | 266 ++++++++++++++++++++++++++++++------ Adjust/ADJActivityState.h | 1 + Adjust/ADJActivityState.m | 9 ++ Adjust/ADJConfig.h | 5 + Adjust/ADJConfig.m | 1 + Adjust/ADJPackageBuilder.h | 4 + Adjust/ADJPackageBuilder.m | 49 ++++--- Adjust/ADJPackageHandler.h | 3 +- Adjust/ADJPackageHandler.m | 101 +++++++++----- Adjust/ADJSdkClickHandler.h | 1 + Adjust/ADJSdkClickHandler.m | 23 ++++ Adjust/ADJUserDefaults.h | 9 ++ Adjust/ADJUserDefaults.m | 20 +++ 14 files changed, 398 insertions(+), 108 deletions(-) diff --git a/Adjust/ADJActivityHandler.h b/Adjust/ADJActivityHandler.h index 7430fac3a..cb22250cb 100644 --- a/Adjust/ADJActivityHandler.h +++ b/Adjust/ADJActivityHandler.h @@ -20,8 +20,10 @@ @property (nonatomic, assign) BOOL background; @property (nonatomic, assign) BOOL delayStart; @property (nonatomic, assign) BOOL updatePackages; +@property (nonatomic, assign) BOOL updatePackagesAttData; @property (nonatomic, assign) BOOL firstLaunch; @property (nonatomic, assign) BOOL sessionResponseProcessed; +@property (nonatomic, assign) BOOL waitingForAttStatus; - (BOOL)isEnabled; - (BOOL)isDisabled; @@ -32,8 +34,10 @@ - (BOOL)isInDelayedStart; - (BOOL)isNotInDelayedStart; - (BOOL)itHasToUpdatePackages; +- (BOOL)itHasToUpdatePackagesAttData; - (BOOL)isFirstLaunch; - (BOOL)hasSessionResponseNotBeenProcessed; +- (BOOL)isWaitingForAttStatus; @end @@ -143,15 +147,15 @@ @interface ADJTrackingStatusManager : NSObject -- (instancetype _Nullable)initWithActivityHandler:(ADJActivityHandler * _Nullable)activityHandler; +@property (nonatomic, readonly, assign) BOOL trackingEnabled; +@property (nonatomic, readonly, assign) int attStatus; +- (instancetype _Nullable)initWithActivityHandler:(ADJActivityHandler * _Nullable)activityHandler; - (void)checkForNewAttStatus; - (void)updateAttStatusFromUserCallback:(int)newAttStatusFromUser; - - (BOOL)canGetAttStatus; - -@property (nonatomic, readonly, assign) BOOL trackingEnabled; -@property (nonatomic, readonly, assign) int attStatus; +- (void)setAppInActiveState:(BOOL)activeState; +- (BOOL)shouldWaitForAttStatus; @end diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 4c483f071..d7cbc6cc1 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -28,15 +28,16 @@ typedef void (^activityHandlerBlockI)(ADJActivityHandler * activityHandler); -static NSString * const kActivityStateFilename = @"AdjustIoActivityState"; -static NSString * const kAttributionFilename = @"AdjustIoAttribution"; -static NSString * const kSessionCallbackParametersFilename = @"AdjustSessionCallbackParameters"; -static NSString * const kSessionPartnerParametersFilename = @"AdjustSessionPartnerParameters"; -static NSString * const kAdjustPrefix = @"adjust_"; -static const char * const kInternalQueueName = "io.adjust.ActivityQueue"; -static NSString * const kForegroundTimerName = @"Foreground timer"; -static NSString * const kBackgroundTimerName = @"Background timer"; -static NSString * const kDelayStartTimerName = @"Delay Start timer"; +static NSString * const kActivityStateFilename = @"AdjustIoActivityState"; +static NSString * const kAttributionFilename = @"AdjustIoAttribution"; +static NSString * const kSessionCallbackParametersFilename = @"AdjustSessionCallbackParameters"; +static NSString * const kSessionPartnerParametersFilename = @"AdjustSessionPartnerParameters"; +static NSString * const kAdjustPrefix = @"adjust_"; +static const char * const kInternalQueueName = "io.adjust.ActivityQueue"; +static const char * const kWaitingForAttQueueName = "io.adjust.WaitingForAttQueue"; +static NSString * const kForegroundTimerName = @"Foreground timer"; +static NSString * const kBackgroundTimerName = @"Background timer"; +static NSString * const kDelayStartTimerName = @"Delay Start timer"; static NSTimeInterval kForegroundTimerInterval; static NSTimeInterval kForegroundTimerStart; @@ -44,6 +45,7 @@ static double kSessionInterval; static double kSubSessionInterval; static const int kAdServicesdRetriesCount = 1; +const NSUInteger kWaitingForAttStatusLimitSeconds = 120; @implementation ADJInternalState @@ -56,8 +58,10 @@ - (BOOL)isInForeground { return !self.background; } - (BOOL)isInDelayedStart { return self.delayStart; } - (BOOL)isNotInDelayedStart { return !self.delayStart; } - (BOOL)itHasToUpdatePackages { return self.updatePackages; } +- (BOOL)itHasToUpdatePackagesAttData { return self.updatePackagesAttData; } - (BOOL)isFirstLaunch { return self.firstLaunch; } - (BOOL)hasSessionResponseNotBeenProcessed { return !self.sessionResponseProcessed; } +- (BOOL)isWaitingForAttStatus { return self.waitingForAttStatus;} @end @@ -187,8 +191,10 @@ - (id)initWithConfig:(ADJConfig *)adjustConfig // does not need to update packages by default if (self.activityState == nil) { self.internalState.updatePackages = NO; + self.internalState.updatePackagesAttData = NO; } else { self.internalState.updatePackages = self.activityState.updatePackages; + self.internalState.updatePackagesAttData = self.activityState.updatePackagesAttData; } if (self.activityState == nil) { self.internalState.firstLaunch = YES; @@ -228,15 +234,18 @@ - (void)applicationDidBecomeActive { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJActivityHandler * selfI) { - [selfI delayStartI:selfI]; - [selfI stopBackgroundTimerI:selfI]; + [selfI delayStartI:selfI]; - [selfI startForegroundTimerI:selfI]; + [selfI activateWaitingForAttStatusI:selfI]; - [selfI.logger verbose:@"Subsession start"]; + [selfI stopBackgroundTimerI:selfI]; - [selfI startI:selfI]; + [selfI startForegroundTimerI:selfI]; + + [selfI.logger verbose:@"Subsession start"]; + + [selfI startI:selfI]; }]; } @@ -246,13 +255,16 @@ - (void)applicationWillResignActive { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJActivityHandler * selfI) { - [selfI stopForegroundTimerI:selfI]; - [selfI startBackgroundTimerI:selfI]; + [selfI pauseWaitingForAttStatusI:selfI]; + + [selfI stopForegroundTimerI:selfI]; + + [selfI startBackgroundTimerI:selfI]; - [selfI.logger verbose:@"Subsession end"]; + [selfI.logger verbose:@"Subsession end"]; - [selfI endI:selfI]; + [selfI endI:selfI]; }]; } @@ -487,6 +499,14 @@ - (void)sendFirstPackages { }]; } +- (void)resumeActivityFromWaitingForAttStatus { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJActivityHandler * selfI) { + [selfI resumeActivityFromWaitingForAttStatusI:selfI]; + }]; +} + - (void)addSessionCallbackParameter:(NSString *)key value:(NSString *)value { [ADJUtil launchInQueue:self.internalQueue @@ -797,6 +817,9 @@ - (void)initI:(ADJActivityHandler *)selfI name:kDelayStartTimerName]; } + // Update Waiting for ATT status state - should be done before the package handler is created. + selfI.internalState.waitingForAttStatus = [selfI.trackingStatusManager shouldWaitForAttStatus]; + [ADJUtil updateUrlSessionConfiguration:selfI.adjustConfig]; ADJUrlStrategy *packageHandlerUrlStrategy = @@ -814,8 +837,7 @@ - (void)initI:(ADJActivityHandler *)selfI // update session parameters in package queue if ([selfI itHasToUpdatePackagesI:selfI]) { [selfI updatePackagesI:selfI]; - } - + } ADJUrlStrategy *attributionHandlerUrlStrategy = [[ADJUrlStrategy alloc] @@ -840,6 +862,25 @@ - (void)initI:(ADJActivityHandler *)selfI userAgent:selfI.adjustConfig.userAgent urlStrategy:sdkClickHandlerUrlStrategy]; + + // Update ATT status and idfa ,if necessary, in packages and sdk_click package queues. + // This should be done after `packageHandler` and `sdkClickHandler` are created. + if (selfI.internalState.waitingForAttStatus) { + selfI.internalState.updatePackagesAttData = YES; + if (selfI.activityState != nil) { + [ADJUtil launchSynchronisedWithObject:[ADJActivityState class] + block:^{ + selfI.activityState.updatePackagesAttData = YES; + }]; + [selfI writeActivityStateI:selfI]; + } + } else { + // update ATT Data in package queue + if ([selfI itHasToUpdatePackagesAttDataI:selfI]) { + [selfI updatePackagesAttStatusAndIdfaI:selfI]; + } + } + [selfI checkLinkMeI:selfI]; [selfI.trackingStatusManager checkForNewAttStatus]; @@ -936,6 +977,7 @@ - (void)processSessionI:(ADJActivityHandler *)selfI { [selfI.activityState resetSessionAttributes:now]; selfI.activityState.enabled = [selfI.internalState isEnabled]; selfI.activityState.updatePackages = [selfI.internalState itHasToUpdatePackages]; + selfI.activityState.updatePackagesAttData = [selfI.internalState itHasToUpdatePackagesAttData]; }]; if (selfI.adjustConfig.allowAdServicesInfoReading == YES) { @@ -2060,6 +2102,14 @@ - (BOOL)itHasToUpdatePackagesI:(ADJActivityHandler *)selfI { } } +- (BOOL)itHasToUpdatePackagesAttDataI:(ADJActivityHandler *)selfI { + if (selfI.activityState != nil) { + return selfI.activityState.updatePackagesAttData; + } else { + return [selfI.internalState itHasToUpdatePackagesAttData]; + } +} + // returns whether or not the activity state should be written - (BOOL)updateActivityStateI:(ADJActivityHandler *)selfI now:(double)now { @@ -2244,22 +2294,18 @@ - (void)resumeSendingI:(ADJActivityHandler *)selfI { [selfI.sdkClickHandler resumeSending]; } -- (BOOL)pausedI:(ADJActivityHandler *)selfI { - return [selfI pausedI:selfI sdkClickHandlerOnly:NO]; -} - -- (BOOL)pausedI:(ADJActivityHandler *)selfI -sdkClickHandlerOnly:(BOOL)sdkClickHandlerOnly -{ +- (BOOL)pausedI:(ADJActivityHandler *)selfI sdkClickHandlerOnly:(BOOL)sdkClickHandlerOnly { if (sdkClickHandlerOnly) { // sdk click handler is paused if either: - return [selfI.internalState isOffline] || // it's offline - ![selfI isEnabledI:selfI]; // is disabled + return [selfI.internalState isOffline] // it's offline + || ![selfI isEnabledI:selfI] // is disabled + || [selfI.internalState isWaitingForAttStatus]; // Waiting for ATT status } // other handlers are paused if either: - return [selfI.internalState isOffline] || // it's offline - ![selfI isEnabledI:selfI] || // is disabled - [selfI.internalState isInDelayedStart]; // is in delayed start + return [selfI.internalState isOffline] // it's offline + || ![selfI isEnabledI:selfI] // is disabled + || [selfI.internalState isInDelayedStart] // is in delayed start + || [selfI.internalState isWaitingForAttStatus]; // Waiting for ATT status } - (BOOL)toSendI:(ADJActivityHandler *)selfI { @@ -2417,7 +2463,7 @@ - (void)sendFirstPackagesI:(ADJActivityHandler *)selfI { - (void)updatePackagesI:(ADJActivityHandler *)selfI { // update activity packages - [selfI.packageHandler updatePackages:selfI.sessionParameters]; + [selfI.packageHandler updatePackagesWithSessionParams:selfI.sessionParameters]; // no longer needs to update packages selfI.internalState.updatePackages = NO; if (selfI.activityState != nil) { @@ -2429,6 +2475,50 @@ - (void)updatePackagesI:(ADJActivityHandler *)selfI { } } +#pragma mark - waiting for ATT status + +- (void)activateWaitingForAttStatusI:(ADJActivityHandler *)selfI { + if (![selfI.internalState isWaitingForAttStatus]) { + return; + } + [selfI.trackingStatusManager setAppInActiveState:YES]; +} + +- (void)pauseWaitingForAttStatusI:(ADJActivityHandler *)selfI { + if (![selfI.internalState isWaitingForAttStatus]) { + return; + } + [selfI.trackingStatusManager setAppInActiveState:NO]; +} + +- (void)resumeActivityFromWaitingForAttStatusI:(ADJActivityHandler *)selfI { + // update packages in queue + [selfI updatePackagesAttStatusAndIdfaI:selfI]; + // update waiting for ATT status flag + selfI.internalState.waitingForAttStatus = NO; + // update the status and try to send first package + [selfI updateHandlersStatusAndSendI:selfI]; +} + +- (void)updatePackagesAttStatusAndIdfaI:(ADJActivityHandler *)selfI { + // update activity packages + int attStatus = [ADJUtil attStatus]; + if (attStatus != 0) { + NSString *idfa = [ADJUtil idfa]; + [selfI.packageHandler updatePackagesWithAttStatus:attStatus idfa:idfa]; + [selfI.sdkClickHandler updatePackagesWithAttStatus:attStatus idfa:idfa]; + } + + selfI.internalState.updatePackagesAttData = NO; + if (selfI.activityState != nil) { + [ADJUtil launchSynchronisedWithObject:[ADJActivityState class] + block:^{ + selfI.activityState.updatePackagesAttData = NO; + }]; + [selfI writeActivityStateI:selfI]; + } +} + #pragma mark - session parameters - (void)addSessionCallbackParameterI:(ADJActivityHandler *)selfI key:(NSString *)key @@ -2771,9 +2861,9 @@ - (BOOL)shouldDisableThirdPartySharingWhenCoppaEnabled:(ADJActivityHandler *)sel @end @interface ADJTrackingStatusManager () - @property (nonatomic, readonly, weak) ADJActivityHandler *activityHandler; - +@property (nonatomic, assign) BOOL activeState; +@property (nonatomic, strong) dispatch_queue_t waitingForAttQueue; @end @implementation ADJTrackingStatusManager @@ -2782,6 +2872,7 @@ - (instancetype)initWithActivityHandler:(ADJActivityHandler *)activityHandler { self = [super init]; _activityHandler = activityHandler; + _waitingForAttQueue = dispatch_queue_create(kWaitingForAttQueueName, DISPATCH_QUEUE_SERIAL); return self; } @@ -2805,18 +2896,18 @@ - (int)attStatus { - (void)checkForNewAttStatus { int readAttStatus = [ADJUtil attStatus]; - BOOL didUpdateAttStatus = [self updateAttStatus:readAttStatus]; - if (!didUpdateAttStatus) { - return; - } - [self.activityHandler trackAttStatusUpdate]; + [self updateAttStatusWithStatus:readAttStatus]; } + - (void)updateAttStatusFromUserCallback:(int)newAttStatusFromUser { - BOOL didUpdateAttStatus = [self updateAttStatus:newAttStatusFromUser]; - if (!didUpdateAttStatus) { - return; + [self updateAttStatusWithStatus:newAttStatusFromUser]; +} + +- (void)updateAttStatusWithStatus:(int)status { + BOOL statusHasBeenUpdated = [self updateAttStatus:status]; + if (statusHasBeenUpdated) { + [self.activityHandler trackAttStatusUpdate]; } - [self.activityHandler trackAttStatusUpdate]; } // internal methods @@ -2842,4 +2933,91 @@ - (BOOL)updateAttStatus:(int)readAttStatus { return YES; } + +- (void)setAppInActiveState:(BOOL)activeState { + dispatch_async(self.waitingForAttQueue, ^{ + self.activeState = activeState; + if (self.activeState) { + [self startWaitingForAttStatus]; + } + }); +} + +- (BOOL)shouldWaitForAttStatus { + if (![self canGetAttStatus]) { + return NO; + } + + // check current ATT status + int attStatus = [ADJUtil attStatus]; + + // return if the status is not ATTrackingManagerAuthorizationStatusNotDetermined + if (attStatus != 0) { + // Delete att_waiting_seconds key from UserDefaults. + [ADJUserDefaults removeAttWaitingRemainingSeconds]; + return NO; + } + + BOOL keyExists = [ADJUserDefaults attWaitingRemainingSecondsKeyExists]; + // check ATT timeout configuration + if (self.activityHandler.adjustConfig.attStatusWaitingTimeout == 0) { + // ATT timmeout is not configured in ADJConfig for current SDK running session. + // Already existing NSUserDefaults key means ATT timeout was configured in the previous SDK initialization. + // Setting `0` to the NSUserDefaults key for skipping ATT waiting configuration in next SDK initializations, + // no matter it is confgured in Adjust configuration or not. + if (keyExists) { + [ADJUserDefaults setAttWaitingRemainingSeconds:0]; + } + return NO; + } + + // Setting timeout value limited to 120 seconds. + NSUInteger timeoutSec = (self.activityHandler.adjustConfig.attStatusWaitingTimeout <= kWaitingForAttStatusLimitSeconds) ? + self.activityHandler.adjustConfig.attStatusWaitingTimeout : kWaitingForAttStatusLimitSeconds; + if (keyExists && [ADJUserDefaults getAttWaitingRemainingSeconds] == 0) { + // Existing NSUserDefaults key with `0` value means: + // OR timeout has elapsed and user didn't succeed to invoke ATT dialog during this time + // OR SDK already has been initialized without AttTimeout. + // We are skipping this ATT status waiting logic. + return NO; + } + + // NSUserDefaults key doesn't exist means => first SDK init with timeout configured). + // OR + // NSUserDefaults key exists with value > 0 means => application was killed before the timeout has been elapsed. + + // We have to set the configured waiting timeout and start ATT status monitoring logic. + [ADJUserDefaults setAttWaitingRemainingSeconds:timeoutSec]; + return YES; +} + +- (void)startWaitingForAttStatus { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), self.waitingForAttQueue, ^{ + [self checkAttStatusPeriodic]; + }); +} + +- (void)checkAttStatusPeriodic { + if (!self.activeState) { + return; + } + // check current ATT status + int attStatus = [ADJUtil attStatus]; + if (attStatus != 0) { + [ADJUserDefaults removeAttWaitingRemainingSeconds]; + [self.activityHandler resumeActivityFromWaitingForAttStatus]; + return; + } + + NSUInteger seconds = [ADJUserDefaults getAttWaitingRemainingSeconds]; + if (seconds == 0) { + [self.activityHandler.logger warn:@"ATT status waiting timeout elapsed - NO ATT STATUS FOUND"]; + [self.activityHandler resumeActivityFromWaitingForAttStatus]; + return; + } + + [ADJUserDefaults setAttWaitingRemainingSeconds:seconds-=1]; + [self startWaitingForAttStatus]; +} + @end diff --git a/Adjust/ADJActivityState.h b/Adjust/ADJActivityState.h index 563096c2a..e9f3b2876 100644 --- a/Adjust/ADJActivityState.h +++ b/Adjust/ADJActivityState.h @@ -20,6 +20,7 @@ @property (nonatomic, copy) NSString *dedupeToken; @property (nonatomic, copy) NSString *deviceToken; @property (nonatomic, assign) BOOL updatePackages; +@property (nonatomic, assign) BOOL updatePackagesAttData; @property (nonatomic, copy) NSString *adid; @property (nonatomic, strong) NSDictionary *attributionDetails; diff --git a/Adjust/ADJActivityState.m b/Adjust/ADJActivityState.m index 6abd6eaac..296b71677 100644 --- a/Adjust/ADJActivityState.m +++ b/Adjust/ADJActivityState.m @@ -41,6 +41,7 @@ - (id)init { self.deviceToken = nil; self.transactionIds = [NSMutableArray arrayWithCapacity:kTransactionIdCount]; self.updatePackages = NO; + self.updatePackagesAttData = NO; self.trackingManagerAuthorizationStatus = -1; return self; @@ -176,6 +177,12 @@ - (id)initWithCoder:(NSCoder *)decoder { self.updatePackages = NO; } + if ([decoder containsValueForKey:@"updatePackagesAttData"]) { + self.updatePackagesAttData = [decoder decodeBoolForKey:@"updatePackagesAttData"]; + } else { + self.updatePackagesAttData = NO; + } + if ([decoder containsValueForKey:@"adid"]) { self.adid = [decoder decodeObjectForKey:@"adid"]; } @@ -212,6 +219,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeBool:self.isThirdPartySharingDisabledForCoppa forKey:@"isThirdPartySharingDisabledForCoppa"]; [encoder encodeObject:self.deviceToken forKey:@"deviceToken"]; [encoder encodeBool:self.updatePackages forKey:@"updatePackages"]; + [encoder encodeBool:self.updatePackagesAttData forKey:@"updatePackagesAttData"]; [encoder encodeObject:self.adid forKey:@"adid"]; [encoder encodeObject:self.attributionDetails forKey:@"attributionDetails"]; [encoder encodeInt:self.trackingManagerAuthorizationStatus @@ -240,6 +248,7 @@ - (id)copyWithZone:(NSZone *)zone { copy.isThirdPartySharingDisabledForCoppa = self.isThirdPartySharingDisabledForCoppa; copy.deviceToken = [self.deviceToken copyWithZone:zone]; copy.updatePackages = self.updatePackages; + copy.updatePackagesAttData = self.updatePackagesAttData; copy.trackingManagerAuthorizationStatus = self.trackingManagerAuthorizationStatus; } diff --git a/Adjust/ADJConfig.h b/Adjust/ADJConfig.h index 2a51924c2..88b84581e 100644 --- a/Adjust/ADJConfig.h +++ b/Adjust/ADJConfig.h @@ -183,6 +183,11 @@ */ @property (nonatomic, assign) double delayStart; +/** + * @brief Define how many seconds to wait for ATT status before sending the first data. + */ +@property (nonatomic, assign) NSUInteger attStatusWaitingTimeout; + /** * @brief User agent for the requests. */ diff --git a/Adjust/ADJConfig.m b/Adjust/ADJConfig.m index 332f4cd0b..3c66f26cb 100644 --- a/Adjust/ADJConfig.m +++ b/Adjust/ADJConfig.m @@ -203,6 +203,7 @@ - (id)copyWithZone:(NSZone *)zone { copy.allowIdfaReading = self.allowIdfaReading; copy.allowAdServicesInfoReading = self.allowAdServicesInfoReading; copy.delayStart = self.delayStart; + copy.attStatusWaitingTimeout = self.attStatusWaitingTimeout; copy.coppaCompliantEnabled = self.coppaCompliantEnabled; copy.userAgent = [self.userAgent copyWithZone:zone]; copy.externalDeviceId = [self.externalDeviceId copyWithZone:zone]; diff --git a/Adjust/ADJPackageBuilder.h b/Adjust/ADJPackageBuilder.h index 2390d7168..b82c92063 100644 --- a/Adjust/ADJPackageBuilder.h +++ b/Adjust/ADJPackageBuilder.h @@ -92,6 +92,10 @@ + (BOOL)isAdServicesPackage:(ADJActivityPackage * _Nullable)activityPackage; ++ (void)addIdfa:(NSString * _Nullable)idfa + toParameters:(NSMutableDictionary * _Nullable)parameters + withConfig:(ADJConfig * _Nullable)adjConfig + logger:(id _Nullable)logger; @end // TODO change to ADJ... extern NSString * _Nullable const ADJAttributionTokenParameter; diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index 930c4ab0b..cbab333cf 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -1221,27 +1221,10 @@ - (NSMutableDictionary *)getSubscriptionParameters:(BOOL)isInDelay forSubscripti } - (void)addIdfaIfPossibleToParameters:(NSMutableDictionary *)parameters { - id logger = [ADJAdjustFactory logger]; - - if (! self.adjustConfig.allowIdfaReading) { - return; - } - - if (self.adjustConfig.coppaCompliantEnabled) { - [logger info:@"Cannot read IDFA with COPPA enabled"]; - return; - } - - NSString *idfa = [ADJUtil idfa]; - - if (idfa == nil - || idfa.length == 0 - || [idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) - { - return; - } - - [ADJPackageBuilder parameters:parameters setString:idfa forKey:@"idfa"]; + [ADJPackageBuilder addIdfa:[ADJUtil idfa] + toParameters:parameters + withConfig:self.adjustConfig + logger:[ADJAdjustFactory logger]]; } - (void)addIdfvIfPossibleToParameters:(NSMutableDictionary *)parameters { @@ -1375,4 +1358,28 @@ + (BOOL)isAdServicesPackage:(ADJActivityPackage *)activityPackage { return ([ADJUtil isNotNull:source] && [source isEqualToString:ADJAdServicesPackageKey]); } ++ (void)addIdfa:(NSString * _Nullable)idfa + toParameters:(NSMutableDictionary * _Nullable)parameters + withConfig:(ADJConfig * _Nullable)adjConfig + logger:(id _Nullable)logger { + + if (! adjConfig.allowIdfaReading) { + return; + } + + if (adjConfig.coppaCompliantEnabled) { + [logger info:@"Cannot read IDFA with COPPA enabled"]; + return; + } + + if (idfa == nil + || idfa.length == 0 + || [idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) + { + return; + } + + [ADJPackageBuilder parameters:parameters setString:idfa forKey:@"idfa"]; +} + @end diff --git a/Adjust/ADJPackageHandler.h b/Adjust/ADJPackageHandler.h index 2cd77a52c..a7cd6f5b0 100644 --- a/Adjust/ADJPackageHandler.h +++ b/Adjust/ADJPackageHandler.h @@ -27,7 +27,8 @@ - (void)sendFirstPackage; - (void)pauseSending; - (void)resumeSending; -- (void)updatePackages:(ADJSessionParameters *)sessionParameters; +- (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameters; +- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa; - (void)flush; - (void)teardown; diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m index 1673bb732..497a4e2fe 100644 --- a/Adjust/ADJPackageHandler.m +++ b/Adjust/ADJPackageHandler.m @@ -54,12 +54,12 @@ - (id)initWithActivityHandler:(id)activityHandler [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler * selfI) { - [selfI initI:selfI - activityHandler:activityHandler - startsSending:startsSending - userAgent:userAgent - urlStrategy:urlStrategy]; - }]; + [selfI initI:selfI + activityHandler:activityHandler + startsSending:startsSending + userAgent:userAgent + urlStrategy:urlStrategy]; + }]; return self; } @@ -68,16 +68,16 @@ - (void)addPackage:(ADJActivityPackage *)package { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI addI:selfI package:package]; - }]; + [selfI addI:selfI package:package]; + }]; } - (void)sendFirstPackage { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI sendFirstI:selfI]; - }]; + [selfI sendFirstI:selfI]; + }]; } - (void)responseCallback:(ADJResponseData *)responseData { @@ -105,8 +105,8 @@ - (void)sendNextPackage:(ADJResponseData *)responseData { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI sendNextI:selfI]; - }]; + [selfI sendNextI:selfI]; + }]; [self.activityHandler finishedTracking:responseData]; } @@ -127,16 +127,11 @@ - (void)closeFirstPackage:(ADJResponseData *)responseData NSString *waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime]; [self.logger verbose:@"Waiting for %@ seconds before retrying the %d time", waitTimeFormatted, self.lastPackageRetriesCount]; - dispatch_after - (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), - self.internalQueue, - ^{ - [self.logger verbose:@"Package handler finished waiting"]; - - dispatch_semaphore_signal(self.sendingSemaphore); - - [self sendFirstPackage]; - }); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, ^{ + [self.logger verbose:@"Package handler finished waiting"]; + dispatch_semaphore_signal(self.sendingSemaphore); + [self sendFirstPackage]; + }); } - (void)pauseSending { @@ -147,16 +142,23 @@ - (void)resumeSending { self.paused = NO; } -- (void)updatePackages:(ADJSessionParameters *)sessionParameters -{ +- (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameters { // make copy to prevent possible Activity Handler changes of it ADJSessionParameters * sessionParametersCopy = [sessionParameters copy]; [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI updatePackagesI:selfI sessionParameters:sessionParametersCopy]; - }]; + [selfI updatePackagesI:selfI sessionParameters:sessionParametersCopy]; + }]; +} + +- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJPackageHandler* selfI) { + [selfI updatePackagesI:selfI withAttStatus:attStatus idfa:idfa]; + }]; } - (void)flush { @@ -188,20 +190,19 @@ + (void)deletePackageQueue { } #pragma mark - internal -- (void) - initI:(ADJPackageHandler *)selfI - activityHandler:(id)activityHandler - startsSending:(BOOL)startsSending - userAgent:(NSString *)userAgent - urlStrategy:(ADJUrlStrategy *)urlStrategy -{ +- (void)initI:(ADJPackageHandler *)selfI +activityHandler:(id)activityHandler +startsSending:(BOOL)startsSending + userAgent:(NSString *)userAgent + urlStrategy:(ADJUrlStrategy *)urlStrategy { + selfI.activityHandler = activityHandler; selfI.paused = !startsSending; selfI.requestHandler = [[ADJRequestHandler alloc] - initWithResponseCallback:self - urlStrategy:urlStrategy - userAgent:userAgent - requestTimeout:[ADJAdjustFactory requestTimeout]]; + initWithResponseCallback:self + urlStrategy:urlStrategy + userAgent:userAgent + requestTimeout:[ADJAdjustFactory requestTimeout]]; selfI.logger = ADJAdjustFactory.logger; selfI.sendingSemaphore = dispatch_semaphore_create(1); [selfI readPackageQueueI:selfI]; @@ -298,6 +299,32 @@ - (void)updatePackagesI:(ADJPackageHandler *)selfI [selfI writePackageQueueS:selfI]; } +- (void)updatePackagesI:(ADJPackageHandler *)selfI + withAttStatus:(int)attStatus + idfa:(NSString *)idfa { + + [selfI.logger debug:@"Updating package handler queue"]; + [selfI.logger verbose:@"ATT Status %ld", (long)attStatus]; + [selfI.logger verbose:@"IDFA: %@", idfa]; + + // create package queue copy for new state of array + NSMutableArray *packageQueueCopy = [NSMutableArray array]; + + for (ADJActivityPackage *activityPackage in selfI.packageQueue) { + [ADJPackageBuilder parameters:activityPackage.parameters setInt:attStatus forKey:@"att_status"]; + [ADJPackageBuilder addIdfa:idfa + toParameters:activityPackage.parameters + withConfig:self.activityHandler.adjustConfig + logger:[ADJAdjustFactory logger]]; + // add to copy queue + [packageQueueCopy addObject:activityPackage]; + } + + // write package queue copy + selfI.packageQueue = packageQueueCopy; + [selfI writePackageQueueS:selfI]; +} + - (void)flushI:(ADJPackageHandler *)selfI { [selfI.packageQueue removeAllObjects]; [selfI writePackageQueueS:selfI]; diff --git a/Adjust/ADJSdkClickHandler.h b/Adjust/ADJSdkClickHandler.h index 3175aa3fd..e6564a5a9 100644 --- a/Adjust/ADJSdkClickHandler.h +++ b/Adjust/ADJSdkClickHandler.h @@ -21,6 +21,7 @@ - (void)pauseSending; - (void)resumeSending; - (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage; +- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa; - (void)teardown; @end diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m index a1f87df7f..5b0ce967c 100644 --- a/Adjust/ADJSdkClickHandler.m +++ b/Adjust/ADJSdkClickHandler.m @@ -91,6 +91,14 @@ - (void)sendNextSdkClick { }]; } +- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJSdkClickHandler *selfI) { + [selfI updatePackagesI:selfI withAttStatus:attStatus idfa:idfa]; + }]; +} + - (void)teardown { [ADJAdjustFactory.logger verbose:@"ADJSdkClickHandler teardown"]; @@ -186,6 +194,21 @@ - (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, work); } +- (void)updatePackagesI:(ADJSdkClickHandler *)selfI + withAttStatus:(int)attStatus + idfa:(NSString *)idfa { + for (ADJActivityPackage *activityPackage in selfI.packageQueue) { + [ADJPackageBuilder parameters:activityPackage.parameters + setInt:attStatus + forKey:@"att_status"]; + [ADJPackageBuilder addIdfa:idfa + toParameters:activityPackage.parameters + withConfig:self.activityHandler.adjustConfig + logger:[ADJAdjustFactory logger]]; + } +} + + - (void)responseCallback:(ADJResponseData *)responseData { if (responseData.jsonResponse) { [self.logger debug: diff --git a/Adjust/ADJUserDefaults.h b/Adjust/ADJUserDefaults.h index 2eb8e25b7..2122c4670 100644 --- a/Adjust/ADJUserDefaults.h +++ b/Adjust/ADJUserDefaults.h @@ -63,4 +63,13 @@ + (NSURL *)getCachedDeeplinkUrl; ++ (BOOL)attWaitingRemainingSecondsKeyExists; + ++ (void)setAttWaitingRemainingSeconds:(NSUInteger)seconds; + ++ (NSUInteger)getAttWaitingRemainingSeconds; + ++ (void)removeAttWaitingRemainingSeconds; + + @end diff --git a/Adjust/ADJUserDefaults.m b/Adjust/ADJUserDefaults.m index 6f90e7a4a..5f4852332 100644 --- a/Adjust/ADJUserDefaults.m +++ b/Adjust/ADJUserDefaults.m @@ -19,6 +19,7 @@ static NSString * const PREFS_KEY_SKAD_REGISTER_CALL_TIME = @"adj_skad_register_call_time"; static NSString * const PREFS_KEY_LINK_ME_CHECKED = @"adj_link_me_checked"; static NSString * const PREFS_KEY_DEEPLINK_URL_CACHED = @"adj_deeplink_url_cached"; +static NSString * const PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS = @"adj_att_timeout_remaining_seconds"; @implementation ADJUserDefaults @@ -127,6 +128,24 @@ + (NSURL *)getCachedDeeplinkUrl { return [[NSUserDefaults standardUserDefaults] URLForKey:PREFS_KEY_DEEPLINK_URL_CACHED]; } ++ (BOOL)attWaitingRemainingSecondsKeyExists { + return (nil != [[NSUserDefaults standardUserDefaults] objectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]); +} + ++ (void)setAttWaitingRemainingSeconds:(NSUInteger)seconds { + [[NSUserDefaults standardUserDefaults] setInteger:seconds forKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; +} + ++ (NSUInteger)getAttWaitingRemainingSeconds { + NSInteger iSeconds = [[NSUserDefaults standardUserDefaults] integerForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; + NSUInteger uiSeconds = (iSeconds < 0) ? 0 : iSeconds; + return uiSeconds; +} + ++ (void)removeAttWaitingRemainingSeconds { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; +} + + (void)clearAdjustStuff { [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_PUSH_TOKEN_DATA]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_PUSH_TOKEN_STRING]; @@ -139,6 +158,7 @@ + (void)clearAdjustStuff { [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_SKAD_REGISTER_CALL_TIME]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_LINK_ME_CHECKED]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_DEEPLINK_URL_CACHED]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; } @end From d8a2a4bf402d192e84c3b27ec11cf35e7fb0fd61 Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Fri, 4 Aug 2023 13:05:49 +0200 Subject: [PATCH 06/31] refac: IDFA is read only if it's allowed by config --- Adjust/ADJPackageBuilder.h | 7 +++---- Adjust/ADJPackageBuilder.m | 15 +++++++-------- Adjust/ADJPackageHandler.m | 7 +++---- Adjust/ADJSdkClickHandler.m | 7 +++---- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/Adjust/ADJPackageBuilder.h b/Adjust/ADJPackageBuilder.h index b82c92063..cb701880c 100644 --- a/Adjust/ADJPackageBuilder.h +++ b/Adjust/ADJPackageBuilder.h @@ -92,10 +92,9 @@ + (BOOL)isAdServicesPackage:(ADJActivityPackage * _Nullable)activityPackage; -+ (void)addIdfa:(NSString * _Nullable)idfa - toParameters:(NSMutableDictionary * _Nullable)parameters - withConfig:(ADJConfig * _Nullable)adjConfig - logger:(id _Nullable)logger; ++ (void)addIdfaToParameters:(NSMutableDictionary * _Nullable)parameters + withConfig:(ADJConfig * _Nullable)adjConfig + logger:(id _Nullable)logger; @end // TODO change to ADJ... extern NSString * _Nullable const ADJAttributionTokenParameter; diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index cbab333cf..9aba16a43 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -1221,10 +1221,9 @@ - (NSMutableDictionary *)getSubscriptionParameters:(BOOL)isInDelay forSubscripti } - (void)addIdfaIfPossibleToParameters:(NSMutableDictionary *)parameters { - [ADJPackageBuilder addIdfa:[ADJUtil idfa] - toParameters:parameters - withConfig:self.adjustConfig - logger:[ADJAdjustFactory logger]]; + [ADJPackageBuilder addIdfaToParameters:parameters + withConfig:self.adjustConfig + logger:[ADJAdjustFactory logger]]; } - (void)addIdfvIfPossibleToParameters:(NSMutableDictionary *)parameters { @@ -1358,10 +1357,9 @@ + (BOOL)isAdServicesPackage:(ADJActivityPackage *)activityPackage { return ([ADJUtil isNotNull:source] && [source isEqualToString:ADJAdServicesPackageKey]); } -+ (void)addIdfa:(NSString * _Nullable)idfa - toParameters:(NSMutableDictionary * _Nullable)parameters - withConfig:(ADJConfig * _Nullable)adjConfig - logger:(id _Nullable)logger { ++ (void)addIdfaToParameters:(NSMutableDictionary * _Nullable)parameters + withConfig:(ADJConfig * _Nullable)adjConfig + logger:(id _Nullable)logger { if (! adjConfig.allowIdfaReading) { return; @@ -1372,6 +1370,7 @@ + (void)addIdfa:(NSString * _Nullable)idfa return; } + NSString *idfa = [ADJUtil idfa]; if (idfa == nil || idfa.length == 0 || [idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m index 497a4e2fe..9c293bb2b 100644 --- a/Adjust/ADJPackageHandler.m +++ b/Adjust/ADJPackageHandler.m @@ -312,10 +312,9 @@ - (void)updatePackagesI:(ADJPackageHandler *)selfI for (ADJActivityPackage *activityPackage in selfI.packageQueue) { [ADJPackageBuilder parameters:activityPackage.parameters setInt:attStatus forKey:@"att_status"]; - [ADJPackageBuilder addIdfa:idfa - toParameters:activityPackage.parameters - withConfig:self.activityHandler.adjustConfig - logger:[ADJAdjustFactory logger]]; + [ADJPackageBuilder addIdfaToParameters:activityPackage.parameters + withConfig:self.activityHandler.adjustConfig + logger:[ADJAdjustFactory logger]]; // add to copy queue [packageQueueCopy addObject:activityPackage]; } diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m index 5b0ce967c..0486d4d5e 100644 --- a/Adjust/ADJSdkClickHandler.m +++ b/Adjust/ADJSdkClickHandler.m @@ -201,10 +201,9 @@ - (void)updatePackagesI:(ADJSdkClickHandler *)selfI [ADJPackageBuilder parameters:activityPackage.parameters setInt:attStatus forKey:@"att_status"]; - [ADJPackageBuilder addIdfa:idfa - toParameters:activityPackage.parameters - withConfig:self.activityHandler.adjustConfig - logger:[ADJAdjustFactory logger]]; + [ADJPackageBuilder addIdfaToParameters:activityPackage.parameters + withConfig:self.activityHandler.adjustConfig + logger:[ADJAdjustFactory logger]]; } } From d681222ed01ff2e721d06fd4343a14ce1348998c Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Fri, 4 Aug 2023 13:20:07 +0200 Subject: [PATCH 07/31] refac: IDFA is read only if it's allowed by config - contd --- Adjust/ADJActivityHandler.m | 5 ++--- Adjust/ADJPackageHandler.h | 2 +- Adjust/ADJPackageHandler.m | 8 +++----- Adjust/ADJSdkClickHandler.h | 2 +- Adjust/ADJSdkClickHandler.m | 7 +++---- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index d7cbc6cc1..f1b5c0f5c 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -2504,9 +2504,8 @@ - (void)updatePackagesAttStatusAndIdfaI:(ADJActivityHandler *)selfI { // update activity packages int attStatus = [ADJUtil attStatus]; if (attStatus != 0) { - NSString *idfa = [ADJUtil idfa]; - [selfI.packageHandler updatePackagesWithAttStatus:attStatus idfa:idfa]; - [selfI.sdkClickHandler updatePackagesWithAttStatus:attStatus idfa:idfa]; + [selfI.packageHandler updatePackagesWithIdfaAndAttStatus:attStatus]; + [selfI.sdkClickHandler updatePackagesWithIdfaAndAttStatus:attStatus]; } selfI.internalState.updatePackagesAttData = NO; diff --git a/Adjust/ADJPackageHandler.h b/Adjust/ADJPackageHandler.h index a7cd6f5b0..d9f5b1ebe 100644 --- a/Adjust/ADJPackageHandler.h +++ b/Adjust/ADJPackageHandler.h @@ -28,7 +28,7 @@ - (void)pauseSending; - (void)resumeSending; - (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameters; -- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa; +- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus; - (void)flush; - (void)teardown; diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m index 9c293bb2b..649e9a4d6 100644 --- a/Adjust/ADJPackageHandler.m +++ b/Adjust/ADJPackageHandler.m @@ -153,11 +153,11 @@ - (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameter }]; } -- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa { +- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI updatePackagesI:selfI withAttStatus:attStatus idfa:idfa]; + [selfI updatePackagesI:selfI withIdfaAndAttStatus:attStatus]; }]; } @@ -300,12 +300,10 @@ - (void)updatePackagesI:(ADJPackageHandler *)selfI } - (void)updatePackagesI:(ADJPackageHandler *)selfI - withAttStatus:(int)attStatus - idfa:(NSString *)idfa { + withIdfaAndAttStatus:(int)attStatus { [selfI.logger debug:@"Updating package handler queue"]; [selfI.logger verbose:@"ATT Status %ld", (long)attStatus]; - [selfI.logger verbose:@"IDFA: %@", idfa]; // create package queue copy for new state of array NSMutableArray *packageQueueCopy = [NSMutableArray array]; diff --git a/Adjust/ADJSdkClickHandler.h b/Adjust/ADJSdkClickHandler.h index e6564a5a9..f33d128a0 100644 --- a/Adjust/ADJSdkClickHandler.h +++ b/Adjust/ADJSdkClickHandler.h @@ -21,7 +21,7 @@ - (void)pauseSending; - (void)resumeSending; - (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage; -- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa; +- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus; - (void)teardown; @end diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m index 0486d4d5e..2c8f096cf 100644 --- a/Adjust/ADJSdkClickHandler.m +++ b/Adjust/ADJSdkClickHandler.m @@ -91,11 +91,11 @@ - (void)sendNextSdkClick { }]; } -- (void)updatePackagesWithAttStatus:(int)attStatus idfa:(NSString *)idfa { +- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJSdkClickHandler *selfI) { - [selfI updatePackagesI:selfI withAttStatus:attStatus idfa:idfa]; + [selfI updatePackagesI:selfI withIdfaAndAttStatus:attStatus]; }]; } @@ -195,8 +195,7 @@ - (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI { } - (void)updatePackagesI:(ADJSdkClickHandler *)selfI - withAttStatus:(int)attStatus - idfa:(NSString *)idfa { + withIdfaAndAttStatus:(int)attStatus { for (ADJActivityPackage *activityPackage in selfI.packageQueue) { [ADJPackageBuilder parameters:activityPackage.parameters setInt:attStatus From e5f3f7be9aa8daf2e1cba421a37a70ed0b70ec2b Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Fri, 4 Aug 2023 19:23:20 +0200 Subject: [PATCH 08/31] chore: minor change for code clarity --- Adjust/ADJActivityHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index f1b5c0f5c..5f922de81 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -3015,7 +3015,7 @@ - (void)checkAttStatusPeriodic { return; } - [ADJUserDefaults setAttWaitingRemainingSeconds:seconds-=1]; + [ADJUserDefaults setAttWaitingRemainingSeconds:(seconds-1)]; [self startWaitingForAttStatus]; } From f0d735009bcacb4ddef45f54e357fbb86461d288 Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Wed, 9 Aug 2023 11:31:06 +0200 Subject: [PATCH 09/31] refac: change ATT status getter call place --- Adjust/ADJActivityHandler.m | 4 ++-- Adjust/ADJPackageHandler.h | 2 +- Adjust/ADJPackageHandler.m | 8 ++++---- Adjust/ADJSdkClickHandler.h | 2 +- Adjust/ADJSdkClickHandler.m | 9 +++++---- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 5f922de81..7d591cca6 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -2504,8 +2504,8 @@ - (void)updatePackagesAttStatusAndIdfaI:(ADJActivityHandler *)selfI { // update activity packages int attStatus = [ADJUtil attStatus]; if (attStatus != 0) { - [selfI.packageHandler updatePackagesWithIdfaAndAttStatus:attStatus]; - [selfI.sdkClickHandler updatePackagesWithIdfaAndAttStatus:attStatus]; + [selfI.packageHandler updatePackagesWithIdfaAndAttStatus]; + [selfI.sdkClickHandler updatePackagesWithIdfaAndAttStatus]; } selfI.internalState.updatePackagesAttData = NO; diff --git a/Adjust/ADJPackageHandler.h b/Adjust/ADJPackageHandler.h index d9f5b1ebe..6916afc4b 100644 --- a/Adjust/ADJPackageHandler.h +++ b/Adjust/ADJPackageHandler.h @@ -28,7 +28,7 @@ - (void)pauseSending; - (void)resumeSending; - (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameters; -- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus; +- (void)updatePackagesWithIdfaAndAttStatus; - (void)flush; - (void)teardown; diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m index 649e9a4d6..1fcf5a95a 100644 --- a/Adjust/ADJPackageHandler.m +++ b/Adjust/ADJPackageHandler.m @@ -153,11 +153,11 @@ - (void)updatePackagesWithSessionParams:(ADJSessionParameters *)sessionParameter }]; } -- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus { +- (void)updatePackagesWithIdfaAndAttStatus { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler* selfI) { - [selfI updatePackagesI:selfI withIdfaAndAttStatus:attStatus]; + [selfI updatePackagesWithIdfaAndAttStatusI:selfI]; }]; } @@ -299,9 +299,9 @@ - (void)updatePackagesI:(ADJPackageHandler *)selfI [selfI writePackageQueueS:selfI]; } -- (void)updatePackagesI:(ADJPackageHandler *)selfI - withIdfaAndAttStatus:(int)attStatus { +- (void)updatePackagesWithIdfaAndAttStatusI:(ADJPackageHandler *)selfI { + int attStatus = [ADJUtil attStatus]; [selfI.logger debug:@"Updating package handler queue"]; [selfI.logger verbose:@"ATT Status %ld", (long)attStatus]; diff --git a/Adjust/ADJSdkClickHandler.h b/Adjust/ADJSdkClickHandler.h index f33d128a0..b298918fc 100644 --- a/Adjust/ADJSdkClickHandler.h +++ b/Adjust/ADJSdkClickHandler.h @@ -21,7 +21,7 @@ - (void)pauseSending; - (void)resumeSending; - (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage; -- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus; +- (void)updatePackagesWithIdfaAndAttStatus; - (void)teardown; @end diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m index 2c8f096cf..b5176b509 100644 --- a/Adjust/ADJSdkClickHandler.m +++ b/Adjust/ADJSdkClickHandler.m @@ -91,11 +91,11 @@ - (void)sendNextSdkClick { }]; } -- (void)updatePackagesWithIdfaAndAttStatus:(int)attStatus { +- (void)updatePackagesWithIdfaAndAttStatus { [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJSdkClickHandler *selfI) { - [selfI updatePackagesI:selfI withIdfaAndAttStatus:attStatus]; + [selfI updatePackagesWithIdfaAndAttStatusI:selfI]; }]; } @@ -194,8 +194,9 @@ - (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, work); } -- (void)updatePackagesI:(ADJSdkClickHandler *)selfI - withIdfaAndAttStatus:(int)attStatus { +- (void)updatePackagesWithIdfaAndAttStatusI:(ADJSdkClickHandler *)selfI { + + int attStatus = [ADJUtil attStatus]; for (ADJActivityPackage *activityPackage in selfI.packageQueue) { [ADJPackageBuilder parameters:activityPackage.parameters setInt:attStatus From 20af601c3de9e8b1b6b5fcad1fede1cb8d6d3482 Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Mon, 14 Aug 2023 15:32:25 +0200 Subject: [PATCH 10/31] refac: change config property name to 'attConsentWaitingInterval' --- Adjust/ADJActivityHandler.m | 6 +++--- Adjust/ADJConfig.h | 2 +- Adjust/ADJConfig.m | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 7d591cca6..6a3ce4520 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -2959,7 +2959,7 @@ - (BOOL)shouldWaitForAttStatus { BOOL keyExists = [ADJUserDefaults attWaitingRemainingSecondsKeyExists]; // check ATT timeout configuration - if (self.activityHandler.adjustConfig.attStatusWaitingTimeout == 0) { + if (self.activityHandler.adjustConfig.attConsentWaitingInterval == 0) { // ATT timmeout is not configured in ADJConfig for current SDK running session. // Already existing NSUserDefaults key means ATT timeout was configured in the previous SDK initialization. // Setting `0` to the NSUserDefaults key for skipping ATT waiting configuration in next SDK initializations, @@ -2971,8 +2971,8 @@ - (BOOL)shouldWaitForAttStatus { } // Setting timeout value limited to 120 seconds. - NSUInteger timeoutSec = (self.activityHandler.adjustConfig.attStatusWaitingTimeout <= kWaitingForAttStatusLimitSeconds) ? - self.activityHandler.adjustConfig.attStatusWaitingTimeout : kWaitingForAttStatusLimitSeconds; + NSUInteger timeoutSec = (self.activityHandler.adjustConfig.attConsentWaitingInterval <= kWaitingForAttStatusLimitSeconds) ? + self.activityHandler.adjustConfig.attConsentWaitingInterval : kWaitingForAttStatusLimitSeconds; if (keyExists && [ADJUserDefaults getAttWaitingRemainingSeconds] == 0) { // Existing NSUserDefaults key with `0` value means: // OR timeout has elapsed and user didn't succeed to invoke ATT dialog during this time diff --git a/Adjust/ADJConfig.h b/Adjust/ADJConfig.h index 88b84581e..d07993a54 100644 --- a/Adjust/ADJConfig.h +++ b/Adjust/ADJConfig.h @@ -186,7 +186,7 @@ /** * @brief Define how many seconds to wait for ATT status before sending the first data. */ -@property (nonatomic, assign) NSUInteger attStatusWaitingTimeout; +@property (nonatomic, assign) NSUInteger attConsentWaitingInterval; /** * @brief User agent for the requests. diff --git a/Adjust/ADJConfig.m b/Adjust/ADJConfig.m index 3c66f26cb..35ec5c49b 100644 --- a/Adjust/ADJConfig.m +++ b/Adjust/ADJConfig.m @@ -203,7 +203,7 @@ - (id)copyWithZone:(NSZone *)zone { copy.allowIdfaReading = self.allowIdfaReading; copy.allowAdServicesInfoReading = self.allowAdServicesInfoReading; copy.delayStart = self.delayStart; - copy.attStatusWaitingTimeout = self.attStatusWaitingTimeout; + copy.attConsentWaitingInterval = self.attConsentWaitingInterval; copy.coppaCompliantEnabled = self.coppaCompliantEnabled; copy.userAgent = [self.userAgent copyWithZone:zone]; copy.externalDeviceId = [self.externalDeviceId copyWithZone:zone]; From 1d675c3869d9bd89556e5e0f8917325e6e862443 Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Tue, 15 Aug 2023 11:37:51 +0200 Subject: [PATCH 11/31] chore: rename ATT waiting USerDefaults key --- Adjust/ADJUserDefaults.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Adjust/ADJUserDefaults.m b/Adjust/ADJUserDefaults.m index 5f4852332..16c6945e1 100644 --- a/Adjust/ADJUserDefaults.m +++ b/Adjust/ADJUserDefaults.m @@ -19,7 +19,7 @@ static NSString * const PREFS_KEY_SKAD_REGISTER_CALL_TIME = @"adj_skad_register_call_time"; static NSString * const PREFS_KEY_LINK_ME_CHECKED = @"adj_link_me_checked"; static NSString * const PREFS_KEY_DEEPLINK_URL_CACHED = @"adj_deeplink_url_cached"; -static NSString * const PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS = @"adj_att_timeout_remaining_seconds"; +static NSString * const PREFS_KEY_ATT_WAITING_REMAINING_SECONDS = @"adj_att_waiting_remaining_seconds"; @implementation ADJUserDefaults @@ -129,21 +129,21 @@ + (NSURL *)getCachedDeeplinkUrl { } + (BOOL)attWaitingRemainingSecondsKeyExists { - return (nil != [[NSUserDefaults standardUserDefaults] objectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]); + return (nil != [[NSUserDefaults standardUserDefaults] objectForKey:PREFS_KEY_ATT_WAITING_REMAINING_SECONDS]); } + (void)setAttWaitingRemainingSeconds:(NSUInteger)seconds { - [[NSUserDefaults standardUserDefaults] setInteger:seconds forKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; + [[NSUserDefaults standardUserDefaults] setInteger:seconds forKey:PREFS_KEY_ATT_WAITING_REMAINING_SECONDS]; } + (NSUInteger)getAttWaitingRemainingSeconds { - NSInteger iSeconds = [[NSUserDefaults standardUserDefaults] integerForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; + NSInteger iSeconds = [[NSUserDefaults standardUserDefaults] integerForKey:PREFS_KEY_ATT_WAITING_REMAINING_SECONDS]; NSUInteger uiSeconds = (iSeconds < 0) ? 0 : iSeconds; return uiSeconds; } + (void)removeAttWaitingRemainingSeconds { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_WAITING_REMAINING_SECONDS]; } + (void)clearAdjustStuff { @@ -158,7 +158,7 @@ + (void)clearAdjustStuff { [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_SKAD_REGISTER_CALL_TIME]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_LINK_ME_CHECKED]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_DEEPLINK_URL_CACHED]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_TIMEOUT_REMAINING_SECONDS]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_ATT_WAITING_REMAINING_SECONDS]; } @end From 971f00f327a3ca5ac046dd6734214c608eb5973e Mon Sep 17 00:00:00 2001 From: uerceg Date: Fri, 26 May 2023 14:56:48 +0200 Subject: [PATCH 12/31] feat: add initial implementation of purchase verification [wip] --- Adjust.xcodeproj/project.pbxproj | 48 +++++ Adjust/ADJActivityHandler.h | 2 + Adjust/ADJActivityHandler.m | 60 +++++- Adjust/ADJActivityKind.h | 3 +- Adjust/ADJActivityKind.m | 4 + Adjust/ADJActivityPackage.h | 2 + Adjust/ADJAdjustFactory.h | 2 + Adjust/ADJAdjustFactory.m | 10 + Adjust/ADJPackageBuilder.h | 4 +- Adjust/ADJPackageBuilder.m | 178 ++++++++++++---- Adjust/ADJPurchase.h | 27 +++ Adjust/ADJPurchase.m | 40 ++++ Adjust/ADJPurchaseVerificationHandler.h | 30 +++ Adjust/ADJPurchaseVerificationHandler.m | 200 ++++++++++++++++++ Adjust/ADJPurchaseVerificationResult.h | 38 ++++ Adjust/ADJPurchaseVerificationResult.m | 13 ++ Adjust/ADJResponseData.h | 5 + Adjust/ADJResponseData.m | 8 + Adjust/ADJUrlStrategy.m | 48 ++++- Adjust/Adjust.h | 15 ++ Adjust/Adjust.m | 18 ++ .../project.pbxproj | 18 ++ .../AdjustExample-ObjC/ViewControllerObjC.m | 15 +- 23 files changed, 737 insertions(+), 51 deletions(-) create mode 100644 Adjust/ADJPurchase.h create mode 100644 Adjust/ADJPurchase.m create mode 100644 Adjust/ADJPurchaseVerificationHandler.h create mode 100644 Adjust/ADJPurchaseVerificationHandler.m create mode 100644 Adjust/ADJPurchaseVerificationResult.h create mode 100644 Adjust/ADJPurchaseVerificationResult.m diff --git a/Adjust.xcodeproj/project.pbxproj b/Adjust.xcodeproj/project.pbxproj index 6b9c6850c..3505c82dd 100644 --- a/Adjust.xcodeproj/project.pbxproj +++ b/Adjust.xcodeproj/project.pbxproj @@ -434,6 +434,24 @@ 9D651C8925B26DF5006D69D6 /* ADJThirdPartySharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D651C8725B26DF5006D69D6 /* ADJThirdPartySharing.m */; }; 9D651C9525B26E1B006D69D6 /* ADJThirdPartySharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D651C9325B26E1B006D69D6 /* ADJThirdPartySharing.m */; }; 9D651C9625B26E1B006D69D6 /* ADJThirdPartySharing.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D651C9425B26E1B006D69D6 /* ADJThirdPartySharing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D775B3E2A1F4B19009D0BE8 /* ADJPurchase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D775B3F2A1F4B19009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */; }; + 9D775B522A1F4B3C009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */; }; + 9D775B532A1F4B3D009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */; }; + 9D775B542A1F4B3D009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */; }; + 9D775B552A1F4B3E009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */; }; + 9D775B592A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D775B5A2A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */; }; + 9D775B5B2A1F7C8B009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */; }; + 9D775B5C2A1F7C8C009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */; }; + 9D775B5D2A1F7C8C009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */; }; + 9D775B5E2A1F7C8D009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */; }; + 9D775B612A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D775B5F2A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.h */; }; + 9D775B622A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; + 9D775B632A1F9CCE009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; + 9D775B642A1F9CCE009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; + 9D775B652A1F9CCF009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; + 9D775B662A1F9CD0009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; 9DB457B01D743704004D69E8 /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF9C8DF1D6F3CA5008E362F /* ADJBackoffStrategy.m */; }; 9DB457B11D743704004D69E8 /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF9C8F71D6F3CA5008E362F /* ADJSdkClickHandler.m */; }; 9DD0E9AE1F44690B00B2A759 /* ADJUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DD0E9AC1F44690B00B2A759 /* ADJUserDefaults.h */; }; @@ -1117,6 +1135,12 @@ 9D651C9325B26E1B006D69D6 /* ADJThirdPartySharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJThirdPartySharing.m; sourceTree = ""; }; 9D651C9425B26E1B006D69D6 /* ADJThirdPartySharing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJThirdPartySharing.h; sourceTree = ""; }; 9D7431E61EB9F9B700969F14 /* AdjustExampleTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AdjustExampleTests.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 9D775B5F2A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; 9D9D154D212EB3D00081445E /* AdjustExample-FbPixel.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "AdjustExample-FbPixel.xcodeproj"; path = "examples/AdjustExample-FbPixel/AdjustExample-FbPixel.xcodeproj"; sourceTree = ""; }; 9DBE560723054FCC0065E19C /* AdjustExample-ObjC.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "AdjustExample-ObjC.xcodeproj"; path = "examples/AdjustExample-ObjC/AdjustExample-ObjC.xcodeproj"; sourceTree = ""; }; 9DD0E9AC1F44690B00B2A759 /* ADJUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJUserDefaults.h; sourceTree = ""; }; @@ -1654,6 +1678,12 @@ 6FAB784B2636DC0E00773869 /* ADJLinkResolution.m */, 9DF212D22909E86A0056D579 /* ADJSKAdNetwork.h */, 9DF212D32909E86A0056D579 /* ADJSKAdNetwork.m */, + 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */, + 9D775B3D2A1F4B19009D0BE8 /* ADJPurchase.m */, + 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */, + 9D775B582A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m */, + 9D775B5F2A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.h */, + 9D775B602A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m */, ); path = Adjust; sourceTree = ""; @@ -2177,11 +2207,13 @@ 9D3A2ABF2625BEB800BD6E44 /* ADJAdRevenue.h in Headers */, 6FAB784C2636DC0E00773869 /* ADJLinkResolution.h in Headers */, 9D651BF625B25A64006D69D6 /* ADJThirdPartySharing.h in Headers */, + 9D775B3E2A1F4B19009D0BE8 /* ADJPurchase.h in Headers */, 96BCFBD41AC99338005A65C5 /* NSData+ADJAdditions.h in Headers */, 96BCFBD21AC99332005A65C5 /* NSString+ADJAdditions.h in Headers */, 96BCFBD51AC9933E005A65C5 /* ADJActivityHandler.h in Headers */, 9DD0E9AE1F44690B00B2A759 /* ADJUserDefaults.h in Headers */, 96BCFBD61AC99345005A65C5 /* ADJActivityKind.h in Headers */, + 9D775B592A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h in Headers */, 96BCFBD71AC99348005A65C5 /* ADJActivityPackage.h in Headers */, 96BCFBD81AC9934B005A65C5 /* ADJActivityState.h in Headers */, 96BCFBD91AC9934E005A65C5 /* ADJAdjustFactory.h in Headers */, @@ -2196,6 +2228,7 @@ 965B7F301CC78F6600098639 /* ADJBackoffStrategy.h in Headers */, 96164D721CC8FA73009431AB /* ADJSdkClickHandler.h in Headers */, 96B671101D788EEC0090A023 /* ADJSessionParameters.h in Headers */, + 9D775B612A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3018,6 +3051,7 @@ 0AB1C99D27DD4B3100509231 /* ADJActivityPackage.m in Sources */, 0AB1C99E27DD4B3100509231 /* ADJActivityState.m in Sources */, 0AB1C99F27DD4B3100509231 /* ADJAdjustFactory.m in Sources */, + 9D775B632A1F9CCE009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, 0AB1C9A027DD4B3100509231 /* ADJLogger.m in Sources */, 0AB1C9A127DD4B3100509231 /* ADJPackageBuilder.m in Sources */, 0AB1C9A227DD4B3100509231 /* ADJPackageParams.m in Sources */, @@ -3032,6 +3066,8 @@ 0AB1C9AA27DD4B3200509231 /* ADJTimerOnce.m in Sources */, 0AB1C9AB27DD4B3200509231 /* ADJTimerCycle.m in Sources */, 0AB1C9AC27DD4B3200509231 /* ADJResponseData.m in Sources */, + 9D775B522A1F4B3C009D0BE8 /* ADJPurchase.m in Sources */, + 9D775B5B2A1F7C8B009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 0AB1C9AD27DD4B3200509231 /* ADJSessionSuccess.m in Sources */, 0AB1C9AE27DD4B3200509231 /* ADJSessionFailure.m in Sources */, 0AB1C9AF27DD4B3200509231 /* ADJEventSuccess.m in Sources */, @@ -3061,6 +3097,7 @@ 0AB1CA3527DF49CC00509231 /* ADJActivityPackage.m in Sources */, 0AB1CA3627DF49CC00509231 /* ADJActivityState.m in Sources */, 0AB1CA3727DF49CC00509231 /* ADJAdjustFactory.m in Sources */, + 9D775B642A1F9CCE009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, 0AB1CA3827DF49CC00509231 /* ADJLogger.m in Sources */, 0AB1CA3927DF49CC00509231 /* ADJPackageBuilder.m in Sources */, 0AB1CA3A27DF49CC00509231 /* ADJPackageParams.m in Sources */, @@ -3075,6 +3112,8 @@ 0AB1CA4227DF49CC00509231 /* ADJTimerOnce.m in Sources */, 0AB1CA4327DF49CC00509231 /* ADJTimerCycle.m in Sources */, 0AB1CA4427DF49CC00509231 /* ADJResponseData.m in Sources */, + 9D775B532A1F4B3D009D0BE8 /* ADJPurchase.m in Sources */, + 9D775B5C2A1F7C8C009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 0AB1CA4527DF49CC00509231 /* ADJSessionSuccess.m in Sources */, 0AB1CA4627DF49CC00509231 /* ADJSessionFailure.m in Sources */, 0AB1CA4727DF49CC00509231 /* ADJEventSuccess.m in Sources */, @@ -3104,6 +3143,7 @@ 0AB1CA7827DF61F200509231 /* ADJActivityPackage.m in Sources */, 0AB1CA7927DF61F200509231 /* ADJActivityState.m in Sources */, 0AB1CA7A27DF61F200509231 /* ADJAdjustFactory.m in Sources */, + 9D775B652A1F9CCF009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, 0AB1CA7B27DF61F200509231 /* ADJLogger.m in Sources */, 0AB1CA7C27DF61F200509231 /* ADJPackageBuilder.m in Sources */, 0AB1CA7D27DF61F200509231 /* ADJPackageParams.m in Sources */, @@ -3118,6 +3158,8 @@ 0AB1CA8527DF61F200509231 /* ADJTimerOnce.m in Sources */, 0AB1CA8627DF61F200509231 /* ADJTimerCycle.m in Sources */, 0AB1CA8727DF61F200509231 /* ADJResponseData.m in Sources */, + 9D775B542A1F4B3D009D0BE8 /* ADJPurchase.m in Sources */, + 9D775B5D2A1F7C8C009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 0AB1CA8827DF61F200509231 /* ADJSessionSuccess.m in Sources */, 0AB1CA8927DF61F200509231 /* ADJSessionFailure.m in Sources */, 0AB1CA8A27DF61F200509231 /* ADJEventSuccess.m in Sources */, @@ -3158,6 +3200,7 @@ 0AB1CB1A27DF68C100509231 /* ADJAttribution.m in Sources */, 0AB1CB1B27DF68C100509231 /* ADJConfig.m in Sources */, 0AB1CB1C27DF68C100509231 /* ADJTimerOnce.m in Sources */, + 9D775B552A1F4B3E009D0BE8 /* ADJPurchase.m in Sources */, 0AB1CB1D27DF68C100509231 /* ADJTimerCycle.m in Sources */, 0AB1CB1E27DF68C100509231 /* ADJResponseData.m in Sources */, 0AB1CB1F27DF68C100509231 /* ADJSessionSuccess.m in Sources */, @@ -3165,9 +3208,11 @@ 0AB1CB2127DF68C100509231 /* ADJEventSuccess.m in Sources */, 0AB1CB2227DF68C100509231 /* ADJEventFailure.m in Sources */, 0AB1CB2327DF68C100509231 /* ADJBackoffStrategy.m in Sources */, + 9D775B662A1F9CD0009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, 0AB1CB2427DF68C100509231 /* ADJSdkClickHandler.m in Sources */, 0A67F1DC2919642000AC684A /* ADJSKAdNetwork.m in Sources */, 0AB1CB2527DF68C100509231 /* ADJSessionParameters.m in Sources */, + 9D775B5E2A1F7C8D009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 0AB1CB2627DF68C100509231 /* ADJUserDefaults.m in Sources */, 0AB1CB2727DF68C100509231 /* ADJSubscription.m in Sources */, 0AB1CB2827DF68C100509231 /* ADJUrlStrategy.m in Sources */, @@ -3207,6 +3252,7 @@ 96E5E38D18BBB48A008E7B30 /* ADJActivityPackage.m in Sources */, 96E5E38E18BBB48A008E7B30 /* ADJActivityState.m in Sources */, 96E5E39218BBB48A008E7B30 /* ADJAdjustFactory.m in Sources */, + 9D775B622A1F9CC5009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, 6FAB784D2636DC0E00773869 /* ADJLinkResolution.m in Sources */, 96E5E39318BBB48A008E7B30 /* ADJLogger.m in Sources */, 96E5E39518BBB48A008E7B30 /* ADJPackageHandler.m in Sources */, @@ -3221,6 +3267,8 @@ 969952CF1A012F5300928462 /* ADJAttributionHandler.m in Sources */, 969952D21A01309200928462 /* ADJAttribution.m in Sources */, 960A8BB91A029A8000F2BB95 /* ADJConfig.m in Sources */, + 9D775B3F2A1F4B19009D0BE8 /* ADJPurchase.m in Sources */, + 9D775B5A2A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 96854A5A1B1F2779002B2874 /* ADJTimerOnce.m in Sources */, 96854A601B1F278C002B2874 /* ADJTimerCycle.m in Sources */, 9D3A2ABE2625BEB800BD6E44 /* ADJAdRevenue.m in Sources */, diff --git a/Adjust/ADJActivityHandler.h b/Adjust/ADJActivityHandler.h index cb22250cb..384c2fbe5 100644 --- a/Adjust/ADJActivityHandler.h +++ b/Adjust/ADJActivityHandler.h @@ -114,6 +114,8 @@ - (void)updateAttStatusFromUserCallback:(int)newAttStatusFromUser; - (void)trackAdRevenue:(ADJAdRevenue * _Nullable)adRevenue; - (void)checkForNewAttStatus; +- (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler; - (ADJPackageParams * _Nullable)packageParams; - (ADJActivityState * _Nullable)activityState; diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 6a3ce4520..fb3b76f7b 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -23,6 +23,7 @@ #import "ADJUserDefaults.h" #import "ADJUrlStrategy.h" #import "ADJSKAdNetwork.h" +#import "ADJPurchaseVerificationHandler.h" NSString * const ADJAdServicesPackageKey = @"apple_ads"; @@ -85,6 +86,7 @@ @interface ADJActivityHandler() @property (nonatomic, strong) ADJPackageHandler *packageHandler; @property (nonatomic, strong) ADJAttributionHandler *attributionHandler; @property (nonatomic, strong) ADJSdkClickHandler *sdkClickHandler; +@property (nonatomic, strong) ADJPurchaseVerificationHandler *purchaseVerificationHandler; @property (nonatomic, strong) ADJActivityState *activityState; @property (nonatomic, strong) ADJTimerCycle *foregroundTimer; @property (nonatomic, strong) ADJTimerOnce *backgroundTimer; @@ -628,6 +630,15 @@ - (void)checkForNewAttStatus { }]; } +- (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJActivityHandler * selfI) { + [selfI verifyPurchaseI:selfI purchase:purchase completionHandler:completionHandler]; + }]; +} + - (void)writeActivityState { [ADJUtil launchInQueue:self.internalQueue selfInject:self @@ -698,6 +709,9 @@ - (void)teardown if (self.sdkClickHandler != nil) { [self.sdkClickHandler teardown]; } + if (self.purchaseVerificationHandler != nil) { + [self.purchaseVerificationHandler teardown]; + } [self teardownActivityStateS]; [self teardownAttributionS]; [self teardownAllSessionParametersS]; @@ -708,6 +722,7 @@ - (void)teardown self.packageHandler = nil; self.attributionHandler = nil; self.sdkClickHandler = nil; + self.purchaseVerificationHandler = nil; self.foregroundTimer = nil; self.backgroundTimer = nil; self.adjustDelegate = nil; @@ -718,13 +733,11 @@ - (void)teardown self.logger = nil; } -+ (void)deleteState -{ ++ (void)deleteState { [ADJActivityHandler deleteActivityState]; [ADJActivityHandler deleteAttribution]; [ADJActivityHandler deleteSessionCallbackParameter]; [ADJActivityHandler deleteSessionPartnerParameter]; - [ADJUserDefaults clearAdjustStuff]; } @@ -862,7 +875,6 @@ - (void)initI:(ADJActivityHandler *)selfI userAgent:selfI.adjustConfig.userAgent urlStrategy:sdkClickHandlerUrlStrategy]; - // Update ATT status and idfa ,if necessary, in packages and sdk_click package queues. // This should be done after `packageHandler` and `sdkClickHandler` are created. if (selfI.internalState.waitingForAttStatus) { @@ -881,6 +893,12 @@ - (void)initI:(ADJActivityHandler *)selfI } } + selfI.purchaseVerificationHandler = [[ADJPurchaseVerificationHandler alloc] + initWithActivityHandler:selfI + startsSending:[selfI toSendI:selfI sdkClickHandlerOnly:YES] + userAgent:selfI.adjustConfig.userAgent + urlStrategy:sdkClickHandlerUrlStrategy]; + [selfI checkLinkMeI:selfI]; [selfI.trackingStatusManager checkForNewAttStatus]; @@ -1398,6 +1416,37 @@ - (void)checkForNewAttStatusI:(ADJActivityHandler *)selfI { [selfI.trackingStatusManager checkForNewAttStatus]; } +- (void)verifyPurchaseI:(ADJActivityHandler *)selfI + purchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { + if (![selfI isEnabledI:selfI]) { + return; + } + if ([ADJUtil isNull:purchase]) { + return; + } + if ([ADJUtil isNull:completionHandler]) { + return; + } + + double now = [NSDate.date timeIntervalSince1970]; + [ADJUtil launchSynchronisedWithObject:[ADJActivityState class] + block:^{ + double lastInterval = now - selfI.activityState.lastActivity; + selfI.activityState.lastInterval = lastInterval; + }]; + ADJPackageBuilder *purchaseVerificationBuilder = [[ADJPackageBuilder alloc] initWithPackageParams:selfI.packageParams + activityState:selfI.activityState + config:selfI.adjustConfig + sessionParameters:selfI.sessionParameters + trackingStatusManager:self.trackingStatusManager + createdAt:now]; + + ADJActivityPackage *purchaseVerificationPackage = [purchaseVerificationBuilder buildPurchaseVerificationPackage:purchase]; + purchaseVerificationPackage.responseBlock = completionHandler; + [selfI.purchaseVerificationHandler sendPurchaseVerificationPackage:purchaseVerificationPackage]; +} + - (void)launchEventResponseTasksI:(ADJActivityHandler *)selfI eventResponseData:(ADJEventResponseData *)eventResponseData { [selfI updateAdidI:selfI adid:eventResponseData.adid]; @@ -2283,8 +2332,10 @@ - (void)pauseSendingI:(ADJActivityHandler *)selfI { // it's possible for the sdk click handler to be active while others are paused if (![selfI toSendI:selfI sdkClickHandlerOnly:YES]) { [selfI.sdkClickHandler pauseSending]; + [selfI.purchaseVerificationHandler pauseSending]; } else { [selfI.sdkClickHandler resumeSending]; + [selfI.purchaseVerificationHandler resumeSending]; } } @@ -2292,6 +2343,7 @@ - (void)resumeSendingI:(ADJActivityHandler *)selfI { [selfI.attributionHandler resumeSending]; [selfI.packageHandler resumeSending]; [selfI.sdkClickHandler resumeSending]; + [selfI.purchaseVerificationHandler resumeSending]; } - (BOOL)pausedI:(ADJActivityHandler *)selfI sdkClickHandlerOnly:(BOOL)sdkClickHandlerOnly { diff --git a/Adjust/ADJActivityKind.h b/Adjust/ADJActivityKind.h index c7867fec9..b8549052e 100644 --- a/Adjust/ADJActivityKind.h +++ b/Adjust/ADJActivityKind.h @@ -25,7 +25,8 @@ typedef NS_ENUM(int, ADJActivityKind) { ADJActivityKindDisableThirdPartySharing = 9, ADJActivityKindSubscription = 10, ADJActivityKindThirdPartySharing = 11, - ADJActivityKindMeasurementConsent = 12 + ADJActivityKindMeasurementConsent = 12, + ADJActivityKindPurchaseVerification = 13 }; @interface ADJActivityKindUtil : NSObject diff --git a/Adjust/ADJActivityKind.m b/Adjust/ADJActivityKind.m index fe6bb5b03..a98a6a12d 100644 --- a/Adjust/ADJActivityKind.m +++ b/Adjust/ADJActivityKind.m @@ -35,6 +35,8 @@ + (ADJActivityKind)activityKindFromString:(NSString *)activityKindString { return ADJActivityKindThirdPartySharing; } else if ([@"measurement_consent" isEqualToString:activityKindString]) { return ADJActivityKindMeasurementConsent; + } else if ([@"purchase_verification" isEqualToString:activityKindString]) { + return ADJActivityKindPurchaseVerification; } else { return ADJActivityKindUnknown; } @@ -64,6 +66,8 @@ + (NSString *)activityKindToString:(ADJActivityKind)activityKind { return @"third_party_sharing"; case ADJActivityKindMeasurementConsent: return @"measurement_consent"; + case ADJActivityKindPurchaseVerification: + return @"purchase_verification"; default: return @"unknown"; } diff --git a/Adjust/ADJActivityPackage.h b/Adjust/ADJActivityPackage.h index 3db72b530..25d698f3c 100644 --- a/Adjust/ADJActivityPackage.h +++ b/Adjust/ADJActivityPackage.h @@ -22,6 +22,8 @@ @property (nonatomic, strong) NSDictionary *callbackParameters; +@property (nonatomic, copy) void (^responseBlock)(id); + // Logs @property (nonatomic, copy) NSString *suffix; diff --git a/Adjust/ADJAdjustFactory.h b/Adjust/ADJAdjustFactory.h index 5be8246b7..286995dd8 100644 --- a/Adjust/ADJAdjustFactory.h +++ b/Adjust/ADJAdjustFactory.h @@ -29,6 +29,7 @@ + (NSString *)baseUrl; + (NSString *)gdprUrl; + (NSString *)subscriptionUrl; ++ (NSString *)purchaseVerificationUrl; + (BOOL)adServicesFrameworkEnabled; + (void)setLogger:(id)logger; @@ -45,6 +46,7 @@ + (void)setBaseUrl:(NSString *)baseUrl; + (void)setGdprUrl:(NSString *)gdprUrl; + (void)setSubscriptionUrl:(NSString *)subscriptionUrl; ++ (void)setPurchaseVerificationUrl:(NSString *)purchaseVerificationUrl; + (void)enableSigning; + (void)disableSigning; diff --git a/Adjust/ADJAdjustFactory.m b/Adjust/ADJAdjustFactory.m index 68355d4db..b15d5ab9c 100644 --- a/Adjust/ADJAdjustFactory.m +++ b/Adjust/ADJAdjustFactory.m @@ -27,6 +27,7 @@ static NSString * internalBaseUrl = nil; static NSString * internalGdprUrl = nil; static NSString * internalSubscriptionUrl = nil; +static NSString * internalPurchaseVerificationUrl = nil; @implementation ADJAdjustFactory @@ -121,6 +122,10 @@ + (NSString *)subscriptionUrl { return internalSubscriptionUrl; } ++ (NSString *)purchaseVerificationUrl { + return internalPurchaseVerificationUrl; +} + + (void)setLogger:(id)logger { internalLogger = logger; } @@ -177,6 +182,10 @@ + (void)setSubscriptionUrl:(NSString *)subscriptionUrl { internalSubscriptionUrl = subscriptionUrl; } ++ (void)setPurchaseVerificationUrl:(NSString *)purchaseVerificationUrl { + internalPurchaseVerificationUrl = purchaseVerificationUrl; +} + + (void)enableSigning { Class signerClass = NSClassFromString(@"ADJSigner"); if (signerClass == nil) { @@ -239,6 +248,7 @@ + (void)teardown:(BOOL)deleteState { internalBaseUrl = nil; internalGdprUrl = nil; internalSubscriptionUrl = nil; + internalPurchaseVerificationUrl = nil; internalAdServicesFrameworkEnabled = YES; } @end diff --git a/Adjust/ADJPackageBuilder.h b/Adjust/ADJPackageBuilder.h index cb701880c..76241c8da 100644 --- a/Adjust/ADJPackageBuilder.h +++ b/Adjust/ADJPackageBuilder.h @@ -42,7 +42,7 @@ - (ADJActivityPackage * _Nullable)buildSessionPackage:(BOOL)isInDelay; - (ADJActivityPackage * _Nullable)buildEventPackage:(ADJEvent * _Nullable)event - isInDelay:(BOOL)isInDelay; + isInDelay:(BOOL)isInDelay; - (ADJActivityPackage * _Nullable)buildInfoPackage:(NSString * _Nullable)infoSource; @@ -58,6 +58,8 @@ - (ADJActivityPackage * _Nullable)buildClickPackage:(NSString * _Nullable)clickSource linkMeUrl:(NSString * _Nullable)linkMeUrl; +- (ADJActivityPackage * _Nullable)buildPurchaseVerificationPackage:(ADJPurchase * _Nullable)purchase; + - (ADJActivityPackage * _Nullable)buildAttributionPackage:(NSString * _Nullable)initiatedBy; - (ADJActivityPackage * _Nullable)buildGdprPackage; diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index 9aba16a43..a9c31502d 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -143,42 +143,6 @@ - (ADJActivityPackage *)buildAdRevenuePackage:(ADJAdRevenue *)adRevenue isInDela return adRevenuePackage; } -- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource { - return [self buildClickPackage:clickSource extraParameters:nil]; -} - -- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource - token:(NSString *)token - errorCodeNumber:(NSNumber *)errorCodeNumber { - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - - if (token != nil) { - [ADJPackageBuilder parameters:parameters - setString:token - forKey:ADJAttributionTokenParameter]; - } - if (errorCodeNumber != nil) { - [ADJPackageBuilder parameters:parameters - setInt:errorCodeNumber.intValue - forKey:@"error_code"]; - } - - return [self buildClickPackage:clickSource extraParameters:parameters]; -} - -- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource - linkMeUrl:(NSString * _Nullable)linkMeUrl { - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - - if (linkMeUrl != nil) { - [ADJPackageBuilder parameters:parameters - setString:linkMeUrl - forKey:@"content"]; - } - - return [self buildClickPackage:clickSource extraParameters:parameters]; -} - - (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource extraParameters:(NSDictionary *)extraParameters { NSMutableDictionary *parameters = [self getClickParameters:clickSource]; if (extraParameters != nil) { @@ -288,6 +252,84 @@ - (ADJActivityPackage *)buildSubscriptionPackage:(ADJSubscription *)subscription return subscriptionPackage; } +- (ADJActivityPackage *)buildPurchaseVerificationPackageWithExtraParams:(NSDictionary *)extraParameters { + NSMutableDictionary *parameters = [self getPurchaseVerificationParameters]; + if (extraParameters != nil) { + [parameters addEntriesFromDictionary:extraParameters]; + } + + ADJActivityPackage *purchaseVerificationPackage = [self defaultActivityPackage]; + // TODO: update this + purchaseVerificationPackage.path = @"/verify"; + purchaseVerificationPackage.activityKind = ADJActivityKindPurchaseVerification; + purchaseVerificationPackage.suffix = @""; + purchaseVerificationPackage.parameters = parameters; + + [self signWithSigV2Plugin:purchaseVerificationPackage]; + purchaseVerificationPackage.parameters = [ADJUtil deepCopyOfDictionary:purchaseVerificationPackage.parameters]; + + return purchaseVerificationPackage; +} + +- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource { + return [self buildClickPackage:clickSource extraParameters:nil]; +} + +- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource + token:(NSString *)token + errorCodeNumber:(NSNumber *)errorCodeNumber { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + + if (token != nil) { + [ADJPackageBuilder parameters:parameters + setString:token + forKey:ADJAttributionTokenParameter]; + } + if (errorCodeNumber != nil) { + [ADJPackageBuilder parameters:parameters + setInt:errorCodeNumber.intValue + forKey:@"error_code"]; + } + + return [self buildClickPackage:clickSource extraParameters:parameters]; +} + +- (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource + linkMeUrl:(NSString * _Nullable)linkMeUrl { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + + if (linkMeUrl != nil) { + [ADJPackageBuilder parameters:parameters + setString:linkMeUrl + forKey:@"content"]; + } + + return [self buildClickPackage:clickSource extraParameters:parameters]; +} + +- (ADJActivityPackage * _Nullable)buildPurchaseVerificationPackage:(ADJPurchase * _Nullable)purchase { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + + if (purchase.receipt != nil) { + NSString *receiptBase64 = [purchase.receipt adjEncodeBase64]; + [ADJPackageBuilder parameters:parameters + setString:receiptBase64 + forKey:@"receipt"]; + } + if (purchase.transactionId != nil) { + [ADJPackageBuilder parameters:parameters + setString:purchase.transactionId + forKey:@"transaction_id"]; + } + if (purchase.productId != nil) { + [ADJPackageBuilder parameters:parameters + setString:purchase.productId + forKey:@"product_id"]; + } + + return [self buildPurchaseVerificationPackageWithExtraParams:parameters]; +} + + (void)parameters:(NSMutableDictionary *)parameters setDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { if (dictionary == nil) { return; @@ -1220,6 +1262,70 @@ - (NSMutableDictionary *)getSubscriptionParameters:(BOOL)isInDelay forSubscripti return parameters; } +- (NSMutableDictionary *)getPurchaseVerificationParameters { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.appSecret forKey:@"app_secret"]; + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.appToken forKey:@"app_token"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.buildNumber forKey:@"app_version"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.versionNumber forKey:@"app_version_short"]; + [ADJPackageBuilder parameters:parameters setBool:YES forKey:@"attribution_deeplink"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.bundleIdentifier forKey:@"bundle_id"]; + [ADJPackageBuilder parameters:parameters setDictionary:[self.sessionParameters.callbackParameters copy] forKey:@"callback_params"]; + [ADJPackageBuilder parameters:parameters setDate1970:self.createdAt forKey:@"created_at"]; + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.defaultTracker forKey:@"default_tracker"]; + [ADJPackageBuilder parameters:parameters setDictionary:self.attributionDetails forKey:@"details"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.deviceName forKey:@"device_name"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.deviceType forKey:@"device_type"]; + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.environment forKey:@"environment"]; + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.externalDeviceId forKey:@"external_device_id"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.fbAnonymousId forKey:@"fb_anon_id"]; + [self addIdfaIfPossibleToParameters:parameters]; + [self addIdfvIfPossibleToParameters:parameters]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.installedAt forKey:@"installed_at"]; + [ADJPackageBuilder parameters:parameters setBool:YES forKey:@"needs_response_details"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.osName forKey:@"os_name"]; + [ADJPackageBuilder parameters:parameters setString:self.packageParams.osVersion forKey:@"os_version"]; + [ADJPackageBuilder parameters:parameters setDictionary:[self.sessionParameters.partnerParameters copy] forKey:@"partner_params"]; + [ADJPackageBuilder parameters:parameters setString:@"ios_purchase2.0.0" forKey:@"sdk_version"]; // TODO: to be removed after backend update + [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.secretId forKey:@"secret_id"]; + [ADJPackageBuilder parameters:parameters setDate:[ADJUserDefaults getSkadRegisterCallTimestamp] forKey:@"skadn_registered_at"]; + [ADJPackageBuilder parameters:parameters setDate1970:(double)self.packageParams.startedAt forKey:@"started_at"]; + + if ([self.trackingStatusManager canGetAttStatus]) { + [ADJPackageBuilder parameters:parameters setInt:self.trackingStatusManager.attStatus + forKey:@"att_status"]; + } else { + [ADJPackageBuilder parameters:parameters setInt:self.trackingStatusManager.trackingEnabled + forKey:@"tracking_enabled"]; + } + + if (self.adjustConfig.isDeviceKnown) { + [ADJPackageBuilder parameters:parameters setBool:self.adjustConfig.isDeviceKnown forKey:@"device_known"]; + } + if (self.adjustConfig.needsCost) { + [ADJPackageBuilder parameters:parameters setBool:self.adjustConfig.needsCost forKey:@"needs_cost"]; + } + + if (self.activityState != nil) { + [ADJPackageBuilder parameters:parameters setDuration:self.activityState.lastInterval forKey:@"last_interval"]; + [ADJPackageBuilder parameters:parameters setString:self.activityState.deviceToken forKey:@"push_token"]; + [ADJPackageBuilder parameters:parameters setInt:self.activityState.sessionCount forKey:@"session_count"]; + [ADJPackageBuilder parameters:parameters setDuration:self.activityState.sessionLength forKey:@"session_length"]; + [ADJPackageBuilder parameters:parameters setInt:self.activityState.subsessionCount forKey:@"subsession_count"]; + [ADJPackageBuilder parameters:parameters setDuration:self.activityState.timeSpent forKey:@"time_spent"]; + if (self.activityState.isPersisted) { + [ADJPackageBuilder parameters:parameters setString:self.activityState.dedupeToken forKey:@"primary_dedupe_token"]; + } else { + [ADJPackageBuilder parameters:parameters setString:self.activityState.dedupeToken forKey:@"secondary_dedupe_token"]; + } + } + + [self injectFeatureFlagsWithParameters:parameters]; + + return parameters; +} + - (void)addIdfaIfPossibleToParameters:(NSMutableDictionary *)parameters { [ADJPackageBuilder addIdfaToParameters:parameters withConfig:self.adjustConfig diff --git a/Adjust/ADJPurchase.h b/Adjust/ADJPurchase.h new file mode 100644 index 000000000..f1b54c95b --- /dev/null +++ b/Adjust/ADJPurchase.h @@ -0,0 +1,27 @@ +// +// ADJPurchase.h +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ADJPurchase : NSObject + +@property (nonatomic, copy, readonly, nonnull) NSString *transactionId; + +@property (nonatomic, copy, readonly, nonnull) NSData *receipt; + +@property (nonatomic, copy, readonly, nonnull) NSString *productId; + +- (nullable id)initWithTransactionId:(nonnull NSString *)transactionId + productId:(nonnull NSString *)productId + andReceipt:(nonnull NSData *)receipt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Adjust/ADJPurchase.m b/Adjust/ADJPurchase.m new file mode 100644 index 000000000..4313250a0 --- /dev/null +++ b/Adjust/ADJPurchase.m @@ -0,0 +1,40 @@ +// +// ADJPurchase.m +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import "ADJPurchase.h" + +@implementation ADJPurchase + +- (nullable id)initWithTransactionId:(NSString *)transactionId + productId:(NSString *)productId + andReceipt:(NSData *)receipt { + self = [super init]; + if (self == nil) { + return nil; + } + + _transactionId = [transactionId copy]; + _productId = [productId copy]; + _receipt = [receipt copy]; + + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + ADJPurchase *copy = [[[self class] allocWithZone:zone] init]; + + if (copy) { + copy->_transactionId = [self.transactionId copyWithZone:zone]; + copy->_receipt = [self.receipt copyWithZone:zone]; + copy->_productId = [self.productId copyWithZone:zone]; + } + + return copy; +} + +@end diff --git a/Adjust/ADJPurchaseVerificationHandler.h b/Adjust/ADJPurchaseVerificationHandler.h new file mode 100644 index 000000000..29aa20834 --- /dev/null +++ b/Adjust/ADJPurchaseVerificationHandler.h @@ -0,0 +1,30 @@ +// +// ADJPurchaseVerificationHandler.h +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import +#import "ADJActivityPackage.h" +#import "ADJActivityHandler.h" +#import "ADJRequestHandler.h" +#import "ADJUrlStrategy.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ADJPurchaseVerificationHandler : NSObject + +- (id)initWithActivityHandler:(id)activityHandler + startsSending:(BOOL)startsSending + userAgent:(NSString *)userAgent + urlStrategy:(ADJUrlStrategy *)urlStrategy; +- (void)pauseSending; +- (void)resumeSending; +- (void)sendPurchaseVerificationPackage:(ADJActivityPackage *)purchaseVerificationPackage; +- (void)teardown; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m new file mode 100644 index 000000000..a4f6a41c4 --- /dev/null +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -0,0 +1,200 @@ +// +// ADJPurchaseVerificationHandler.m +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import "ADJPurchaseVerificationHandler.h" +#import "ADJUtil.h" +#import "ADJLogger.h" +#import "ADJAdjustFactory.h" +#import "ADJBackoffStrategy.h" +#import "ADJUserDefaults.h" +#import "ADJPackageBuilder.h" + +static const char * const kInternalQueueName = "com.adjust.PurchaseVerificationQueue"; + +@interface ADJPurchaseVerificationHandler() + +@property (nonatomic, strong) NSMutableArray *packageQueue; +@property (nonatomic, strong) dispatch_queue_t internalQueue; +@property (nonatomic, strong) ADJRequestHandler *requestHandler; + +@property (nonatomic, assign) BOOL paused; +@property (nonatomic, strong) ADJBackoffStrategy *backoffStrategy; + +@property (nonatomic, weak) id logger; +@property (nonatomic, weak) id activityHandler; + +@property (nonatomic, assign) NSInteger lastPackageRetriesCount; + +@end + +@implementation ADJPurchaseVerificationHandler + +#pragma mark - Public instance methods + +- (id)initWithActivityHandler:(id)activityHandler + startsSending:(BOOL)startsSending + userAgent:(NSString *)userAgent + urlStrategy:(ADJUrlStrategy *)urlStrategy { + self = [super init]; + if (self == nil) { + return nil; + } + + self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL); + self.logger = ADJAdjustFactory.logger; + self.lastPackageRetriesCount = 0; + + self.requestHandler = [[ADJRequestHandler alloc] initWithResponseCallback:self + urlStrategy:urlStrategy + userAgent:userAgent + requestTimeout:[ADJAdjustFactory requestTimeout]]; + + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJPurchaseVerificationHandler *selfI) { + [selfI initI:selfI activityHandler:activityHandler startsSending:startsSending]; + }]; + return self; +} + +- (void)pauseSending { + self.paused = YES; +} + +- (void)resumeSending { + self.paused = NO; + [self sendNextPurchaseVerificationPackage]; +} + +- (void)sendPurchaseVerificationPackage:(ADJActivityPackage *)purchaseVerificationPackage { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJPurchaseVerificationHandler *selfI) { + [selfI sendPurchaseVerificationPackageI:selfI purchaseVerificationPackage:purchaseVerificationPackage]; + }]; +} + +- (void)sendNextPurchaseVerificationPackage { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJPurchaseVerificationHandler *selfI) { + [selfI sendNextPurchaseVerificationPackageI:selfI]; + }]; +} + +- (void)teardown { + [ADJAdjustFactory.logger verbose:@"ADJPurchaseVerificationHandler teardown"]; + + if (self.packageQueue != nil) { + [self.packageQueue removeAllObjects]; + } + + self.internalQueue = nil; + self.logger = nil; + self.backoffStrategy = nil; + self.packageQueue = nil; + self.activityHandler = nil; +} + +#pragma mark - Private & helper methods + +- (void)initI:(ADJPurchaseVerificationHandler *)selfI +activityHandler:(id)activityHandler + startsSending:(BOOL)startsSending { + selfI.activityHandler = activityHandler; + selfI.paused = !startsSending; + selfI.backoffStrategy = [ADJAdjustFactory sdkClickHandlerBackoffStrategy]; + selfI.packageQueue = [NSMutableArray array]; +} + +- (void)sendPurchaseVerificationPackageI:(ADJPurchaseVerificationHandler *)selfI + purchaseVerificationPackage:(ADJActivityPackage *)purchaseVerificationPackage { + [selfI.packageQueue addObject:purchaseVerificationPackage]; + [selfI.logger debug:@"Added purchase_verification %d", selfI.packageQueue.count]; + [selfI.logger verbose:@"%@", purchaseVerificationPackage.extendedString]; + [selfI sendNextPurchaseVerificationPackage]; +} + +- (void)sendNextPurchaseVerificationPackageI:(ADJPurchaseVerificationHandler *)selfI { + if (selfI.paused) { + return; + } + NSUInteger queueSize = selfI.packageQueue.count; + if (queueSize == 0) { + return; + } + if ([selfI.activityHandler isGdprForgotten]) { + [selfI.logger debug:@"purchase_verification request won't be fired for forgotten user"]; + return; + } + + ADJActivityPackage *purchaseVerificationPackage = [self.packageQueue objectAtIndex:0]; + [self.packageQueue removeObjectAtIndex:0]; + + if (![purchaseVerificationPackage isKindOfClass:[ADJActivityPackage class]]) { + [selfI.logger error:@"Failed to read purchase_verification package"]; + [selfI sendNextPurchaseVerificationPackage]; + return; + } + + dispatch_block_t work = ^{ + NSDictionary *sendingParameters = @{ + @"sent_at": [ADJUtil formatSeconds1970:[NSDate.date timeIntervalSince1970]] + }; + [selfI.requestHandler sendPackageByPOST:purchaseVerificationPackage + sendingParameters:sendingParameters]; + [selfI sendNextPurchaseVerificationPackage]; + }; + + if (selfI.lastPackageRetriesCount <= 0) { + work(); + return; + } + + NSTimeInterval waitTime = [ADJUtil waitingTime:selfI.lastPackageRetriesCount backoffStrategy:self.backoffStrategy]; + NSString *waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime]; + [self.logger verbose:@"Waiting for %@ seconds before retrying purchase_verification for the %d time", + waitTimeFormatted, + selfI.lastPackageRetriesCount]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, work); +} + +- (void)responseCallback:(ADJResponseData *)responseData { + if (responseData.jsonResponse) { + [self.logger debug: + @"Got purchase_verification JSON response with message: %@", responseData.message]; + ADJPurchaseVerificationResult *verificationResult = [[ADJPurchaseVerificationResult alloc] init]; + verificationResult.verificationStatus = responseData.jsonResponse[@"verification_status"]; + verificationResult.code = [(NSNumber *)responseData.jsonResponse[@"code"] intValue]; + verificationResult.message = responseData.jsonResponse[@"message"]; + responseData.purchaseVerificationPackage.responseBlock(verificationResult); + } else { + [self.logger error: + @"Could not get purchase_verification JSON response with message: %@", responseData.message]; + // TODO: handle invalid JSON error case + // TODO: handle all the remaining error cases + responseData.purchaseVerificationPackage.responseBlock(nil); + } + // Check if any package response contains information that user has opted out. + // If yes, disable SDK and flush any potentially stored packages that happened afterwards. + if (responseData.trackingState == ADJTrackingStateOptedOut) { + self.lastPackageRetriesCount = 0; + [self.activityHandler setTrackingStateOptedOut]; + return; + } + if (responseData.jsonResponse == nil) { + self.lastPackageRetriesCount++; + [self.logger error:@"Retrying purchase_verification package for the %d time", self.lastPackageRetriesCount]; + [self sendPurchaseVerificationPackage:responseData.purchaseVerificationPackage]; + return; + } + self.lastPackageRetriesCount = 0; + [self.activityHandler finishedTracking:responseData]; +} + +@end diff --git a/Adjust/ADJPurchaseVerificationResult.h b/Adjust/ADJPurchaseVerificationResult.h new file mode 100644 index 000000000..3e8f40409 --- /dev/null +++ b/Adjust/ADJPurchaseVerificationResult.h @@ -0,0 +1,38 @@ +// +// ADJPurchaseVerificationResult.h +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ADJPurchaseVerificationResult : NSObject + +/** + * @property message + * + * @brief Text message about current state of receipt verification. + */ +@property (nonatomic, copy) NSString *message; + +/** + * @property code + * + * @brief Response code returned from Adjust backend server. + */ +@property (nonatomic, assign) int code; + +/** + * @property verificationStatus + * + * @brief State of verification (success / failure / unknown / not verified) + */ +@property (nonatomic, copy) NSString *verificationStatus; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Adjust/ADJPurchaseVerificationResult.m b/Adjust/ADJPurchaseVerificationResult.m new file mode 100644 index 000000000..356f37619 --- /dev/null +++ b/Adjust/ADJPurchaseVerificationResult.m @@ -0,0 +1,13 @@ +// +// ADJPurchaseVerificationResult.m +// Adjust +// +// Created by Uglješa Erceg (@uerceg) on May 25th 2023. +// Copyright © 2023 Adjust. All rights reserved. +// + +#import "ADJPurchaseVerificationResult.h" + +@implementation ADJPurchaseVerificationResult + +@end diff --git a/Adjust/ADJResponseData.h b/Adjust/ADJResponseData.h index dc4014062..7006d5aa5 100644 --- a/Adjust/ADJResponseData.h +++ b/Adjust/ADJResponseData.h @@ -45,6 +45,8 @@ typedef NS_ENUM(int, ADJTrackingState) { @property (nonatomic, strong) ADJActivityPackage *sdkPackage; +@property (nonatomic, strong) ADJActivityPackage *purchaseVerificationPackage; + + (id)buildResponseData:(ADJActivityPackage *)activityPackage; @end @@ -60,6 +62,9 @@ typedef NS_ENUM(int, ADJTrackingState) { @interface ADJSdkClickResponseData : ADJResponseData @end +@interface ADJPurchaseVerificationResponseData : ADJResponseData +@end + @interface ADJEventResponseData : ADJResponseData @property (nonatomic, copy) NSString *eventToken; diff --git a/Adjust/ADJResponseData.m b/Adjust/ADJResponseData.m index 977e8d12c..93a5a95ee 100644 --- a/Adjust/ADJResponseData.m +++ b/Adjust/ADJResponseData.m @@ -57,6 +57,10 @@ + (id)buildResponseData:(ADJActivityPackage *)activityPackage { case ADJActivityKindAttribution: responseData = [[ADJAttributionResponseData alloc] init]; break; + case ADJActivityKindPurchaseVerification: + responseData = [[ADJPurchaseVerificationResponseData alloc] init]; + responseData.purchaseVerificationPackage = activityPackage; + break; default: responseData = [[ADJResponseData alloc] init]; break; @@ -129,6 +133,10 @@ @implementation ADJSdkClickResponseData @end +@implementation ADJPurchaseVerificationResponseData + +@end + @implementation ADJEventResponseData - (id)initWithEventToken:(NSString *)eventToken diff --git a/Adjust/ADJUrlStrategy.m b/Adjust/ADJUrlStrategy.m index c2c6a213a..6850a2f12 100644 --- a/Adjust/ADJUrlStrategy.m +++ b/Adjust/ADJUrlStrategy.m @@ -13,40 +13,49 @@ static NSString * const baseUrl = @"https://app.adjust.com"; static NSString * const gdprUrl = @"https://gdpr.adjust.com"; static NSString * const subscriptionUrl = @"https://subscription.adjust.com"; +static NSString * const purchaseVerificationUrl = @"https://ssrv.adjust.com"; static NSString * const baseUrlIndia = @"https://app.adjust.net.in"; static NSString * const gdprUrlIndia = @"https://gdpr.adjust.net.in"; static NSString * const subscriptionUrlIndia = @"https://subscription.adjust.net.in"; +static NSString * const purchaseVerificationUrlIndia = @"https://ssrv.adjust.net.in"; static NSString * const baseUrlChina = @"https://app.adjust.world"; static NSString * const gdprUrlChina = @"https://gdpr.adjust.world"; static NSString * const subscriptionUrlChina = @"https://subscription.adjust.world"; +static NSString * const purchaseVerificationUrlChina = @"https://ssrv.adjust.world"; static NSString * const baseUrlCn = @"https://app.adjust.cn"; -static NSString * const gdprUrlCn = @"https://gdpr.adjust.com"; -static NSString * const subscriptionUrlCn = @"https://subscription.adjust.com"; +static NSString * const gdprUrlCn = @"https://gdpr.adjust.com"; // TODO: switch to .cn +static NSString * const subscriptionUrlCn = @"https://subscription.adjust.com"; // TODO: switch to .cn +static NSString * const purchaseVerificationUrlCn = @"https://ssrv.adjust.cn"; static NSString * const baseUrlEU = @"https://app.eu.adjust.com"; static NSString * const gdprUrlEU = @"https://gdpr.eu.adjust.com"; static NSString * const subscriptionUrlEU = @"https://subscription.eu.adjust.com"; +static NSString * const purchaseVerificationUrlEU = @"https://ssrv.eu.adjust.com"; static NSString * const baseUrlTR = @"https://app.tr.adjust.com"; static NSString * const gdprUrlTR = @"https://gdpr.tr.adjust.com"; static NSString * const subscriptionUrlTR = @"https://subscription.tr.adjust.com"; +static NSString * const purchaseVerificationUrlTR = @"https://ssrv.tr.adjust.com"; static NSString * const baseUrlUS = @"https://app.us.adjust.com"; static NSString * const gdprUrlUS = @"https://gdpr.us.adjust.com"; static NSString * const subscriptionUrlUS = @"https://subscription.us.adjust.com"; +static NSString * const purchaseVerificationUrlUS = @"https://ssrv.us.adjust.com"; @interface ADJUrlStrategy () @property (nonatomic, copy) NSArray *baseUrlChoicesArray; @property (nonatomic, copy) NSArray *gdprUrlChoicesArray; @property (nonatomic, copy) NSArray *subscriptionUrlChoicesArray; +@property (nonatomic, copy) NSArray *purchaseVerificationUrlChoicesArray; @property (nonatomic, copy) NSString *overridenBaseUrl; @property (nonatomic, copy) NSString *overridenGdprUrl; @property (nonatomic, copy) NSString *overridenSubscriptionUrl; +@property (nonatomic, copy) NSString *overridenPurchaseVerificationUrl; @property (nonatomic, assign) BOOL wasLastAttemptSuccess; @@ -58,8 +67,7 @@ @interface ADJUrlStrategy () @implementation ADJUrlStrategy - (instancetype)initWithUrlStrategyInfo:(NSString *)urlStrategyInfo - extraPath:(NSString *)extraPath -{ + extraPath:(NSString *)extraPath { self = [super init]; _extraPath = extraPath ?: @""; @@ -68,13 +76,15 @@ - (instancetype)initWithUrlStrategyInfo:(NSString *)urlStrategyInfo _gdprUrlChoicesArray = [ADJUrlStrategy gdprUrlChoicesWithUrlStrategyInfo:urlStrategyInfo]; _subscriptionUrlChoicesArray = [ADJUrlStrategy subscriptionUrlChoicesWithUrlStrategyInfo:urlStrategyInfo]; + _purchaseVerificationUrlChoicesArray = [ADJUrlStrategy + purchaseVerificationUrlChoicesWithUrlStrategyInfo:urlStrategyInfo]; _overridenBaseUrl = [ADJAdjustFactory baseUrl]; _overridenGdprUrl = [ADJAdjustFactory gdprUrl]; _overridenSubscriptionUrl = [ADJAdjustFactory subscriptionUrl]; + _overridenPurchaseVerificationUrl = [ADJAdjustFactory purchaseVerificationUrl]; _wasLastAttemptSuccess = NO; - _choiceIndex = 0; _startingChoiceIndex = 0; @@ -135,6 +145,24 @@ - (instancetype)initWithUrlStrategyInfo:(NSString *)urlStrategyInfo } } ++ (NSArray *)purchaseVerificationUrlChoicesWithUrlStrategyInfo:(NSString *)urlStrategyInfo { + if ([urlStrategyInfo isEqualToString:ADJUrlStrategyIndia]) { + return @[purchaseVerificationUrlIndia, purchaseVerificationUrl]; + } else if ([urlStrategyInfo isEqualToString:ADJUrlStrategyChina]) { + return @[purchaseVerificationUrlChina, purchaseVerificationUrl]; + } else if ([urlStrategyInfo isEqualToString:ADJUrlStrategyCn]) { + return @[purchaseVerificationUrlCn, purchaseVerificationUrl]; + } else if ([urlStrategyInfo isEqualToString:ADJDataResidencyEU]) { + return @[purchaseVerificationUrlEU]; + } else if ([urlStrategyInfo isEqualToString:ADJDataResidencyTR]) { + return @[purchaseVerificationUrlTR]; + } else if ([urlStrategyInfo isEqualToString:ADJDataResidencyUS]) { + return @[purchaseVerificationUrlUS]; + } else { + return @[purchaseVerificationUrl, purchaseVerificationUrlIndia, purchaseVerificationUrlChina]; + } +} + - (NSString *)getUrlHostStringByPackageKind:(ADJActivityKind)activityKind { if (activityKind == ADJActivityKindGdpr) { if (self.overridenGdprUrl != nil) { @@ -148,6 +176,12 @@ - (NSString *)getUrlHostStringByPackageKind:(ADJActivityKind)activityKind { } else { return [self.subscriptionUrlChoicesArray objectAtIndex:self.choiceIndex]; } + } else if (activityKind == ADJActivityKindPurchaseVerification) { + if (self.overridenPurchaseVerificationUrl != nil) { + return self.overridenPurchaseVerificationUrl; + } else { + return [self.purchaseVerificationUrlChoicesArray objectAtIndex:self.choiceIndex]; + } } else { if (self.overridenBaseUrl != nil) { return self.overridenBaseUrl; @@ -170,14 +204,16 @@ - (BOOL)shouldRetryAfterFailure:(ADJActivityKind)activityKind { choiceListSize = [_gdprUrlChoicesArray count]; } else if (activityKind == ADJActivityKindSubscription) { choiceListSize = [_subscriptionUrlChoicesArray count]; + } else if (activityKind == ADJActivityKindPurchaseVerification) { + choiceListSize = [_purchaseVerificationUrlChoicesArray count]; } else { choiceListSize = [_baseUrlChoicesArray count]; } NSUInteger nextChoiceIndex = (self.choiceIndex + 1) % choiceListSize; self.choiceIndex = nextChoiceIndex; - BOOL nextChoiceHasNotReturnedToStartingChoice = self.choiceIndex != self.startingChoiceIndex; + return nextChoiceHasNotReturnedToStartingChoice; } diff --git a/Adjust/Adjust.h b/Adjust/Adjust.h index 4bde4847a..26b8f84f5 100644 --- a/Adjust/Adjust.h +++ b/Adjust/Adjust.h @@ -14,12 +14,15 @@ #import "ADJThirdPartySharing.h" #import "ADJAdRevenue.h" #import "ADJLinkResolution.h" +#import "ADJPurchase.h" +#import "ADJPurchaseVerificationResult.h" @interface AdjustTestOptions : NSObject @property (nonatomic, copy, nullable) NSString *baseUrl; @property (nonatomic, copy, nullable) NSString *gdprUrl; @property (nonatomic, copy, nullable) NSString *subscriptionUrl; +@property (nonatomic, copy, nullable) NSString *purchaseVerificationUrl; @property (nonatomic, copy, nullable) NSString *extraPath; @property (nonatomic, copy, nullable) NSNumber *timerIntervalInMilliseconds; @property (nonatomic, copy, nullable) NSNumber *timerStartInMilliseconds; @@ -364,6 +367,15 @@ extern NSString * __nonnull const ADJDataResidencyUS; */ + (nullable NSURL *)lastDeeplink; +/** + * @brief Verify in-app-purchase. + * + * @param purchase Purchase object. + * @param completionHandler Callback where verification result will be repoted. + */ ++ (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler; + /** * @brief Method used for internal testing only. Don't use it in production. */ @@ -454,4 +466,7 @@ extern NSString * __nonnull const ADJDataResidencyUS; - (nullable NSURL *)lastDeeplink; +- (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler; + @end diff --git a/Adjust/Adjust.m b/Adjust/Adjust.m index 7cac6fb98..92b5eda77 100644 --- a/Adjust/Adjust.m +++ b/Adjust/Adjust.m @@ -319,6 +319,13 @@ + (NSURL *)lastDeeplink { } } ++ (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { + @synchronized (self) { + [[Adjust getInstance] verifyPurchase:purchase completionHandler:completionHandler]; + } +} + + (void)setTestOptions:(AdjustTestOptions *)testOptions { @synchronized (self) { if (testOptions.teardown) { @@ -648,6 +655,14 @@ - (NSURL *)lastDeeplink { return [ADJUserDefaults getCachedDeeplinkUrl]; } +- (void)verifyPurchase:(nonnull ADJPurchase *)purchase + completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { + if (![self checkActivityHandler]) { + return; + } + [self.activityHandler verifyPurchase:purchase completionHandler:completionHandler]; +} + - (void)teardown { if (self.activityHandler == nil) { [self.logger error:@"Adjust already down or not initialized"]; @@ -670,6 +685,9 @@ - (void)setTestOptions:(AdjustTestOptions *)testOptions { if (testOptions.subscriptionUrl != nil) { [ADJAdjustFactory setSubscriptionUrl:testOptions.subscriptionUrl]; } + if (testOptions.purchaseVerificationUrl != nil) { + [ADJAdjustFactory setPurchaseVerificationUrl:testOptions.purchaseVerificationUrl]; + } if (testOptions.timerIntervalInMilliseconds != nil) { NSTimeInterval timerIntervalInSeconds = [testOptions.timerIntervalInMilliseconds intValue] / 1000.0; [ADJAdjustFactory setTimerInterval:timerIntervalInSeconds]; diff --git a/examples/AdjustExample-ObjC/AdjustExample-ObjC.xcodeproj/project.pbxproj b/examples/AdjustExample-ObjC/AdjustExample-ObjC.xcodeproj/project.pbxproj index cc93d27e1..efa2deed0 100644 --- a/examples/AdjustExample-ObjC/AdjustExample-ObjC.xcodeproj/project.pbxproj +++ b/examples/AdjustExample-ObjC/AdjustExample-ObjC.xcodeproj/project.pbxproj @@ -45,6 +45,9 @@ 9D449E2E1E6ED88F00E7E80B /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D449E091E6ED88F00E7E80B /* ADJBackoffStrategy.m */; }; 9D449E2F1E6ED88F00E7E80B /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D449E0B1E6ED88F00E7E80B /* ADJSdkClickHandler.m */; }; 9D449E311E6ED88F00E7E80B /* ADJSessionParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D449E0F1E6ED88F00E7E80B /* ADJSessionParameters.m */; }; + 9D775B6D2A1FA6BC009D0BE8 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B692A1FA6BC009D0BE8 /* ADJPurchase.m */; }; + 9D775B6E2A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B6A2A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.m */; }; + 9D775B6F2A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D775B6B2A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.m */; }; 9DAA5C6725AFA4B600C718DD /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DAA5C6625AFA4B600C718DD /* AdServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 9DC95F261C104CEF00138E4B /* ViewControllerObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DC95F251C104CEF00138E4B /* ViewControllerObjC.m */; }; 9DC95F2A1C10515300138E4B /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DC95F291C10515300138E4B /* Constants.m */; }; @@ -132,6 +135,12 @@ 9D449E0B1E6ED88F00E7E80B /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; 9D449E0E1E6ED88F00E7E80B /* ADJSessionParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSessionParameters.h; sourceTree = ""; }; 9D449E0F1E6ED88F00E7E80B /* ADJSessionParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSessionParameters.m; sourceTree = ""; }; + 9D775B672A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 9D775B682A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 9D775B692A1FA6BC009D0BE8 /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 9D775B6A2A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; + 9D775B6B2A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 9D775B6C2A1FA6BC009D0BE8 /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; 9DAA5C6625AFA4B600C718DD /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks/AdServices.framework; sourceTree = DEVELOPER_DIR; }; 9DC95F241C104CEF00138E4B /* ViewControllerObjC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewControllerObjC.h; sourceTree = ""; }; 9DC95F251C104CEF00138E4B /* ViewControllerObjC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewControllerObjC.m; sourceTree = ""; }; @@ -294,6 +303,12 @@ 6FAB78A52636DD4000773869 /* ADJLinkResolution.m */, 9DD7199E290A9FF700762C02 /* ADJSKAdNetwork.h */, 9DD7199D290A9FF700762C02 /* ADJSKAdNetwork.m */, + 9D775B6C2A1FA6BC009D0BE8 /* ADJPurchase.h */, + 9D775B692A1FA6BC009D0BE8 /* ADJPurchase.m */, + 9D775B672A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.h */, + 9D775B6A2A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.m */, + 9D775B682A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.h */, + 9D775B6B2A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.m */, ); name = Adjust; path = ../../../Adjust; @@ -421,10 +436,13 @@ 9D449E261E6ED88F00E7E80B /* ADJTimerOnce.m in Sources */, 963909411BCBFCF300A2E8A4 /* main.m in Sources */, 9DF92D992630EDCD000FC3FC /* ADJPackageParams.m in Sources */, + 9D775B6D2A1FA6BC009D0BE8 /* ADJPurchase.m in Sources */, 6FBEE90B24E420FA00FEF3F1 /* ADJUrlStrategy.m in Sources */, 9D449E2B1E6ED88F00E7E80B /* ADJEventSuccess.m in Sources */, 9D449E211E6ED88F00E7E80B /* ADJEvent.m in Sources */, 9D449E2E1E6ED88F00E7E80B /* ADJBackoffStrategy.m in Sources */, + 9D775B6E2A1FA6BC009D0BE8 /* ADJPurchaseVerificationHandler.m in Sources */, + 9D775B6F2A1FA6BC009D0BE8 /* ADJPurchaseVerificationResult.m in Sources */, 9D449E2F1E6ED88F00E7E80B /* ADJSdkClickHandler.m in Sources */, 9D449E1A1E6ED88F00E7E80B /* ADJActivityState.m in Sources */, 9DD7199F290A9FF700762C02 /* ADJSKAdNetwork.m in Sources */, diff --git a/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m b/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m index be6761996..45c7fab2f 100644 --- a/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m +++ b/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m @@ -35,9 +35,18 @@ - (void)didReceiveMemoryWarning { } - (IBAction)clickTrackSimpleEvent:(UIButton *)sender { - ADJEvent *event = [ADJEvent eventWithEventToken:kEventToken1]; - - [Adjust trackEvent:event]; + // ADJEvent *event = [ADJEvent eventWithEventToken:kEventToken1]; + // [Adjust trackEvent:event]; + + ADJPurchase *purchase = [[ADJPurchase alloc] initWithTransactionId:@"transaction-id-very-nice" + productId:@"product-id-very-nice" + andReceipt:[@"receipt-very-nice" dataUsingEncoding:NSUTF8StringEncoding]]; + [Adjust verifyPurchase:purchase completionHandler:^(ADJPurchaseVerificationResult * _Nonnull verificationResult) { + NSLog(@"Purchase verification response arrived!"); + NSLog(@"Status: %@", verificationResult.verificationStatus); + NSLog(@"Code: %d", verificationResult.code); + NSLog(@"Message: %@", verificationResult.message); + }]; } - (IBAction)clickTrackRevenueEvent:(UIButton *)sender { From 6f92f0dfb8260c2547aab012c60723aac10b1e3d Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 31 May 2023 09:06:00 +0200 Subject: [PATCH 13/31] feat: add logging for cases when verification is aborted --- Adjust/ADJActivityHandler.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index fb3b76f7b..85733ec80 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1420,12 +1420,15 @@ - (void)verifyPurchaseI:(ADJActivityHandler *)selfI purchase:(nonnull ADJPurchase *)purchase completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { if (![selfI isEnabledI:selfI]) { + [selfI.logger warn:@"Purchase verification aborted because SDK is disabled"]; return; } if ([ADJUtil isNull:purchase]) { + [selfI.logger warn:@"Purchase verification aborted because purchase instance is null"]; return; } if ([ADJUtil isNull:completionHandler]) { + [selfI.logger warn:@"Purchase verification aborted because completion handler is null"]; return; } From dfb0de13389f989353eeea4c735a39b31b35371b Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 31 May 2023 09:11:15 +0200 Subject: [PATCH 14/31] chore: rename purchase veirfication callback in activity package --- Adjust/ADJActivityHandler.m | 2 +- Adjust/ADJActivityPackage.h | 2 +- Adjust/ADJPurchaseVerificationHandler.m | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 85733ec80..c655c175d 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1446,7 +1446,7 @@ - (void)verifyPurchaseI:(ADJActivityHandler *)selfI createdAt:now]; ADJActivityPackage *purchaseVerificationPackage = [purchaseVerificationBuilder buildPurchaseVerificationPackage:purchase]; - purchaseVerificationPackage.responseBlock = completionHandler; + purchaseVerificationPackage.purchaseVerificationCallback = completionHandler; [selfI.purchaseVerificationHandler sendPurchaseVerificationPackage:purchaseVerificationPackage]; } diff --git a/Adjust/ADJActivityPackage.h b/Adjust/ADJActivityPackage.h index 25d698f3c..71198a51c 100644 --- a/Adjust/ADJActivityPackage.h +++ b/Adjust/ADJActivityPackage.h @@ -22,7 +22,7 @@ @property (nonatomic, strong) NSDictionary *callbackParameters; -@property (nonatomic, copy) void (^responseBlock)(id); +@property (nonatomic, copy) void (^purchaseVerificationCallback)(id); // Logs diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m index a4f6a41c4..c6eccb025 100644 --- a/Adjust/ADJPurchaseVerificationHandler.m +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -172,13 +172,13 @@ - (void)responseCallback:(ADJResponseData *)responseData { verificationResult.verificationStatus = responseData.jsonResponse[@"verification_status"]; verificationResult.code = [(NSNumber *)responseData.jsonResponse[@"code"] intValue]; verificationResult.message = responseData.jsonResponse[@"message"]; - responseData.purchaseVerificationPackage.responseBlock(verificationResult); + responseData.purchaseVerificationPackage.purchaseVerificationCallback(verificationResult); } else { [self.logger error: @"Could not get purchase_verification JSON response with message: %@", responseData.message]; // TODO: handle invalid JSON error case // TODO: handle all the remaining error cases - responseData.purchaseVerificationPackage.responseBlock(nil); + responseData.purchaseVerificationPackage.purchaseVerificationCallback(nil); } // Check if any package response contains information that user has opted out. // If yes, disable SDK and flush any potentially stored packages that happened afterwards. From 538da294aa6fb35b4e841b971958b01084bfa5e2 Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 31 May 2023 10:09:20 +0200 Subject: [PATCH 15/31] feat: add handling of various not_verified verification cases --- Adjust/ADJActivityHandler.m | 13 +++++++++---- Adjust/ADJPurchaseVerificationHandler.m | 6 ++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index c655c175d..cb03f9713 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1423,14 +1423,19 @@ - (void)verifyPurchaseI:(ADJActivityHandler *)selfI [selfI.logger warn:@"Purchase verification aborted because SDK is disabled"]; return; } - if ([ADJUtil isNull:purchase]) { - [selfI.logger warn:@"Purchase verification aborted because purchase instance is null"]; - return; - } if ([ADJUtil isNull:completionHandler]) { [selfI.logger warn:@"Purchase verification aborted because completion handler is null"]; return; } + if ([ADJUtil isNull:purchase]) { + [selfI.logger warn:@"Purchase verification aborted because purchase instance is null"]; + ADJPurchaseVerificationResult *verificationResult = [[ADJPurchaseVerificationResult alloc] init]; + verificationResult.verificationStatus = @"not_verified"; + verificationResult.code = 100; + verificationResult.message = @"Purchase verification aborted because purchase instance is null"; + completionHandler(verificationResult); + return; + } double now = [NSDate.date timeIntervalSince1970]; [ADJUtil launchSynchronisedWithObject:[ADJActivityState class] diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m index c6eccb025..5e61c4ba4 100644 --- a/Adjust/ADJPurchaseVerificationHandler.m +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -176,8 +176,10 @@ - (void)responseCallback:(ADJResponseData *)responseData { } else { [self.logger error: @"Could not get purchase_verification JSON response with message: %@", responseData.message]; - // TODO: handle invalid JSON error case - // TODO: handle all the remaining error cases + ADJPurchaseVerificationResult *verificationResult = [[ADJPurchaseVerificationResult alloc] init]; + verificationResult.verificationStatus = @"not_verified"; + verificationResult.code = 101; + verificationResult.message = responseData.message; responseData.purchaseVerificationPackage.purchaseVerificationCallback(nil); } // Check if any package response contains information that user has opted out. From d2ac31950622003d7b80ee0e890227d224ed5ab1 Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 31 May 2023 11:25:05 +0200 Subject: [PATCH 16/31] fix: pass verification result to callback instead of nil --- Adjust/ADJPurchaseVerificationHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m index 5e61c4ba4..10d5af308 100644 --- a/Adjust/ADJPurchaseVerificationHandler.m +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -180,7 +180,7 @@ - (void)responseCallback:(ADJResponseData *)responseData { verificationResult.verificationStatus = @"not_verified"; verificationResult.code = 101; verificationResult.message = responseData.message; - responseData.purchaseVerificationPackage.purchaseVerificationCallback(nil); + responseData.purchaseVerificationPackage.purchaseVerificationCallback(verificationResult); } // Check if any package response contains information that user has opted out. // If yes, disable SDK and flush any potentially stored packages that happened afterwards. From 0ad311a2c06af3a604663557120ceca553b3a58d Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 31 May 2023 12:16:30 +0200 Subject: [PATCH 17/31] chore: align pv status codes with android ones --- Adjust/ADJActivityHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index cb03f9713..72c6c3788 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1431,7 +1431,7 @@ - (void)verifyPurchaseI:(ADJActivityHandler *)selfI [selfI.logger warn:@"Purchase verification aborted because purchase instance is null"]; ADJPurchaseVerificationResult *verificationResult = [[ADJPurchaseVerificationResult alloc] init]; verificationResult.verificationStatus = @"not_verified"; - verificationResult.code = 100; + verificationResult.code = 101; verificationResult.message = @"Purchase verification aborted because purchase instance is null"; completionHandler(verificationResult); return; From 04341fb4c6d0228437579b1fba5530a1efc1e938 Mon Sep 17 00:00:00 2001 From: uerceg Date: Mon, 5 Jun 2023 12:10:18 +0200 Subject: [PATCH 18/31] feat: hook purchase verification endpoint logic to test app --- Adjust/ADJActivityHandler.m | 5 +++++ .../AdjustTestApp/ATAAdjustCommandExecutor.m | 1 + .../AdjustTestApp/AdjustTestApp/ViewController.h | 10 ++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 72c6c3788..b371b11a5 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -105,6 +105,7 @@ @interface ADJActivityHandler() @property (nonatomic, copy) NSString* basePath; @property (nonatomic, copy) NSString* gdprPath; @property (nonatomic, copy) NSString* subscriptionPath; +@property (nonatomic, copy) NSString* purchaseVerificationPath; - (void)prepareDeeplinkI:(ADJActivityHandler *_Nullable)selfI responseData:(ADJAttributionResponseData *_Nullable)attributionResponseData NS_EXTENSION_UNAVAILABLE_IOS(""); @@ -687,6 +688,10 @@ - (NSString *)getSubscriptionPath { return _subscriptionPath; } +- (NSString *)getPurchaseVerificationPath { + return _purchaseVerificationPath; +} + - (void)teardown { [ADJAdjustFactory.logger verbose:@"ADJActivityHandler teardown"]; diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m index 8beacac05..843e43bb8 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m @@ -109,6 +109,7 @@ - (void)testOptions:(NSDictionary *)parameters { testOptions.baseUrl = baseUrl; testOptions.gdprUrl = gdprUrl; testOptions.subscriptionUrl = subscriptionUrl; + testOptions.purchaseVerificationUrl = purchaseVerificationUrl; if ([parameters objectForKey:@"basePath"]) { self.extraPath = [parameters objectForKey:@"basePath"][0]; diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h index 98bf50ab8..824b888ac 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h @@ -12,12 +12,14 @@ //static NSString * baseUrl = @"http://127.0.0.1:8080"; //static NSString * gdprUrl = @"http://127.0.0.1:8080"; //static NSString * subscriptionUrl = @"http://127.0.0.1:8080"; +//static NSString * purchaseVerificationUrl = @"http://127.0.0.1:8080"; //static NSString * controlUrl = @"ws://127.0.0.1:1987"; // device - static NSString * baseUrl = @"http://192.168.86.44:8080"; - static NSString * gdprUrl = @"http://192.168.86.44:8080"; - static NSString * subscriptionUrl = @"http://192.168.86.44:8080"; - static NSString * controlUrl = @"ws://192.168.86.44:1987"; +static NSString * baseUrl = @"http://192.168.86.44:8080"; +static NSString * gdprUrl = @"http://192.168.86.44:8080"; +static NSString * subscriptionUrl = @"http://192.168.86.44:8080"; +static NSString * purchaseVerificationUrl = @"http://192.168.86.44:8080"; +static NSString * controlUrl = @"ws://192.168.86.44:1987"; @interface ViewController : UIViewController From ca9589c0ae339ed0b6767bbcb290a12411faeff7 Mon Sep 17 00:00:00 2001 From: uerceg Date: Mon, 5 Jun 2023 15:22:32 +0200 Subject: [PATCH 19/31] feat: add ability to e2e test purchase verification feature --- .../AdjustTestApp.xcodeproj/project.pbxproj | 18 ++++++++++ .../AdjustTestApp/ATAAdjustCommandExecutor.m | 34 ++++++++++++++++--- .../AdjustTestApp/ViewController.h | 20 +++++------ .../AdjustTestApp/ViewController.m | 2 +- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp.xcodeproj/project.pbxproj b/AdjustTests/AdjustTestApp/AdjustTestApp.xcodeproj/project.pbxproj index 2bf8f766c..0dcf5d44c 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp.xcodeproj/project.pbxproj +++ b/AdjustTests/AdjustTestApp/AdjustTestApp.xcodeproj/project.pbxproj @@ -72,6 +72,9 @@ 9D1D8EA2219246EF0088E3CF /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D1D8EA1219246EF0088E3CF /* CoreTelephony.framework */; }; 9D2F24082447DDCB00B7CA90 /* ADJSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2F24072447DDCA00B7CA90 /* ADJSubscription.m */; }; 9D3A2AC226263AFC00BD6E44 /* ADJAdRevenue.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D3A2AC026263AFB00BD6E44 /* ADJAdRevenue.m */; }; + 9D3A34B82A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D3A34B22A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.m */; }; + 9D3A34B92A2DF5FB0029CE4F /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D3A34B52A2DF5FB0029CE4F /* ADJPurchase.m */; }; + 9D3A34BA2A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D3A34B72A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.m */; }; 9D75AFDB210217FF0079A36C /* ATAAdjustDelegateDeferredDeeplink.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D75AFDA210217FF0079A36C /* ATAAdjustDelegateDeferredDeeplink.m */; }; 9DAA5C6A25B0F81200C718DD /* AdServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DAA5C6925B0F81100C718DD /* AdServices.framework */; }; 9DF38229260E9BA90033F5A1 /* NSNumber+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF38227260E9BA90033F5A1 /* NSNumber+ADJAdditions.m */; }; @@ -209,6 +212,12 @@ 9D2F24072447DDCA00B7CA90 /* ADJSubscription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSubscription.m; sourceTree = ""; }; 9D3A2AC026263AFB00BD6E44 /* ADJAdRevenue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAdRevenue.m; sourceTree = ""; }; 9D3A2AC126263AFB00BD6E44 /* ADJAdRevenue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJAdRevenue.h; sourceTree = ""; }; + 9D3A34B22A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 9D3A34B32A2DF5FB0029CE4F /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 9D3A34B42A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 9D3A34B52A2DF5FB0029CE4F /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 9D3A34B62A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 9D3A34B72A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; 9D75AFD9210217FF0079A36C /* ATAAdjustDelegateDeferredDeeplink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ATAAdjustDelegateDeferredDeeplink.h; sourceTree = ""; }; 9D75AFDA210217FF0079A36C /* ATAAdjustDelegateDeferredDeeplink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ATAAdjustDelegateDeferredDeeplink.m; sourceTree = ""; }; 9DAA5C6925B0F81100C718DD /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = System/Library/Frameworks/AdServices.framework; sourceTree = SDKROOT; }; @@ -274,6 +283,12 @@ 6F3A5E292018CE14000AACD0 /* Adjust */ = { isa = PBXGroup; children = ( + 9D3A34B32A2DF5FB0029CE4F /* ADJPurchase.h */, + 9D3A34B52A2DF5FB0029CE4F /* ADJPurchase.m */, + 9D3A34B62A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.h */, + 9D3A34B72A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.m */, + 9D3A34B42A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.h */, + 9D3A34B22A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.m */, 9D0B5C152929787B007009C1 /* ADJSKAdNetwork.h */, 9D0B5C142929787B007009C1 /* ADJSKAdNetwork.m */, 9D3A2AC126263AFB00BD6E44 /* ADJAdRevenue.h */, @@ -514,6 +529,7 @@ 6F3A5EA32018CE3A000AACD0 /* ATLBlockingQueue.m in Sources */, 9D0B5C162929787B007009C1 /* ADJSKAdNetwork.m in Sources */, 6F3A5E712018CE14000AACD0 /* ADJRequestHandler.m in Sources */, + 9D3A34BA2A2DF5FB0029CE4F /* ADJPurchaseVerificationHandler.m in Sources */, 6F3A5E732018CE14000AACD0 /* ADJSdkClickHandler.m in Sources */, 6F3A5E892018CE14000AACD0 /* ADJAdjustFactory.m in Sources */, 6F3A5E9F2018CE3A000AACD0 /* ATLUtil.m in Sources */, @@ -555,9 +571,11 @@ 6F3A5E782018CE14000AACD0 /* ADJActivityState.m in Sources */, 9DF38229260E9BA90033F5A1 /* NSNumber+ADJAdditions.m in Sources */, 6FBEE92024E421B200FEF3F1 /* ADJUrlStrategy.m in Sources */, + 9D3A34B92A2DF5FB0029CE4F /* ADJPurchase.m in Sources */, 6F3A5E8E2018CE14000AACD0 /* ADJActivityHandler.m in Sources */, 6F08422C2007769F00568A31 /* AppDelegate.m in Sources */, 524BACAA221C4EE800624F6C /* PSWebSocketUTF8Decoder.m in Sources */, + 9D3A34B82A2DF5FB0029CE4F /* ADJPurchaseVerificationResult.m in Sources */, 6F3A5E762018CE14000AACD0 /* ADJEventFailure.m in Sources */, 6F3A5E7F2018CE14000AACD0 /* ADJConfig.m in Sources */, 524BACA9221C4EE800624F6C /* PSWebSocketNetworkThread.m in Sources */, diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m index 843e43bb8..28f0295c4 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m @@ -101,6 +101,8 @@ - (void)executeCommand:(NSString *)className [self trackAdRevenueV2:parameters]; } else if ([methodName isEqualToString:@"getLastDeeplink"]) { [self getLastDeeplink:parameters]; + } else if ([methodName isEqualToString:@"verifyPurchase"]) { + [self verifyPurchase:parameters]; } } @@ -740,13 +742,37 @@ - (void)trackAdRevenueV2:(NSDictionary *)parameters { } - (void)getLastDeeplink:(NSDictionary *)parameters { - NSURL * lastDeeplink = [Adjust lastDeeplink]; + NSURL *lastDeeplink = [Adjust lastDeeplink]; + NSString *lastDeeplinkString = lastDeeplink == nil ? @"" : [lastDeeplink absoluteString]; + [self.testLibrary addInfoToSend:@"last_deeplink" value:lastDeeplinkString]; + [self.testLibrary sendInfoToServer:self.extraPath]; +} - NSString * lastDeeplinkString = lastDeeplink == nil ? @"" : [lastDeeplink absoluteString]; +- (void)verifyPurchase:(NSDictionary *)parameters { + NSData *receipt; + NSString *transactionId; + NSString *productId; - [self.testLibrary addInfoToSend:@"last_deeplink" value:lastDeeplinkString]; + if ([parameters objectForKey:@"receipt"]) { + NSString *receiptString = [parameters objectForKey:@"receipt"][0]; + receipt = [receiptString dataUsingEncoding:NSUTF8StringEncoding]; + } + if ([parameters objectForKey:@"transactionId"]) { + transactionId = [parameters objectForKey:@"transactionId"][0]; + } + if ([parameters objectForKey:@"productId"]) { + productId = [parameters objectForKey:@"productId"][0]; + } - [self.testLibrary sendInfoToServer:self.extraPath]; + ADJPurchase *purchase = [[ADJPurchase alloc] initWithTransactionId:transactionId + productId:productId + andReceipt:receipt]; + [Adjust verifyPurchase:purchase completionHandler:^(ADJPurchaseVerificationResult * _Nonnull verificationResult) { + [self.testLibrary addInfoToSend:@"verification_status" value:verificationResult.verificationStatus]; + [self.testLibrary addInfoToSend:@"code" value:[NSString stringWithFormat:@"%d", verificationResult.code]]; + [self.testLibrary addInfoToSend:@"message" value:verificationResult.message]; + [self.testLibrary sendInfoToServer:self.extraPath]; + }]; } @end diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h index 824b888ac..a6b48ee45 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h @@ -9,17 +9,17 @@ #import // simulator -//static NSString * baseUrl = @"http://127.0.0.1:8080"; -//static NSString * gdprUrl = @"http://127.0.0.1:8080"; -//static NSString * subscriptionUrl = @"http://127.0.0.1:8080"; -//static NSString * purchaseVerificationUrl = @"http://127.0.0.1:8080"; -//static NSString * controlUrl = @"ws://127.0.0.1:1987"; +static NSString * baseUrl = @"http://127.0.0.1:8080"; +static NSString * gdprUrl = @"http://127.0.0.1:8080"; +static NSString * subscriptionUrl = @"http://127.0.0.1:8080"; +static NSString * purchaseVerificationUrl = @"http://127.0.0.1:8080"; +static NSString * controlUrl = @"ws://127.0.0.1:1987"; // device -static NSString * baseUrl = @"http://192.168.86.44:8080"; -static NSString * gdprUrl = @"http://192.168.86.44:8080"; -static NSString * subscriptionUrl = @"http://192.168.86.44:8080"; -static NSString * purchaseVerificationUrl = @"http://192.168.86.44:8080"; -static NSString * controlUrl = @"ws://192.168.86.44:1987"; +//static NSString * baseUrl = @"http://192.168.86.44:8080"; +//static NSString * gdprUrl = @"http://192.168.86.44:8080"; +//static NSString * subscriptionUrl = @"http://192.168.86.44:8080"; +//static NSString * purchaseVerificationUrl = @"http://192.168.86.44:8080"; +//static NSString * controlUrl = @"ws://192.168.86.44:1987"; @interface ViewController : UIViewController diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m index 91615b7c2..767e67e13 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m @@ -31,7 +31,7 @@ - (void)viewDidLoad { [self.adjustCommandExecutor setTestLibrary:self.testLibrary]; // [self.testLibrary addTestDirectory:@"event-callbacks"]; - // [self.testLibrary addTest:@"Test_Parameters"]; + [self.testLibrary addTest:@"Test_PurchaseVerification_ios_after_install"]; // [self.testLibrary doNotExitAfterEnd]; [self startTestSession]; From a098af31c4e5e7383e2002641fcb076f2a754dfe Mon Sep 17 00:00:00 2001 From: uerceg Date: Mon, 5 Jun 2023 15:41:09 +0200 Subject: [PATCH 20/31] feat: add error handling if pv invoked prior to sdk initialization --- Adjust/ADJPurchaseVerificationHandler.m | 2 +- Adjust/Adjust.m | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m index 10d5af308..6e2c18866 100644 --- a/Adjust/ADJPurchaseVerificationHandler.m +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -178,7 +178,7 @@ - (void)responseCallback:(ADJResponseData *)responseData { @"Could not get purchase_verification JSON response with message: %@", responseData.message]; ADJPurchaseVerificationResult *verificationResult = [[ADJPurchaseVerificationResult alloc] init]; verificationResult.verificationStatus = @"not_verified"; - verificationResult.code = 101; + verificationResult.code = 102; verificationResult.message = responseData.message; responseData.purchaseVerificationPackage.purchaseVerificationCallback(verificationResult); } diff --git a/Adjust/Adjust.m b/Adjust/Adjust.m index 92b5eda77..f339db903 100644 --- a/Adjust/Adjust.m +++ b/Adjust/Adjust.m @@ -658,6 +658,13 @@ - (NSURL *)lastDeeplink { - (void)verifyPurchase:(nonnull ADJPurchase *)purchase completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { if (![self checkActivityHandler]) { + if (completionHandler != nil) { + ADJPurchaseVerificationResult *result = [[ADJPurchaseVerificationResult alloc] init]; + result.verificationStatus = @"not_verified"; + result.code = 100; + result.message = @"SDK needs to be initialized before making purchase verification request"; + completionHandler(result); + } return; } [self.activityHandler verifyPurchase:purchase completionHandler:completionHandler]; From 27fa6810096784795c4e76a4bc30bcb34930a209 Mon Sep 17 00:00:00 2001 From: uerceg Date: Tue, 6 Jun 2023 16:04:12 +0200 Subject: [PATCH 21/31] feat: add purchase verification parameters to event --- Adjust/ADJEvent.h | 19 +++++++++++ Adjust/ADJEvent.m | 13 ++++++++ Adjust/ADJPackageBuilder.m | 33 +++++++++++-------- .../AdjustTestApp/ATAAdjustCommandExecutor.m | 22 +++++++++++++ .../AdjustTestApp/ViewController.m | 2 +- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/Adjust/ADJEvent.h b/Adjust/ADJEvent.h index a2363c228..a95e7d489 100644 --- a/Adjust/ADJEvent.h +++ b/Adjust/ADJEvent.h @@ -58,6 +58,11 @@ */ @property (nonatomic, assign, readonly) BOOL emptyReceipt; +/** + * @brief IAP product ID. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *productId; + /** * @brief Create Event object with event token. * @@ -119,6 +124,20 @@ */ - (void)setCallbackId:(nonnull NSString *)callbackId; +/** + * @brief Set the product ID of a In-App Purchases to perform IAP verification. + * + * @param productId The product ID of the purchased item. + */ +- (void)setProductId:(NSString * _Nonnull)productId; + +/** + * @brief Set the receipt of a In-App Purchases to perform IAP verification. + * + * @param receipt The receipt obtained after successful IAP. + */ +- (void)setReceipt:(NSData * _Nonnull)receipt; + /** * @brief Check if created adjust event object is valid. * diff --git a/Adjust/ADJEvent.m b/Adjust/ADJEvent.m index ecb6b1cd8..e97de4da8 100644 --- a/Adjust/ADJEvent.m +++ b/Adjust/ADJEvent.m @@ -129,6 +129,18 @@ - (NSDictionary *)partnerParameters { } } +- (void)setProductId:(NSString *)productId { + @synchronized (self) { + _productId = [productId copy]; + } +} + +- (void)setReceipt:(NSData *)receipt { + @synchronized (self) { + _receipt = [receipt copy]; + } +} + - (BOOL)checkEventToken:(NSString *)eventToken { if ([ADJUtil isNull:eventToken]) { [self.logger error:@"Missing Event Token"]; @@ -202,6 +214,7 @@ - (id)copyWithZone:(NSZone *)zone { copy->_transactionId = [self.transactionId copyWithZone:zone]; copy->_receipt = [self.receipt copyWithZone:zone]; copy->_emptyReceipt = self.emptyReceipt; + copy->_productId = [self.productId copyWithZone:zone]; } return copy; diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index a9c31502d..46a3e219e 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -509,10 +509,25 @@ - (NSMutableDictionary *)getEventParameters:(BOOL)isInDelay forEventPackage:(ADJ [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.secretId forKey:@"secret_id"]; [ADJPackageBuilder parameters:parameters setDate:[ADJUserDefaults getSkadRegisterCallTimestamp] forKey:@"skadn_registered_at"]; [ADJPackageBuilder parameters:parameters setDate1970:(double)self.packageParams.startedAt forKey:@"started_at"]; - - if (event.transactionId) { - [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"deduplication_id"]; - } + + // TODO: long time ago deprecated way of purchase verification, to be removed + // if (event.emptyReceipt) { + // NSString *emptyReceipt = @"empty"; + // [ADJPackageBuilder parameters:parameters setString:emptyReceipt forKey:@"receipt"]; + // [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; + // } else if (event.receipt != nil) { + // NSString *receiptBase64 = [event.receipt adjEncodeBase64]; + // [ADJPackageBuilder parameters:parameters setString:receiptBase64 forKey:@"receipt"]; + // [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; + // } + // TODO: event.transactionId being historically used for deduplication + for IAP verification + // if (event.transactionId) { + // [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"deduplication_id"]; + // } + [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; + [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"deduplication_id"]; + [ADJPackageBuilder parameters:parameters setString:event.productId forKey:@"product_id"]; + [ADJPackageBuilder parameters:parameters setString:[event.receipt adjEncodeBase64] forKey:@"receipt"]; if ([self.trackingStatusManager canGetAttStatus]) { [ADJPackageBuilder parameters:parameters setInt:self.trackingStatusManager.attStatus @@ -555,16 +570,6 @@ - (NSMutableDictionary *)getEventParameters:(BOOL)isInDelay forEventPackage:(ADJ [ADJPackageBuilder parameters:parameters setDictionary:mergedPartnerParameters forKey:@"partner_params"]; } - if (event.emptyReceipt) { - NSString *emptyReceipt = @"empty"; - [ADJPackageBuilder parameters:parameters setString:emptyReceipt forKey:@"receipt"]; - [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; - } else if (event.receipt != nil) { - NSString *receiptBase64 = [event.receipt adjEncodeBase64]; - [ADJPackageBuilder parameters:parameters setString:receiptBase64 forKey:@"receipt"]; - [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; - } - [self injectFeatureFlagsWithParameters:parameters]; return parameters; diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m index 28f0295c4..bc4f9bc83 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m @@ -478,6 +478,28 @@ - (void)event:(NSDictionary *)parameters { } [adjustEvent setCallbackId:callbackId]; } + + if ([parameters objectForKey:@"productId"]) { + NSString *productId = [parameters objectForKey:@"productId"][0]; + if (productId == (id)[NSNull null]) { + productId = nil; + } + [adjustEvent setProductId:productId]; + } + + if ([parameters objectForKey:@"transactionId"]) { + NSString *transactionId = [parameters objectForKey:@"transactionId"][0]; + if (transactionId == (id)[NSNull null]) { + transactionId = nil; + } + [adjustEvent setTransactionId:transactionId]; + } + + if ([parameters objectForKey:@"receipt"]) { + NSString *receiptString = [parameters objectForKey:@"receipt"][0]; + NSData *receipt = [receiptString dataUsingEncoding:NSUTF8StringEncoding]; + [adjustEvent setReceipt:receipt]; + } } - (void)trackEvent:(NSDictionary *)parameters { diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m index 767e67e13..cd3837cb4 100644 --- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m +++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m @@ -31,7 +31,7 @@ - (void)viewDidLoad { [self.adjustCommandExecutor setTestLibrary:self.testLibrary]; // [self.testLibrary addTestDirectory:@"event-callbacks"]; - [self.testLibrary addTest:@"Test_PurchaseVerification_ios_after_install"]; + // [self.testLibrary addTest:@"Test_PurchaseVerification_ios_after_install"]; // [self.testLibrary doNotExitAfterEnd]; [self startTestSession]; From ba40d6137a38eed509fbf78bbec0de182beac707 Mon Sep 17 00:00:00 2001 From: uerceg Date: Thu, 8 Jun 2023 17:54:43 +0200 Subject: [PATCH 22/31] chore: remove hardcoded sdk_version from verification payload --- Adjust/ADJPackageBuilder.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index 46a3e219e..922863304 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -259,7 +259,6 @@ - (ADJActivityPackage *)buildPurchaseVerificationPackageWithExtraParams:(NSDicti } ADJActivityPackage *purchaseVerificationPackage = [self defaultActivityPackage]; - // TODO: update this purchaseVerificationPackage.path = @"/verify"; purchaseVerificationPackage.activityKind = ADJActivityKindPurchaseVerification; purchaseVerificationPackage.suffix = @""; @@ -510,7 +509,7 @@ - (NSMutableDictionary *)getEventParameters:(BOOL)isInDelay forEventPackage:(ADJ [ADJPackageBuilder parameters:parameters setDate:[ADJUserDefaults getSkadRegisterCallTimestamp] forKey:@"skadn_registered_at"]; [ADJPackageBuilder parameters:parameters setDate1970:(double)self.packageParams.startedAt forKey:@"started_at"]; - // TODO: long time ago deprecated way of purchase verification, to be removed + // FYI: long time ago deprecated way of purchase verification, to be removed // if (event.emptyReceipt) { // NSString *emptyReceipt = @"empty"; // [ADJPackageBuilder parameters:parameters setString:emptyReceipt forKey:@"receipt"]; @@ -520,7 +519,7 @@ - (NSMutableDictionary *)getEventParameters:(BOOL)isInDelay forEventPackage:(ADJ // [ADJPackageBuilder parameters:parameters setString:receiptBase64 forKey:@"receipt"]; // [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"transaction_id"]; // } - // TODO: event.transactionId being historically used for deduplication + for IAP verification + // FYI: event.transactionId being historically used for deduplication + for IAP verification // if (event.transactionId) { // [ADJPackageBuilder parameters:parameters setString:event.transactionId forKey:@"deduplication_id"]; // } @@ -1292,7 +1291,6 @@ - (NSMutableDictionary *)getPurchaseVerificationParameters { [ADJPackageBuilder parameters:parameters setString:self.packageParams.osName forKey:@"os_name"]; [ADJPackageBuilder parameters:parameters setString:self.packageParams.osVersion forKey:@"os_version"]; [ADJPackageBuilder parameters:parameters setDictionary:[self.sessionParameters.partnerParameters copy] forKey:@"partner_params"]; - [ADJPackageBuilder parameters:parameters setString:@"ios_purchase2.0.0" forKey:@"sdk_version"]; // TODO: to be removed after backend update [ADJPackageBuilder parameters:parameters setString:self.adjustConfig.secretId forKey:@"secret_id"]; [ADJPackageBuilder parameters:parameters setDate:[ADJUserDefaults getSkadRegisterCallTimestamp] forKey:@"skadn_registered_at"]; [ADJPackageBuilder parameters:parameters setDate1970:(double)self.packageParams.startedAt forKey:@"started_at"]; From 0bc650f40cd738fce9531b41b7faec835878f089 Mon Sep 17 00:00:00 2001 From: uerceg Date: Thu, 8 Jun 2023 17:57:26 +0200 Subject: [PATCH 23/31] feat: update version number to 4.34.0 --- Adjust.podspec | 4 ++-- Adjust/ADJUtil.m | 2 +- Adjust/Adjust.h | 2 +- AdjustBridge/AdjustBridgeRegister.m | 2 +- VERSION | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Adjust.podspec b/Adjust.podspec index 75a9b30f5..fb0d50448 100644 --- a/Adjust.podspec +++ b/Adjust.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = "Adjust" - s.version = "4.33.6" + s.version = "4.34.0" s.summary = "This is the iOS SDK of adjust. You can read more about it at http://adjust.com." s.homepage = "https://github.com/adjust/ios_sdk" s.license = { :type => 'MIT', :file => 'MIT-LICENSE' } s.author = { "Adjust" => "sdk@adjust.com" } - s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.33.6" } + s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.34.0" } s.ios.deployment_target = '9.0' s.tvos.deployment_target = '9.0' s.framework = 'SystemConfiguration' diff --git a/Adjust/ADJUtil.m b/Adjust/ADJUtil.m index 840169665..56debe900 100644 --- a/Adjust/ADJUtil.m +++ b/Adjust/ADJUtil.m @@ -35,7 +35,7 @@ static NSRegularExpression *shortUniversalLinkRegex = nil; static NSRegularExpression *excludedDeeplinkRegex = nil; -static NSString * const kClientSdk = @"ios4.33.6"; +static NSString * const kClientSdk = @"ios4.34.0"; static NSString * const kDeeplinkParam = @"deep_link="; static NSString * const kSchemeDelimiter = @"://"; static NSString * const kDefaultScheme = @"AdjustUniversalScheme"; diff --git a/Adjust/Adjust.h b/Adjust/Adjust.h index 26b8f84f5..1e82fc8b7 100644 --- a/Adjust/Adjust.h +++ b/Adjust/Adjust.h @@ -2,7 +2,7 @@ // Adjust.h // Adjust SDK // -// V4.33.6 +// V4.34.0 // Created by Christian Wellenbrock (@wellle) on 23rd July 2013. // Copyright (c) 2012-2021 Adjust GmbH. All rights reserved. // diff --git a/AdjustBridge/AdjustBridgeRegister.m b/AdjustBridge/AdjustBridgeRegister.m index c9f6911f3..9000149ef 100644 --- a/AdjustBridge/AdjustBridgeRegister.m +++ b/AdjustBridge/AdjustBridgeRegister.m @@ -275,7 +275,7 @@ + (NSString *)adjust_js { if (this.sdkPrefix) { return this.sdkPrefix; } else { - return 'web-bridge4.33.6'; + return 'web-bridge4.34.0'; } }, setTestOptions: function(testOptions) { diff --git a/VERSION b/VERSION index aba0abb01..b6a235845 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.33.6 +4.34.0 From 87c0d89bc0f12b30e6c1a42608b3f1c898843a24 Mon Sep 17 00:00:00 2001 From: uerceg Date: Thu, 29 Jun 2023 10:38:12 -0400 Subject: [PATCH 24/31] feat: disable pv for tr data residency (for now) --- Adjust/ADJActivityHandler.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index b371b11a5..6d9cf963b 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1424,6 +1424,10 @@ - (void)checkForNewAttStatusI:(ADJActivityHandler *)selfI { - (void)verifyPurchaseI:(ADJActivityHandler *)selfI purchase:(nonnull ADJPurchase *)purchase completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { + if ([selfI.adjustConfig.urlStrategy isEqualToString:ADJDataResidencyTR]) { + [selfI.logger warn:@"Purchase verification not available for Turkey data residency region right now"]; + return; + } if (![selfI isEnabledI:selfI]) { [selfI.logger warn:@"Purchase verification aborted because SDK is disabled"]; return; From 1ae197e74c3fec0b970344997ec87baf71de2d5b Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 11:23:17 +0200 Subject: [PATCH 25/31] feat: disable pv for data residency users (for now) --- Adjust/ADJActivityHandler.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 6d9cf963b..af928a8ac 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -1424,8 +1424,10 @@ - (void)checkForNewAttStatusI:(ADJActivityHandler *)selfI { - (void)verifyPurchaseI:(ADJActivityHandler *)selfI purchase:(nonnull ADJPurchase *)purchase completionHandler:(void (^_Nonnull)(ADJPurchaseVerificationResult * _Nonnull verificationResult))completionHandler { - if ([selfI.adjustConfig.urlStrategy isEqualToString:ADJDataResidencyTR]) { - [selfI.logger warn:@"Purchase verification not available for Turkey data residency region right now"]; + if ([selfI.adjustConfig.urlStrategy isEqualToString:ADJDataResidencyEU] || + [selfI.adjustConfig.urlStrategy isEqualToString:ADJDataResidencyUS] || + [selfI.adjustConfig.urlStrategy isEqualToString:ADJDataResidencyTR]) { + [selfI.logger warn:@"Purchase verification not available for data residency users right now"]; return; } if (![selfI isEnabledI:selfI]) { From 159da8482db86b50abfa9d03d6c552fc64f1b4a8 Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 11:38:06 +0200 Subject: [PATCH 26/31] feat: hook pv to the att delay flow --- Adjust/ADJActivityHandler.m | 24 ++++++++++++------------ Adjust/ADJPurchaseVerificationHandler.h | 1 + Adjust/ADJPurchaseVerificationHandler.m | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index af928a8ac..e9abbcc31 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -875,13 +875,18 @@ - (void)initI:(ADJActivityHandler *)selfI extraPath:preLaunchActions.extraPath]; selfI.sdkClickHandler = [[ADJSdkClickHandler alloc] - initWithActivityHandler:selfI - startsSending:[selfI toSendI:selfI sdkClickHandlerOnly:YES] - userAgent:selfI.adjustConfig.userAgent - urlStrategy:sdkClickHandlerUrlStrategy]; + initWithActivityHandler:selfI + startsSending:[selfI toSendI:selfI sdkClickHandlerOnly:YES] + userAgent:selfI.adjustConfig.userAgent + urlStrategy:sdkClickHandlerUrlStrategy]; + selfI.purchaseVerificationHandler = [[ADJPurchaseVerificationHandler alloc] + initWithActivityHandler:selfI + startsSending:[selfI toSendI:selfI sdkClickHandlerOnly:YES] + userAgent:selfI.adjustConfig.userAgent + urlStrategy:sdkClickHandlerUrlStrategy]; - // Update ATT status and idfa ,if necessary, in packages and sdk_click package queues. - // This should be done after `packageHandler` and `sdkClickHandler` are created. + // Update ATT status and IDFA, if necessary, in packages and sdk_click/verify packages queues. + // This should be done after packageHandler, sdkClickHandler and purchaseVerificationHandler are created. if (selfI.internalState.waitingForAttStatus) { selfI.internalState.updatePackagesAttData = YES; if (selfI.activityState != nil) { @@ -898,12 +903,6 @@ - (void)initI:(ADJActivityHandler *)selfI } } - selfI.purchaseVerificationHandler = [[ADJPurchaseVerificationHandler alloc] - initWithActivityHandler:selfI - startsSending:[selfI toSendI:selfI sdkClickHandlerOnly:YES] - userAgent:selfI.adjustConfig.userAgent - urlStrategy:sdkClickHandlerUrlStrategy]; - [selfI checkLinkMeI:selfI]; [selfI.trackingStatusManager checkForNewAttStatus]; @@ -2577,6 +2576,7 @@ - (void)updatePackagesAttStatusAndIdfaI:(ADJActivityHandler *)selfI { if (attStatus != 0) { [selfI.packageHandler updatePackagesWithIdfaAndAttStatus]; [selfI.sdkClickHandler updatePackagesWithIdfaAndAttStatus]; + [selfI.purchaseVerificationHandler updatePackagesWithIdfaAndAttStatus]; } selfI.internalState.updatePackagesAttData = NO; diff --git a/Adjust/ADJPurchaseVerificationHandler.h b/Adjust/ADJPurchaseVerificationHandler.h index 29aa20834..64a28856b 100644 --- a/Adjust/ADJPurchaseVerificationHandler.h +++ b/Adjust/ADJPurchaseVerificationHandler.h @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)pauseSending; - (void)resumeSending; - (void)sendPurchaseVerificationPackage:(ADJActivityPackage *)purchaseVerificationPackage; +- (void)updatePackagesWithIdfaAndAttStatus; - (void)teardown; @end diff --git a/Adjust/ADJPurchaseVerificationHandler.m b/Adjust/ADJPurchaseVerificationHandler.m index 6e2c18866..51b1c5950 100644 --- a/Adjust/ADJPurchaseVerificationHandler.m +++ b/Adjust/ADJPurchaseVerificationHandler.m @@ -87,6 +87,14 @@ - (void)sendNextPurchaseVerificationPackage { }]; } +- (void)updatePackagesWithIdfaAndAttStatus { + [ADJUtil launchInQueue:self.internalQueue + selfInject:self + block:^(ADJPurchaseVerificationHandler *selfI) { + [selfI updatePackagesWithIdfaAndAttStatusI:selfI]; + }]; +} + - (void)teardown { [ADJAdjustFactory.logger verbose:@"ADJPurchaseVerificationHandler teardown"]; @@ -164,6 +172,18 @@ - (void)sendNextPurchaseVerificationPackageI:(ADJPurchaseVerificationHandler *)s dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, work); } +- (void)updatePackagesWithIdfaAndAttStatusI:(ADJPurchaseVerificationHandler *)selfI { + int attStatus = [ADJUtil attStatus]; + for (ADJActivityPackage *activityPackage in selfI.packageQueue) { + [ADJPackageBuilder parameters:activityPackage.parameters + setInt:attStatus + forKey:@"att_status"]; + [ADJPackageBuilder addIdfaToParameters:activityPackage.parameters + withConfig:self.activityHandler.adjustConfig + logger:[ADJAdjustFactory logger]]; + } +} + - (void)responseCallback:(ADJResponseData *)responseData { if (responseData.jsonResponse) { [self.logger debug: From 7e32fe4838adb90ea50681735c7448da7f071b1f Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 11:57:02 +0200 Subject: [PATCH 27/31] chore: clean up the example apps --- .../AdjustExample-ObjC/ViewControllerObjC.m | 14 ++------------ examples/AdjustExample-Swift/Podfile.lock | 10 +++++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m b/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m index 45c7fab2f..82e59dfec 100644 --- a/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m +++ b/examples/AdjustExample-ObjC/AdjustExample-ObjC/ViewControllerObjC.m @@ -35,18 +35,8 @@ - (void)didReceiveMemoryWarning { } - (IBAction)clickTrackSimpleEvent:(UIButton *)sender { - // ADJEvent *event = [ADJEvent eventWithEventToken:kEventToken1]; - // [Adjust trackEvent:event]; - - ADJPurchase *purchase = [[ADJPurchase alloc] initWithTransactionId:@"transaction-id-very-nice" - productId:@"product-id-very-nice" - andReceipt:[@"receipt-very-nice" dataUsingEncoding:NSUTF8StringEncoding]]; - [Adjust verifyPurchase:purchase completionHandler:^(ADJPurchaseVerificationResult * _Nonnull verificationResult) { - NSLog(@"Purchase verification response arrived!"); - NSLog(@"Status: %@", verificationResult.verificationStatus); - NSLog(@"Code: %d", verificationResult.code); - NSLog(@"Message: %@", verificationResult.message); - }]; + ADJEvent *event = [ADJEvent eventWithEventToken:kEventToken1]; + [Adjust trackEvent:event]; } - (IBAction)clickTrackRevenueEvent:(UIButton *)sender { diff --git a/examples/AdjustExample-Swift/Podfile.lock b/examples/AdjustExample-Swift/Podfile.lock index c97241b82..bcba97baf 100644 --- a/examples/AdjustExample-Swift/Podfile.lock +++ b/examples/AdjustExample-Swift/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - Adjust (4.33.6): - - Adjust/Core (= 4.33.6) - - Adjust/Core (4.33.6) + - Adjust (4.34.0): + - Adjust/Core (= 4.34.0) + - Adjust/Core (4.34.0) DEPENDENCIES: - Adjust (from `../../`) @@ -11,8 +11,8 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - Adjust: c3b6c3734928a617fefce81dc223fd5f104162be + Adjust: 6d8152f2df622d9a6c369d3d8b7492f14213d4e3 PODFILE CHECKSUM: 4c79da456db9adb90cdd42adc7f721c7bb6490cd -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 From b072d56b7c4081b12dee29017dc8ad39bc04e72e Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 12:09:21 +0200 Subject: [PATCH 28/31] docs: update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ccdcba10..9b1bd3d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### Version 4.34.0 (16th August 2023) +#### Added +- Added ability to delay SDK start in order to wait for an answer to the ATT dialog. You can set the number of seconds to wait (capped internally to 120) by calling the `setAttConsentWaitingInterval:` method of the `ADJConfig` instance. +- Added support for purchase verification. In case you are using this feature, you can now use it by calling `verifyPurchase:completionHandler:` method of the `Adjust` instance. + +--- + ### Version 4.33.6 (25th July 2023) #### Fixed - Fixed memory leak occurrences when tracking events (https://github.com/adjust/ios_sdk/issues/668). From 8a762bee01ea94793397752cd1f1de079702c51c Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 14:28:23 +0200 Subject: [PATCH 29/31] refac: clean up skan logic a bit --- Adjust/ADJSKAdNetwork.m | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Adjust/ADJSKAdNetwork.m b/Adjust/ADJSKAdNetwork.m index b38cc8fc5..bf312778d 100644 --- a/Adjust/ADJSKAdNetwork.m +++ b/Adjust/ADJSKAdNetwork.m @@ -46,7 +46,7 @@ - (instancetype)init { #pragma mark - SKAdNetwork API - (void)registerAppForAdNetworkAttribution { - Class class = [self getSKAdNetworkClass]; + Class class = NSClassFromString(@"SKAdNetwork"); SEL selector = NSSelectorFromString(@"registerAppForAdNetworkAttribution"); if (@available(iOS 14.0, *)) { if ([self isApiAvailableForClass:class andSelector:selector]) { @@ -63,7 +63,7 @@ - (void)registerAppForAdNetworkAttribution { } - (void)updateConversionValue:(NSInteger)conversionValue { - Class class = [self getSKAdNetworkClass]; + Class class = NSClassFromString(@"SKAdNetwork"); SEL selector = NSSelectorFromString(@"updateConversionValue:"); if (@available(iOS 14.0, *)) { if ([self isApiAvailableForClass:class andSelector:selector]) { @@ -82,7 +82,7 @@ - (void)updateConversionValue:(NSInteger)conversionValue { - (void)updatePostbackConversionValue:(NSInteger)conversionValue completionHandler:(void (^)(NSError *error))completion { - Class class = [self getSKAdNetworkClass]; + Class class = NSClassFromString(@"SKAdNetwork"); SEL selector = NSSelectorFromString(@"updatePostbackConversionValue:completionHandler:"); if (@available(iOS 15.4, *)) { if ([self isApiAvailableForClass:class andSelector:selector]) { @@ -102,7 +102,7 @@ - (void)updatePostbackConversionValue:(NSInteger)conversionValue - (void)updatePostbackConversionValue:(NSInteger)fineValue coarseValue:(NSString *)coarseValue completionHandler:(void (^)(NSError *error))completion { - Class class = [self getSKAdNetworkClass]; + Class class = NSClassFromString(@"SKAdNetwork"); SEL selector = NSSelectorFromString(@"updatePostbackConversionValue:coarseValue:completionHandler:"); if (@available(iOS 16.1, *)) { if ([self isApiAvailableForClass:class andSelector:selector]) { @@ -124,7 +124,7 @@ - (void)updatePostbackConversionValue:(NSInteger)fineValue coarseValue:(NSString *)coarseValue lockWindow:(BOOL)lockWindow completionHandler:(void (^)(NSError *error))completion { - Class class = [self getSKAdNetworkClass]; + Class class = NSClassFromString(@"SKAdNetwork"); SEL selector = NSSelectorFromString(@"updatePostbackConversionValue:coarseValue:lockWindow:completionHandler:"); if (@available(iOS 16.1, *)) { if ([self isApiAvailableForClass:class andSelector:selector]) { @@ -146,6 +146,10 @@ - (void)updatePostbackConversionValue:(NSInteger)fineValue #pragma mark - Adjust helper methods - (void)adjRegisterWithCompletionHandler:(void (^)(NSError *error))callback { + if (NSClassFromString(@"SKAdNetwork") == nil) { + [self.logger debug:@"StoreKit.framework not found in the app (SKAdNetwork class not found)"]; + return; + } if ([ADJUserDefaults getSkadRegisterCallTimestamp] != nil) { [self.logger debug:@"Call to register app with SKAdNetwork already made for this install"]; callback(nil); @@ -170,6 +174,7 @@ - (void)adjRegisterWithCompletionHandler:(void (^)(NSError *error))callback { } else { [self.logger error:@"SKAdNetwork API not available on this iOS version"]; callback(nil); + return; } [self writeSkAdNetworkRegisterCallTimestamp]; @@ -179,7 +184,11 @@ - (void)adjUpdateConversionValue:(NSInteger)conversionValue coarseValue:(NSString *)coarseValue lockWindow:(NSNumber *)lockWindow completionHandler:(void (^)(NSError *error))callback { - // let's make sure first that the conversionValue makes sense + if (NSClassFromString(@"SKAdNetwork") == nil) { + [self.logger debug:@"StoreKit.framework not found in the app (SKAdNetwork class not found)"]; + return; + } + // let's make sure that the conversionValue makes sense if (conversionValue < 0) { callback(nil); return; @@ -297,8 +306,4 @@ - (NSString *)getSkAdNetworkCoarseConversionValue:(NSString *)adjustCoarseValue #endif } -- (Class)getSKAdNetworkClass { - return NSClassFromString(@"SKAdNetwork"); -} - @end From cd03c89e1d23a38fd2751404bee1996123ca9f53 Mon Sep 17 00:00:00 2001 From: uerceg Date: Wed, 16 Aug 2023 16:31:30 +0200 Subject: [PATCH 30/31] docs: update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b1bd3d11..c13f2ac95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### Version 4.34.0 (16th August 2023) +### Version 4.34.0 (17th August 2023) #### Added - Added ability to delay SDK start in order to wait for an answer to the ATT dialog. You can set the number of seconds to wait (capped internally to 120) by calling the `setAttConsentWaitingInterval:` method of the `ADJConfig` instance. - Added support for purchase verification. In case you are using this feature, you can now use it by calling `verifyPurchase:completionHandler:` method of the `Adjust` instance. From bee5cd5c45f4c33dc80da68a5d153a55cbad094b Mon Sep 17 00:00:00 2001 From: Genady Buchatsky Date: Thu, 17 Aug 2023 14:44:30 +0200 Subject: [PATCH 31/31] chore: add missing pv files to project targets --- Adjust.xcodeproj/project.pbxproj | 112 +++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/Adjust.xcodeproj/project.pbxproj b/Adjust.xcodeproj/project.pbxproj index 3505c82dd..cb43483ba 100644 --- a/Adjust.xcodeproj/project.pbxproj +++ b/Adjust.xcodeproj/project.pbxproj @@ -233,6 +233,38 @@ 0AB1CB4027DF69B700509231 /* WebViewJavascriptBridge_JS.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9648C5E81CD1765E00A3B049 /* WebViewJavascriptBridge_JS.h */; }; 0AB1CB4127DF69B700509231 /* WebViewJavascriptBridgeBase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9648C5EA1CD1765E00A3B049 /* WebViewJavascriptBridgeBase.h */; }; 0AB1CB4227DF69B700509231 /* WKWebViewJavascriptBridge.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9648C5EC1CD1765E00A3B049 /* WKWebViewJavascriptBridge.h */; }; + 0ABE89C12A8E49500099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89BB2A8E49500099CCF5 /* ADJPurchaseVerificationHandler.m */; }; + 0ABE89C22A8E49500099CCF5 /* ADJPurchaseVerificationResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89BC2A8E49500099CCF5 /* ADJPurchaseVerificationResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89C32A8E49500099CCF5 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89BD2A8E49500099CCF5 /* ADJPurchaseVerificationResult.m */; }; + 0ABE89C42A8E49500099CCF5 /* ADJPurchase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89BE2A8E49500099CCF5 /* ADJPurchase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89C52A8E49500099CCF5 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89BF2A8E49500099CCF5 /* ADJPurchase.m */; }; + 0ABE89C62A8E49500099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89C02A8E49500099CCF5 /* ADJPurchaseVerificationHandler.h */; }; + 0ABE89CD2A8E49B50099CCF5 /* ADJPurchase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89C72A8E49B50099CCF5 /* ADJPurchase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89CE2A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89C82A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.m */; }; + 0ABE89CF2A8E49B50099CCF5 /* ADJPurchaseVerificationResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89C92A8E49B50099CCF5 /* ADJPurchaseVerificationResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89D02A8E49B50099CCF5 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89CA2A8E49B50099CCF5 /* ADJPurchase.m */; }; + 0ABE89D12A8E49B50099CCF5 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89CB2A8E49B50099CCF5 /* ADJPurchaseVerificationResult.m */; }; + 0ABE89D22A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89CC2A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.h */; }; + 0ABE89D92A8E49C20099CCF5 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89D32A8E49C20099CCF5 /* ADJPurchase.m */; }; + 0ABE89DA2A8E49C20099CCF5 /* ADJPurchase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89D42A8E49C20099CCF5 /* ADJPurchase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89DB2A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89D52A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.h */; }; + 0ABE89DC2A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89D62A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.m */; }; + 0ABE89DD2A8E49C20099CCF5 /* ADJPurchaseVerificationResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89D72A8E49C20099CCF5 /* ADJPurchaseVerificationResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89DE2A8E49C20099CCF5 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89D82A8E49C20099CCF5 /* ADJPurchaseVerificationResult.m */; }; + 0ABE89E52A8E49D40099CCF5 /* ADJPurchaseVerificationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89DF2A8E49D40099CCF5 /* ADJPurchaseVerificationResult.m */; }; + 0ABE89E62A8E49D40099CCF5 /* ADJPurchase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89E02A8E49D40099CCF5 /* ADJPurchase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89E72A8E49D40099CCF5 /* ADJPurchase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89E12A8E49D40099CCF5 /* ADJPurchase.m */; }; + 0ABE89E82A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89E22A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.h */; }; + 0ABE89E92A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ABE89E32A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.m */; }; + 0ABE89EA2A8E49D40099CCF5 /* ADJPurchaseVerificationResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ABE89E42A8E49D40099CCF5 /* ADJPurchaseVerificationResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0ABE89EB2A8E4AA60099CCF5 /* ADJPurchase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */; }; + 0ABE89EC2A8E4AA60099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */; }; + 0ABE89ED2A8E4AB70099CCF5 /* ADJPurchase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */; }; + 0ABE89EE2A8E4AB70099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */; }; + 0ABE89EF2A8E4AF90099CCF5 /* ADJPurchase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */; }; + 0ABE89F02A8E4AF90099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */; }; + 0ABE89F12A8E4B060099CCF5 /* ADJPurchase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B3C2A1F4B19009D0BE8 /* ADJPurchase.h */; }; + 0ABE89F22A8E4B060099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9D775B572A1F7C7A009D0BE8 /* ADJPurchaseVerificationResult.h */; }; 6F84512425B1B1380004C7C0 /* ADJThirdPartySharing.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F84511025B1B1380004C7C0 /* ADJThirdPartySharing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6F84512525B1B1380004C7C0 /* ADJThirdPartySharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F84512325B1B1380004C7C0 /* ADJThirdPartySharing.m */; }; 6FAB784C2636DC0E00773869 /* ADJLinkResolution.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FAB784A2636DC0E00773869 /* ADJLinkResolution.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -794,6 +826,8 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + 0ABE89EB2A8E4AA60099CCF5 /* ADJPurchase.h in CopyFiles */, + 0ABE89EC2A8E4AA60099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */, 0AB1C97627DD450D00509231 /* AdjustSdk.h in CopyFiles */, 0AB1C9BA27DD4D7C00509231 /* Adjust.h in CopyFiles */, 0AB1C9BB27DD4D7C00509231 /* ADJLogger.h in CopyFiles */, @@ -817,6 +851,8 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + 0ABE89ED2A8E4AB70099CCF5 /* ADJPurchase.h in CopyFiles */, + 0ABE89EE2A8E4AB70099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */, 0AB1CA2A27DF462200509231 /* AdjustSdkTv.h in CopyFiles */, 0AB1CA5227DF4A2B00509231 /* Adjust.h in CopyFiles */, 0AB1CA5327DF4A2B00509231 /* ADJLogger.h in CopyFiles */, @@ -840,6 +876,8 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + 0ABE89EF2A8E4AF90099CCF5 /* ADJPurchase.h in CopyFiles */, + 0ABE89F02A8E4AF90099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */, 0AB1CA6D27DF5D3200509231 /* AdjustSdkIm.h in CopyFiles */, 0AB1CA9527DF621D00509231 /* Adjust.h in CopyFiles */, 0AB1CA9627DF621D00509231 /* ADJLogger.h in CopyFiles */, @@ -863,6 +901,8 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + 0ABE89F12A8E4B060099CCF5 /* ADJPurchase.h in CopyFiles */, + 0ABE89F22A8E4B060099CCF5 /* ADJPurchaseVerificationResult.h in CopyFiles */, 0AB1CADA27DF671300509231 /* AdjustSdkWebBridge.h in CopyFiles */, 0AB1CB3127DF699E00509231 /* Adjust.h in CopyFiles */, 0AB1CB3227DF699E00509231 /* ADJLogger.h in CopyFiles */, @@ -920,6 +960,30 @@ 0AB1CAD527DF671300509231 /* libAdjustSdkWebBridge.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAdjustSdkWebBridge.a; sourceTree = BUILT_PRODUCTS_DIR; }; 0AB1CAD727DF671300509231 /* AdjustSdkWebBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AdjustSdkWebBridge.h; sourceTree = ""; }; 0AB1CB4427DF6C8E00509231 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 0ABE89BB2A8E49500099CCF5 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; + 0ABE89BC2A8E49500099CCF5 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 0ABE89BD2A8E49500099CCF5 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 0ABE89BE2A8E49500099CCF5 /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 0ABE89BF2A8E49500099CCF5 /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 0ABE89C02A8E49500099CCF5 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 0ABE89C72A8E49B50099CCF5 /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 0ABE89C82A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; + 0ABE89C92A8E49B50099CCF5 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 0ABE89CA2A8E49B50099CCF5 /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 0ABE89CB2A8E49B50099CCF5 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 0ABE89CC2A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 0ABE89D32A8E49C20099CCF5 /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 0ABE89D42A8E49C20099CCF5 /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 0ABE89D52A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 0ABE89D62A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; + 0ABE89D72A8E49C20099CCF5 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; + 0ABE89D82A8E49C20099CCF5 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 0ABE89DF2A8E49D40099CCF5 /* ADJPurchaseVerificationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationResult.m; sourceTree = ""; }; + 0ABE89E02A8E49D40099CCF5 /* ADJPurchase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchase.h; sourceTree = ""; }; + 0ABE89E12A8E49D40099CCF5 /* ADJPurchase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchase.m; sourceTree = ""; }; + 0ABE89E22A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationHandler.h; sourceTree = ""; }; + 0ABE89E32A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPurchaseVerificationHandler.m; sourceTree = ""; }; + 0ABE89E42A8E49D40099CCF5 /* ADJPurchaseVerificationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJPurchaseVerificationResult.h; sourceTree = ""; }; 52BD7374221C3EDB004F2E87 /* PocketSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PocketSocket.xcodeproj; path = PocketSocket/PocketSocket.xcodeproj; sourceTree = ""; }; 6F0842182007766700568A31 /* AdjustTestLibrary.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AdjustTestLibrary.xcodeproj; path = AdjustTestLibrary/AdjustTestLibrary.xcodeproj; sourceTree = ""; }; 6F084240200776A000568A31 /* AdjustTestApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AdjustTestApp.xcodeproj; path = AdjustTestApp/AdjustTestApp.xcodeproj; sourceTree = ""; }; @@ -1790,6 +1854,12 @@ 6FAB78902636DCE700773869 /* ADJLinkResolution.m */, 9D49D168290FEBD100042345 /* ADJSKAdNetwork.h */, 9D49D169290FEBD100042345 /* ADJSKAdNetwork.m */, + 0ABE89E02A8E49D40099CCF5 /* ADJPurchase.h */, + 0ABE89E12A8E49D40099CCF5 /* ADJPurchase.m */, + 0ABE89E22A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.h */, + 0ABE89E32A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.m */, + 0ABE89E42A8E49D40099CCF5 /* ADJPurchaseVerificationResult.h */, + 0ABE89DF2A8E49D40099CCF5 /* ADJPurchaseVerificationResult.m */, ); path = Adjust; sourceTree = SOURCE_ROOT; @@ -1976,6 +2046,12 @@ 6FAB78772636DCB600773869 /* ADJLinkResolution.m */, 9D49D160290FEBB200042345 /* ADJSKAdNetwork.h */, 9D49D161290FEBB200042345 /* ADJSKAdNetwork.m */, + 0ABE89C72A8E49B50099CCF5 /* ADJPurchase.h */, + 0ABE89CA2A8E49B50099CCF5 /* ADJPurchase.m */, + 0ABE89CC2A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.h */, + 0ABE89C82A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.m */, + 0ABE89C92A8E49B50099CCF5 /* ADJPurchaseVerificationResult.h */, + 0ABE89CB2A8E49B50099CCF5 /* ADJPurchaseVerificationResult.m */, ); path = Adjust; sourceTree = SOURCE_ROOT; @@ -2061,6 +2137,12 @@ 6FAB786A2636DC8400773869 /* ADJLinkResolution.m */, 9D49D14A290FEBA200042345 /* ADJSKAdNetwork.h */, 9D49D15D290FEBA200042345 /* ADJSKAdNetwork.m */, + 0ABE89BE2A8E49500099CCF5 /* ADJPurchase.h */, + 0ABE89BF2A8E49500099CCF5 /* ADJPurchase.m */, + 0ABE89C02A8E49500099CCF5 /* ADJPurchaseVerificationHandler.h */, + 0ABE89BB2A8E49500099CCF5 /* ADJPurchaseVerificationHandler.m */, + 0ABE89BC2A8E49500099CCF5 /* ADJPurchaseVerificationResult.h */, + 0ABE89BD2A8E49500099CCF5 /* ADJPurchaseVerificationResult.m */, ); path = Adjust; sourceTree = SOURCE_ROOT; @@ -2166,6 +2248,12 @@ 6FAB78842636DCCD00773869 /* ADJLinkResolution.m */, 9D49D164290FEBC000042345 /* ADJSKAdNetwork.h */, 9D49D165290FEBC000042345 /* ADJSKAdNetwork.m */, + 0ABE89D42A8E49C20099CCF5 /* ADJPurchase.h */, + 0ABE89D32A8E49C20099CCF5 /* ADJPurchase.m */, + 0ABE89D52A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.h */, + 0ABE89D62A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.m */, + 0ABE89D72A8E49C20099CCF5 /* ADJPurchaseVerificationResult.h */, + 0ABE89D82A8E49C20099CCF5 /* ADJPurchaseVerificationResult.m */, ); path = Adjust; sourceTree = SOURCE_ROOT; @@ -2236,6 +2324,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0ABE89EA2A8E49D40099CCF5 /* ADJPurchaseVerificationResult.h in Headers */, + 0ABE89E62A8E49D40099CCF5 /* ADJPurchase.h in Headers */, 9D0E2E06210B570600133B4F /* AdjustSdkWebBridge.h in Headers */, 9D0E2EBA210B575600133B4F /* AdjustBridge.h in Headers */, 9D0E2EAF210B575600133B4F /* AdjustBridgeRegister.h in Headers */, @@ -2254,6 +2344,7 @@ 9D0E2EA4210B575600133B4F /* ADJSessionSuccess.h in Headers */, 9D0E2EA8210B575600133B4F /* ADJLogger.h in Headers */, 6F84512425B1B1380004C7C0 /* ADJThirdPartySharing.h in Headers */, + 0ABE89E82A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */, 9D49D16A290FEBD100042345 /* ADJSKAdNetwork.h in Headers */, 6FAB78932636DCE700773869 /* ADJLinkResolution.h in Headers */, 9D3A2AD1262650C300BD6E44 /* ADJAdRevenue.h in Headers */, @@ -2285,6 +2376,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0ABE89CF2A8E49B50099CCF5 /* ADJPurchaseVerificationResult.h in Headers */, + 0ABE89CD2A8E49B50099CCF5 /* ADJPurchase.h in Headers */, 9DE354D62100726300D211C9 /* AdjustSdkIm.h in Headers */, 9DEAF0F9210072BC005CAEDB /* Adjust.h in Headers */, 6FBEE92C24E4230800FEF3F1 /* ADJUrlStrategy.h in Headers */, @@ -2317,6 +2410,7 @@ 9DEAF133210072BC005CAEDB /* ADJSessionParameters.h in Headers */, 9DEAF125210072BC005CAEDB /* ADJRequestHandler.h in Headers */, 9DEAF10D210072BC005CAEDB /* ADJActivityHandler.h in Headers */, + 0ABE89D22A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */, 9DEAF103210072BC005CAEDB /* ADJPackageBuilder.h in Headers */, 9DEAF0FA210072BC005CAEDB /* ADJActivityPackage.h in Headers */, 9DEAF114210072BC005CAEDB /* ADJAttributionHandler.h in Headers */, @@ -2329,6 +2423,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0ABE89C42A8E49500099CCF5 /* ADJPurchase.h in Headers */, + 0ABE89C22A8E49500099CCF5 /* ADJPurchaseVerificationResult.h in Headers */, 9DFA37B71C0F21D600782607 /* AdjustSdk.h in Headers */, 9DF9C9431D6F3CA5008E362F /* Adjust.h in Headers */, 9DF9C9231D6F3CA5008E362F /* ADJEvent.h in Headers */, @@ -2361,6 +2457,7 @@ 9DF9C9451D6F3CA5008E362F /* ADJUtil.h in Headers */, 9DF9C91B1D6F3CA5008E362F /* ADJAttributionHandler.h in Headers */, 9DF9C93F1D6F3CA5008E362F /* ADJTimerOnce.h in Headers */, + 0ABE89C62A8E49500099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */, 9DF9C93D1D6F3CA5008E362F /* ADJTimerCycle.h in Headers */, 9DF9C9331D6F3CA5008E362F /* ADJResponseData.h in Headers */, 96164D861CCA4D27009431AB /* ADJBackoffStrategy.h in Headers */, @@ -2373,6 +2470,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0ABE89DD2A8E49C20099CCF5 /* ADJPurchaseVerificationResult.h in Headers */, + 0ABE89DA2A8E49C20099CCF5 /* ADJPurchase.h in Headers */, 9DFB06131D747070006D48FC /* AdjustSdkTv.h in Headers */, 9DFB06941D7470C0006D48FC /* Adjust.h in Headers */, 9DFB06741D7470C0006D48FC /* ADJEvent.h in Headers */, @@ -2410,6 +2509,7 @@ 9DFB066E1D7470C0006D48FC /* ADJBackoffStrategy.h in Headers */, 9DFB06861D7470C0006D48FC /* ADJSdkClickHandler.h in Headers */, 96B671181D788F7A0090A023 /* ADJSessionParameters.h in Headers */, + 0ABE89DB2A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3322,14 +3422,17 @@ 9D0E2E6D210B575600133B4F /* ADJTimerOnce.m in Sources */, 9D0E2E77210B575600133B4F /* ADJEvent.m in Sources */, 9D0E2E8D210B575600133B4F /* ADJConfig.m in Sources */, + 0ABE89E72A8E49D40099CCF5 /* ADJPurchase.m in Sources */, 9D2F24042447DD6000B7CA90 /* ADJSubscription.m in Sources */, 9DF3821C260E9B8D0033F5A1 /* NSNumber+ADJAdditions.m in Sources */, 9D0E2E6E210B575600133B4F /* ADJAttributionHandler.m in Sources */, + 0ABE89E92A8E49D40099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */, 9DF92D8F2630ED7B000FC3FC /* ADJPackageParams.m in Sources */, 9D49D16B290FEBD100042345 /* ADJSKAdNetwork.m in Sources */, 9D0E2E87210B575600133B4F /* ADJBackoffStrategy.m in Sources */, 9D0E2EBB210B575600133B4F /* AdjustBridgeRegister.m in Sources */, 6F84512525B1B1380004C7C0 /* ADJThirdPartySharing.m in Sources */, + 0ABE89E52A8E49D40099CCF5 /* ADJPurchaseVerificationResult.m in Sources */, 9D0E2E84210B575600133B4F /* ADJTimerCycle.m in Sources */, 6FBEE93524E4232800FEF3F1 /* ADJUrlStrategy.m in Sources */, 9D0E2EA5210B575600133B4F /* ADJPackageBuilder.m in Sources */, @@ -3365,6 +3468,7 @@ 6FAB78792636DCB600773869 /* ADJLinkResolution.m in Sources */, 9DEAF113210072BC005CAEDB /* Adjust.m in Sources */, 9DEAF136210072BC005CAEDB /* ADJActivityHandler.m in Sources */, + 0ABE89CE2A8E49B50099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */, 9D651C8925B26DF5006D69D6 /* ADJThirdPartySharing.m in Sources */, 9DEAF10A210072BC005CAEDB /* ADJSessionFailure.m in Sources */, 9DEAF105210072BC005CAEDB /* ADJSessionSuccess.m in Sources */, @@ -3378,8 +3482,10 @@ 9DEAF0F7210072BC005CAEDB /* ADJTimerOnce.m in Sources */, 6FBEE92D24E4230800FEF3F1 /* ADJUrlStrategy.m in Sources */, 9DEAF101210072BC005CAEDB /* ADJEvent.m in Sources */, + 0ABE89D02A8E49B50099CCF5 /* ADJPurchase.m in Sources */, 9DEAF117210072BC005CAEDB /* ADJConfig.m in Sources */, 9D3A2ACA2626505800BD6E44 /* ADJAdRevenue.m in Sources */, + 0ABE89D12A8E49B50099CCF5 /* ADJPurchaseVerificationResult.m in Sources */, 9DEAF0F8210072BC005CAEDB /* ADJAttributionHandler.m in Sources */, 9DEAF111210072BC005CAEDB /* ADJBackoffStrategy.m in Sources */, 9DEAF10E210072BC005CAEDB /* ADJTimerCycle.m in Sources */, @@ -3408,6 +3514,7 @@ 6FAB786C2636DC8400773869 /* ADJLinkResolution.m in Sources */, 9DF9C9181D6F3CA5008E362F /* ADJAdjustFactory.m in Sources */, 9DF9C92C1D6F3CA5008E362F /* ADJLogger.m in Sources */, + 0ABE89C12A8E49500099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */, 6FBE0C6F2577CDAC00EC2CE0 /* ADJThirdPartySharing.m in Sources */, 9DF9C92E1D6F3CA5008E362F /* ADJPackageBuilder.m in Sources */, 9DF9C9301D6F3CA5008E362F /* ADJPackageHandler.m in Sources */, @@ -3419,6 +3526,7 @@ 9DF9C91C1D6F3CA5008E362F /* ADJAttributionHandler.m in Sources */, 9DF9C91A1D6F3CA5008E362F /* ADJAttribution.m in Sources */, 9DF9C9201D6F3CA5008E362F /* ADJConfig.m in Sources */, + 0ABE89C32A8E49500099CCF5 /* ADJPurchaseVerificationResult.m in Sources */, 6FBEE92724E422EB00FEF3F1 /* ADJUrlStrategy.m in Sources */, 9DF9C9401D6F3CA5008E362F /* ADJTimerOnce.m in Sources */, 9D3A2AC52626501D00BD6E44 /* ADJAdRevenue.m in Sources */, @@ -3429,6 +3537,7 @@ 9DF9C9281D6F3CA5008E362F /* ADJEventSuccess.m in Sources */, 9DF9C9261D6F3CA5008E362F /* ADJEventFailure.m in Sources */, 9DB457B01D743704004D69E8 /* ADJBackoffStrategy.m in Sources */, + 0ABE89C52A8E49500099CCF5 /* ADJPurchase.m in Sources */, 9DB457B11D743704004D69E8 /* ADJSdkClickHandler.m in Sources */, 96B671151D788F4A0090A023 /* ADJSessionParameters.m in Sources */, 9DF381F5260E9AF50033F5A1 /* NSNumber+ADJAdditions.m in Sources */, @@ -3445,6 +3554,7 @@ 9DFB06951D7470C0006D48FC /* Adjust.m in Sources */, 9DFB065B1D7470C0006D48FC /* ADJActivityHandler.m in Sources */, 9DFB065D1D7470C0006D48FC /* ADJActivityKind.m in Sources */, + 0ABE89DC2A8E49C20099CCF5 /* ADJPurchaseVerificationHandler.m in Sources */, 9DFB065F1D7470C0006D48FC /* ADJActivityPackage.m in Sources */, 9DFB06611D7470C0006D48FC /* ADJActivityState.m in Sources */, 9DFB06691D7470C0006D48FC /* ADJAdjustFactory.m in Sources */, @@ -3462,10 +3572,12 @@ 9DFB066D1D7470C0006D48FC /* ADJAttributionHandler.m in Sources */, 9DFB066B1D7470C0006D48FC /* ADJAttribution.m in Sources */, 9DFB06711D7470C0006D48FC /* ADJConfig.m in Sources */, + 0ABE89D92A8E49C20099CCF5 /* ADJPurchase.m in Sources */, 6FBEE93124E4231400FEF3F1 /* ADJUrlStrategy.m in Sources */, 9DFB06911D7470C0006D48FC /* ADJTimerOnce.m in Sources */, 9D3A2ACE2626508F00BD6E44 /* ADJAdRevenue.m in Sources */, 9DFB068F1D7470C0006D48FC /* ADJTimerCycle.m in Sources */, + 0ABE89DE2A8E49C20099CCF5 /* ADJPurchaseVerificationResult.m in Sources */, 9DFB06851D7470C0006D48FC /* ADJResponseData.m in Sources */, 9DFB068B1D7470C0006D48FC /* ADJSessionSuccess.m in Sources */, 9DFB06891D7470C0006D48FC /* ADJSessionFailure.m in Sources */,