Skip to content

Commit

Permalink
Add Attribution Tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
f2prateek committed Sep 1, 2016
1 parent 76f834b commit 0c646e1
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Analytics/Classes/Internal/SEGHTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@

- (NSURLSessionDataTask *)settingsForWriteKey:(NSString *)writeKey completionHandler:(void (^)(BOOL success, NSDictionary *settings))completionHandler;

- (NSURLSessionDataTask *)attributionWithWriteKey:(NSString *)writeKey forDevice:(NSDictionary *)context completionHandler:(void (^)(BOOL success, NSDictionary *properties))completionHandler;

@end
63 changes: 61 additions & 2 deletions Analytics/Classes/Internal/SEGHTTPClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ - (NSURLSessionUploadTask *)upload:(NSDictionary *)batch forWriteKey:(NSString *
exception = exc;
}
if (error || exception) {
SEGLog(@"%@ Error serializing JSON for batch upload %@", self, error);
SEGLog(@"Error serializing JSON for batch upload %@", error);
completionHandler(NO); // Don't retry this batch.
return nil;
}
Expand Down Expand Up @@ -131,7 +131,66 @@ - (NSURLSessionDataTask *)settingsForWriteKey:(NSString *)writeKey completionHan
return;
}

// 2xx response codes.
completionHandler(YES, responseJson);
}];
[task resume];
return task;
}

- (NSURLSessionDataTask *)attributionWithWriteKey:(NSString *)writeKey forDevice:(NSDictionary *)context completionHandler:(void (^)(BOOL success, NSDictionary *properties))completionHandler;

{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.HTTPAdditionalHeaders = @{
@"Accept-Encoding" : @"gzip",
@"Content-Encoding" : @"gzip",
@"Content-Type" : @"application/json",
@"Authorization" : [@"Basic " stringByAppendingString:[self authorizationHeader:writeKey]],
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

NSURL *url = [NSURL URLWithString:@"https://mobile-service.segment.com/v1/attribution"];
NSMutableURLRequest *request = self.requestFactory(url);
[request setHTTPMethod:@"POST"];

NSError *error = nil;
NSException *exception = nil;
NSData *payload = nil;
@try {
payload = [NSJSONSerialization dataWithJSONObject:context options:0 error:&error];
}
@catch (NSException *exc) {
exception = exc;
}
if (error || exception) {
SEGLog(@"Error serializing context to JSON %@", error);
completionHandler(NO, nil);
return nil;
}
NSData *gzippedPayload = [payload seg_gzippedData];

NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:gzippedPayload completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
if (error) {
SEGLog(@"Error making request %@.", error);
completionHandler(NO, nil);
return;
}

NSInteger code = ((NSHTTPURLResponse *)response).statusCode;
if (code > 300) {
SEGLog(@"Server responded with unexpected HTTP code %d.", code);
completionHandler(NO, nil);
return;
}

NSError *jsonError = nil;
id responseJson = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError != nil) {
SEGLog(@"Error deserializing response body %@.", jsonError);
completionHandler(NO, nil);
return;
}

completionHandler(YES, responseJson);
}];
[task resume];
Expand Down
36 changes: 35 additions & 1 deletion Analytics/Classes/Internal/SEGSegmentIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ @interface SEGSegmentIntegration ()
@property (nonatomic, copy) NSString *userId;
@property (nonatomic, strong) NSURL *apiURL;
@property (nonatomic, strong) SEGHTTPClient *httpClient;
@property (nonatomic, strong) NSURLSessionDataTask *attributionRequest;

@end

Expand Down Expand Up @@ -115,8 +116,10 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics
[[NSUserDefaults standardUserDefaults] removeObjectForKey:SEGTraitsKey];
}
}];

#endif
[self dispatchBackground:^{
[self trackAttributionData:self.configuration.trackAttributionData];
}];

dispatch_sync(dispatch_get_main_queue(), ^{
self.flushTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(flush) userInfo:nil repeats:YES];
Expand Down Expand Up @@ -634,4 +637,35 @@ - (void)persistQueue
#endif
}

NSString *const SEGTrackedAttributionKey = @"SEGTrackedAttributionKey";

- (void)trackAttributionData:(BOOL)trackAttributionData
{
#if TARGET_OS_IPHONE
if (!trackAttributionData) {
return;
}

BOOL trackedAttribution = [[NSUserDefaults standardUserDefaults] boolForKey:SEGTrackedAttributionKey];
if (trackedAttribution) {
return;
}

NSDictionary *staticContext = self.cachedStaticContext;
NSDictionary *liveContext = [self liveContext];
NSMutableDictionary *context = [NSMutableDictionary dictionaryWithCapacity:staticContext.count + liveContext.count];
[context addEntriesFromDictionary:staticContext];
[context addEntriesFromDictionary:liveContext];

self.attributionRequest = [self.httpClient attributionWithWriteKey:self.configuration.writeKey forDevice:[context copy] completionHandler:^(BOOL success, NSDictionary *properties) {
if (success) {
[self.analytics track:@"Install Attributed" properties:properties];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:SEGTrackedAttributionKey];
}

self.attributionRequest = nil;
}];
#endif
}

@end
5 changes: 5 additions & 0 deletions Analytics/Classes/SEGAnalytics.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ typedef NSMutableURLRequest * (^SEGRequestFactory)(NSURL *);
*/
@property (nonatomic, assign) BOOL trackDeepLinks;

/**
* Whether the analytics client should automatically track attribution data from enabled providers using the mobile service.
*/
@property (nonatomic, assign) BOOL trackAttributionData;

