From 5168f96a32b44507f0cb547f780aa4732ec7803a Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 27 Jul 2018 11:42:22 +0100 Subject: [PATCH] Only store completionHandlers on iOS native side when user adds listener Note the following cases. This commit is catering for case 3: 1. User is listening for onNotificationDisplayed and is manually calling the completionHandler - manually called completionHandler is removed - automatically called completionHandler is guarded against on iOS native side 2. User is listening for onNotificationDisplayed and is not calling the completionHandler: - automatically called completionHandler is removed 3. User is not listening for onNotificationDisplayed - On rn side we can only automatically call completionHandler if the user _is_ listening. This means we need to detect if the user is listening or not. --- .../notifications/RNFirebaseNotifications.m | 35 ++++++++++++++++--- lib/modules/notifications/index.js | 21 +++++++++-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 8222661d..decfac32 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -17,6 +17,7 @@ @end @implementation RNFirebaseNotifications { + BOOL isUserHandlingOnNotificationDisplayed; NSMutableDictionary *completionHandlers; } @@ -56,8 +57,6 @@ RCT_EXPORT_MODULE(); // Set static instance for use from AppDelegate theRNFirebaseNotifications = self; - - completionHandlers = [[NSMutableDictionary alloc] init]; } // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side @@ -99,6 +98,21 @@ RCT_EXPORT_MODULE(); } } +RCT_EXPORT_METHOD(startHandlingNotificationDisplayed) { + isUserHandlingOnNotificationDisplayed = YES; + completionHandlers = [[NSMutableDictionary alloc] init]; +} + +RCT_EXPORT_METHOD(stopHandlingNotificationDisplayed) { + isUserHandlingOnNotificationDisplayed = NO; + NSArray *allHandlers = completionHandlers.allValues; + for (void (^ completionHandler)(UIBackgroundFetchResult) in allHandlers) { + completionHandler(UIBackgroundFetchResultNoData); + } + [completionHandlers removeAllObjects]; + completionHandlers = nil; +} + RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetchResult) { UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData; if ([@"noData" isEqualToString:rnFetchResult]) { @@ -117,6 +131,15 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetch } } +- (void)executeOrStoreCompletionHandler:(void (^ _Nonnull)(UIBackgroundFetchResult))completionHandler notification:(NSDictionary *)notification { + if(isUserHandlingOnNotificationDisplayed) { + NSString *handlerKey = notification[@"notificationId"]; + completionHandlers[handlerKey] = completionHandler; + } else { + completionHandler(UIBackgroundFetchResultNoData); + } +} + // Listen for background messages - (void)didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { @@ -124,9 +147,12 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetch // Pass them over to the RNFirebaseMessaging handler instead if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) { [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; + completionHandler(UIBackgroundFetchResultNoData); return; } + NSDictionary *notification = [self parseUserInfo:userInfo]; + NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; @@ -141,10 +167,10 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetch // - foreground notifications also go through willPresentNotification // - background notification presses also go through didReceiveNotificationResponse // This prevents duplicate messages from hitting the JS app + completionHandler(UIBackgroundFetchResultNoData); return; } - NSDictionary *notification = [self parseUserInfo:userInfo]; // For onOpened events, we set the default action name as iOS 8/9 has no concept of actions if (event == NOTIFICATIONS_NOTIFICATION_OPENED) { notification = @{ @@ -153,8 +179,7 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetch }; } - NSString *handlerKey = notification[@"notificationId"]; - completionHandlers[handlerKey] = completionHandler; + [self executeOrStoreCompletionHandler:completionHandler notification:notification]; [self sendJSEvent:self name:event body:notification]; } diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 01dcaf4d..5cc52591 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -54,6 +54,11 @@ const NATIVE_EVENTS = [ export const MODULE_NAME = 'RNFirebaseNotifications'; export const NAMESPACE = 'notifications'; +const hasListeners = (eventType: string): boolean => { + const listeners = SharedEventEmitter.listeners(eventType); + return listeners && listeners.length; +}; + // iOS 8/9 scheduling // fireDate: Date; // timeZone: TimeZone; @@ -255,11 +260,23 @@ export default class Notifications extends ModuleBase { } getLogger(this).info('Creating onNotificationDisplayed listener'); - SharedEventEmitter.addListener('onNotificationDisplayed', listener); + SharedEventEmitter.addListener( + 'onNotificationDisplayed', + listener + ); + if (hasListeners('onNotificationDisplayed')) { + getNativeModule(this).startHandlingNotificationDisplayed(); + } return () => { getLogger(this).info('Removing onNotificationDisplayed listener'); - SharedEventEmitter.removeListener('onNotificationDisplayed', listener); + SharedEventEmitter.removeListener( + 'onNotificationDisplayed', + listener + ); + if (!hasListeners('onNotificationDisplayed')) { + getNativeModule(this).stopHandlingNotificationDisplayed(); + } }; }