Skip to content

Commit

Permalink
Add support for push notification open tracking (close #335)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhadam committed Apr 4, 2018
1 parent 496f857 commit b497fe1
Show file tree
Hide file tree
Showing 22 changed files with 965 additions and 155 deletions.
35 changes: 35 additions & 0 deletions Snowplow/SPEvent.h
Expand Up @@ -24,6 +24,7 @@

@class SPPayload;
@class SPSelfDescribingJson;
@class SPNotificationContent;

// Builder Protocols : Defines all setter functions

Expand Down Expand Up @@ -86,6 +87,26 @@
- (void) setCurrency:(NSString *)currency;
@end

@protocol SPNotificationContentBuilder <SPEventBuilder>
- (void) setTitle:(NSString *)title;
- (void) setSubtitle:(NSString *)subtitle;
- (void) setBody:(NSString *)body;
- (void) setBadge:(NSNumber *)badge;
- (void) setSound:(NSString *)sound;
- (void) setLaunchImageName:(NSString *)name;
- (void) setUserInfo:(NSDictionary *)userInfo;
- (void) setAttachments:(NSArray *)attachments;
@end

@protocol SPPushNotificationBuilder <SPEventBuilder>
- (void) setAction:(NSString *)action;
- (void) setDeliveryDate:(NSString *)date;
- (void) setTrigger:(NSString *)trigger;
- (void) setCategoryIdentifier:(NSString *)category;
- (void) setThreadIdentifier:(NSString *)thread;
- (void) setNotification:(SPNotificationContent *)content;
@end

// Base Event

@interface SPEvent : NSObject <SPEventBuilder>
Expand Down Expand Up @@ -150,3 +171,17 @@
+ (instancetype) build:(void(^)(id<SPEcommTransactionItemBuilder>builder))buildBlock;
- (SPPayload *) getPayload;
@end

// Push Notification Content Event

@interface SPNotificationContent : SPEvent <SPNotificationContentBuilder>
+ (instancetype) build:(void(^)(id<SPNotificationContentBuilder>builder))buildBlock;
- (NSDictionary *) getPayload;
@end

// Push Notification Event

@interface SPPushNotification : SPEvent <SPPushNotificationBuilder>
+ (instancetype) build:(void(^)(id<SPPushNotificationBuilder>builder))buildBlock;
- (SPSelfDescribingJson *) getPayload;
@end
188 changes: 188 additions & 0 deletions Snowplow/SPEvent.m
Expand Up @@ -540,3 +540,191 @@ - (SPPayload *) getPayload {
}

@end

// Push Notification Content

@implementation SPNotificationContent {
NSString * _title;
NSString * _subtitle;
NSString * _body;
NSNumber * _badge;
NSString * _sound;
NSString * _launchImageName;
NSDictionary * _userInfo;
NSArray * _attachments;
}

+ (instancetype) build:(void(^)(id<SPNotificationContentBuilder>builder))buildBlock {
SPNotificationContent* event = [SPNotificationContent new];
if (buildBlock) { buildBlock(event); }
[event preconditions];
return event;
}

- (id) init {
self = [super init];
return self;
}

- (void) preconditions {
[SPUtilities checkArgument:([_title length] != 0) withMessage:@"Title cannot be nil or empty."];
[SPUtilities checkArgument:([_body length] != 0) withMessage:@"Body cannot be nil or empty."];
[SPUtilities checkArgument:(_badge != nil) withMessage:@"Badge cannot be nil."];
[self basePreconditions];
}

// --- Builder Methods

- (void) setTitle:(NSString *)title {
_title = title;
}

- (void) setSubtitle:(NSString *)subtitle {
_subtitle = subtitle;
}

- (void) setBody:(NSString *)body {
_body = body;
}

- (void) setBadge:(NSNumber *)badge {
_badge = badge;
}

- (void) setSound:(NSString *)sound {
_sound = sound;
}

- (void) setLaunchImageName:(NSString *)name {
_launchImageName = name;
}

- (void) setUserInfo:(NSDictionary *)userInfo {
_userInfo = [SPUtilities replaceHyphenatedKeysWithCamelcase:userInfo];
}

- (void) setAttachments:(NSArray *)attachments {
_attachments = attachments;
}

// --- Public Methods

- (NSDictionary *) getPayload {
NSMutableDictionary * event = [[NSMutableDictionary alloc] init];
[event setObject:_title forKey:kSPPnTitle];
[event setObject:_body forKey:kSPPnBody];
[event setValue:_badge forKey:kSPPnBadge];
if (_subtitle != nil) {
[event setObject:_subtitle forKey:kSPPnSubtitle];
}
if (_subtitle != nil) {
[event setObject:_subtitle forKey:kSPPnSubtitle];
}
if (_sound != nil) {
[event setObject:_sound forKey:kSPPnSound];
}
if (_launchImageName != nil) {
[event setObject:_launchImageName forKey:kSPPnLaunchImageName];
}
if (_userInfo != nil) {
NSMutableDictionary * aps = nil;
NSMutableDictionary * newUserInfo = nil;

// modify contentAvailable value "1" and "0" to @YES and @NO to comply with schema
if (![[_userInfo valueForKeyPath:@"aps.contentAvailable"] isEqual:nil] &&
[[_userInfo objectForKey:@"aps"] isKindOfClass:[NSDictionary class]]) {
aps = [[NSMutableDictionary alloc] initWithDictionary:_userInfo[@"aps"]];

if ([[_userInfo valueForKeyPath:@"aps.contentAvailable"] isEqual:@1]) {
[aps setObject:@YES forKey:@"contentAvailable"];
} else if ([[_userInfo valueForKeyPath:@"aps.contentAvailable"] isEqual:@0]) {
[aps setObject:@NO forKey:@"contentAvailable"];
}
newUserInfo = [[NSMutableDictionary alloc] initWithDictionary:_userInfo];
[newUserInfo setObject:aps forKey:@"aps"];
}
[event setObject:[[NSDictionary alloc] initWithDictionary:newUserInfo] forKey:kSPPnUserInfo];
}
if (_attachments != nil) {
[event setObject:_attachments forKey:kSPPnAttachments];
}

return [[NSDictionary alloc] initWithDictionary:event copyItems:YES];
}

@end

// Push Notification Event

@implementation SPPushNotification {
NSString * _action;
NSString * _trigger;
NSString * _date;
NSString * _category;
NSString * _thread;
SPNotificationContent * _notification;
}

+ (instancetype) build:(void(^)(id<SPPushNotificationBuilder>builder))buildBlock {
SPPushNotification* event = [SPPushNotification new];
if (buildBlock) { buildBlock(event); }
[event preconditions];
return event;
}

- (id) init {
self = [super init];
return self;
}

- (void) preconditions {
[SPUtilities checkArgument:([_date length] != 0) withMessage:@"Delivery date cannot be nil or empty."];
[SPUtilities checkArgument:([_action length] != 0) withMessage:@"Action cannot be nil or empty."];
[SPUtilities checkArgument:([_trigger length] != 0) withMessage:@"Trigger cannot be nil or empty."];
[SPUtilities checkArgument:([_category length] != 0) withMessage:@"Category identifier cannot be nil or empty."];
[SPUtilities checkArgument:([_thread length] != 0) withMessage:@"Thread identifier cannot be nil or empty."];
[SPUtilities checkArgument:(_notification != nil) withMessage:@"Notification cannot be nil."];
[self basePreconditions];
}

// --- Builder Methods

- (void) setAction:(NSString *)action {
_action = action;
}

- (void) setDeliveryDate:(NSString *)date {
_date = date;
}

- (void) setTrigger:(NSString *)trigger {
_trigger = trigger;
}

- (void) setCategoryIdentifier:(NSString *)category {
_category = category;
}

- (void) setThreadIdentifier:(NSString *)thread {
_thread = thread;
}

- (void) setNotification:(SPNotificationContent *)content {
_notification = content;
}

// --- Public Methods

- (SPSelfDescribingJson *) getPayload {
NSMutableDictionary * event = [[NSMutableDictionary alloc] init];

[event setObject:[_notification getPayload] forKey:kSPPushNotification];
[event setObject:_trigger forKey:kSPPushTrigger];
[event setObject:_action forKey:kSPPushAction];
[event setObject:_date forKey:kSPPushDeliveryDate];
[event setObject:_category forKey:kSPPushCategoryId];
[event setObject:_thread forKey:kSPPushThreadId];
return [[SPSelfDescribingJson alloc] initWithSchema:kSPPushNotificationSchema andData:event];
}

@end
6 changes: 6 additions & 0 deletions Snowplow/SPTracker.h
Expand Up @@ -32,6 +32,7 @@
@class SPScreenView;
@class SPTiming;
@class SPEcommerce;
@class SPPushNotification;

@protocol SPTrackerBuilder <NSObject>

Expand Down Expand Up @@ -135,4 +136,9 @@
*/
- (void) trackEcommerceEvent:(SPEcommerce *)event;

/**
* Tracks an Open Push Notification object.
*/
- (void) trackPushNotificationEvent:(SPPushNotification *)event;

@end
10 changes: 10 additions & 0 deletions Snowplow/SPTracker.m
Expand Up @@ -251,6 +251,16 @@ - (void) trackEcommerceItemEvent:(SPEcommerceItem *)event {
[self addEventWithPayload:[event getPayload] andContext:[event getContexts] andEventId:[event getEventId]];
}

- (void) trackPushNotificationEvent:(SPPushNotification *)event {
SPUnstructured * unstruct = [SPUnstructured build:^(id<SPUnstructuredBuilder> builder) {
[builder setEventData:[event getPayload]];
[builder setTimestamp:[event getTimestamp]];
[builder setContexts:[event getContexts]];
[builder setEventId:[event getEventId]];
}];
[self trackUnstructuredEvent:unstruct];
}

// Event Decoration

- (void) addEventWithPayload:(SPPayload *)pb andContext:(NSMutableArray *)contextArray andEventId:(NSString *)eventId {
Expand Down
30 changes: 30 additions & 0 deletions Snowplow/SPUtilities.h
Expand Up @@ -21,6 +21,7 @@
//

#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>

@interface SPUtilities : NSObject

Expand Down Expand Up @@ -180,4 +181,33 @@
*/
+ (NSDictionary *) removeNullValuesFromDictWithDict:(NSDictionary *)dict;

/**
* Maps a trigger object to the corresponding simplified string.
* @ param trigger A UNNotificationTrigger object
* @ return a string describing the type of trigger
*/
+ (NSString *) getTriggerType:(UNNotificationTrigger *)trigger NS_AVAILABLE_IOS(10.0);

/**
* Converts a UNNotificationAttachment array into an array of string dictionaries
* @ param UNNotificationAttachment attachments
* @ return an array of string dictionaries
*/

+ (NSArray<NSDictionary *> *) convertAttachments:(NSArray<UNNotificationAttachment *> *)attachments NS_AVAILABLE_IOS(10.0);

/**
* Converts a kebab-case string keys into a camel-case string keys
* @ param NSDictionary dict
* @ return a dictionary
*/
+ (NSDictionary *) replaceHyphenatedKeysWithCamelcase:(NSDictionary *)dict;

/**
* Converts a kebab-case string into a camel-case string
* @ param NSString key
* @ return a string
*/
+ (NSString *) camelcaseParsedKey:(NSString *)key;

@end

0 comments on commit b497fe1

Please sign in to comment.