Last active
August 25, 2016 21:23
-
-
Save davidbreenwex/6d8ccba97a1030f179a79962d1789d9d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "SwrvePlotManager.h" | |
#import "SwrveCommon.h" | |
#import "SwrveLocationDelegate.h" | |
NSString *const GEOFENCE_LABEL_TEMPLATE = @"${geofence.label}"; | |
NSString *const LOCATION_EVENT_IMPRESSION = @"location_campaign_impression"; | |
NSString *const LOCATION_EVENT_ENGAGED = @"location_campaign_engaged"; | |
@implementation SwrvePlotManager | |
@synthesize locationCampaigns; | |
- (NSMutableArray*)filterLocationCampaigns:(PlotFilterNotifications *)filterNotifications { | |
DebugLog(@"LocationCampaigns: Offered PlotFilterNotifications of size %lu.", (unsigned long)filterNotifications.uiNotifications.count); | |
UIApplicationState swrveState = [[UIApplication sharedApplication] applicationState]; | |
// -------------------------------------------------------------------------------------------------------------- | |
// BEGIN LEAFLY MOD 1 - Remove check to see if app is in foreground until after campaign payload has been processed | |
// -------------------------------------------------------------------------------------------------------------- | |
//if(swrveState == UIApplicationStateActive) { | |
// DebugLog(@"LocationCampaigns: App in foreground (UIApplicationStateActive) so not showing any notification."); | |
// return [NSMutableArray array]; | |
//} | |
// -------------------------------------------------------------------------------------------------------------- | |
// END LEAFLY MOD 1 - Add this check in 2 places below | |
// -------------------------------------------------------------------------------------------------------------- | |
SwrveLocationManager * locationManager = [self getSwrveLocationManager]; | |
NSMutableArray* locationCampaignsMatched = [NSMutableArray array]; | |
for (UILocalNotification *localNotification in filterNotifications.uiNotifications) { | |
NSString *payload = [localNotification.userInfo objectForKey:PlotNotificationActionKey]; | |
if (!payload) { | |
DebugLog(@"Error in filterLocationCampaigns. No payload.", nil); | |
continue; | |
} | |
PlotPayload *locationPayload = [[PlotPayload alloc] initWithPayload:payload]; | |
if (locationPayload == nil || locationPayload.campaignId == nil) { | |
DebugLog(@"Error in filterLocationCampaigns. Problem parsing payload: %@", payload); | |
continue; | |
} | |
SwrveLocationCampaign *locationCampaign = [locationManager locationWithCampaignId:locationPayload.campaignId]; | |
if (locationCampaign == nil || locationCampaign.message == nil || locationCampaign.message.locationMessageId == nil) { | |
DebugLog(@"LocationCampaign not downloaded, or not targeted, or invalid. Payload: %@", payload); | |
continue; | |
} | |
[locationCampaignsMatched addObject:localNotification]; | |
} | |
NSMutableArray* notificationsToSend = [NSMutableArray array]; | |
if (locationCampaignsMatched.count == 0) { | |
DebugLog(@"No LocationCampaigns were matched.", nil); | |
} else { | |
for(UILocalNotification *notificationToSend in locationCampaignsMatched) { | |
NSString *payload = [notificationToSend.userInfo objectForKey:PlotNotificationActionKey]; | |
PlotPayload *plotPayload = [[PlotPayload alloc] initWithPayload:payload]; | |
DebugLog(@"LocationCampaigns: Matched campaignId:%@", plotPayload.campaignId); | |
SwrveLocationCampaign *locationCampaign = [locationManager locationWithCampaignId:plotPayload.campaignId]; | |
SwrveLocationMessage *locationMessage = locationCampaign.message; | |
if ([self applyTemplatingToMessage:locationMessage with:plotPayload]) { | |
notificationToSend.alertBody = locationMessage.body; | |
} else { | |
DebugLog(@"LocationCampaigns: Not showing locationMessage %@. Missing data from plot payload: %@", locationMessage, plotPayload); | |
continue; | |
} | |
// Add locationMessageId for engagement event later | |
NSMutableDictionary *userInfoCopy = [NSMutableDictionary dictionaryWithDictionary:notificationToSend.userInfo]; | |
[userInfoCopy setObject:locationMessage.toJson forKey:PlotNotificationActionKey]; | |
// ------------------------------------------------------------------------------------------------------------------------ | |
// BEGIN LEAFLY MOD 2 - Get the payload string - Parse it - Send user_property update based on its contents | |
// ------------------------------------------------------------------------------------------------------------------------ | |
NSError *error = nil; | |
NSString *lastGeoCampaignUserPropertyKey = @"last_event_attended"; | |
NSString *inAppCampaignTriggerKey = @"in_app_campaign_trigger"; | |
NSString *appID = @"insert_your_app_id_here"; | |
NSString *apiKey = @"insert_your_api_key_here"; | |
NSString *userID = [SwrveCommon sharedInstance].userID; | |
// Deserialized action json payload from campaign | |
NSData *jsonData = [userInfoCopy[@"action"] dataUsingEncoding:NSUTF8StringEncoding]; | |
NSDictionary *locationMessageDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; | |
// Extract deeplink from notification | |
NSString *deeplink = nil; | |
if (!error) { | |
SwrveLocationMessage *locationMessage = [[SwrveLocationMessage alloc] initWithDictionary:locationMessageDictionary]; | |
NSDictionary *payloadDictionary = locationMessage.getPayloadDictionary; | |
deeplink = [payloadDictionary objectForKey:@"_sd"]; | |
} | |
NSString *lastGeoCampaignUserPropertyValue = nil; | |
NSString *triggeringEventNameValue = nil; | |
// Extract user property that will be set from deeplink | |
if( deeplink != nil ) { | |
NSURL *url = [NSURL URLWithString:deeplink]; | |
NSArray *pairs = [url.query componentsSeparatedByString:@"&"]; | |
//Parse deeplink key/value pairs | |
for(NSString *pair in pairs){ | |
// Extract user_property to update from the deeplink | |
if ([[[pair componentsSeparatedByString:@"="] objectAtIndex:0] isEqualToString:lastGeoCampaignUserPropertyKey]) { | |
lastGeoCampaignUserPropertyValue = [[pair componentsSeparatedByString:@"="] objectAtIndex:1]; | |
} | |
// Extract event name used to trigger In App Campaign - only send event when app is in foreground as this is the only time campaigns can be shown | |
if ([[[pair componentsSeparatedByString:@"="] objectAtIndex:0] isEqualToString:inAppCampaignTriggerKey] && swrveState == UIApplicationStateActive) { | |
triggeringEventNameValue = [[pair componentsSeparatedByString:@"="] objectAtIndex:1]; | |
} | |
} | |
} | |
// Send user property as user event. Must be sent via rest API because user_initiated | |
// must be set to false so DAU isn't impacted. | |
if (lastGeoCampaignUserPropertyValue != nil) { | |
NSString *userUpdateURL = [NSString stringWithFormat: @"https://%@.api.swrve.com/1/user?api_key=%@&user=%@&%@=%@&user_initiated=false", | |
appID, | |
apiKey, | |
userID, | |
lastGeoCampaignUserPropertyKey, | |
lastGeoCampaignUserPropertyValue]; | |
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:userUpdateURL]] | |
queue:[NSOperationQueue mainQueue] | |
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {}]; | |
} | |
// Send Swrve event which can be used to trigger an In App Campaign | |
if (triggeringEventNameValue != nil) { | |
NSMutableDictionary* json = [[NSMutableDictionary alloc] init]; | |
[json setValue:NullableNSString(triggeringEventNameValue) forKey:@"name"]; | |
id<SwrveLocationDelegate> extended = (id<SwrveLocationDelegate>) [SwrveCommon sharedInstance]; | |
[extended queueEvent:@"event" data:json triggerCallback:YES]; | |
[extended sendQueuedEvents]; | |
} | |
// Check to see if application is in the foreground before sending impression event | |
if(swrveState == UIApplicationStateActive) { | |
DebugLog(@"LocationCampaigns: App in foreground (UIApplicationStateActive) so not showing any notification."); | |
return [NSMutableArray array]; | |
} | |
// -------------------------------------------------------------------------------------------------------------------------- | |
// END LEAFLY MOD 2 | |
// -------------------------------------------------------------------------------------------------------------------------- | |
[notificationToSend setUserInfo:userInfoCopy]; | |
[notificationsToSend addObject:notificationToSend]; | |
// Queue the impression event | |
NSMutableDictionary* eventJson = [[NSMutableDictionary alloc] init]; | |
[eventJson setValue:locationMessage.locationMessageId forKey:@"id"]; | |
id<SwrveLocationDelegate> extended = (id<SwrveLocationDelegate>) [SwrveCommon sharedInstance]; | |
[extended queueEvent:LOCATION_EVENT_IMPRESSION data:eventJson triggerCallback:NO]; | |
[extended sendQueuedEvents]; | |
} | |
} | |
// ------------------------------------------------------------------------------------------------------------------- | |
// BEGIN LEAFLY MOD 3 - Check to see if app is in foreground before trying to show the notification | |
// ------------------------------------------------------------------------------------------------------------------- | |
if(swrveState == UIApplicationStateActive) { | |
DebugLog(@"LocationCampaigns: App in foreground (UIApplicationStateActive) so not showing any notification."); | |
return [NSMutableArray array]; | |
} | |
// ------------------------------------------------------------------------------------------------------------------- | |
// END LEAFLY MOD 3 | |
// ------------------------------------------------------------------------------------------------------------------- | |
[filterNotifications showNotifications:notificationsToSend]; | |
return notificationsToSend; | |
} | |
- (SwrveLocationManager *)getSwrveLocationManager { | |
SwrveLocationManager *locationManager = [[SwrveLocationManager alloc] init]; | |
id<SwrveLocationDelegate> extended = (id<SwrveLocationDelegate>) [SwrveCommon sharedInstance]; | |
NSData* data = [extended getCampaignData:SWRVE_CAMPAIGN_LOCATION]; | |
if (data != nil) { | |
NSError* error = nil; | |
NSDictionary* locationCampaignsDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; | |
if (error) { | |
DebugLog(@"Error init location campaigns.\nError: %@\njson: %@", error, data); | |
} else { | |
[locationManager updateWithDictionary:locationCampaignsDict]; | |
} | |
} | |
// Debug information only | |
if ([[locationManager locationCampaigns] count] < 20) { | |
NSMutableString *campaignIds = [[NSMutableString alloc] init]; | |
for (id key in locationManager.locationCampaigns) { | |
[campaignIds appendString:key]; | |
[campaignIds appendString:@","]; | |
} | |
DebugLog(@"LocationCampaigns in cache:%@", campaignIds); | |
} | |
return locationManager; | |
} | |
- (BOOL)applyTemplatingToMessage:(SwrveLocationMessage *)locationMessage with:(PlotPayload *)plotPayload { | |
NSError *error = nil; | |
NSString *templatedText = [self applyTemplatingToString:locationMessage.body with:plotPayload error:&error]; | |
if(error) { | |
return NO; | |
} | |
[locationMessage setBody:templatedText]; | |
// if the message payload is already empty, then no need to apply templating to payload, but return YES as this is ok | |
NSDictionary *payloadDictionary = [locationMessage getPayloadDictionary]; | |
if([payloadDictionary count] > 0) { | |
NSMutableDictionary *templatedPayloadDictionary = [[NSMutableDictionary alloc] init]; | |
for (NSString* key in payloadDictionary) { | |
id value = [payloadDictionary objectForKey:key]; | |
NSString *templatedValue = [self applyTemplatingToString:value with:plotPayload error:&error]; | |
if(error) { | |
return NO; | |
} | |
[templatedPayloadDictionary setObject:templatedValue forKey:key]; | |
} | |
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:templatedPayloadDictionary options:0 error:&error]; | |
if (error) { | |
return NO; | |
} | |
NSString *templatedPayload = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; | |
[locationMessage setPayload:templatedPayload]; | |
} | |
return YES; | |
} | |
- (NSString *)applyTemplatingToString:(NSString *)text with:(PlotPayload *)plotPayload error:(NSError **)error { | |
NSRange searchedRange = NSMakeRange(0, [text length]); | |
NSString *pattern = @"\\$\\{([^\\}]*)\\}"; | |
NSError *regExError = nil; | |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:®ExError]; | |
if (regExError) { | |
DebugLog(@"Error applying NSRegularExpression pattern for location templating.\nError: %@\npattern: %@", regExError, pattern); | |
return nil; | |
} else { | |
NSArray *matches = [regex matchesInString:text options:0 range:searchedRange]; | |
NSMutableDictionary *matchedStrings = [[NSMutableDictionary alloc] init]; | |
NSMutableDictionary *geofenceMetaData = [self getGeofenceMetaDataFrom:plotPayload]; | |
for (NSTextCheckingResult *match in matches) { | |
NSString *templateFullValue = [text substringWithRange:[match range]]; | |
NSString *fallback = [self fallbackFromString:templateFullValue]; | |
NSString *metaData = [text substringWithRange:[match rangeAtIndex:1]]; | |
if (fallback != nil) { | |
NSRange range = [metaData rangeOfString:@"|fallback=\""]; | |
metaData = [metaData substringToIndex:range.location]; // remove fallback text | |
} | |
NSString *metaDataValue = [geofenceMetaData objectForKey:metaData]; | |
if (metaDataValue && [metaDataValue length] > 0) { | |
[matchedStrings setObject:metaDataValue forKey:templateFullValue]; | |
} else if (fallback != nil) { | |
[matchedStrings setObject:fallback forKey:templateFullValue]; | |
} else { | |
*error = [NSError errorWithDomain:@"com.swrve.sdk" code:500 userInfo:@{@"Error reason" : @"Missing customField/geofenceLabel in plotPayload"}]; | |
return nil; | |
} | |
} | |
for (id matchedString in matchedStrings) { | |
text = [text stringByReplacingOccurrencesOfString:matchedString withString:[matchedStrings valueForKey:matchedString]]; | |
} | |
} | |
return text; | |
} | |
- (NSMutableDictionary *)getGeofenceMetaDataFrom:(PlotPayload *)plotPayload { | |
NSMutableDictionary *geofenceMetaData = [[NSMutableDictionary alloc] init]; | |
if (plotPayload != nil && [plotPayload geofenceLabel] != nil) { | |
[geofenceMetaData setObject:[plotPayload geofenceLabel] forKey:@"geofence.label"]; | |
} | |
if (plotPayload != nil && [plotPayload customFields] != nil) { | |
[geofenceMetaData addEntriesFromDictionary:[plotPayload customFields]]; | |
} | |
return geofenceMetaData; | |
} | |
- (NSString *)fallbackFromString:(NSString *)templateFullValue { | |
NSRange searchedRange = NSMakeRange(0, [templateFullValue length]); | |
NSString *pattern = @"\\|fallback=\"([^\\}]*)\"\\}"; | |
NSError *regExError = nil; | |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:®ExError]; | |
if (regExError) { | |
DebugLog(@"Error applying NSRegularExpression pattern for location templating fallback.\nError: %@\npattern: %@", regExError, pattern); | |
return nil; | |
} else { | |
NSArray *matches = [regex matchesInString:templateFullValue options:0 range:searchedRange]; | |
for (NSTextCheckingResult *match in matches) { | |
NSRange group1 = [match rangeAtIndex:1]; | |
NSString *fallback = [templateFullValue substringWithRange:group1]; | |
return fallback; | |
} | |
} | |
return nil; | |
} | |
- (int) engageLocationCampaign:(UILocalNotification*)localNotification withData:(NSString*)locationMessageJson { | |
#pragma unused (localNotification) | |
DebugLog(@"LocationCampaigns: engageLocationCampaign with data:%@", locationMessageJson); | |
int success = SWRVE_FAILURE; | |
NSData *jsonData = [locationMessageJson dataUsingEncoding:NSUTF8StringEncoding]; | |
NSError *error = nil; | |
NSDictionary *locationMessageDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; | |
if (!error) { | |
SwrveLocationMessage *locationMessage = [[SwrveLocationMessage alloc] initWithDictionary:locationMessageDictionary]; | |
// Queue the engaged event | |
NSMutableDictionary* eventJson = [[NSMutableDictionary alloc] init]; | |
[eventJson setValue:locationMessage.locationMessageId forKey:@"id"]; | |
id<SwrveLocationDelegate> extended = (id<SwrveLocationDelegate>) [SwrveCommon sharedInstance]; | |
[extended queueEvent:LOCATION_EVENT_ENGAGED data:eventJson triggerCallback:NO]; | |
[extended sendQueuedEvents]; | |
success = SWRVE_SUCCESS; | |
NSDictionary *payloadDictionary = locationMessage.getPayloadDictionary; | |
NSString *deeplink = [payloadDictionary objectForKey:@"_sd"]; | |
if (deeplink) { | |
DebugLog(@"LocationCampaigns: Opening deeplink:%@", deeplink); | |
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:deeplink]]; | |
if(canOpen) { | |
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:deeplink]]; | |
} else { | |
DebugLog(@"LocationCampaigns: Cannot open deeplink:%@", deeplink); | |
} | |
} | |
} else { | |
DebugLog(@"LocationCampaigns: Error parsing data in engageLocationCampaign. Data:%@", locationMessageJson); | |
} | |
return success; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Original SwrvePlotManager.m file