From e000b71845a852a1ed763a6c22a5e3b8911c8c26 Mon Sep 17 00:00:00 2001 From: Jonathan Stanton Date: Wed, 26 Oct 2016 21:35:33 -0700 Subject: [PATCH] Remote notification completion handler Summary: **Motivation** To allow handling of remote notifications using the preferred delegate [application:didReceiveRemoteNotification:fetchCompletionHandler](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler:). React native right now is setup to use [application:didReceiveRemoteNotification](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:) which doesn't give the engineer the option to call back to the app via a completion handler and inform the app that it is done with handling the push notification. The primary reason to use the `fetchCompletionHandler` would be to wake up the app to fetch updates so the next time the user opens the app it will be up to date. A new method will be exposed: `PushNotificationIOS# Closes https://github.com/facebook/react-native/pull/8040 Differential Revision: D4081766 Pulled By: bestander fbshipit-source-id: 2819061bd3a926cb1c9c743af5aabab8c0faec3d --- .../PushNotificationIOS.js | 53 +++++++++++++++++++ .../RCTPushNotificationManager.h | 3 ++ .../RCTPushNotificationManager.m | 49 +++++++++++++++-- 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 7d3f7e9fa..2d550ab7d 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -24,6 +24,12 @@ const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; const NOTIF_REGISTRATION_ERROR_EVENT = 'remoteNotificationRegistrationError'; const DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived'; +export type FetchResult = { + NewData: string, + NoData: string, + ResultFailed: string, +}; + /** * An event emitted by PushNotificationIOS. */ @@ -84,6 +90,17 @@ export type PushNotificationEventName = $Enum<{ * { * [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; * } + * // Required for the notification event. You must call the completion handler after handling the remote notification. + * - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo + * fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + * { + * [RCTPushNotificationManager didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; + * } + * // Optionally implement this method over the previous to receive remote notifications. However + * // implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead of this one whenever possible. + * // If your delegate implements both methods, the app object calls the `application:didReceiveRemoteNotification:fetchCompletionHandler:` method + * // Either this method or `application:didReceiveRemoteNotification:fetchCompletionHandler:` is required in order to receive remote notifications. + * // * // Required for the registrationError event. * - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error * { @@ -106,6 +123,15 @@ class PushNotificationIOS { _alert: string | Object; _sound: string; _badgeCount: number; + _notificationId: string; + _isRemote: boolean; + _remoteNotificationCompleteCalllbackCalled: boolean; + + static FetchResult: FetchResult = { + NewData: 'UIBackgroundFetchResultNewData', + NoData: 'UIBackgroundFetchResultNoData', + ResultFailed: 'UIBackgroundFetchResultFailed', + }; /** * Schedules the localNotification for immediate presentation. @@ -341,6 +367,11 @@ class PushNotificationIOS { */ constructor(nativeNotif: Object) { this._data = {}; + this._remoteNotificationCompleteCalllbackCalled = false; + this._isRemote = nativeNotif.remote; + if (this._isRemote) { + this._notificationId = nativeNotif.notificationId; + } if (nativeNotif.remote) { // Extract data from Apple's `aps` dict as defined: @@ -364,6 +395,28 @@ class PushNotificationIOS { } } + /** + * This method is available for remote notifications that have been received via: + * `application:didReceiveRemoteNotification:fetchCompletionHandler:` + * https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler: + * + * Call this to execute when the remote notification handling is complete. When + * calling this block, pass in the fetch result value that best describes + * the results of your operation. You *must* call this handler and should do so + * as soon as possible. For a list of possible values, see `PushNotificationIOS.FetchResult`. + * + * If you do not call this method your background remote notifications could + * be throttled, to read more about it see the above documentation link. + */ + finish(fetchResult: FetchResult) { + if (!this._isRemote || !this._notificationId || this._remoteNotificationCompleteCalllbackCalled) { + return; + } + this._remoteNotificationCompleteCalllbackCalled = true; + + RCTPushNotificationManager.onFinishRemoteNotification(this._notificationId, fetchResult); + } + /** * An alias for `getAlert` to get the notification's main message string */ diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index 41da2dc8a..38988f58d 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -11,10 +11,13 @@ @interface RCTPushNotificationManager : RCTEventEmitter +typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result); + #if !TARGET_OS_TV + (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + (void)didReceiveRemoteNotification:(NSDictionary *)notification; ++ (void)didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(RCTRemoteNotificationCallback)completionHandler; + (void)didReceiveLocalNotification:(UILocalNotification *)notification; + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; #endif diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 5c464fc52..03b1cec9f 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -39,6 +39,10 @@ RCT_ENUM_CONVERTER(NSCalendarUnit, @end +@interface RCTPushNotificationManager () +@property (nonatomic, strong) NSMutableDictionary *remoteNotificationCallbacks; +@end + @implementation RCTConvert (UILocalNotification) + (UILocalNotification *)UILocalNotification:(id)json @@ -58,6 +62,12 @@ RCT_ENUM_CONVERTER(NSCalendarUnit, return notification; } +RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ + @"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData), + @"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData), + @"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed), +}), UIBackgroundFetchResultNoData, integerValue) + @end #endif //TARGET_OS_TV @@ -167,9 +177,19 @@ RCT_EXPORT_MODULE() + (void)didReceiveRemoteNotification:(NSDictionary *)notification { + NSDictionary *userInfo = @{@"notification": notification}; [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived object:self - userInfo:notification]; + userInfo:userInfo]; +} + ++ (void)didReceiveRemoteNotification:(NSDictionary *)notification + fetchCompletionHandler:(RCTRemoteNotificationCallback)completionHandler +{ + NSDictionary *userInfo = @{@"notification": notification, @"completionHandler": completionHandler}; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived + object:self + userInfo:userInfo]; } + (void)didReceiveLocalNotification:(UILocalNotification *)notification @@ -186,9 +206,20 @@ RCT_EXPORT_MODULE() - (void)handleRemoteNotificationReceived:(NSNotification *)notification { - NSMutableDictionary *userInfo = [notification.userInfo mutableCopy]; - userInfo[@"remote"] = @YES; - [self sendEventWithName:@"remoteNotificationReceived" body:userInfo]; + NSMutableDictionary *remoteNotification = [NSMutableDictionary dictionaryWithDictionary:notification.userInfo[@"notification"]]; + RCTRemoteNotificationCallback completionHandler = notification.userInfo[@"completionHandler"]; + NSString* notificationId = [[NSUUID UUID] UUIDString]; + remoteNotification[@"notificationId"] = notificationId; + remoteNotification[@"remote"] = @YES; + if (completionHandler) { + if (!self.remoteNotificationCallbacks) { + // Lazy initialization + self.remoteNotificationCallbacks = [NSMutableDictionary dictionary]; + } + self.remoteNotificationCallbacks[notificationId] = completionHandler; + } + + [self sendEventWithName:@"remoteNotificationReceived" body:notification.userInfo]; } - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification @@ -224,6 +255,16 @@ RCT_EXPORT_MODULE() _requestPermissionsResolveBlock = nil; } +RCT_EXPORT_METHOD(onFinishRemoteNotification:(NSString*)notificationId fetchResult:(UIBackgroundFetchResult)result) { + RCTRemoteNotificationCallback completionHandler = self.remoteNotificationCallbacks[notificationId]; + if (!completionHandler) { + RCTLogError(@"There is no completion handler with notification id: %@", notificationId) + return; + } + completionHandler(result); + [self.remoteNotificationCallbacks removeObjectForKey:notificationId]; +} + /** * Update the application icon badge number on the home screen */