diff --git a/README.md b/README.md index c36a6e0b..1d704499 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,36 @@ if ( device.platform == 'android' || device.platform == 'Android' || device.plat pushTransportReadyCallback: replace_with_pushTransportReady_callback, launchApplicationOnPush: true }); +} else if ( device.platform == 'iOS'){ + pushNotification.register( + tokenHandler, + errorHandler, + { + "badge":"true", + "sound":"true", + "alert":"true", + "ecb":"onNotificationAPN", + "categories": [ + { + "identifier": "new-message", + "actions": [ + { + "title': 'View', + "identifier': "view", + "authentication": "false", + "mode": "foreground" + }, + { + "title": "Delete", + "identifier": "delete", + "authentication': "false", + "destructive": "true", + "mode": "background" + } + ] + } + ] + }); } else { pushNotification.register( tokenHandler, @@ -405,6 +435,18 @@ function onNotificationAPN (event) { var snd = new Media(event.sound); snd.play(); } + + // iOS8 only + if ( event.category ) + { + notification action category + } + + // iOS8 only + if ( event.identifier ) + { + notification action identifier + } if ( event.badge ) { diff --git a/src/ios/AppDelegate+notification.m b/src/ios/AppDelegate+notification.m index aa563a34..007dcc70 100644 --- a/src/ios/AppDelegate+notification.m +++ b/src/ios/AppDelegate+notification.m @@ -115,4 +115,25 @@ - (void)dealloc self.launchNotification = nil; // clear the association and release the object } + +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler +{ + NSLog(@"didReceiveNotificationAction"); + + self.launchNotification = nil; + + PushPlugin *pushHandler = [self getCommandInstance:@"PushPlugin"]; + pushHandler.notificationMessage = userInfo; + pushHandler.identifier = identifier; + pushHandler.isInline = NO; + [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO]; + + // need a way to cancel this to remove it from notification center + // [application cancelRemoteNotification:notification] ; + + // Mandatory to call, as soon as you're done + completionHandler(); +} + + @end diff --git a/src/ios/PushPlugin.h b/src/ios/PushPlugin.h index 7e7ba4bc..6a99dc6b 100644 --- a/src/ios/PushPlugin.h +++ b/src/ios/PushPlugin.h @@ -33,6 +33,7 @@ BOOL isInline; NSString *notificationCallbackId; NSString *callback; + NSString *identifier; BOOL ready; } @@ -41,6 +42,7 @@ @property (nonatomic, copy) NSString *notificationCallbackId; @property (nonatomic, copy) NSString *callback; +@property (nonatomic, strong) NSString *identifier; @property (nonatomic, strong) NSDictionary *notificationMessage; @property BOOL isInline; diff --git a/src/ios/PushPlugin.m b/src/ios/PushPlugin.m index 7686ae9d..8fa86742 100644 --- a/src/ios/PushPlugin.m +++ b/src/ios/PushPlugin.m @@ -1,16 +1,16 @@ /* Copyright 2009-2011 Urban Airship Inc. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binaryform must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided withthe distribution. - + THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO @@ -28,6 +28,7 @@ @implementation PushPlugin @synthesize notificationMessage; +@synthesize identifier; @synthesize isInline; @synthesize callbackId; @@ -37,189 +38,250 @@ @implementation PushPlugin - (void)unregister:(CDVInvokedUrlCommand*)command; { - self.callbackId = command.callbackId; - + self.callbackId = command.callbackId; + [[UIApplication sharedApplication] unregisterForRemoteNotifications]; [self successWithMessage:@"unregistered"]; } - (void)register:(CDVInvokedUrlCommand*)command; { - self.callbackId = command.callbackId; - + self.callbackId = command.callbackId; + NSMutableDictionary* options = [command.arguments objectAtIndex:0]; - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UIUserNotificationType UserNotificationTypes = UIUserNotificationTypeNone; -#endif + UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeNone; - + id badgeArg = [options objectForKey:@"badge"]; id soundArg = [options objectForKey:@"sound"]; id alertArg = [options objectForKey:@"alert"]; - + id categoryArg = [options objectForKey:@"categories"]; + if ([badgeArg isKindOfClass:[NSString class]]) { if ([badgeArg isEqualToString:@"true"]) { notificationTypes |= UIRemoteNotificationTypeBadge; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UserNotificationTypes |= UIUserNotificationTypeBadge; -#endif } } else if ([badgeArg boolValue]) { notificationTypes |= UIRemoteNotificationTypeBadge; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UserNotificationTypes |= UIUserNotificationTypeBadge; -#endif } - + if ([soundArg isKindOfClass:[NSString class]]) { if ([soundArg isEqualToString:@"true"]) { notificationTypes |= UIRemoteNotificationTypeSound; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UserNotificationTypes |= UIUserNotificationTypeSound; -#endif - } + } } else if ([soundArg boolValue]) { notificationTypes |= UIRemoteNotificationTypeSound; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UserNotificationTypes |= UIUserNotificationTypeSound; -#endif } - + if ([alertArg isKindOfClass:[NSString class]]) { if ([alertArg isEqualToString:@"true"]) { notificationTypes |= UIRemoteNotificationTypeAlert; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - UserNotificationTypes |= UIUserNotificationTypeAlert; -#endif - } + } } else if ([alertArg boolValue]) { notificationTypes |= UIRemoteNotificationTypeAlert; + } + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + + UIUserNotificationType UserNotificationTypes = UIUserNotificationTypeNone; + + if ([badgeArg isKindOfClass:[NSString class]]) + { + if ([badgeArg isEqualToString:@"true"]) { + UserNotificationTypes |= UIUserNotificationTypeBadge; + } + } + else if ([badgeArg boolValue]) { + UserNotificationTypes |= UIUserNotificationTypeBadge; + } + + if ([soundArg isKindOfClass:[NSString class]]) + { + if ([soundArg isEqualToString:@"true"]) { + UserNotificationTypes |= UIUserNotificationTypeSound; + } + } + else if ([soundArg boolValue]) { + UserNotificationTypes |= UIUserNotificationTypeSound; + } + + if ([alertArg isKindOfClass:[NSString class]]) + { + if ([alertArg isEqualToString:@"true"]) { + UserNotificationTypes |= UIUserNotificationTypeAlert; + } + } + else if ([alertArg boolValue]) { UserNotificationTypes |= UIUserNotificationTypeAlert; -#endif } - + + notificationTypes |= UIRemoteNotificationTypeNewsstandContentAvailability; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 UserNotificationTypes |= UIUserNotificationActivationModeBackground; + + UIUserNotificationSettings *settings; + + if ([categoryArg isKindOfClass:[NSArray class]]) { + + NSMutableArray *categoryArray = [[NSMutableArray alloc] init]; + for (id categoryObject in categoryArg) { + UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init]; + category.identifier = [categoryObject objectForKey:@"identifier"]; + + NSMutableArray *actions = [[NSMutableArray alloc] init]; + + for (id actionObject in [categoryObject objectForKey:@"actions"]) { + UIMutableUserNotificationAction *action = [UIMutableUserNotificationAction new]; + + id actionAuthentication = [actionObject objectForKey:@"authentication"]; + id actionMode = [actionObject objectForKey:@"mode"]; + + action.title = [actionObject objectForKey:@"title"]; + action.identifier = [actionObject objectForKey:@"identifier"]; + + if ([actionAuthentication isKindOfClass:[NSString class]]) { + if ([actionAuthentication isEqualToString:@"true"]) { + action.authenticationRequired = YES; + } else { + action.authenticationRequired = NO; + } + } else if ([actionAuthentication boolValue]) { + action.authenticationRequired = actionAuthentication; + } + + if ([actionMode isKindOfClass:[NSString class]] && [actionMode isEqualToString:@"background"]) { + action.activationMode = UIUserNotificationActivationModeBackground; + } else { + action.activationMode = UIUserNotificationActivationModeForeground; + } + + [actions addObject:action]; + } + + [category setActions:actions forContext:UIUserNotificationActionContextDefault]; + + [categoryArray addObject:category]; + } + NSSet *categorySet = [NSSet setWithArray:categoryArray]; + + settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:categorySet]; + } else { + settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:nil]; + } + + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + + #endif - + self.callback = [options objectForKey:@"ecb"]; - + if (notificationTypes == UIRemoteNotificationTypeNone) NSLog(@"PushPlugin.register: Push notification type is set to none"); - + isInline = NO; - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 - if ([[UIApplication sharedApplication]respondsToSelector:@selector(registerUserNotificationSettings:)]) { - UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; - [[UIApplication sharedApplication] registerForRemoteNotifications]; - } else { - [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes]; - } -#else - [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes]; -#endif - - if (notificationMessage) // if there is a pending startup notification - [self notificationReceived]; // go ahead and process it + + if (notificationMessage) // if there is a pending startup notification + [self notificationReceived]; // go ahead and process it } /* -- (void)isEnabled:(NSMutableArray *)arguments withDict:(NSMutableDictionary *)options { - UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; - NSString *jsStatement = [NSString stringWithFormat:@"navigator.PushPlugin.isEnabled = %d;", type != UIRemoteNotificationTypeNone]; - NSLog(@"JSStatement %@",jsStatement); -} -*/ + - (void)isEnabled:(NSMutableArray *)arguments withDict:(NSMutableDictionary *)options { + UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; + NSString *jsStatement = [NSString stringWithFormat:@"navigator.PushPlugin.isEnabled = %d;", type != UIRemoteNotificationTypeNone]; + NSLog(@"JSStatement %@",jsStatement); + } + */ - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - + NSMutableDictionary *results = [NSMutableDictionary dictionary]; NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString: @" " withString: @""]; [results setValue:token forKey:@"deviceToken"]; - - #if !TARGET_IPHONE_SIMULATOR - // Get Bundle Info for Remote Registration (handy if you have more than one app) - [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] forKey:@"appName"]; - [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"appVersion"]; - - // Check what Notifications the user has turned on. We registered for all three, but they may have manually disabled some or all of them. - NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; - - // Set the defaults to disabled unless we find otherwise... - NSString *pushBadge = @"disabled"; - NSString *pushAlert = @"disabled"; - NSString *pushSound = @"disabled"; - - // Check what Registered Types are turned on. This is a bit tricky since if two are enabled, and one is off, it will return a number 2... not telling you which - // one is actually disabled. So we are literally checking to see if rnTypes matches what is turned on, instead of by number. The "tricky" part is that the - // single notification types will only match if they are the ONLY one enabled. Likewise, when we are checking for a pair of notifications, it will only be - // true if those two notifications are on. This is why the code is written this way - if(rntypes & UIRemoteNotificationTypeBadge){ - pushBadge = @"enabled"; - } - if(rntypes & UIRemoteNotificationTypeAlert) { - pushAlert = @"enabled"; - } - if(rntypes & UIRemoteNotificationTypeSound) { - pushSound = @"enabled"; - } - - [results setValue:pushBadge forKey:@"pushBadge"]; - [results setValue:pushAlert forKey:@"pushAlert"]; - [results setValue:pushSound forKey:@"pushSound"]; - - // Get the users Device Model, Display Name, Token & Version Number - UIDevice *dev = [UIDevice currentDevice]; - [results setValue:dev.name forKey:@"deviceName"]; - [results setValue:dev.model forKey:@"deviceModel"]; - [results setValue:dev.systemVersion forKey:@"deviceSystemVersion"]; - - [self successWithMessage:[NSString stringWithFormat:@"%@", token]]; - #endif + +#if !TARGET_IPHONE_SIMULATOR + // Get Bundle Info for Remote Registration (handy if you have more than one app) + [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] forKey:@"appName"]; + [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"appVersion"]; + + // Check what Notifications the user has turned on. We registered for all three, but they may have manually disabled some or all of them. + NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; + + // Set the defaults to disabled unless we find otherwise... + NSString *pushBadge = @"disabled"; + NSString *pushAlert = @"disabled"; + NSString *pushSound = @"disabled"; + + // Check what Registered Types are turned on. This is a bit tricky since if two are enabled, and one is off, it will return a number 2... not telling you which + // one is actually disabled. So we are literally checking to see if rnTypes matches what is turned on, instead of by number. The "tricky" part is that the + // single notification types will only match if they are the ONLY one enabled. Likewise, when we are checking for a pair of notifications, it will only be + // true if those two notifications are on. This is why the code is written this way + if(rntypes & UIRemoteNotificationTypeBadge){ + pushBadge = @"enabled"; + } + if(rntypes & UIRemoteNotificationTypeAlert) { + pushAlert = @"enabled"; + } + if(rntypes & UIRemoteNotificationTypeSound) { + pushSound = @"enabled"; + } + + [results setValue:pushBadge forKey:@"pushBadge"]; + [results setValue:pushAlert forKey:@"pushAlert"]; + [results setValue:pushSound forKey:@"pushSound"]; + + // Get the users Device Model, Display Name, Token & Version Number + UIDevice *dev = [UIDevice currentDevice]; + [results setValue:dev.name forKey:@"deviceName"]; + [results setValue:dev.model forKey:@"deviceModel"]; + [results setValue:dev.systemVersion forKey:@"deviceSystemVersion"]; + + [self successWithMessage:[NSString stringWithFormat:@"%@", token]]; +#endif } - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - [self failWithMessage:@"" withError:error]; + [self failWithMessage:@"" withError:error]; } - (void)notificationReceived { NSLog(@"Notification received"); - + if (notificationMessage && self.callback) { NSMutableString *jsonStr = [NSMutableString stringWithString:@"{"]; - + [self parseDictionary:notificationMessage intoJSON:jsonStr]; - + if (isInline) { - [jsonStr appendFormat:@"foreground:\"%d\"", 1]; + [jsonStr appendFormat:@"\"foreground\":\"%d\"", 1]; isInline = NO; } - else - [jsonStr appendFormat:@"foreground:\"%d\"", 0]; - + else + [jsonStr appendFormat:@"\"foreground\":\"%d\"", 0]; + + if (identifier) { + [jsonStr appendString:[NSString stringWithFormat:@",\"identifier\":\"%@\"", identifier]]; + identifier = nil; + } + [jsonStr appendString:@"}"]; - + NSLog(@"Msg: %@", jsonStr); - NSString * jsCallBack = [NSString stringWithFormat:@"%@(%@);", self.callback, jsonStr]; [self.webView stringByEvaluatingJavaScriptFromString:jsCallBack]; - + self.notificationMessage = nil; } } @@ -229,20 +291,20 @@ -(void)parseDictionary:(NSDictionary *)inDictionary intoJSON:(NSMutableString *) { NSArray *keys = [inDictionary allKeys]; NSString *key; - + for (key in keys) { id thisObject = [inDictionary objectForKey:key]; - + if ([thisObject isKindOfClass:[NSDictionary class]]) [self parseDictionary:thisObject intoJSON:jsonString]; else if ([thisObject isKindOfClass:[NSString class]]) - [jsonString appendFormat:@"\"%@\":\"%@\",", - key, - [[[[inDictionary objectForKey:key] + [jsonString appendFormat:@"\"%@\":\"%@\",", + key, + [[[[inDictionary objectForKey:key] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] - stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""] - stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]]; + stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""] + stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]]; else { [jsonString appendFormat:@"\"%@\":\"%@\",", key, [inDictionary objectForKey:key]]; } @@ -250,14 +312,14 @@ -(void)parseDictionary:(NSDictionary *)inDictionary intoJSON:(NSMutableString *) } - (void)setApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command { - + self.callbackId = command.callbackId; - + NSMutableDictionary* options = [command.arguments objectAtIndex:0]; int badge = [[options objectForKey:@"badge"] intValue] ?: 0; - + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge]; - + [self successWithMessage:[NSString stringWithFormat:@"app badge count set to %d", badge]]; } -(void)successWithMessage:(NSString *)message @@ -273,7 +335,7 @@ -(void)failWithMessage:(NSString *)message withError:(NSError *)error { NSString *errorMessage = (error) ? [NSString stringWithFormat:@"%@ - %@", message, [error localizedDescription]] : message; CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; - + [self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId]; }