diff --git a/Adjust.podspec b/Adjust.podspec
index 445c89093..ae51132a3 100644
--- a/Adjust.podspec
+++ b/Adjust.podspec
@@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = "Adjust"
- s.version = "4.12.3"
+ s.version = "4.13.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 = { "Christian Wellenbrock" => "welle@adjust.com" }
- s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.12.3" }
+ s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.13.0" }
s.ios.deployment_target = '6.0'
s.tvos.deployment_target = '9.0'
s.framework = 'SystemConfiguration'
diff --git a/Adjust.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Adjust.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Adjust.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Adjust/ADJActivityHandler.h b/Adjust/ADJActivityHandler.h
index c8e1640a1..2070ffdc8 100644
--- a/Adjust/ADJActivityHandler.h
+++ b/Adjust/ADJActivityHandler.h
@@ -42,6 +42,7 @@
@property (nonatomic, copy) NSNumber *enabled;
@property (nonatomic, assign) BOOL offline;
@property (nonatomic, copy) NSString *basePath;
+@property (nonatomic, copy) NSString *gdprPath;
- (id)init;
@@ -67,10 +68,12 @@
- (void)launchAttributionResponseTasks:(ADJAttributionResponseData *)attributionResponseData;
- (void)setEnabled:(BOOL)enabled;
- (BOOL)isEnabled;
+- (BOOL)isGdprForgotten;
- (void)appWillOpenUrl:(NSURL*)url;
- (void)setDeviceToken:(NSData *)deviceToken;
-
+- (void)setGdprForgetMe;
+- (void)setTrackingStateOptedOut;
- (void)setAskingAttribution:(BOOL)askingAttribution;
- (BOOL)updateAttributionI:(id)selfI attribution:(ADJAttribution *)attribution;
@@ -91,6 +94,7 @@
- (void)resetSessionCallbackParameters;
- (void)resetSessionPartnerParameters;
- (NSString *)getBasePath;
+- (NSString *)getGdprPath;
- (void)teardown;
+ (void)deleteState;
diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m
index 6a3a32b4e..54ddd6ba5 100644
--- a/Adjust/ADJActivityHandler.m
+++ b/Adjust/ADJActivityHandler.m
@@ -102,6 +102,7 @@ @interface ADJActivityHandler()
@property (nonatomic, copy) ADJConfig *adjustConfig;
@property (nonatomic, copy) NSData* deviceTokenData;
@property (nonatomic, copy) NSString* basePath;
+@property (nonatomic, copy) NSString* gdprPath;
@end
@@ -192,6 +193,9 @@ - (id)initWithConfig:(ADJConfig *)adjustConfig
if (savedPreLaunch.basePath != nil) {
self.basePath = savedPreLaunch.basePath;
}
+ if (savedPreLaunch.gdprPath != nil) {
+ self.gdprPath = savedPreLaunch.gdprPath;
+ }
self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL);
[ADJUtil launchInQueue:self.internalQueue
@@ -201,11 +205,12 @@ - (id)initWithConfig:(ADJConfig *)adjustConfig
preLaunchActionsArray:savedPreLaunch.preLaunchActionsArray];
}];
-
+ /* Not needed, done already in initI:preLaunchActionsArray: method.
// self.deviceTokenData = savedPreLaunch.deviceTokenData;
if (self.activityState != nil) {
[self setDeviceToken:[ADJUserDefaults getPushToken]];
}
+ */
[self addNotificationObserver];
@@ -330,6 +335,10 @@ - (BOOL)isEnabled {
return [self isEnabledI:self];
}
+- (BOOL)isGdprForgotten {
+ return [self isGdprForgottenI:self];
+}
+
- (NSString *)adid {
if (self.activityState == nil) {
return nil;
@@ -353,6 +362,23 @@ - (void)setDeviceToken:(NSData *)deviceToken {
}];
}
+- (void)setGdprForgetMe {
+ [ADJUtil launchInQueue:self.internalQueue
+ selfInject:self
+ block:^(ADJActivityHandler * selfI) {
+ [selfI setGdprForgetMeI:selfI];
+ }];
+}
+
+- (void)setTrackingStateOptedOut {
+ [ADJUtil launchInQueue:self.internalQueue
+ selfInject:self
+ block:^(ADJActivityHandler * selfI) {
+ [selfI setTrackingStateOptedOutI:selfI];
+ }];
+}
+
+
- (void)setAttributionDetails:(NSDictionary *)attributionDetails
error:(NSError *)error
retriesLeft:(int)retriesLeft
@@ -532,6 +558,10 @@ - (NSString *)getBasePath {
return _basePath;
}
+- (NSString *)getGdprPath {
+ return _gdprPath;
+}
+
- (void)teardown
{
[ADJAdjustFactory.logger verbose:@"ADJActivityHandler teardown"];
@@ -635,11 +665,16 @@ - (void)initI:(ADJActivityHandler *)selfI
} else {
if (selfI.activityState != nil) {
NSData *deviceToken = [ADJUserDefaults getPushToken];
-
[selfI setDeviceToken:deviceToken];
}
}
+ if (selfI.activityState != nil) {
+ if ([ADJUserDefaults getGdprForgetMe]) {
+ [selfI setGdprForgetMe];
+ }
+ }
+
selfI.foregroundTimer = [ADJTimerCycle timerWithBlock:^{
[selfI foregroundTimerFired];
}
@@ -737,8 +772,13 @@ - (void)processSessionI:(ADJActivityHandler *)selfI {
// track the first session package only if it's enabled
if ([selfI.internalState isEnabled]) {
- selfI.activityState.sessionCount = 1; // this is the first session
- [selfI transferSessionPackageI:selfI now:now];
+ // If user chose to be forgotten before install has ever tracked, don't track it.
+ if (![ADJUserDefaults getGdprForgetMe]) {
+ selfI.activityState.sessionCount = 1; // this is the first session
+ [selfI transferSessionPackageI:selfI now:now];
+ } else {
+ [selfI setGdprForgetMeI:selfI];
+ }
}
[selfI.activityState resetSessionAttributes:now];
@@ -781,11 +821,13 @@ - (void)processSessionI:(ADJActivityHandler *)selfI {
}
- (void)trackNewSessionI:(double)now withActivityHandler:(ADJActivityHandler *)selfI {
- double lastInterval = now - selfI.activityState.lastActivity;
+ if (selfI.activityState.isGdprForgotten) {
+ return;
+ }
+ double lastInterval = now - selfI.activityState.lastActivity;
selfI.activityState.sessionCount++;
selfI.activityState.lastInterval = lastInterval;
-
[selfI transferSessionPackageI:selfI now:now];
[selfI.activityState resetSessionAttributes:now];
[selfI writeActivityStateI:selfI];
@@ -840,6 +882,7 @@ - (void)eventI:(ADJActivityHandler *)selfI
if (![selfI isEnabledI:selfI]) return;
if (![selfI checkEventI:selfI event:event]) return;
if (![selfI checkTransactionIdI:selfI transactionId:event.transactionId]) return;
+ if (selfI.activityState.isGdprForgotten) { return; }
double now = [NSDate.date timeIntervalSince1970];
@@ -1044,6 +1087,14 @@ - (void)setEnabledI:(ADJActivityHandler *)selfI enabled:(BOOL)enabled {
return;
}
+ // If user is forgotten, forbid re-enabling.
+ if (enabled) {
+ if ([selfI isGdprForgottenI:selfI]) {
+ [selfI.logger debug:@"Re-enabling SDK for forgotten user not allowed"];
+ return;
+ }
+ }
+
// save new enabled state in internal state
selfI.internalState.enabled = enabled;
@@ -1056,6 +1107,10 @@ - (void)setEnabledI:(ADJActivityHandler *)selfI enabled:(BOOL)enabled {
return;
}
+ // Save new enabled state in activity state.
+ selfI.activityState.enabled = enabled;
+ [selfI writeActivityStateI:selfI];
+
// Check if upon enabling install has been tracked.
if (enabled) {
if (![ADJUserDefaults getInstallTracked]) {
@@ -1068,12 +1123,12 @@ - (void)setEnabledI:(ADJActivityHandler *)selfI enabled:(BOOL)enabled {
if (deviceToken != nil && ![selfI.activityState.deviceToken isEqualToString:[ADJUtil convertDeviceToken:deviceToken]]) {
[self setDeviceToken:deviceToken];
}
+
+ if ([ADJUserDefaults getGdprForgetMe]) {
+ [selfI setGdprForgetMe];
+ }
}
- // save new enabled state in activity state
- selfI.activityState.enabled = enabled;
- [selfI writeActivityStateI:selfI];
-
[selfI checkStatusI:selfI
pausingState:!enabled
pausingMessage:@"Pausing handlers due to SDK being disabled"
@@ -1266,6 +1321,9 @@ - (void)setDeviceTokenI:(ADJActivityHandler *)selfI
if (!selfI.activityState) {
return;
}
+ if (selfI.activityState.isGdprForgotten) {
+ return;
+ }
NSString *deviceTokenString = [ADJUtil convertDeviceToken:deviceToken];
@@ -1303,6 +1361,51 @@ - (void)setDeviceTokenI:(ADJActivityHandler *)selfI
}
}
+- (void)setGdprForgetMeI:(ADJActivityHandler *)selfI {
+ if (![selfI isEnabledI:selfI]) {
+ return;
+ }
+ if (!selfI.activityState) {
+ return;
+ }
+ if (selfI.activityState.isGdprForgotten == YES) {
+ [ADJUserDefaults removeGdprForgetMe];
+ return;
+ }
+
+ selfI.activityState.isGdprForgotten = YES;
+ [selfI writeActivityStateI:selfI];
+
+ // Send GDPR package
+ double now = [NSDate.date timeIntervalSince1970];
+ ADJPackageBuilder *gdprBuilder = [[ADJPackageBuilder alloc] initWithDeviceInfo:selfI.deviceInfo
+ activityState:selfI.activityState
+ config:selfI.adjustConfig
+ sessionParameters:selfI.sessionParameters
+ createdAt:now];
+
+ ADJActivityPackage *gdprPackage = [gdprBuilder buildGdprPackage];
+ [selfI.packageHandler addPackage:gdprPackage];
+
+ [ADJUserDefaults removeGdprForgetMe];
+
+ if (selfI.adjustConfig.eventBufferingEnabled) {
+ [selfI.logger info:@"Buffered gdpr %@", gdprPackage.suffix];
+ } else {
+ [selfI.packageHandler sendFirstPackage];
+ }
+}
+
+- (void)setTrackingStateOptedOutI:(ADJActivityHandler *)selfI {
+ // In case of web opt out, once response from backend arrives isGdprForgotten field in this moment defaults to NO.
+ // Set it to YES regardless of state, since at this moment it should be YES.
+ selfI.activityState.isGdprForgotten = YES;
+ [selfI writeActivityStateI:selfI];
+
+ [selfI setEnabled:NO];
+ [selfI.packageHandler flush];
+}
+
#pragma mark - private
- (BOOL)isEnabledI:(ADJActivityHandler *)selfI {
@@ -1313,6 +1416,14 @@ - (BOOL)isEnabledI:(ADJActivityHandler *)selfI {
}
}
+- (BOOL)isGdprForgottenI:(ADJActivityHandler *)selfI {
+ if (selfI.activityState != nil) {
+ return selfI.activityState.isGdprForgotten;
+ } else {
+ return NO;
+ }
+}
+
- (BOOL)itHasToUpdatePackagesI:(ADJActivityHandler *)selfI {
if (selfI.activityState != nil) {
return selfI.activityState.updatePackages;
diff --git a/Adjust/ADJActivityKind.h b/Adjust/ADJActivityKind.h
index 4beccccc2..728f13b92 100644
--- a/Adjust/ADJActivityKind.h
+++ b/Adjust/ADJActivityKind.h
@@ -20,6 +20,7 @@ typedef NS_ENUM(int, ADJActivityKind) {
ADJActivityKindClick = 4,
ADJActivityKindAttribution = 5,
ADJActivityKindInfo = 6,
+ ADJActivityKindGdpr = 7
};
@interface ADJActivityKindUtil : NSObject
diff --git a/Adjust/ADJActivityKind.m b/Adjust/ADJActivityKind.m
index 9fce00922..bc525f497 100644
--- a/Adjust/ADJActivityKind.m
+++ b/Adjust/ADJActivityKind.m
@@ -23,6 +23,8 @@ + (ADJActivityKind)activityKindFromString:(NSString *)activityKindString {
return ADJActivityKindAttribution;
} else if ([@"info" isEqualToString:activityKindString]) {
return ADJActivityKindInfo;
+ } else if ([@"gdpr" isEqualToString:activityKindString]) {
+ return ADJActivityKindGdpr;
} else {
return ADJActivityKindUnknown;
}
@@ -40,6 +42,8 @@ + (NSString *)activityKindToString:(ADJActivityKind)activityKind {
return @"attribution";
case ADJActivityKindInfo:
return @"info";
+ case ADJActivityKindGdpr:
+ return @"gdpr";
default:
return @"unknown";
}
diff --git a/Adjust/ADJActivityState.h b/Adjust/ADJActivityState.h
index 35e46d194..45327850c 100644
--- a/Adjust/ADJActivityState.h
+++ b/Adjust/ADJActivityState.h
@@ -12,6 +12,7 @@
// Persistent data
@property (nonatomic, assign) BOOL enabled;
+@property (nonatomic, assign) BOOL isGdprForgotten;
@property (nonatomic, assign) BOOL askingAttribution;
@property (nonatomic, copy) NSString *uuid;
diff --git a/Adjust/ADJActivityState.m b/Adjust/ADJActivityState.m
index 3dbd054b1..df58eaeb9 100644
--- a/Adjust/ADJActivityState.m
+++ b/Adjust/ADJActivityState.m
@@ -36,6 +36,7 @@ - (id)init {
self.lastActivity = -1;
self.lastInterval = -1;
self.enabled = YES;
+ self.isGdprForgotten = NO;
self.askingAttribution = NO;
self.deviceToken = nil;
self.transactionIds = [NSMutableArray arrayWithCapacity:kTransactionIdCount];
@@ -150,9 +151,9 @@ - (NSString *)generateUniqueKey {
}
- (NSString *)description {
- return [NSString stringWithFormat:@"ec:%d sc:%d ssc:%d ask:%d sl:%.1f ts:%.1f la:%.1f dt:%@",
+ return [NSString stringWithFormat:@"ec:%d sc:%d ssc:%d ask:%d sl:%.1f ts:%.1f la:%.1f dt:%@ gdprf:%d",
self.eventCount, self.sessionCount, self.subsessionCount, self.askingAttribution, self.sessionLength,
- self.timeSpent, self.lastActivity, self.deviceToken];
+ self.timeSpent, self.lastActivity, self.deviceToken, self.isGdprForgotten];
}
#pragma mark - NSCoding protocol methods
@@ -193,6 +194,12 @@ - (id)initWithCoder:(NSCoder *)decoder {
} else {
self.enabled = YES;
}
+
+ if ([decoder containsValueForKey:@"isGdprForgotten"]) {
+ self.isGdprForgotten = [decoder decodeBoolForKey:@"isGdprForgotten"];
+ } else {
+ self.isGdprForgotten = NO;
+ }
if ([decoder containsValueForKey:@"askingAttribution"]) {
self.askingAttribution = [decoder decodeBoolForKey:@"askingAttribution"];
@@ -233,6 +240,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.uuid forKey:@"uuid"];
[encoder encodeObject:self.transactionIds forKey:@"transactionIds"];
[encoder encodeBool:self.enabled forKey:@"enabled"];
+ [encoder encodeBool:self.isGdprForgotten forKey:@"isGdprForgotten"];
[encoder encodeBool:self.askingAttribution forKey:@"askingAttribution"];
[encoder encodeObject:self.deviceToken forKey:@"deviceToken"];
[encoder encodeBool:self.updatePackages forKey:@"updatePackages"];
@@ -255,6 +263,7 @@ - (id)copyWithZone:(NSZone *)zone {
copy.lastInterval = self.lastInterval;
copy.eventCount = self.eventCount;
copy.enabled = self.enabled;
+ copy.isGdprForgotten = self.isGdprForgotten;
copy.lastActivity = self.lastActivity;
copy.askingAttribution = self.askingAttribution;
copy.deviceToken = [self.deviceToken copyWithZone:zone];
diff --git a/Adjust/ADJAdjustFactory.h b/Adjust/ADJAdjustFactory.h
index 115d33f27..e8d73160d 100644
--- a/Adjust/ADJAdjustFactory.h
+++ b/Adjust/ADJAdjustFactory.h
@@ -20,7 +20,8 @@
+ (id)packageHandlerForActivityHandler:(id)activityHandler
startsSending:(BOOL)startsSending;
-+ (id)requestHandlerForPackageHandler:(id)packageHandler;
++ (id)requestHandlerForPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler;
+ (id)activityHandlerWithConfig:(ADJConfig *)adjustConfig
savedPreLaunch:(ADJSavedPreLaunch *)savedPreLaunch;
+ (id)sdkClickHandlerForActivityHandler:(id)activityHandler
@@ -40,6 +41,7 @@
+ (BOOL)testing;
+ (NSTimeInterval)maxDelayStart;
+ (NSString *)baseUrl;
++ (NSString *)gdprUrl;
+ (void)setPackageHandler:(id)packageHandler;
+ (void)setRequestHandler:(id)requestHandler;
@@ -56,6 +58,7 @@
+ (void)setTesting:(BOOL)testing;
+ (void)setMaxDelayStart:(NSTimeInterval)maxDelayStart;
+ (void)setBaseUrl:(NSString *)baseUrl;
++ (void)setGdprUrl:(NSString *)gdprUrl;
+ (void)teardown:(BOOL)deleteState;
@end
diff --git a/Adjust/ADJAdjustFactory.m b/Adjust/ADJAdjustFactory.m
index b1e734c1b..5f515eecf 100644
--- a/Adjust/ADJAdjustFactory.m
+++ b/Adjust/ADJAdjustFactory.m
@@ -26,6 +26,8 @@
static NSString * const kBaseUrl = @"https://app.adjust.com";
static NSString * internalBaseUrl = @"https://app.adjust.com";
+static NSString * const kGdprUrl = @"https://gdpr.adjust.com";
+static NSString * internalGdprUrl = @"https://gdpr.adjust.com";
@implementation ADJAdjustFactory
@@ -38,11 +40,14 @@ @implementation ADJAdjustFactory
return [internalPackageHandler initWithActivityHandler:activityHandler startsSending:startsSending];
}
-+ (id)requestHandlerForPackageHandler:(id)packageHandler {
++ (id)requestHandlerForPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler {
if (internalRequestHandler == nil) {
- return [ADJRequestHandler handlerWithPackageHandler:packageHandler];
+ return [ADJRequestHandler handlerWithPackageHandler:packageHandler
+ andActivityHandler:activityHandler];
}
- return [internalRequestHandler initWithPackageHandler:packageHandler];
+ return [internalRequestHandler initWithPackageHandler:packageHandler
+ andActivityHandler:activityHandler];
}
+ (id)activityHandlerWithConfig:(ADJConfig *)adjustConfig
@@ -147,6 +152,10 @@ + (NSString *)baseUrl {
return internalBaseUrl;
}
++ (NSString *)gdprUrl {
+ return internalGdprUrl;
+}
+
+ (void)setPackageHandler:(id)packageHandler {
internalPackageHandler = packageHandler;
}
@@ -207,6 +216,10 @@ + (void)setBaseUrl:(NSString *)baseUrl {
internalBaseUrl = baseUrl;
}
++ (void)setGdprUrl:(NSString *)gdprUrl {
+ internalGdprUrl = gdprUrl;
+}
+
+ (void)teardown:(BOOL)deleteState {
if (deleteState) {
[ADJActivityHandler deleteState];
@@ -228,5 +241,6 @@ + (void)teardown:(BOOL)deleteState {
internalTesting = NO;
internalMaxDelayStart = -1;
internalBaseUrl = kBaseUrl;
+ internalGdprUrl = kGdprUrl;
}
@end
diff --git a/Adjust/ADJAttributionHandler.m b/Adjust/ADJAttributionHandler.m
index 98f309385..b6fafc384 100644
--- a/Adjust/ADJAttributionHandler.m
+++ b/Adjust/ADJAttributionHandler.m
@@ -98,7 +98,8 @@ - (void)getAttribution {
selfInject:self
block:^(ADJAttributionHandler* selfI) {
[selfI waitRequestAttributionWithDelayI:selfI
- milliSecondsDelay:0];
+ milliSecondsDelay:0
+ isSdkAskingForIt:YES];
}];
}
@@ -147,7 +148,8 @@ - (void)checkAttributionI:(ADJAttributionHandler*)selfI
[selfI.activityHandler setAskingAttribution:YES];
[selfI waitRequestAttributionWithDelayI:selfI
- milliSecondsDelay:[timerMilliseconds intValue]];
+ milliSecondsDelay:[timerMilliseconds intValue]
+ isSdkAskingForIt:NO];
return;
}
@@ -182,6 +184,10 @@ - (void)requestAttributionI:(ADJAttributionHandler*)selfI {
[selfI.logger debug:@"Attribution handler is paused"];
return;
}
+ if ([selfI.activityHandler isGdprForgotten]) {
+ [selfI.logger debug:@"Attribution request won't be fired for forgotten user"];
+ return;
+ }
[selfI.logger verbose:@"%@", selfI.attributionPackage.extendedString];
NSURL * baseUrl = [NSURL URLWithString:[ADJAdjustFactory baseUrl]];
@@ -192,6 +198,13 @@ - (void)requestAttributionI:(ADJAttributionHandler*)selfI {
activityPackage:selfI.attributionPackage
responseDataHandler:^(ADJResponseData * responseData)
{
+ // 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) {
+ [selfI.activityHandler setTrackingStateOptedOut];
+ return;
+ }
+
if ([responseData isKindOfClass:[ADJAttributionResponseData class]]) {
[selfI checkAttributionResponse:(ADJAttributionResponseData*)responseData];
}
@@ -200,13 +213,19 @@ - (void)requestAttributionI:(ADJAttributionHandler*)selfI {
- (void)waitRequestAttributionWithDelayI:(ADJAttributionHandler*)selfI
milliSecondsDelay:(int)milliSecondsDelay
-{
+ isSdkAskingForIt:(BOOL)isSdkAsking {
NSTimeInterval secondsDelay = milliSecondsDelay / 1000;
NSTimeInterval nextAskIn = [selfI.attributionTimer fireIn];
if (nextAskIn > secondsDelay) {
return;
}
+ if (isSdkAsking) {
+ [selfI.attributionPackage.parameters setObject:@"sdk" forKey:@"initiated_by"];
+ } else {
+ [selfI.attributionPackage.parameters setObject:@"backend" forKey:@"initiated_by"];
+ }
+
if (milliSecondsDelay > 0) {
[selfI.logger debug:@"Waiting to query attribution in %d milliseconds", milliSecondsDelay];
}
diff --git a/Adjust/ADJPackageBuilder.h b/Adjust/ADJPackageBuilder.h
index 2f2ed2117..a8a82194d 100644
--- a/Adjust/ADJPackageBuilder.h
+++ b/Adjust/ADJPackageBuilder.h
@@ -37,6 +37,8 @@
- (ADJActivityPackage *)buildAttributionPackage;
+- (ADJActivityPackage *)buildGdprPackage;
+
- (ADJActivityPackage *)buildEventPackage:(ADJEvent *)event
isInDelay:(BOOL)isInDelay;
diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m
index 3c68bd080..c34d9a076 100644
--- a/Adjust/ADJPackageBuilder.m
+++ b/Adjust/ADJPackageBuilder.m
@@ -171,6 +171,19 @@ - (ADJActivityPackage *)buildAttributionPackage {
return attributionPackage;
}
+- (ADJActivityPackage *)buildGdprPackage {
+ NSMutableDictionary *parameters = [self idsParameters];
+ // [ADJPackageBuilder parameters:parameters setString:@"push" forKey:@"source"];
+
+ ADJActivityPackage *gdprPackage = [self defaultActivityPackage];
+ gdprPackage.path = @"/gdpr_forget_device";
+ gdprPackage.activityKind = ADJActivityKindGdpr;
+ gdprPackage.suffix = @"";
+ gdprPackage.parameters = parameters;
+
+ return gdprPackage;
+}
+
#pragma mark - Private & helper methods
- (ADJActivityPackage *)defaultActivityPackage {
diff --git a/Adjust/ADJPackageHandler.h b/Adjust/ADJPackageHandler.h
index 66c3b871a..a69a2df97 100644
--- a/Adjust/ADJPackageHandler.h
+++ b/Adjust/ADJPackageHandler.h
@@ -26,7 +26,9 @@
- (void)pauseSending;
- (void)resumeSending;
- (void)updatePackages:(ADJSessionParameters *)sessionParameters;
+- (void)flush;
- (NSString *)getBasePath;
+- (NSString *)getGdprPath;
- (void)teardown;
+ (void)deleteState;
diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m
index 6d6f2c49f..48f22115f 100644
--- a/Adjust/ADJPackageHandler.m
+++ b/Adjust/ADJPackageHandler.m
@@ -30,6 +30,7 @@ @interface ADJPackageHandler()
@property (nonatomic, weak) id activityHandler;
@property (nonatomic, weak) id logger;
@property (nonatomic, copy) NSString *basePath;
+@property (nonatomic, copy) NSString *gdprPath;
@end
@@ -51,6 +52,7 @@ - (id)initWithActivityHandler:(id)activityHandler
self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL);
self.backoffStrategy = [ADJAdjustFactory packageHandlerBackoffStrategy];
self.basePath = [activityHandler getBasePath];
+ self.gdprPath = [activityHandler getGdprPath];
[ADJUtil launchInQueue:self.internalQueue
selfInject:self
@@ -135,10 +137,20 @@ - (void)updatePackages:(ADJSessionParameters *)sessionParameters
}];
}
+- (void)flush {
+ [ADJUtil launchInQueue:self.internalQueue selfInject:self block:^(ADJPackageHandler *selfI) {
+ [selfI flushI:selfI];
+ }];
+}
+
- (NSString *)getBasePath {
return _basePath;
}
+- (NSString *)getGdprPath {
+ return _gdprPath;
+}
+
- (void)teardown {
[ADJAdjustFactory.logger verbose:@"ADJPackageHandler teardown"];
if (self.sendingSemaphore != nil) {
@@ -171,7 +183,8 @@ - (void)initI:(ADJPackageHandler *)selfI
{
selfI.activityHandler = activityHandler;
selfI.paused = !startsSending;
- selfI.requestHandler = [ADJAdjustFactory requestHandlerForPackageHandler:selfI];
+ selfI.requestHandler = [ADJAdjustFactory requestHandlerForPackageHandler:selfI
+ andActivityHandler:selfI.activityHandler];
selfI.logger = ADJAdjustFactory.logger;
selfI.sendingSemaphore = dispatch_semaphore_create(1);
[selfI readPackageQueueI:selfI];
@@ -214,8 +227,11 @@ - (void)sendFirstI:(ADJPackageHandler *)selfI
}
- (void)sendNextI:(ADJPackageHandler *)selfI {
- [selfI.packageQueue removeObjectAtIndex:0];
- [selfI writePackageQueueS:selfI];
+ if ([selfI.packageQueue count] > 0) {
+ [selfI.packageQueue removeObjectAtIndex:0];
+ [selfI writePackageQueueS:selfI];
+ }
+
dispatch_semaphore_signal(selfI.sendingSemaphore);
[selfI sendFirstI:selfI];
}
@@ -250,6 +266,11 @@ - (void)updatePackagesI:(ADJPackageHandler *)selfI
[selfI writePackageQueueS:selfI];
}
+- (void)flushI:(ADJPackageHandler *)selfI {
+ [selfI.packageQueue removeAllObjects];
+ [selfI writePackageQueueS:selfI];
+}
+
#pragma mark - private
- (void)readPackageQueueI:(ADJPackageHandler *)selfI {
[NSKeyedUnarchiver setClass:[ADJActivityPackage class] forClassName:@"AIActivityPackage"];
diff --git a/Adjust/ADJRequestHandler.h b/Adjust/ADJRequestHandler.h
index 197e538ed..5dc8d1772 100644
--- a/Adjust/ADJRequestHandler.h
+++ b/Adjust/ADJRequestHandler.h
@@ -7,14 +7,15 @@
//
#import
-
#import "ADJPackageHandler.h"
@protocol ADJRequestHandler
-- (id)initWithPackageHandler:(id)packageHandler;
+- (id)initWithPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler;
-- (void)sendPackage:(ADJActivityPackage *)activityPackage queueSize:(NSUInteger)queueSize;
+- (void)sendPackage:(ADJActivityPackage *)activityPackage
+ queueSize:(NSUInteger)queueSize;
- (void)teardown;
@@ -22,6 +23,7 @@
@interface ADJRequestHandler : NSObject
-+ (id)handlerWithPackageHandler:(id)packageHandler;
++ (id)handlerWithPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler;
@end
diff --git a/Adjust/ADJRequestHandler.m b/Adjust/ADJRequestHandler.m
index 30834ca6c..5e8e1bc90 100644
--- a/Adjust/ADJRequestHandler.m
+++ b/Adjust/ADJRequestHandler.m
@@ -24,19 +24,26 @@ @interface ADJRequestHandler()
@property (nonatomic, weak) id packageHandler;
+@property (nonatomic, weak) id activityHandler;
+
@property (nonatomic, copy) NSString *basePath;
+@property (nonatomic, copy) NSString *gdprPath;
+
@end
@implementation ADJRequestHandler
#pragma mark - Public methods
-+ (ADJRequestHandler *)handlerWithPackageHandler:(id)packageHandler {
- return [[ADJRequestHandler alloc] initWithPackageHandler:packageHandler];
++ (ADJRequestHandler *)handlerWithPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler {
+ return [[ADJRequestHandler alloc] initWithPackageHandler:packageHandler
+ andActivityHandler:activityHandler];
}
-- (id)initWithPackageHandler:(id)packageHandler {
+- (id)initWithPackageHandler:(id)packageHandler
+ andActivityHandler:(id)activityHandler {
self = [super init];
if (self == nil) {
@@ -45,8 +52,10 @@ - (id)initWithPackageHandler:(id)packageHandler {
self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL);
self.packageHandler = packageHandler;
+ self.activityHandler = activityHandler;
self.logger = ADJAdjustFactory.logger;
self.basePath = [packageHandler getBasePath];
+ self.gdprPath = [packageHandler getGdprPath];
return self;
}
@@ -65,17 +74,28 @@ - (void)teardown {
self.logger = nil;
self.internalQueue = nil;
self.packageHandler = nil;
+ self.activityHandler = nil;
}
#pragma mark - Private & helper methods
- (void)sendI:(ADJRequestHandler *)selfI activityPackage:(ADJActivityPackage *)activityPackage queueSize:(NSUInteger)queueSize {
NSURL *url;
- NSString * baseUrl = [ADJAdjustFactory baseUrl];
- if (selfI.basePath != nil) {
- url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", baseUrl, selfI.basePath]];
+
+ if (activityPackage.activityKind == ADJActivityKindGdpr) {
+ NSString *gdprUrl = [ADJAdjustFactory gdprUrl];
+ if (selfI.gdprPath != nil) {
+ url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", gdprUrl, selfI.gdprPath]];
+ } else {
+ url = [NSURL URLWithString:gdprUrl];
+ }
} else {
- url = [NSURL URLWithString:baseUrl];
+ NSString *baseUrl = [ADJAdjustFactory baseUrl];
+ if (selfI.basePath != nil) {
+ url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", baseUrl, selfI.basePath]];
+ } else {
+ url = [NSURL URLWithString:baseUrl];
+ }
}
[ADJUtil sendPostRequest:url
@@ -84,12 +104,17 @@ - (void)sendI:(ADJRequestHandler *)selfI activityPackage:(ADJActivityPackage *)a
suffixErrorMessage:@"Will retry later"
activityPackage:activityPackage
responseDataHandler:^(ADJResponseData *responseData) {
+ // 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) {
+ [selfI.activityHandler setTrackingStateOptedOut];
+ return;
+ }
if (responseData.jsonResponse == nil) {
[selfI.packageHandler closeFirstPackage:responseData activityPackage:activityPackage];
-
return;
}
-
+
[selfI.packageHandler sendNextPackage:responseData];
}];
}
diff --git a/Adjust/ADJResponseData.h b/Adjust/ADJResponseData.h
index 378a9bb03..edf0f6f6a 100644
--- a/Adjust/ADJResponseData.h
+++ b/Adjust/ADJResponseData.h
@@ -15,6 +15,10 @@
#import "ADJSessionFailure.h"
#import "ADJActivityPackage.h"
+typedef NS_ENUM(int, ADJTrackingState) {
+ ADJTrackingStateOptedOut = 1
+};
+
@interface ADJResponseData : NSObject
@property (nonatomic, assign) ADJActivityKind activityKind;
@@ -29,6 +33,8 @@
@property (nonatomic, assign) BOOL willRetry;
+@property (nonatomic, assign) ADJTrackingState trackingState;
+
@property (nonatomic, strong) NSDictionary *jsonResponse;
@property (nonatomic, copy) ADJAttribution *attribution;
diff --git a/Adjust/ADJResponseData.m b/Adjust/ADJResponseData.m
index 9ca71e45b..a929a6095 100644
--- a/Adjust/ADJResponseData.m
+++ b/Adjust/ADJResponseData.m
@@ -60,8 +60,8 @@ + (id)buildResponseData:(ADJActivityPackage *)activityPackage {
}
- (NSString *)description {
- return [NSString stringWithFormat:@"message:%@ timestamp:%@ adid:%@ success:%d willRetry:%d attribution:%@ json:%@",
- self.message, self.timeStamp, self.adid, self.success, self.willRetry, self.attribution, self.jsonResponse];
+ return [NSString stringWithFormat:@"message:%@ timestamp:%@ adid:%@ success:%d willRetry:%d attribution:%@ trackingState:%d, json:%@",
+ self.message, self.timeStamp, self.adid, self.success, self.willRetry, self.attribution, self.trackingState, self.jsonResponse];
}
#pragma mark - NSCopying
@@ -75,6 +75,7 @@ - (id)copyWithZone:(NSZone *)zone {
copy.adid = [self.adid copyWithZone:zone];
copy.success = self.success;
copy.willRetry = self.willRetry;
+ copy.trackingState = self.trackingState;
copy.jsonResponse = [self.jsonResponse copyWithZone:zone];
copy.attribution = [self.attribution copyWithZone:zone];
}
diff --git a/Adjust/ADJSdkClickHandler.h b/Adjust/ADJSdkClickHandler.h
index 3f8c63bcd..b74ddfda7 100644
--- a/Adjust/ADJSdkClickHandler.h
+++ b/Adjust/ADJSdkClickHandler.h
@@ -1,9 +1,9 @@
//
// ADJSdkClickHandler.h
-// Adjust
+// Adjust SDK
//
-// Created by Pedro Filipe on 21/04/16.
-// Copyright © 2016 adjust GmbH. All rights reserved.
+// Created by Pedro Filipe (@nonelse) on 21st April 2016.
+// Copyright © 2016 Adjust GmbH. All rights reserved.
//
#import
@@ -14,7 +14,6 @@
- (id)initWithActivityHandler:(id)activityHandler
startsSending:(BOOL)startsSending;
-
- (void)pauseSending;
- (void)resumeSending;
- (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage;
diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m
index 8781c9063..fe8da9019 100644
--- a/Adjust/ADJSdkClickHandler.m
+++ b/Adjust/ADJSdkClickHandler.m
@@ -1,55 +1,59 @@
//
// ADJSdkClickHandler.m
-// Adjust
+// Adjust SDK
//
-// Created by Pedro Filipe on 21/04/16.
-// Copyright © 2016 adjust GmbH. All rights reserved.
+// Created by Pedro Filipe (@nonelse) on 21st April 2016.
+// Copyright © 2016 Adjust GmbH. All rights reserved.
//
-#import "ADJSdkClickHandler.h"
+#import "ADJUtil.h"
#import "ADJLogger.h"
#import "ADJAdjustFactory.h"
+#import "ADJSdkClickHandler.h"
#import "ADJBackoffStrategy.h"
-#import "ADJUtil.h"
-static const char * const kInternalQueueName = "com.adjust.SdkClickQueue";
+static const char * const kInternalQueueName = "com.adjust.SdkClickQueue";
-#pragma mark - private
@interface ADJSdkClickHandler()
+@property (nonatomic, copy) NSString *basePath;
+@property (nonatomic, strong) NSMutableArray *packageQueue;
@property (nonatomic, strong) dispatch_queue_t internalQueue;
-@property (nonatomic, weak) id logger;
-@property (nonatomic, strong) ADJBackoffStrategy * backoffStrategy;
+
@property (nonatomic, assign) BOOL paused;
-@property (nonatomic, strong) NSMutableArray *packageQueue;
+@property (nonatomic, strong) ADJBackoffStrategy *backoffStrategy;
+
+@property (nonatomic, weak) id logger;
@property (nonatomic, weak) id activityHandler;
-@property (nonatomic, copy) NSString *basePath;
@end
@implementation ADJSdkClickHandler
+#pragma mark - Public class methods
+
+ (id)handlerWithActivityHandler:(id)activityHandler
- startsSending:(BOOL)startsSending
-{
+ startsSending:(BOOL)startsSending {
return [[ADJSdkClickHandler alloc] initWithActivityHandler:activityHandler
- startsSending:startsSending];
+ startsSending:startsSending];
}
+#pragma mark - Public instance methods
+
- (id)initWithActivityHandler:(id)activityHandler
- startsSending:(BOOL)startsSending
-{
+ startsSending:(BOOL)startsSending {
self = [super init];
- if (self == nil) return nil;
+ if (self == nil) {
+ return nil;
+ }
self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL);
-
self.logger = ADJAdjustFactory.logger;
self.basePath = [activityHandler getBasePath];
[ADJUtil launchInQueue:self.internalQueue
selfInject:self
- block:^(ADJSdkClickHandler * selfI) {
+ block:^(ADJSdkClickHandler *selfI) {
[selfI initI:selfI
activityHandler:activityHandler
startsSending:startsSending];
@@ -63,14 +67,13 @@ - (void)pauseSending {
- (void)resumeSending {
self.paused = NO;
-
[self sendNextSdkClick];
}
- (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage {
[ADJUtil launchInQueue:self.internalQueue
selfInject:self
- block:^(ADJSdkClickHandler * selfI) {
+ block:^(ADJSdkClickHandler *selfI) {
[selfI sendSdkClickI:selfI sdkClickPackage:sdkClickPackage];
}];
}
@@ -78,16 +81,18 @@ - (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage {
- (void)sendNextSdkClick {
[ADJUtil launchInQueue:self.internalQueue
selfInject:self
- block:^(ADJSdkClickHandler * selfI) {
+ block:^(ADJSdkClickHandler *selfI) {
[selfI sendNextSdkClickI:selfI];
}];
}
- (void)teardown {
[ADJAdjustFactory.logger verbose:@"ADJSdkClickHandler teardown"];
+
if (self.packageQueue != nil) {
[self.packageQueue removeAllObjects];
}
+
self.internalQueue = nil;
self.logger = nil;
self.backoffStrategy = nil;
@@ -95,11 +100,11 @@ - (void)teardown {
self.activityHandler = nil;
}
-#pragma mark - internal
-- (void)initI:(ADJSdkClickHandler *)selfI
+#pragma mark - Private & helper methods
+
+- (void)initI:(ADJSdkClickHandler *)selfI
activityHandler:(id)activityHandler
-startsSending:(BOOL)startsSending
-{
+ startsSending:(BOOL)startsSending {
selfI.activityHandler = activityHandler;
selfI.paused = !startsSending;
selfI.backoffStrategy = [ADJAdjustFactory sdkClickHandlerBackoffStrategy];
@@ -107,35 +112,37 @@ - (void)initI:(ADJSdkClickHandler *)selfI
}
- (void)sendSdkClickI:(ADJSdkClickHandler *)selfI
- sdkClickPackage:(ADJActivityPackage *)sdkClickPackage
-{
+ sdkClickPackage:(ADJActivityPackage *)sdkClickPackage {
[selfI.packageQueue addObject:sdkClickPackage];
-
[selfI.logger debug:@"Added sdk_click %d", selfI.packageQueue.count];
[selfI.logger verbose:@"%@", sdkClickPackage.extendedString];
-
[selfI sendNextSdkClick];
}
-- (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI
-{
- if (selfI.paused) return;
+- (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI {
+ if (selfI.paused) {
+ return;
+ }
NSUInteger queueSize = selfI.packageQueue.count;
- if (queueSize == 0) return;
+ if (queueSize == 0) {
+ return;
+ }
+ if ([selfI.activityHandler isGdprForgotten]) {
+ [selfI.logger debug:@"sdk_click request won't be fired for forgotten user"];
+ return;
+ }
ADJActivityPackage *sdkClickPackage = [self.packageQueue objectAtIndex:0];
[self.packageQueue removeObjectAtIndex:0];
if (![sdkClickPackage isKindOfClass:[ADJActivityPackage class]]) {
[selfI.logger error:@"Failed to read sdk_click package"];
-
[selfI sendNextSdkClick];
-
return;
}
NSURL *url;
- NSString * baseUrl = [ADJAdjustFactory baseUrl];
+ NSString *baseUrl = [ADJAdjustFactory baseUrl];
if (selfI.basePath != nil) {
url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", baseUrl, selfI.basePath]];
} else {
@@ -148,12 +155,16 @@ - (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI
prefixErrorMessage:sdkClickPackage.failureMessage
suffixErrorMessage:@"Will retry later"
activityPackage:sdkClickPackage
- responseDataHandler:^(ADJResponseData * responseData)
- {
+ responseDataHandler:^(ADJResponseData *responseData) {
+ // 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) {
+ [selfI.activityHandler setTrackingStateOptedOut];
+ return;
+ }
if (responseData.jsonResponse == nil) {
NSInteger retries = [sdkClickPackage increaseRetries];
[selfI.logger error:@"Retrying sdk_click package for the %d time", retries];
-
[selfI sendSdkClick:sdkClickPackage];
return;
}
@@ -165,14 +176,13 @@ - (void)sendNextSdkClickI:(ADJSdkClickHandler *)selfI
};
NSInteger retries = [sdkClickPackage retries];
-
if (retries <= 0) {
work();
return;
}
NSTimeInterval waitTime = [ADJUtil waitingTime:retries backoffStrategy:self.backoffStrategy];
- NSString * waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime];
+ NSString *waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime];
[self.logger verbose:@"Waiting for %@ seconds before retrying sdk_click for the %d time", waitTimeFormatted, retries];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), self.internalQueue, work);
diff --git a/Adjust/ADJUserDefaults.h b/Adjust/ADJUserDefaults.h
index 0c66531e5..cb20f548f 100644
--- a/Adjust/ADJUserDefaults.h
+++ b/Adjust/ADJUserDefaults.h
@@ -22,4 +22,10 @@
+ (BOOL)getInstallTracked;
++ (void)setGdprForgetMe;
+
++ (BOOL)getGdprForgetMe;
+
++ (void)removeGdprForgetMe;
+
@end
diff --git a/Adjust/ADJUserDefaults.m b/Adjust/ADJUserDefaults.m
index 103f159d4..09d92b853 100644
--- a/Adjust/ADJUserDefaults.m
+++ b/Adjust/ADJUserDefaults.m
@@ -9,6 +9,7 @@
#import "ADJUserDefaults.h"
static NSString * const PREFS_KEY_PUSH_TOKEN = @"adj_push_token";
+static NSString * const PREFS_KEY_GDPR_FORGET_ME = @"adj_gdpr_forget_me";
static NSString * const PREFS_KEY_INSTALL_TRACKED = @"adj_install_tracked";
@implementation ADJUserDefaults
@@ -38,9 +39,24 @@ + (BOOL)getInstallTracked {
return [[NSUserDefaults standardUserDefaults] boolForKey:PREFS_KEY_INSTALL_TRACKED];
}
++ (void)setGdprForgetMe {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:PREFS_KEY_GDPR_FORGET_ME];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
++ (BOOL)getGdprForgetMe {
+ return [[NSUserDefaults standardUserDefaults] boolForKey:PREFS_KEY_GDPR_FORGET_ME];
+}
+
++ (void)removeGdprForgetMe {
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_GDPR_FORGET_ME];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+ (void)clearAdjustStuff {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_PUSH_TOKEN];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_INSTALL_TRACKED];
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:PREFS_KEY_GDPR_FORGET_ME];
[[NSUserDefaults standardUserDefaults] synchronize];
}
diff --git a/Adjust/ADJUtil.m b/Adjust/ADJUtil.m
index d796ba92b..7ab079c32 100644
--- a/Adjust/ADJUtil.m
+++ b/Adjust/ADJUtil.m
@@ -41,7 +41,7 @@
static NSString *userAgent = nil;
-static NSString * const kClientSdk = @"ios4.12.3";
+static NSString * const kClientSdk = @"ios4.13.0";
static NSString * const kDeeplinkParam = @"deep_link=";
static NSString * const kSchemeDelimiter = @"://";
static NSString * const kDefaultScheme = @"AdjustUniversalScheme";
@@ -860,6 +860,12 @@ + (ADJResponseData *)completionHandler:(NSData *)data
NSInteger statusCode = urlResponse.statusCode;
[ADJAdjustFactory.logger verbose:@"Response: %@", responseString];
+
+ if (statusCode == 429) {
+ [ADJAdjustFactory.logger error:@"Too frequent requests to the endpoint (429)"];
+ return responseData;
+ }
+
[ADJUtil saveJsonResponse:data responseData:responseData];
if ([ADJUtil isNull:responseData.jsonResponse]) {
@@ -872,6 +878,13 @@ + (ADJResponseData *)completionHandler:(NSData *)data
responseData.timeStamp = [responseData.jsonResponse objectForKey:@"timestamp"];
responseData.adid = [responseData.jsonResponse objectForKey:@"adid"];
+ NSString *trackingState = [responseData.jsonResponse objectForKey:@"tracking_state"];
+ if (trackingState != nil) {
+ if ([trackingState isEqualToString:@"opted_out"]) {
+ responseData.trackingState = ADJTrackingStateOptedOut;
+ }
+ }
+
if (messageResponse == nil) {
messageResponse = @"No message found";
}
diff --git a/Adjust/Adjust.h b/Adjust/Adjust.h
index c5706bdb1..d2fca9a10 100644
--- a/Adjust/Adjust.h
+++ b/Adjust/Adjust.h
@@ -2,7 +2,7 @@
// Adjust.h
// Adjust
//
-// V4.12.3
+// V4.13.0
// Created by Christian Wellenbrock (wellle) on 23rd July 2013.
// Copyright © 2012-2017 Adjust GmbH. All rights reserved.
//
@@ -14,7 +14,9 @@
@interface AdjustTestOptions : NSObject
@property (nonatomic, copy, nullable) NSString *baseUrl;
+@property (nonatomic, copy, nullable) NSString *gdprUrl;
@property (nonatomic, copy, nullable) NSString *basePath;
+@property (nonatomic, copy, nullable) NSString *gdprPath;
@property (nonatomic, copy, nullable) NSNumber *timerIntervalInMilliseconds;
@property (nonatomic, copy, nullable) NSNumber *timerStartInMilliseconds;
@property (nonatomic, copy, nullable) NSNumber *sessionIntervalInMilliseconds;
@@ -205,6 +207,11 @@ extern NSString * __nonnull const ADJEnvironmentProduction;
*/
+ (void)resetSessionPartnerParameters;
+/**
+ * @brief Give right user to be forgotten in accordance with GDPR law.
+ */
++ (void)gdprForgetMe;
+
/**
* Obtain singleton Adjust object.
*/
@@ -244,6 +251,8 @@ extern NSString * __nonnull const ADJEnvironmentProduction;
- (void)addSessionCallbackParameter:(nonnull NSString *)key value:(nonnull NSString *)value;
+- (void)gdprForgetMe;
+
- (BOOL)isEnabled;
- (nullable NSString *)adid;
diff --git a/Adjust/Adjust.m b/Adjust/Adjust.m
index 9a4fddaca..6f18c4092 100644
--- a/Adjust/Adjust.m
+++ b/Adjust/Adjust.m
@@ -144,6 +144,10 @@ + (void)resetSessionPartnerParameters {
[[Adjust getInstance] resetSessionPartnerParameters];
}
++ (void)gdprForgetMe {
+ [[Adjust getInstance] gdprForgetMe];
+}
+
+ (ADJAttribution *)attribution {
return [[Adjust getInstance] attribution];
}
@@ -352,6 +356,16 @@ - (void)resetSessionPartnerParameters {
}];
}
+- (void)gdprForgetMe {
+ [ADJUserDefaults setGdprForgetMe];
+
+ if ([self checkActivityHandler:@"GDPR forget me"]) {
+ if (self.activityHandler.isEnabled) {
+ [self.activityHandler setGdprForgetMe];
+ }
+ }
+}
+
- (ADJAttribution *)attribution {
if (![self checkActivityHandler]) {
return nil;
@@ -382,9 +396,15 @@ - (void)setTestOptions:(AdjustTestOptions *)testOptions {
if (testOptions.basePath != nil) {
self.savedPreLaunch.basePath = testOptions.basePath;
}
+ if (testOptions.gdprPath != nil) {
+ self.savedPreLaunch.gdprPath = testOptions.gdprPath;
+ }
if (testOptions.baseUrl != nil) {
[ADJAdjustFactory setBaseUrl:testOptions.baseUrl];
}
+ if (testOptions.gdprUrl != nil) {
+ [ADJAdjustFactory setGdprUrl:testOptions.gdprUrl];
+ }
if (testOptions.timerIntervalInMilliseconds != nil) {
NSTimeInterval timerIntervalInSeconds = [testOptions.timerIntervalInMilliseconds intValue] / 1000.0;
[ADJAdjustFactory setTimerInterval:timerIntervalInSeconds];
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.h b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.h
index c3282565d..f63688305 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.h
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.h
@@ -2,8 +2,8 @@
// ATAAdjustCommandExecutor.h
// AdjustTestApp
//
-// Created by Pedro da Silva (@nonelse) on 23rd August 2017.
-// Copyright © 2017 Adjust GmbH. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
#import
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m
index 67f58bed7..879b9fab7 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ATAAdjustCommandExecutor.m
@@ -2,8 +2,8 @@
// ATAAdjustCommandExecutor.m
// AdjustTestApp
//
-// Created by Pedro da Silva (@nonelse) on 23rd August 2017.
-// Copyright © 2017 Adjust GmbH. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
#import "Adjust.h"
@@ -19,10 +19,11 @@
@interface ATAAdjustCommandExecutor ()
+@property (nonatomic, copy) NSString *basePath;
+@property (nonatomic, copy) NSString *gdprPath;
@property (nonatomic, strong) NSMutableDictionary *savedConfigs;
@property (nonatomic, strong) NSMutableDictionary *savedEvents;
@property (nonatomic, strong) NSObject *adjustDelegate;
-@property (nonatomic, copy) NSString *basePath;
@end
@@ -39,6 +40,7 @@ - (id)init {
self.savedEvents = [NSMutableDictionary dictionary];
self.adjustDelegate = nil;
self.basePath = nil;
+ self.gdprPath = nil;
return self;
}
@@ -84,14 +86,19 @@ - (void)executeCommand:(NSString *)className
[self setPushToken:parameters];
} else if ([methodName isEqualToString:@"openDeeplink"]) {
[self openDeeplink:parameters];
+ } else if ([methodName isEqualToString:@"gdprForgetMe"]) {
+ [self gdprForgetMe:parameters];
}
}
- (void)testOptions:(NSDictionary *)parameters {
- AdjustTestOptions * testOptions = [[AdjustTestOptions alloc] init];
+ AdjustTestOptions *testOptions = [[AdjustTestOptions alloc] init];
testOptions.baseUrl = baseUrl;
+ testOptions.gdprUrl = gdprUrl;
+
if ([parameters objectForKey:@"basePath"]) {
self.basePath = [parameters objectForKey:@"basePath"][0];
+ self.gdprPath = [parameters objectForKey:@"basePath"][0];
}
if ([parameters objectForKey:@"timerInterval"]) {
NSString *timerIntervalMilliS = [parameters objectForKey:@"timerInterval"][0];
@@ -116,6 +123,7 @@ - (void)testOptions:(NSDictionary *)parameters {
if ([teardownOption isEqualToString:@"resetSdk"]) {
testOptions.teardown = YES;
testOptions.basePath = self.basePath;
+ testOptions.gdprPath = self.gdprPath;
}
if ([teardownOption isEqualToString:@"deleteState"]) {
testOptions.deleteState = YES;
@@ -131,12 +139,14 @@ - (void)testOptions:(NSDictionary *)parameters {
if ([teardownOption isEqualToString:@"sdk"]) {
testOptions.teardown = YES;
testOptions.basePath = nil;
+ testOptions.gdprPath = nil;
}
if ([teardownOption isEqualToString:@"test"]) {
self.savedConfigs = nil;
self.savedEvents = nil;
self.adjustDelegate = nil;
self.basePath = nil;
+ self.gdprPath = nil;
testOptions.timerIntervalInMilliseconds = [NSNumber numberWithInt:-1000];
testOptions.timerStartInMilliseconds = [NSNumber numberWithInt:-1000];
testOptions.sessionIntervalInMilliseconds = [NSNumber numberWithInt:-1000];
@@ -144,6 +154,7 @@ - (void)testOptions:(NSDictionary *)parameters {
}
}
}
+
[Adjust setTestOptions:testOptions];
}
@@ -250,41 +261,36 @@ - (void)config:(NSDictionary *)parameters {
if ([parameters objectForKey:@"attributionCallbackSendAll"]) {
NSLog(@"attributionCallbackSendAll detected");
-
- self.adjustDelegate = [[ATAAdjustDelegateAttribution alloc] initWithTestLibrary:self.testLibrary andBasePath:self.basePath];
-
+ self.adjustDelegate = [[ATAAdjustDelegateAttribution alloc] initWithTestLibrary:self.testLibrary
+ andBasePath:self.basePath];
// swizzleAttributionCallback = YES;
}
if ([parameters objectForKey:@"sessionCallbackSendSuccess"]) {
NSLog(@"sessionCallbackSendSuccess detected");
-
- self.adjustDelegate = [[ATAAdjustDelegateSessionSuccess alloc] initWithTestLibrary:self.testLibrary andBasePath:self.basePath];
-
+ self.adjustDelegate = [[ATAAdjustDelegateSessionSuccess alloc] initWithTestLibrary:self.testLibrary
+ andBasePath:self.basePath];
// swizzleSessionSuccessCallback = YES;
}
if ([parameters objectForKey:@"sessionCallbackSendFailure"]) {
NSLog(@"sessionCallbackSendFailure detected");
-
- self.adjustDelegate = [[ATAAdjustDelegateSessionFailure alloc] initWithTestLibrary:self.testLibrary andBasePath:self.basePath];
-
+ self.adjustDelegate = [[ATAAdjustDelegateSessionFailure alloc] initWithTestLibrary:self.testLibrary
+ andBasePath:self.basePath];
// swizzleSessionFailureCallback = YES;
}
if ([parameters objectForKey:@"eventCallbackSendSuccess"]) {
NSLog(@"eventCallbackSendSuccess detected");
-
- self.adjustDelegate = [[ATAAdjustDelegateEventSuccess alloc] initWithTestLibrary:self.testLibrary andBasePath:self.basePath];
-
+ self.adjustDelegate = [[ATAAdjustDelegateEventSuccess alloc] initWithTestLibrary:self.testLibrary
+ andBasePath:self.basePath];
// swizzleEventSuccessCallback = YES;
}
if ([parameters objectForKey:@"eventCallbackSendFailure"]) {
NSLog(@"eventCallbackSendFailure detected");
-
- self.adjustDelegate = [[ATAAdjustDelegateEventFailure alloc] initWithTestLibrary:self.testLibrary andBasePath:self.basePath];
-
+ self.adjustDelegate = [[ATAAdjustDelegateEventFailure alloc] initWithTestLibrary:self.testLibrary
+ andBasePath:self.basePath];
// swizzleEventFailureCallback = YES;
}
@@ -303,7 +309,6 @@ - (void)start:(NSDictionary *)parameters {
[self config:parameters];
NSNumber *configNumber = [NSNumber numberWithInt:0];
-
if ([parameters objectForKey:@"configName"]) {
NSString *configName = [parameters objectForKey:@"configName"][0];
NSString *configNumberS = [configName substringFromIndex:[configName length] - 1];
@@ -311,15 +316,12 @@ - (void)start:(NSDictionary *)parameters {
}
ADJConfig *adjustConfig = [self.savedConfigs objectForKey:configNumber];
-
[Adjust appDidLaunch:adjustConfig];
-
[self.savedConfigs removeObjectForKey:[NSNumber numberWithInt:0]];
}
- (void)event:(NSDictionary *)parameters {
NSNumber *eventNumber = [NSNumber numberWithInt:0];
-
if ([parameters objectForKey:@"eventName"]) {
NSString *eventName = [parameters objectForKey:@"eventName"][0];
NSString *eventNumberS = [eventName substringFromIndex:[eventName length] - 1];
@@ -332,7 +334,6 @@ - (void)event:(NSDictionary *)parameters {
adjustEvent = [self.savedEvents objectForKey:eventNumber];
} else {
NSString *eventToken = [parameters objectForKey:@"eventToken"][0];
-
adjustEvent = [ADJEvent eventWithEventToken:eventToken];
[self.savedEvents setObject:adjustEvent forKey:eventNumber];
}
@@ -341,7 +342,6 @@ - (void)event:(NSDictionary *)parameters {
NSArray *currencyAndRevenue = [parameters objectForKey:@"revenue"];
NSString *currency = currencyAndRevenue[0];
double revenue = [currencyAndRevenue[1] doubleValue];
-
[adjustEvent setRevenue:revenue currency:currency];
}
@@ -376,7 +376,6 @@ - (void)trackEvent:(NSDictionary *)parameters {
[self event:parameters];
NSNumber *eventNumber = [NSNumber numberWithInt:0];
-
if ([parameters objectForKey:@"eventName"]) {
NSString *eventName = [parameters objectForKey:@"eventName"][0];
NSString *eventNumberS = [eventName substringFromIndex:[eventName length] - 1];
@@ -384,9 +383,7 @@ - (void)trackEvent:(NSDictionary *)parameters {
}
ADJEvent *adjustEvent = [self.savedEvents objectForKey:eventNumber];
-
[Adjust trackEvent:adjustEvent];
-
[self.savedEvents removeObjectForKey:[NSNumber numberWithInt:0]];
}
@@ -466,4 +463,8 @@ - (void)openDeeplink:(NSDictionary *)parameters {
[Adjust appWillOpenUrl:deeplink];
}
+- (void)gdprForgetMe:(NSDictionary *)parameters {
+ [Adjust gdprForgetMe];
+}
+
@end
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.h b/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.h
index cd4666705..3c4b6f468 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.h
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.h
@@ -2,8 +2,8 @@
// AppDelegate.h
// AdjustTestApp
//
-// Created by Pedro on 23.08.17.
-// Copyright © 2017 adjust. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
#import
@@ -12,6 +12,4 @@
@property (strong, nonatomic) UIWindow *window;
-
@end
-
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.m b/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.m
index 2ca9ff07f..34bfc054c 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.m
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/AppDelegate.m
@@ -2,8 +2,8 @@
// AppDelegate.m
// AdjustTestApp
//
-// Created by Pedro on 23.08.17.
-// Copyright © 2017 adjust. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
#import "AppDelegate.h"
@@ -14,38 +14,31 @@ @interface AppDelegate ()
@implementation AppDelegate
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
-
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
-
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
-
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
-
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
-
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-
@end
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h
index 6dda4cee2..80b41dd90 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.h
@@ -2,13 +2,14 @@
// ViewController.h
// AdjustTestApp
//
-// Created by Pedro on 23.08.17.
-// Copyright © 2017 adjust. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
#import
static NSString * baseUrl = @"http://127.0.0.1:8080";
+static NSString * gdprUrl = @"http://127.0.0.1:8080";
@interface ViewController : UIViewController
diff --git a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m
index 80552bd99..bfc1ce488 100644
--- a/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m
+++ b/AdjustTests/AdjustTestApp/AdjustTestApp/ViewController.m
@@ -2,19 +2,20 @@
// ViewController.m
// AdjustTestApp
//
-// Created by Pedro on 23.08.17.
-// Copyright © 2017 adjust. All rights reserved.
+// Created by Pedro Silva (@nonelse) on 23rd August 2017.
+// Copyright © 2017-2018 Adjust GmbH. All rights reserved.
//
-#import "ViewController.h"
#import "Adjust.h"
+#import "ViewController.h"
#import "ATLTestLibrary.h"
-#import "ATAAdjustCommandExecutor.h"
#import "ADJAdjustFactory.h"
+#import "ATAAdjustCommandExecutor.h"
@interface ViewController ()
-@property (nonatomic, strong) ATLTestLibrary * testLibrary;
-@property (nonatomic, strong) ATAAdjustCommandExecutor * adjustCommandExecutor;
+
+@property (nonatomic, strong) ATLTestLibrary *testLibrary;
+@property (nonatomic, strong) ATAAdjustCommandExecutor *adjustCommandExecutor;
@end
@@ -22,12 +23,10 @@ @implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
- // Do any additional setup after loading the view, typically from a nib.
self.adjustCommandExecutor = [[ATAAdjustCommandExecutor alloc] init];
-
- self.testLibrary = [ATLTestLibrary testLibraryWithBaseUrl:baseUrl andCommandDelegate:self.adjustCommandExecutor];
-
+ self.testLibrary = [ATLTestLibrary testLibraryWithBaseUrl:baseUrl
+ andCommandDelegate:self.adjustCommandExecutor];
[self.adjustCommandExecutor setTestLibrary:self.testLibrary];
// [self.testLibrary addTestDirectory:@"current/sdkInfo"];
@@ -37,16 +36,15 @@ - (void)viewDidLoad {
}
- (void)startTestSession {
- [self.testLibrary startTestSession:@"ios4.12.3"];
+ [self.testLibrary startTestSession:@"ios4.13.0"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
}
+
- (IBAction)restartTestClick:(UIButton *)sender {
[self startTestSession];
}
-
@end
diff --git a/AdjustTests/AdjustTestLibrary/AdjustTestLibrary.xcodeproj/project.pbxproj b/AdjustTests/AdjustTestLibrary/AdjustTestLibrary.xcodeproj/project.pbxproj
index 490dfccc1..75f7eef3e 100644
--- a/AdjustTests/AdjustTestLibrary/AdjustTestLibrary.xcodeproj/project.pbxproj
+++ b/AdjustTests/AdjustTestLibrary/AdjustTestLibrary.xcodeproj/project.pbxproj
@@ -325,7 +325,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 6.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -374,7 +374,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 6.0;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = iphoneos;
diff --git a/AdjustTests/AdjustUnitTests/ADJPackageFields.m b/AdjustTests/AdjustUnitTests/ADJPackageFields.m
index 068da236a..351007fb6 100644
--- a/AdjustTests/AdjustUnitTests/ADJPackageFields.m
+++ b/AdjustTests/AdjustUnitTests/ADJPackageFields.m
@@ -16,7 +16,7 @@ - (id) init {
// default values
self.appToken = @"qwerty123456";
- self.clientSdk = @"ios4.12.3";
+ self.clientSdk = @"ios4.13.0";
self.suffix = @"";
self.environment = @"sandbox";
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca27be8b1..7449cc35f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+### Version 4.13.0 (27th April 2018)
+#### Added
+- Added `gdprForgetMe` method to `Adjust` interface to enable possibility for user to be forgotten in accordance with GDPR law.
+
+---
+
### Version 4.12.3 (23rd February 2018)
#### Added
- Added `AdjustTestLibraryStatic` target to the project.
diff --git a/README.md b/README.md
index c1a9dd642..5735605c9 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
## Summary
-This is the iOS SDK of adjust™. You can read more about adjust™ at [adjust.com].
+This is the iOS SDK of Adjust™. You can read more about Adjust™ at [adjust.com].
If your app is an app which uses web views you would like to use adjust tracking from Javascript code, please consult our [iOS web views SDK guide][ios-web-views-guide].
@@ -30,6 +30,7 @@ If your app is an app which uses web views you would like to use adjust tracking
* [Disable tracking](#disable-tracking)
* [Offline mode](#offline-mode)
* [Event buffering](#event-buffering)
+ * [GDPR right to be forgotten](#gdpr-forget-me)
* [SDK signature](#sdk-signature)
* [Background tracking](#background-tracking)
* [Device IDs](#device-ids)
@@ -67,13 +68,13 @@ We will describe the steps to integrate the adjust SDK into your iOS project. We
If you're using [CocoaPods][cocoapods], you can add the following line to your `Podfile` and continue from [this step](#sdk-integrate):
```ruby
-pod 'Adjust', '~> 4.12.3'
+pod 'Adjust', '~> 4.13.0'
```
or:
```ruby
-pod 'Adjust', :git => 'https://github.com/adjust/ios_sdk.git', :tag => 'v4.12.3'
+pod 'Adjust', :git => 'https://github.com/adjust/ios_sdk.git', :tag => 'v4.13.0'
```
---
@@ -512,6 +513,16 @@ If your app makes heavy use of event tracking, you might want to delay some HTTP
If nothing is set, event buffering is **disabled by default**.
+### GDPR right to be forgotten
+
+In accordance with article 17 of the EU's General Data Protection Regulation (GDPR), you can notify Adjust when a user has exercised their right to be forgotten. Calling the following method will instruct the Adjust SDK to communicate the user's choice to be forgotten to the Adjust backend:
+
+```objc
+[Adjust gdprForgetMe];
+```
+
+Upon receiving this information, Adjust will erase the user's data and the Adjust SDK will stop tracking the user. No requests from this device will be sent to Adjust in the future.
+
### SDK signature
The Adjust SDK signature is enabled on a client-by-client basis. If you are interested in using this feature, please contact your account manager.
diff --git a/VERSION b/VERSION
index d140ced1c..813b83b65 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-4.12.3
+4.13.0
diff --git a/doc/english/migrate.md b/doc/english/migrate.md
index b3fca2602..10de70383 100644
--- a/doc/english/migrate.md
+++ b/doc/english/migrate.md
@@ -1,4 +1,4 @@
-## Migrate your adjust SDK for iOS to v4.12.3 from v3.4.0
+## Migrate your adjust SDK for iOS to v4.13.0 from v3.4.0
### Initial setup
diff --git a/doc/japanese/migrate_ja.md b/doc/japanese/migrate_ja.md
index 2c7766780..07d26a30b 100644
--- a/doc/japanese/migrate_ja.md
+++ b/doc/japanese/migrate_ja.md
@@ -1,4 +1,4 @@
-## iOS用adjust SDKのv3.4.0からv4.12.3への移行
+## iOS用adjust SDKのv3.4.0からv4.13.0への移行
### 初期設定
diff --git a/doc/migrate.md b/doc/migrate.md
index b3fca2602..10de70383 100644
--- a/doc/migrate.md
+++ b/doc/migrate.md
@@ -1,4 +1,4 @@
-## Migrate your adjust SDK for iOS to v4.12.3 from v3.4.0
+## Migrate your adjust SDK for iOS to v4.13.0 from v3.4.0
### Initial setup