diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index cab4ba362..189bc7ead 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -21,6 +21,7 @@ var _initialNotification = RCTPushNotificationManager && var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; +var DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived'; /** * Handle push notifications for your app, including permission handling and @@ -59,6 +60,11 @@ var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; * { * [RCTPushNotificationManager didReceiveRemoteNotification:notification]; * } + * // Required for the localNotification event. + * - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification + * { + * [RCTPushNotificationManager didReceiveLocalNotification:notification]; + * } * ``` */ class PushNotificationIOS { @@ -74,7 +80,6 @@ class PushNotificationIOS { * * - `alertBody` : The message displayed in the notification alert. * - `soundName` : The sound played when the notification is fired (optional). - * */ static presentLocalNotification(details: Object) { RCTPushNotificationManager.presentLocalNotification(details); @@ -88,7 +93,7 @@ class PushNotificationIOS { * - `fireDate` : The date and time when the system should deliver the notification. * - `alertBody` : The message displayed in the notification alert. * - `soundName` : The sound played when the notification is fired (optional). - * + * - `userInfo` : An optional object containing additional notification data. */ static scheduleLocalNotification(details: Object) { RCTPushNotificationManager.scheduleLocalNotification(details); @@ -115,6 +120,17 @@ class PushNotificationIOS { RCTPushNotificationManager.getApplicationIconBadgeNumber(callback); } + /** + * Cancel local notifications. + * + * Optionally restricts the set of canceled notifications to those + * notifications whose `userInfo` fields match the corresponding fields + * in the `userInfo` argument. + */ + static cancelLocalNotifications(userInfo: Object) { + RCTPushNotificationManager.cancelLocalNotifications(userInfo); + } + /** * Attaches a listener to remote notification events while the app is running * in the foreground or the background. @@ -128,8 +144,8 @@ class PushNotificationIOS { */ static addEventListener(type: string, handler: Function) { invariant( - type === 'notification' || type === 'register', - 'PushNotificationIOS only supports `notification` and `register` events' + type === 'notification' || type === 'register' || type === 'localNotification', + 'PushNotificationIOS only supports `notification`, `register` and `localNotification` events' ); var listener; if (type === 'notification') { @@ -139,6 +155,13 @@ class PushNotificationIOS { handler(new PushNotificationIOS(notifData)); } ); + } else if (type === 'localNotification') { + listener = RCTDeviceEventEmitter.addListener( + DEVICE_LOCAL_NOTIF_EVENT, + (notifData) => { + handler(new PushNotificationIOS(notifData)); + } + ); } else if (type === 'register') { listener = RCTDeviceEventEmitter.addListener( NOTIF_REGISTER_EVENT, @@ -220,8 +243,8 @@ class PushNotificationIOS { */ static removeEventListener(type: string, handler: Function) { invariant( - type === 'notification' || type === 'register', - 'PushNotificationIOS only supports `notification` and `register` events' + type === 'notification' || type === 'register' || type === 'localNotification', + 'PushNotificationIOS only supports `notification`, `register` and `localNotification` events' ); var listener = _notifHandlers.get(handler); if (!listener) { diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index dd18b9518..b7ab54015 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -16,5 +16,6 @@ + (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + (void)didReceiveRemoteNotification:(NSDictionary *)notification; ++ (void)didReceiveLocalNotification:(UILocalNotification *)notification; @end diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 33de7ba62..e9c68a5ec 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -24,6 +24,7 @@ #endif +NSString *const RCTLocalNotificationReceived = @"LocalNotificationReceived"; NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered"; @@ -36,6 +37,7 @@ NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegister notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; + notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; return notification; } @@ -56,6 +58,10 @@ RCT_EXPORT_MODULE() { _bridge = bridge; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleLocalNotificationReceived:) + name:RCTLocalNotificationReceived + object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationReceived:) name:RCTRemoteNotificationReceived @@ -68,7 +74,8 @@ RCT_EXPORT_MODULE() - (NSDictionary *)constantsToExport { - NSDictionary *initialNotification = [_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; + NSDictionary *initialNotification = + [_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; return @{@"initialNotification": RCTNullIfNil(initialNotification)}; } @@ -87,12 +94,9 @@ RCT_EXPORT_MODULE() for (NSUInteger i = 0; i < deviceTokenLength; i++) { [hexString appendFormat:@"%02x", bytes[i]]; } - NSDictionary *userInfo = @{ - @"deviceToken" : [hexString copy] - }; [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered object:self - userInfo:userInfo]; + userInfo:@{@"deviceToken" : [hexString copy]}]; } + (void)didReceiveRemoteNotification:(NSDictionary *)notification @@ -102,6 +106,26 @@ RCT_EXPORT_MODULE() userInfo:notification]; } ++ (void)didReceiveLocalNotification:(UILocalNotification *)notification +{ + NSMutableDictionary *details = [NSMutableDictionary new]; + if (notification.alertBody) { + details[@"alertBody"] = notification.alertBody; + } + if (notification.userInfo) { + details[@"userInfo"] = RCTJSONClean(notification.userInfo); + } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived + object:self + userInfo:details]; +} + +- (void)handleLocalNotificationReceived:(NSNotification *)notification +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"localNotificationReceived" + body:notification.userInfo]; +} + - (void)handleRemoteNotificationReceived:(NSNotification *)notification { [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationReceived" @@ -127,9 +151,7 @@ RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(NSInteger)number) */ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) { - callback(@[ - @(RCTSharedApplication().applicationIconBadgeNumber) - ]); + callback(@[@(RCTSharedApplication().applicationIconBadgeNumber)]); } RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) @@ -140,13 +162,13 @@ RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions) { - if ([permissions[@"alert"] boolValue]) { + if ([RCTConvert BOOL:permissions[@"alert"]]) { types |= UIUserNotificationTypeAlert; } - if ([permissions[@"badge"] boolValue]) { + if ([RCTConvert BOOL:permissions[@"badge"]]) { types |= UIUserNotificationTypeBadge; } - if ([permissions[@"sound"] boolValue]) { + if ([RCTConvert BOOL:permissions[@"sound"]]) { types |= UIUserNotificationTypeSound; } } else { @@ -155,7 +177,8 @@ RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) UIApplication *app = RCTSharedApplication(); if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) { - UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil]; + UIUserNotificationSettings *notificationSettings = + [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil]; [app registerUserNotificationSettings:notificationSettings]; [app registerForRemoteNotifications]; } else { @@ -171,8 +194,7 @@ RCT_EXPORT_METHOD(abandonPermissions) RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { if (RCTRunningInAppExtension()) { - NSDictionary *permissions = @{@"alert": @NO, @"badge": @NO, @"sound": @NO}; - callback(@[permissions]); + callback(@[@{@"alert": @NO, @"badge": @NO, @"sound": @NO}]); return; } @@ -189,12 +211,11 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) } - NSMutableDictionary *permissions = [NSMutableDictionary new]; - permissions[@"alert"] = @((types & UIUserNotificationTypeAlert) > 0); - permissions[@"badge"] = @((types & UIUserNotificationTypeBadge) > 0); - permissions[@"sound"] = @((types & UIUserNotificationTypeSound) > 0); - - callback(@[permissions]); + callback(@[@{ + @"alert": @((types & UIUserNotificationTypeAlert) > 0), + @"badge": @((types & UIUserNotificationTypeBadge) > 0), + @"sound": @((types & UIUserNotificationTypeSound) > 0), + }]); } RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) @@ -212,4 +233,21 @@ RCT_EXPORT_METHOD(cancelAllLocalNotifications) [RCTSharedApplication() cancelAllLocalNotifications]; } +RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo) +{ + for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) { + __block BOOL matchesAll = YES; + NSDictionary *notificationInfo = notification.userInfo; + [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if (![notificationInfo[key] isEqual:obj]) { + matchesAll = NO; + *stop = YES; + } + }]; + if (matchesAll) { + [[UIApplication sharedApplication] cancelLocalNotification:notification]; + } + } +} + @end diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index c751dabbd..9d63bdbf3 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -23,7 +23,7 @@ RCT_EXTERN NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSErr RCT_EXTERN id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error); RCT_EXTERN id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error); -// Santize a JSON string by stripping invalid objects and/or NaN values +// Sanitize a JSON object by stripping invalid types and/or NaN values RCT_EXTERN id RCTJSONClean(id object); // Get MD5 hash of a string