Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0d53579
Downgrade AdjustTestLibrary min supported sdk to 6.0
Mar 16, 2018
6bf8715
Flag if SDK has initialised attribution request or an ask_in
uerceg Mar 26, 2018
67cc324
Parameter name/value convention change
uerceg Mar 26, 2018
9d1a5ea
Parameter naming v3
uerceg Mar 27, 2018
ce95195
Sending of forget package and queue flushing
uerceg Apr 6, 2018
dcfb76b
Forbid SDK re-enabling for forgotten user during app lifetime
uerceg Apr 6, 2018
17ffec6
Don't track install session if user decided to be forgoten before it
uerceg Apr 9, 2018
081a7d7
For now treat any kind of GDPR package answer as valid one
uerceg Apr 9, 2018
abbabf2
Synchronise queue flushing
uerceg Apr 9, 2018
601970e
Revert example app functionality
uerceg Apr 9, 2018
e777b55
Track forget package if user decided to be forgotten while SDK was di…
uerceg Apr 9, 2018
5d3d2a1
Send next logic adjustment due to flushing
uerceg Apr 9, 2018
9a1a362
Aim gdpr_forget_device endpoint
uerceg Apr 9, 2018
ee01df7
Add handling of gdprForgetMe to command executor
uerceg Apr 9, 2018
2779969
isForgotten -> isGdprForgotten rename
uerceg Apr 10, 2018
d76b674
Further isForgotten -> isGdprForgotten renaming
uerceg Apr 10, 2018
8a468f2
Don't send any attribution packages if user is forgotten
uerceg Apr 10, 2018
854d9c8
Don't send any sdk_click packages if user is forgotten
uerceg Apr 10, 2018
af4c708
ADJSdkClickHandler cleanup
uerceg Apr 10, 2018
8da5bb5
Except tracking_state=opted_out from backend once user is forgotten
uerceg Apr 10, 2018
28544da
Call trackingStateOptedOut from all responses
nonelse Apr 13, 2018
3ec39cd
Handling of GDPR URL path
uerceg Apr 16, 2018
c739b66
Write isGdprForgotten in case of web opt out and sync activity handle…
uerceg Apr 17, 2018
d0f0ad5
Don't send forget package for already fogotten user due to defaults c…
uerceg Apr 17, 2018
7732cc6
Save new enabled state sooner upon (re)enabling
uerceg Apr 17, 2018
55dbd7f
New version 4.12.3
uerceg Apr 17, 2018
26adfdc
Small cleanups from GDPR PR
uerceg Apr 20, 2018
0ce05e3
Sync behaviour with Android and Windows
uerceg Apr 25, 2018
2b13626
Add PREFS_KEY_GDPR_FORGET_ME to clearAdjustStuff method
uerceg Apr 25, 2018
5960abb
Test app cleanup
uerceg Apr 25, 2018
d6274ae
Update CHANGELOG.md
uerceg Apr 26, 2018
bb6d027
Update README.md
uerceg Apr 26, 2018
319b312
Retry on HTTP 429
uerceg Apr 26, 2018
b812f5a
Return after noticing about being opted out
uerceg Apr 26, 2018
d847838
Return after setTrackingStateOptedOut
uerceg Apr 26, 2018
c29fc30
Close the door for session creation if isGdprForgotten
uerceg Apr 27, 2018
59b83bf
Add log message for 429 case
uerceg Apr 27, 2018
7506af4
Check for isGdprForgotten first
uerceg Apr 27, 2018
64ecfb5
Close the doors for setDeviceToken and trackEvent if isGdprForgotten
uerceg Apr 27, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Adjust.podspec
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
6 changes: 5 additions & 1 deletion Adjust/ADJActivityHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<ADJActivityHandler>)selfI attribution:(ADJAttribution *)attribution;
Expand All @@ -91,6 +94,7 @@
- (void)resetSessionCallbackParameters;
- (void)resetSessionPartnerParameters;
- (NSString *)getBasePath;
- (NSString *)getGdprPath;

- (void)teardown;
+ (void)deleteState;
Expand Down
131 changes: 121 additions & 10 deletions Adjust/ADJActivityHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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];

Expand Down Expand Up @@ -330,6 +335,10 @@ - (BOOL)isEnabled {
return [self isEnabledI:self];
}

- (BOOL)isGdprForgotten {
return [self isGdprForgottenI:self];
}

- (NSString *)adid {
if (self.activityState == nil) {
return nil;
Expand All @@ -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
Expand Down Expand Up @@ -532,6 +558,10 @@ - (NSString *)getBasePath {
return _basePath;
}

- (NSString *)getGdprPath {
return _gdprPath;
}

- (void)teardown
{
[ADJAdjustFactory.logger verbose:@"ADJActivityHandler teardown"];
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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;

Expand All @@ -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]) {
Expand All @@ -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"
Expand Down Expand Up @@ -1266,6 +1321,9 @@ - (void)setDeviceTokenI:(ADJActivityHandler *)selfI
if (!selfI.activityState) {
return;
}
if (selfI.activityState.isGdprForgotten) {
return;
}

NSString *deviceTokenString = [ADJUtil convertDeviceToken:deviceToken];

Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions Adjust/ADJActivityKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef NS_ENUM(int, ADJActivityKind) {
ADJActivityKindClick = 4,
ADJActivityKindAttribution = 5,
ADJActivityKindInfo = 6,
ADJActivityKindGdpr = 7
};

@interface ADJActivityKindUtil : NSObject
Expand Down
4 changes: 4 additions & 0 deletions Adjust/ADJActivityKind.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -40,6 +42,8 @@ + (NSString *)activityKindToString:(ADJActivityKind)activityKind {
return @"attribution";
case ADJActivityKindInfo:
return @"info";
case ADJActivityKindGdpr:
return @"gdpr";
default:
return @"unknown";
}
Expand Down
1 change: 1 addition & 0 deletions Adjust/ADJActivityState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions Adjust/ADJActivityState.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"];
Expand Down Expand Up @@ -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"];
Expand All @@ -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];
Expand Down
Loading