/**
* Dictionary indicating the options the app was launched with.
*/
Expand Down
4 changes: 0 additions & 4 deletions Analytics/Classes/SEGAnalytics.m
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ - (void)dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - NSNotificationCenter Callback
NSString *const SEGVersionKey = @"SEGVersionKey";
NSString *const SEGBuildKey = @"SEGBuildKey";

Expand Down Expand Up @@ -196,7 +194,6 @@ - (void)trackApplicationLifecycleEvents:(BOOL)trackApplicationLifecycleEvents
}];
}


[self track:@"Application Opened" properties:@{
@"version" : currentVersion,
@"build" : @(currentBuild)
Expand All @@ -206,7 +203,6 @@ - (void)trackApplicationLifecycleEvents:(BOOL)trackApplicationLifecycleEvents
[[NSUserDefaults standardUserDefaults] setInteger:currentBuild forKey:SEGBuildKey];
}


- (void)onAppForeground:(NSNotification *)note
{
[self refreshSettings];
Expand Down
8 changes: 8 additions & 0 deletions Example/Analytics/SEGAppDelegate.m
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
#import "SEGAppDelegate.h"
#import <Analytics/SEGAnalytics.h>


@implementation SEGAppDelegate

// https://segment.com/segment-engineering/sources/ios/overview
NSString *const SEGMENT_WRITE_KEY = @"QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE";

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SEGAnalyticsConfiguration *configuration = [SEGAnalyticsConfiguration configurationWithWriteKey:SEGMENT_WRITE_KEY];
configuration.trackApplicationLifecycleEvents = YES;
configuration.trackAttributionData = YES;
[SEGAnalytics setupWithConfiguration:configuration];
return YES;
}

Expand Down
86 changes: 86 additions & 0 deletions Example/Tests/SEGHTTPClientTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,92 @@
});
});

describe(@"attribution", ^{
it(@"fails for json error", ^{
NSDictionary *device = @{
// Dates cannot be serialized as is so the json serialzation will fail.
@"sentAt" : [NSDate date]
};

SEGHTTPClient *client = [[SEGHTTPClient alloc] initWithRequestFactory:nil];

waitUntil(^(DoneCallback done) {
NSURLSessionDataTask *task = [client attributionWithWriteKey:@"bar" forDevice:device completionHandler:^(BOOL success, NSDictionary *properties) {
expect(success).to.equal(NO);
done();
}];
expect(task).to.equal(nil);
});
});

it(@"succeeds for 2xx response", ^{
NSDictionary *context = @{
@"os" : @{
@"name" : @"iPhone OS",
@"version" : @"8.1.3"
},
@"ip" : @"8.8.8.8"
};

stubRequest(@"POST", @"https://mobile-service.segment.com/v1/attribution")
.withHeaders(@{
@"Accept-Encoding" : @"gzip",
@"Authorization" : @"Basic Zm9vOg==",
@"Content-Encoding" : @"gzip",
@"Content-Length" : @"72",
@"Content-Type" : @"application/json"
})
.andReturn(200)
.withBody(@"{\"provider\": \"mock\"}");

SEGHTTPClient *client = [[SEGHTTPClient alloc] initWithRequestFactory:nil];


waitUntil(^(DoneCallback done) {
NSURLSessionDataTask *task = [client attributionWithWriteKey:@"foo" forDevice:context completionHandler:^(BOOL success, NSDictionary *properties) {
expect(success).to.equal(YES);
expect(properties).to.equal(@{
@"provider" : @"mock"
});
done();
}];
expect(task.state).will.equal(NSURLSessionTaskStateCompleted);
});
});

it(@"fails for non 2xx response", ^{
NSDictionary *context = @{
@"os" : @{
@"name" : @"iPhone OS",
@"version" : @"8.1.3"
},
@"ip" : @"8.8.8.8"
};

stubRequest(@"POST", @"https://mobile-service.segment.com/v1/attribution")
.withHeaders(@{
@"Accept-Encoding" : @"gzip",
@"Authorization" : @"Basic Zm9vOg==",
@"Content-Encoding" : @"gzip",
@"Content-Length" : @"72",
@"Content-Type" : @"application/json"
})
.andReturn(404)
.withBody(@"not found");

SEGHTTPClient *client = [[SEGHTTPClient alloc] initWithRequestFactory:nil];

waitUntil(^(DoneCallback done) {
NSURLSessionDataTask *task = [client attributionWithWriteKey:@"foo" forDevice:context completionHandler:^(BOOL success, NSDictionary *properties) {
expect(success).to.equal(NO);
expect(properties).to.equal(nil);
done();
}];
expect(task.state).will.equal(NSURLSessionTaskStateCompleted);
});
});
});

});

SpecEnd
4 changes: 2 additions & 2 deletions Example/Tests/Tests.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
__block SEGAnalytics *analytics = nil;

beforeEach(^{
SEGAnalyticsConfiguration *configuration = [SEGAnalyticsConfiguration configurationWithWriteKey:@"dpu3lo79nb"];
SEGAnalyticsConfiguration *configuration = [SEGAnalyticsConfiguration configurationWithWriteKey:@"QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE"];
[SEGAnalytics setupWithConfiguration:configuration];
analytics = [SEGAnalytics sharedAnalytics];
});

it(@"initialized correctly", ^{
expect(analytics.configuration.flushAt).to.equal(20);
expect(analytics.configuration.writeKey).to.equal(@"dpu3lo79nb");
expect(analytics.configuration.writeKey).to.equal(@"QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE");
expect(analytics.configuration.shouldUseLocationServices).to.equal(@NO);
expect(analytics.configuration.enableAdvertisingTracking).to.equal(@YES);
});
Expand Down

0 comments on commit 0c646e1

Please sign in to comment.