Skip to content

Instantly share code, notes, and snippets.

@davidbreenwex
Last active August 25, 2016 21:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidbreenwex/6d8ccba97a1030f179a79962d1789d9d to your computer and use it in GitHub Desktop.
Save davidbreenwex/6d8ccba97a1030f179a79962d1789d9d to your computer and use it in GitHub Desktop.
#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:&regExError];
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:&regExError];
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
@davidbreenwex
Copy link
Author

Original SwrvePlotManager.m file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment