From e6cd793f91b75af790c2c42d53497d521c0558f9 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 20 Jul 2018 11:43:06 +0100 Subject: [PATCH 01/17] Add exported native function to complete handling of ios notification --- .../notifications/RNFirebaseNotifications.m | 38 +++++++++++++++---- lib/modules/notifications/index.js | 4 ++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 274f4d7d..8222661d 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -16,7 +16,9 @@ #endif @end -@implementation RNFirebaseNotifications +@implementation RNFirebaseNotifications { + NSMutableDictionary *completionHandlers; +} static RNFirebaseNotifications *theRNFirebaseNotifications = nil; // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side @@ -54,6 +56,8 @@ 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 @@ -95,6 +99,24 @@ RCT_EXPORT_MODULE(); } } +RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetchResult) { + UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData; + if ([@"noData" isEqualToString:rnFetchResult]) { + fetchResult = UIBackgroundFetchResultNoData; + } else if ([@"newData" isEqualToString:rnFetchResult]) { + fetchResult = UIBackgroundFetchResultNewData; + } else if ([@"failed" isEqualToString:rnFetchResult]) { + fetchResult = UIBackgroundFetchResultFailed; + } + + void (^completionHandler)(UIBackgroundFetchResult) = completionHandlers[handlerKey]; + completionHandlers[handlerKey] = nil; + + if(completionHandler != nil) { + completionHandler(fetchResult); + } +} + // Listen for background messages - (void)didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { @@ -102,7 +124,6 @@ RCT_EXPORT_MODULE(); // 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; } @@ -120,7 +141,6 @@ RCT_EXPORT_MODULE(); // - 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; } @@ -133,8 +153,10 @@ RCT_EXPORT_MODULE(); }; } + NSString *handlerKey = notification[@"notificationId"]; + completionHandlers[handlerKey] = completionHandler; + [self sendJSEvent:self name:event body:notification]; - completionHandler(UIBackgroundFetchResultNoData); } // ******************************************************* @@ -226,7 +248,7 @@ RCT_EXPORT_METHOD(cancelNotification:(NSString*) notificationId if ([self isIOS89]) { for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) { NSDictionary *notificationInfo = notification.userInfo; - if ([notificationId isEqualToString:[notificationInfo valueForKey:@"notificationId"]]) { + if ([notificationId isEqualToString:notificationInfo[@"notificationId"]]) { [RCTSharedApplication() cancelLocalNotification:notification]; } } @@ -372,7 +394,7 @@ RCT_EXPORT_METHOD(setBadge:(NSInteger) number resolve(nil); }); } - + RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { jsReady = TRUE; resolve(nil); @@ -487,11 +509,11 @@ RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPro NSString *identifier = a[@"identifier"]; NSURL *url = [NSURL fileURLWithPath:a[@"url"]]; NSMutableDictionary *attachmentOptions = nil; - + if (a[@"options"]) { NSDictionary *options = a[@"options"]; attachmentOptions = [[NSMutableDictionary alloc] init]; - + for (id key in options) { if ([key isEqualToString:@"typeHint"]) { attachmentOptions[UNNotificationAttachmentOptionsTypeHintKey] = options[key]; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index b42d3d65..f472d79c 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -200,6 +200,10 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).getScheduledNotifications(); } + complete(handlerId: string, fetchResult: string): Promise { + return getNativeModule(this).complete(handlerId, fetchResult); + } + onNotification( nextOrObserver: OnNotification | OnNotificationObserver ): () => any { From b6885b0125aa287839c71004cc175986044d798b Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Mon, 23 Jul 2018 16:13:57 +0100 Subject: [PATCH 02/17] inject onNotificationDisplayed with completion handler --- lib/modules/notifications/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index f472d79c..2f2db9c9 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -89,9 +89,12 @@ export default class Notifications extends ModuleBase { // public event name: onNotificationDisplayed 'notifications_notification_displayed', (notification: NativeNotification) => { + const rnNotification = new Notification(notification); + const done = (fetchResult: string) => getNativeModule(this).complete(rnNotification.notificationId, fetchResult); SharedEventEmitter.emit( 'onNotificationDisplayed', - new Notification(notification) + rnNotification, + done ); } ); @@ -200,10 +203,6 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).getScheduledNotifications(); } - complete(handlerId: string, fetchResult: string): Promise { - return getNativeModule(this).complete(handlerId, fetchResult); - } - onNotification( nextOrObserver: OnNotification | OnNotificationObserver ): () => any { From fac0767d5de791b54ca2b18365a5caa43c910dbf Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Mon, 23 Jul 2018 16:52:24 +0100 Subject: [PATCH 03/17] pass noop for android platform --- lib/modules/notifications/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 2f2db9c9..dacb3c9b 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -90,7 +90,15 @@ export default class Notifications extends ModuleBase { 'notifications_notification_displayed', (notification: NativeNotification) => { const rnNotification = new Notification(notification); - const done = (fetchResult: string) => getNativeModule(this).complete(rnNotification.notificationId, fetchResult); + const done = Platform.select({ + ios: (fetchResult: string) => + getNativeModule(this).complete( + rnNotification.notificationId, + fetchResult + ), + android: () => {}, + }); + SharedEventEmitter.emit( 'onNotificationDisplayed', rnNotification, From 0d14a5e3b30dc428e89ad161368d872f7a908146 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 27 Jul 2018 09:03:00 +0100 Subject: [PATCH 04/17] add notificationId to logging --- lib/modules/notifications/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index dacb3c9b..01dcaf4d 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -91,11 +91,17 @@ export default class Notifications extends ModuleBase { (notification: NativeNotification) => { const rnNotification = new Notification(notification); const done = Platform.select({ - ios: (fetchResult: string) => + ios: (fetchResult: string) => { + getLogger(this).debug( + `Completion handler called for notificationId=${ + rnNotification.notificationId + }` + ); getNativeModule(this).complete( rnNotification.notificationId, fetchResult - ), + ); + }, android: () => {}, }); From 5168f96a32b44507f0cb547f780aa4732ec7803a Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 27 Jul 2018 11:42:22 +0100 Subject: [PATCH 05/17] 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(); + } }; } From b53695ad52a1a94c9409205cb798af52c77eb63b Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 27 Jul 2018 14:45:20 +0100 Subject: [PATCH 06/17] Add RCTConverter+UIBackgroundFetchResult to handle auto conversion --- ios/RNFirebase.xcodeproj/project.pbxproj | 15 +++++++++++++++ .../RCTConvert+UIBackgroundFetchResult.h | 5 +++++ .../RCTConvert+UIBackgroundFetchResult.m | 9 +++++++++ .../notifications/RNFirebaseNotifications.m | 17 +++++++---------- lib/modules/notifications/index.js | 18 ++++++++++++++++++ 5 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.h create mode 100644 ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.m diff --git a/ios/RNFirebase.xcodeproj/project.pbxproj b/ios/RNFirebase.xcodeproj/project.pbxproj index 88370b00..4ed4efc0 100644 --- a/ios/RNFirebase.xcodeproj/project.pbxproj +++ b/ios/RNFirebase.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 168785AD210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 168785AB210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.m */; }; 17AF4F6B1F59CDBF00C02336 /* RNFirebaseLinks.m in Sources */ = {isa = PBXBuildFile; fileRef = 17AF4F6A1F59CDBF00C02336 /* RNFirebaseLinks.m */; }; 27540F9A209F3641001F4AF4 /* RNFirebaseFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 27540F99209F3641001F4AF4 /* RNFirebaseFunctions.m */; }; 8300A7AE1F31E143001B16AB /* RNFirebaseDatabaseReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8300A7AD1F31E143001B16AB /* RNFirebaseDatabaseReference.m */; }; @@ -50,6 +51,8 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFirebase.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 168785AB210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+UIBackgroundFetchResult.m"; sourceTree = ""; }; + 168785AC210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+UIBackgroundFetchResult.h"; sourceTree = ""; }; 17AF4F691F59CDBF00C02336 /* RNFirebaseLinks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseLinks.h; sourceTree = ""; }; 17AF4F6A1F59CDBF00C02336 /* RNFirebaseLinks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseLinks.m; sourceTree = ""; }; 27540F98209F361B001F4AF4 /* RNFirebaseFunctions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFunctions.h; sourceTree = ""; }; @@ -126,6 +129,16 @@ name = Products; sourceTree = ""; }; + 168785A0210B50AA00E4BD57 /* converters */ = { + isa = PBXGroup; + children = ( + 168785AC210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.h */, + 168785AB210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.m */, + ); + name = converters; + path = RNFirebase/converters; + sourceTree = ""; + }; 17AF4F681F59CDBF00C02336 /* links */ = { isa = PBXGroup; children = ( @@ -149,6 +162,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 168785A0210B50AA00E4BD57 /* converters */, 27540F97209F35DF001F4AF4 /* functions */, 83AAA0762063DEC2007EC5F7 /* invites */, 838E372420231E15004DCD3A /* notifications */, @@ -398,6 +412,7 @@ 27540F9A209F3641001F4AF4 /* RNFirebaseFunctions.m in Sources */, 838E372320231DF0004DCD3A /* RNFirebaseInstanceId.m in Sources */, 839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */, + 168785AD210B584E00E4BD57 /* RCTConvert+UIBackgroundFetchResult.m in Sources */, 839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */, 17AF4F6B1F59CDBF00C02336 /* RNFirebaseLinks.m in Sources */, 8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */, diff --git a/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.h b/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.h new file mode 100644 index 00000000..e0ed190a --- /dev/null +++ b/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.h @@ -0,0 +1,5 @@ +#import + +@interface RCTConvert (UIBackgroundFetchResult) + +@end diff --git a/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.m b/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.m new file mode 100644 index 00000000..28d78ba2 --- /dev/null +++ b/ios/RNFirebase/converters/RCTConvert+UIBackgroundFetchResult.m @@ -0,0 +1,9 @@ +#import "RCTConvert+UIBackgroundFetchResult.h" + +@implementation RCTConvert (UIBackgroundFetchResult) +RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ @"backgroundFetchResultNoData" : @(UIBackgroundFetchResultNoData), + @"backgroundFetchResultNewData" : @(UIBackgroundFetchResultNewData), + @"backgroundFetchResultFailed" : @(UIBackgroundFetchResultFailed)}), + UIBackgroundFetchResultNoData, integerValue) + +@end diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index decfac32..9de4e54f 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -113,16 +113,7 @@ RCT_EXPORT_METHOD(stopHandlingNotificationDisplayed) { completionHandlers = nil; } -RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(NSString *)rnFetchResult) { - UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData; - if ([@"noData" isEqualToString:rnFetchResult]) { - fetchResult = UIBackgroundFetchResultNoData; - } else if ([@"newData" isEqualToString:rnFetchResult]) { - fetchResult = UIBackgroundFetchResultNewData; - } else if ([@"failed" isEqualToString:rnFetchResult]) { - fetchResult = UIBackgroundFetchResultFailed; - } - +RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(UIBackgroundFetchResult)fetchResult) { void (^completionHandler)(UIBackgroundFetchResult) = completionHandlers[handlerKey]; completionHandlers[handlerKey] = nil; @@ -787,6 +778,12 @@ RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPro return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_OPENED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; } +- (NSDictionary *) constantsToExport { + return @{ @"backgroundFetchResultNoData" : @(UIBackgroundFetchResultNoData), + @"backgroundFetchResultNewData" : @(UIBackgroundFetchResultNewData), + @"backgroundFetchResultFailed" : @(UIBackgroundFetchResultFailed)}; +} + + (BOOL)requiresMainQueueSetup { return YES; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 5cc52591..9a3a739f 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -45,6 +45,12 @@ type OnNotificationOpenedObserver = { next: NotificationOpen, }; +type BackgroundFetchResult = { + noData: string, + newData: string, + failure: string, +}; + const NATIVE_EVENTS = [ 'notifications_notification_displayed', 'notifications_notification_opened', @@ -78,6 +84,7 @@ const hasListeners = (eventType: string): boolean => { */ export default class Notifications extends ModuleBase { _android: AndroidNotifications; + _backgroundFetchResult: BackgroundFetchResult; constructor(app: App) { super(app, { @@ -89,6 +96,13 @@ export default class Notifications extends ModuleBase { }); this._android = new AndroidNotifications(this); + const nativeModule = getNativeModule(this); + this._backgroundFetchResult = { + noData: nativeModule.backgroundFetchResultNoData, + newData: nativeModule.backgroundFetchResultNewData, + failure: nativeModule.backgroundFetchResultFailure, + }; + SharedEventEmitter.addListener( // sub to internal native event - this fans out to // public event name: onNotificationDisplayed @@ -153,6 +167,10 @@ export default class Notifications extends ModuleBase { return this._android; } + get backgroundFetchResult(): BackgroundFetchResult { + return { ...this._backgroundFetchResult }; + } + /** * Cancel all notifications */ From 0291605934640004667ba44498efc66da7f985fe Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Mon, 6 Aug 2018 15:19:05 +0100 Subject: [PATCH 07/17] Protect against multiple calls to start/stop handling native methods --- ios/RNFirebase/notifications/RNFirebaseNotifications.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 9de4e54f..3a2b6556 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -99,12 +99,22 @@ RCT_EXPORT_MODULE(); } RCT_EXPORT_METHOD(startHandlingNotificationDisplayed) { + if(isUserHandlingOnNotificationDisplayed == YES) { + return; + } + isUserHandlingOnNotificationDisplayed = YES; + completionHandlers = [[NSMutableDictionary alloc] init]; } RCT_EXPORT_METHOD(stopHandlingNotificationDisplayed) { + if(isUserHandlingOnNotificationDisplayed == NO) { + return; + } + isUserHandlingOnNotificationDisplayed = NO; + NSArray *allHandlers = completionHandlers.allValues; for (void (^ completionHandler)(UIBackgroundFetchResult) in allHandlers) { completionHandler(UIBackgroundFetchResultNoData); From ab42108234aafd3b395b528048f78f30801044f6 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Tue, 7 Aug 2018 14:26:16 +0100 Subject: [PATCH 08/17] Add auto call to done which awaits the listener before doing so The idea is to catch any cases where the API user is not calling done manually. This has the issue that we've modified pre-existing function to take a Promisified callback instead of just a callback. Any user upgrading to a version that contains this that does not read the upgrade guide and make appropriate changes will: - be passing a plain callback (not promisified) - not be calling done - may be performing async (something that triggers a later callback) in their onNotificationDisplayed handler. This all means that we will be auto calling `done` prior to them performing any background work. This is exactly how things worked prior to these changes so perhaps the user is no worse off. --- lib/modules/notifications/index.js | 50 +++++++++++++++++------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 9a3a739f..b31beebd 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -33,7 +33,17 @@ import type { Schedule, } from './types'; -type OnNotification = Notification => any; +type BackgroundFetchResultValue = string; + +type BackgroundFetchResult = { + noData: BackgroundFetchResultValue, + newData: BackgroundFetchResultValue, + failure: BackgroundFetchResultValue, +}; + +type CompletionHandler = BackgroundFetchResultValue => void; + +type OnNotification = (Notification, CompletionHandler) => Promise; type OnNotificationObserver = { next: OnNotification, @@ -45,12 +55,6 @@ type OnNotificationOpenedObserver = { next: NotificationOpen, }; -type BackgroundFetchResult = { - noData: string, - newData: string, - failure: string, -}; - const NATIVE_EVENTS = [ 'notifications_notification_displayed', 'notifications_notification_opened', @@ -266,32 +270,36 @@ export default class Notifications extends ModuleBase { onNotificationDisplayed( nextOrObserver: OnNotification | OnNotificationObserver ): () => any { - let listener; - if (isFunction(nextOrObserver)) { - listener = nextOrObserver; - } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { - listener = nextOrObserver.next; - } else { + const isNext = isFunction(nextOrObserver); + const isObserver = isObject(nextOrObserver) && isFunction(nextOrObserver.next); + if (!isNext && !isObserver) { throw new Error( 'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.' ); } + let listener: OnNotification; + if (nextOrObserver.next) { + listener = nextOrObserver.next; + } else { + listener = nextOrObserver; + } + + const autoCompletingListener: OnNotification = async (notification, done) => { + const listenerResult = await listener(notification, done); + done(this.backgroundFetchResult.noData); + return listenerResult; + }; + getLogger(this).info('Creating onNotificationDisplayed listener'); - SharedEventEmitter.addListener( - 'onNotificationDisplayed', - listener - ); + SharedEventEmitter.addListener('onNotificationDisplayed', autoCompletingListener); if (hasListeners('onNotificationDisplayed')) { getNativeModule(this).startHandlingNotificationDisplayed(); } return () => { getLogger(this).info('Removing onNotificationDisplayed listener'); - SharedEventEmitter.removeListener( - 'onNotificationDisplayed', - listener - ); + SharedEventEmitter.removeListener('onNotificationDisplayed', autoCompletingListener); if (!hasListeners('onNotificationDisplayed')) { getNativeModule(this).stopHandlingNotificationDisplayed(); } From a8b4435abf3aee4533afa1d6a807d4f64580827e Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 10 Aug 2018 10:15:04 +0100 Subject: [PATCH 09/17] Update ts types --- lib/index.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 65bf3579..1c5450de 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1106,6 +1106,9 @@ declare module 'react-native-firebase' { deleteChannel(channelId: string): Promise; } + type BackgroundFetchResultValue = string; + type CompletionHandler = (backgroundFetchResult: BackgroundFetchResultValue) => void; + interface Notifications { android: AndroidNotifications; @@ -1135,7 +1138,7 @@ declare module 'react-native-firebase' { ): () => any; onNotificationDisplayed( - listener: (notification: Notification) => any + listener: (notification: Notification, done: CompletionHandler) => Promise ): () => any; onNotificationOpened( From 6a69ae66b101c765a9823b97192219136a90ce9c Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Fri, 10 Aug 2018 16:50:40 +0100 Subject: [PATCH 10/17] Auto complete early when no user listeners. This is to clear up memory on the objective-c side without needing to signal the start/stop of user listeners. --- .../notifications/RNFirebaseNotifications.m | 41 ++----------------- lib/modules/notifications/index.js | 40 +++++++++--------- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 3a2b6556..f6a19e84 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -17,7 +17,6 @@ @end @implementation RNFirebaseNotifications { - BOOL isUserHandlingOnNotificationDisplayed; NSMutableDictionary *completionHandlers; } @@ -57,6 +56,7 @@ 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 @@ -98,31 +98,6 @@ RCT_EXPORT_MODULE(); } } -RCT_EXPORT_METHOD(startHandlingNotificationDisplayed) { - if(isUserHandlingOnNotificationDisplayed == YES) { - return; - } - - isUserHandlingOnNotificationDisplayed = YES; - - completionHandlers = [[NSMutableDictionary alloc] init]; -} - -RCT_EXPORT_METHOD(stopHandlingNotificationDisplayed) { - if(isUserHandlingOnNotificationDisplayed == NO) { - return; - } - - 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:(UIBackgroundFetchResult)fetchResult) { void (^completionHandler)(UIBackgroundFetchResult) = completionHandlers[handlerKey]; completionHandlers[handlerKey] = nil; @@ -132,15 +107,6 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(UIBackgroundFetchR } } -- (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 { @@ -153,6 +119,7 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(UIBackgroundFetchR } NSDictionary *notification = [self parseUserInfo:userInfo]; + NSString *handlerKey = notification[@"notificationId"]; NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { @@ -180,8 +147,8 @@ RCT_EXPORT_METHOD(complete:(NSString*)handlerKey fetchResult:(UIBackgroundFetchR }; } - [self executeOrStoreCompletionHandler:completionHandler notification:notification]; - + completionHandlers[handlerKey] = completionHandler; + [self sendJSEvent:self name:event body:notification]; } diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index b31beebd..c9a58f03 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -128,11 +128,14 @@ export default class Notifications extends ModuleBase { android: () => {}, }); - SharedEventEmitter.emit( - 'onNotificationDisplayed', - rnNotification, - done - ); + const publicEventName = 'onNotificationDisplayed'; + + if (!hasListeners(publicEventName)) { + // if user is not handling completion then we need to + done(this.backgroundFetchResult.noData); + } + + SharedEventEmitter.emit(publicEventName, rnNotification, done); } ); @@ -271,10 +274,12 @@ export default class Notifications extends ModuleBase { nextOrObserver: OnNotification | OnNotificationObserver ): () => any { const isNext = isFunction(nextOrObserver); - const isObserver = isObject(nextOrObserver) && isFunction(nextOrObserver.next); - if (!isNext && !isObserver) { + const isObserver = + isObject(nextOrObserver) && isFunction(nextOrObserver.next); + const eventName = 'onNotificationDisplayed'; + if (!isNext && !isObserver) { throw new Error( - 'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.' + `Notifications.${eventName} failed: First argument must be a function or observer object with a \`next\` function.` ); } @@ -285,24 +290,21 @@ export default class Notifications extends ModuleBase { listener = nextOrObserver; } - const autoCompletingListener: OnNotification = async (notification, done) => { + const autoCompletingListener: OnNotification = async ( + notification, + done + ) => { const listenerResult = await listener(notification, done); done(this.backgroundFetchResult.noData); return listenerResult; }; - getLogger(this).info('Creating onNotificationDisplayed listener'); - SharedEventEmitter.addListener('onNotificationDisplayed', autoCompletingListener); - if (hasListeners('onNotificationDisplayed')) { - getNativeModule(this).startHandlingNotificationDisplayed(); - } + getLogger(this).info(`Creating ${eventName} listener`); + SharedEventEmitter.addListener(eventName, autoCompletingListener); return () => { - getLogger(this).info('Removing onNotificationDisplayed listener'); - SharedEventEmitter.removeListener('onNotificationDisplayed', autoCompletingListener); - if (!hasListeners('onNotificationDisplayed')) { - getNativeModule(this).stopHandlingNotificationDisplayed(); - } + getLogger(this).info(`Removing ${eventName} listener`); + SharedEventEmitter.removeListener(eventName, autoCompletingListener); }; } From 1ad4b8c0dd96941a687440c137a5338352569945 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Tue, 14 Aug 2018 14:25:21 +0100 Subject: [PATCH 11/17] Make the user handling of completion all or nothing The user controls this with a `_shouldAutoComplete` property. If true we'll immediately call the completion handler for them. If false then control is given to the user and we won't try and auto complete. --- src/index.d.ts | 2 +- src/modules/notifications/index.js | 58 +++++++++++++----------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index 4d2d8f07..a477009b 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1168,7 +1168,7 @@ declare module 'react-native-firebase' { ): () => any; onNotificationDisplayed( - listener: (notification: Notification, done: CompletionHandler) => Promise + listener: (notification: Notification, done: CompletionHandler) => any ): () => any; onNotificationOpened( diff --git a/src/modules/notifications/index.js b/src/modules/notifications/index.js index c9a58f03..aa96e903 100644 --- a/src/modules/notifications/index.js +++ b/src/modules/notifications/index.js @@ -43,7 +43,7 @@ type BackgroundFetchResult = { type CompletionHandler = BackgroundFetchResultValue => void; -type OnNotification = (Notification, CompletionHandler) => Promise; +type OnNotification = (Notification, CompletionHandler) => any; type OnNotificationObserver = { next: OnNotification, @@ -64,11 +64,6 @@ 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; @@ -88,6 +83,7 @@ const hasListeners = (eventType: string): boolean => { */ export default class Notifications extends ModuleBase { _android: AndroidNotifications; + _shouldAutoComplete: boolean; _backgroundFetchResult: BackgroundFetchResult; constructor(app: App) { @@ -107,6 +103,8 @@ export default class Notifications extends ModuleBase { failure: nativeModule.backgroundFetchResultFailure, }; + this.startAutoCompleting(); + SharedEventEmitter.addListener( // sub to internal native event - this fans out to // public event name: onNotificationDisplayed @@ -130,8 +128,7 @@ export default class Notifications extends ModuleBase { const publicEventName = 'onNotificationDisplayed'; - if (!hasListeners(publicEventName)) { - // if user is not handling completion then we need to + if (this._shouldAutoComplete) { done(this.backgroundFetchResult.noData); } @@ -178,6 +175,14 @@ export default class Notifications extends ModuleBase { return { ...this._backgroundFetchResult }; } + startAutoCompleting(): void { + this._shouldAutoComplete = true; + } + + stopAutoCompleting(): void { + this._shouldAutoComplete = false; + } + /** * Cancel all notifications */ @@ -273,38 +278,23 @@ export default class Notifications extends ModuleBase { onNotificationDisplayed( nextOrObserver: OnNotification | OnNotificationObserver ): () => any { - const isNext = isFunction(nextOrObserver); - const isObserver = - isObject(nextOrObserver) && isFunction(nextOrObserver.next); - const eventName = 'onNotificationDisplayed'; - if (!isNext && !isObserver) { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { throw new Error( - `Notifications.${eventName} failed: First argument must be a function or observer object with a \`next\` function.` + 'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.' ); } - let listener: OnNotification; - if (nextOrObserver.next) { - listener = nextOrObserver.next; - } else { - listener = nextOrObserver; - } - - const autoCompletingListener: OnNotification = async ( - notification, - done - ) => { - const listenerResult = await listener(notification, done); - done(this.backgroundFetchResult.noData); - return listenerResult; - }; - - getLogger(this).info(`Creating ${eventName} listener`); - SharedEventEmitter.addListener(eventName, autoCompletingListener); + getLogger(this).info('Creating onNotificationDisplayed listener'); + SharedEventEmitter.addListener('onNotificationDisplayed', listener); return () => { - getLogger(this).info(`Removing ${eventName} listener`); - SharedEventEmitter.removeListener(eventName, autoCompletingListener); + getLogger(this).info('Removing onNotificationDisplayed listener'); + SharedEventEmitter.removeListener('onNotificationDisplayed', listener); }; } From 000a4ec724b017de701e8259e537ec6eaa3a8ce4 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Tue, 14 Aug 2018 15:28:21 +0100 Subject: [PATCH 12/17] Move ios API into IOSNotifications --- src/modules/notifications/IOSNotifications.js | 31 ++++++++++++++ src/modules/notifications/index.js | 41 +++++-------------- 2 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 src/modules/notifications/IOSNotifications.js diff --git a/src/modules/notifications/IOSNotifications.js b/src/modules/notifications/IOSNotifications.js new file mode 100644 index 00000000..59c86605 --- /dev/null +++ b/src/modules/notifications/IOSNotifications.js @@ -0,0 +1,31 @@ +import { getNativeModule } from '../../utils/native'; + +import type Notifications from '.'; + +export type BackgroundFetchResultValue = string; +type BackgroundFetchResult = { + noData: BackgroundFetchResultValue, + newData: BackgroundFetchResultValue, + failure: BackgroundFetchResultValue, +}; + +export default class IOSNotifications { + _backgroundFetchResult: BackgroundFetchResult; + + shouldAutoComplete: boolean; + + constructor(notifications: Notifications) { + this.shouldAutoComplete = true; + + const nativeModule = getNativeModule(notifications); + this._backgroundFetchResult = { + noData: nativeModule.backgroundFetchResultNoData, + newData: nativeModule.backgroundFetchResultNewData, + failure: nativeModule.backgroundFetchResultFailure, + }; + } + + get backgroundFetchResult(): BackgroundFetchResult { + return { ...this._backgroundFetchResult }; + } +} diff --git a/src/modules/notifications/index.js b/src/modules/notifications/index.js index aa96e903..96f904a4 100644 --- a/src/modules/notifications/index.js +++ b/src/modules/notifications/index.js @@ -12,6 +12,9 @@ import AndroidAction from './AndroidAction'; import AndroidChannel from './AndroidChannel'; import AndroidChannelGroup from './AndroidChannelGroup'; import AndroidNotifications from './AndroidNotifications'; +import IOSNotifications, { + type BackgroundFetchResultValue, +} from './IOSNotifications'; import AndroidRemoteInput from './AndroidRemoteInput'; import Notification from './Notification'; import { @@ -33,14 +36,6 @@ import type { Schedule, } from './types'; -type BackgroundFetchResultValue = string; - -type BackgroundFetchResult = { - noData: BackgroundFetchResultValue, - newData: BackgroundFetchResultValue, - failure: BackgroundFetchResultValue, -}; - type CompletionHandler = BackgroundFetchResultValue => void; type OnNotification = (Notification, CompletionHandler) => any; @@ -83,8 +78,8 @@ export const NAMESPACE = 'notifications'; */ export default class Notifications extends ModuleBase { _android: AndroidNotifications; - _shouldAutoComplete: boolean; - _backgroundFetchResult: BackgroundFetchResult; + + _ios: IOSNotifications; constructor(app: App) { super(app, { @@ -95,15 +90,7 @@ export default class Notifications extends ModuleBase { namespace: NAMESPACE, }); this._android = new AndroidNotifications(this); - - const nativeModule = getNativeModule(this); - this._backgroundFetchResult = { - noData: nativeModule.backgroundFetchResultNoData, - newData: nativeModule.backgroundFetchResultNewData, - failure: nativeModule.backgroundFetchResultFailure, - }; - - this.startAutoCompleting(); + this._ios = new IOSNotifications(this); SharedEventEmitter.addListener( // sub to internal native event - this fans out to @@ -128,8 +115,8 @@ export default class Notifications extends ModuleBase { const publicEventName = 'onNotificationDisplayed'; - if (this._shouldAutoComplete) { - done(this.backgroundFetchResult.noData); + if (this.ios.shouldAutoComplete) { + done(this.ios.backgroundFetchResult.noData); } SharedEventEmitter.emit(publicEventName, rnNotification, done); @@ -171,16 +158,8 @@ export default class Notifications extends ModuleBase { return this._android; } - get backgroundFetchResult(): BackgroundFetchResult { - return { ...this._backgroundFetchResult }; - } - - startAutoCompleting(): void { - this._shouldAutoComplete = true; - } - - stopAutoCompleting(): void { - this._shouldAutoComplete = false; + get ios(): IOSNotifications { + return this._ios; } /** From 0af7c02955d11b36eaafca2be51141e3a36d0be0 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Tue, 14 Aug 2018 16:34:31 +0100 Subject: [PATCH 13/17] Move autocompletion into IOSNotification class --- src/modules/notifications/IOSNotification.js | 29 ++++++++++++++++- src/modules/notifications/Notification.js | 31 ++++++++++++------ src/modules/notifications/index.js | 33 +++++--------------- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/modules/notifications/IOSNotification.js b/src/modules/notifications/IOSNotification.js index 2a0af539..5a360a8e 100644 --- a/src/modules/notifications/IOSNotification.js +++ b/src/modules/notifications/IOSNotification.js @@ -3,11 +3,18 @@ * IOSNotification representation wrapper */ import type Notification from './Notification'; +import IOSNotifications, { + type BackgroundFetchResultValue, +} from './IOSNotifications'; import type { IOSAttachment, IOSAttachmentOptions, NativeIOSNotification, } from './types'; +import { getLogger } from '../../utils/log'; +import { getNativeModule } from '../../utils/native'; + +type CompletionHandler = BackgroundFetchResultValue => void; export default class IOSNotification { _alertAction: string | void; @@ -31,7 +38,13 @@ export default class IOSNotification { _threadIdentifier: string | void; // N/A | threadIdentifier - constructor(notification: Notification, data?: NativeIOSNotification) { + _complete: CompletionHandler; + + constructor( + notification: Notification, + notifications: IOSNotifications, + data?: NativeIOSNotification + ) { this._notification = notification; if (data) { @@ -44,6 +57,20 @@ export default class IOSNotification { this._threadIdentifier = data.threadIdentifier; } + const complete = (fetchResult: BackgroundFetchResultValue) => { + const { notificationId } = notification; + getLogger(notifications).debug( + `Completion handler called for notificationId=${notificationId}` + ); + getNativeModule(notifications).complete(notificationId, fetchResult); + }; + + if (notifications.shouldAutoComplete) { + complete(notifications.backgroundFetchResult.noData); + } else { + this._complete = complete; + } + // Defaults this._attachments = this._attachments || []; } diff --git a/src/modules/notifications/Notification.js b/src/modules/notifications/Notification.js index 566befd8..6ce80313 100644 --- a/src/modules/notifications/Notification.js +++ b/src/modules/notifications/Notification.js @@ -8,6 +8,7 @@ import IOSNotification from './IOSNotification'; import { generatePushID, isObject } from '../../utils'; import type { NativeNotification } from './types'; +import type Notifications from '.'; export type NotificationOpen = {| action: string, @@ -37,17 +38,27 @@ export default class Notification { // N/A | subtitle | subText _title: string; // alertTitle | title | contentTitle - constructor(data?: NativeNotification) { - this._android = new AndroidNotification(this, data && data.android); - this._ios = new IOSNotification(this, data && data.ios); + constructor( + nativeNotification?: NativeNotification, + notifications: Notifications + ) { + this._android = new AndroidNotification( + this, + nativeNotification && nativeNotification.android + ); + this._ios = new IOSNotification( + this, + notifications.ios, + nativeNotification && nativeNotification.ios + ); - if (data) { - this._body = data.body; - this._data = data.data; - this._notificationId = data.notificationId; - this._sound = data.sound; - this._subtitle = data.subtitle; - this._title = data.title; + if (nativeNotification) { + this._body = nativeNotification.body; + this._data = nativeNotification.data; + this._notificationId = nativeNotification.notificationId; + this._sound = nativeNotification.sound; + this._subtitle = nativeNotification.subtitle; + this._title = nativeNotification.title; } // Defaults diff --git a/src/modules/notifications/index.js b/src/modules/notifications/index.js index 96f904a4..101fd14c 100644 --- a/src/modules/notifications/index.js +++ b/src/modules/notifications/index.js @@ -97,29 +97,10 @@ export default class Notifications extends ModuleBase { // public event name: onNotificationDisplayed 'notifications_notification_displayed', (notification: NativeNotification) => { - const rnNotification = new Notification(notification); - const done = Platform.select({ - ios: (fetchResult: string) => { - getLogger(this).debug( - `Completion handler called for notificationId=${ - rnNotification.notificationId - }` - ); - getNativeModule(this).complete( - rnNotification.notificationId, - fetchResult - ); - }, - android: () => {}, - }); - - const publicEventName = 'onNotificationDisplayed'; - - if (this.ios.shouldAutoComplete) { - done(this.ios.backgroundFetchResult.noData); - } - - SharedEventEmitter.emit(publicEventName, rnNotification, done); + SharedEventEmitter.emit( + 'onNotificationDisplayed', + new Notification(notification, this) + ); } ); @@ -130,7 +111,7 @@ export default class Notifications extends ModuleBase { (notificationOpen: NativeNotificationOpen) => { SharedEventEmitter.emit('onNotificationOpened', { action: notificationOpen.action, - notification: new Notification(notificationOpen.notification), + notification: new Notification(notificationOpen.notification, this), results: notificationOpen.results, }); } @@ -143,7 +124,7 @@ export default class Notifications extends ModuleBase { (notification: NativeNotification) => { SharedEventEmitter.emit( 'onNotification', - new Notification(notification) + new Notification(notification, this) ); } ); @@ -215,7 +196,7 @@ export default class Notifications extends ModuleBase { if (notificationOpen) { return { action: notificationOpen.action, - notification: new Notification(notificationOpen.notification), + notification: new Notification(notificationOpen.notification, this), results: notificationOpen.results, }; } From cfb5f3a3041fb641ed9affcac3f5f2d483313975 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Wed, 15 Aug 2018 10:24:24 +0100 Subject: [PATCH 14/17] Ensure notificationId is set before constructing IOSNotification --- src/modules/notifications/Notification.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/modules/notifications/Notification.js b/src/modules/notifications/Notification.js index 6ce80313..9eec6b21 100644 --- a/src/modules/notifications/Notification.js +++ b/src/modules/notifications/Notification.js @@ -42,6 +42,15 @@ export default class Notification { nativeNotification?: NativeNotification, notifications: Notifications ) { + if (nativeNotification) { + this._body = nativeNotification.body; + this._data = nativeNotification.data; + this._notificationId = nativeNotification.notificationId; + this._sound = nativeNotification.sound; + this._subtitle = nativeNotification.subtitle; + this._title = nativeNotification.title; + } + this._android = new AndroidNotification( this, nativeNotification && nativeNotification.android @@ -52,15 +61,6 @@ export default class Notification { nativeNotification && nativeNotification.ios ); - if (nativeNotification) { - this._body = nativeNotification.body; - this._data = nativeNotification.data; - this._notificationId = nativeNotification.notificationId; - this._sound = nativeNotification.sound; - this._subtitle = nativeNotification.subtitle; - this._title = nativeNotification.title; - } - // Defaults this._data = this._data || {}; // TODO: Is this the best way to generate an ID? From 363c315d60397597911ac8aee5a1c8cd50850f3a Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Wed, 15 Aug 2018 10:31:40 +0100 Subject: [PATCH 15/17] Pass notifications (not ios notifications) into IOSNotification --- src/modules/notifications/IOSNotification.js | 11 +++++------ src/modules/notifications/Notification.js | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modules/notifications/IOSNotification.js b/src/modules/notifications/IOSNotification.js index 5a360a8e..cc1dbf9b 100644 --- a/src/modules/notifications/IOSNotification.js +++ b/src/modules/notifications/IOSNotification.js @@ -3,9 +3,8 @@ * IOSNotification representation wrapper */ import type Notification from './Notification'; -import IOSNotifications, { - type BackgroundFetchResultValue, -} from './IOSNotifications'; +import type Notifications from '.'; +import { type BackgroundFetchResultValue } from './IOSNotifications'; import type { IOSAttachment, IOSAttachmentOptions, @@ -42,7 +41,7 @@ export default class IOSNotification { constructor( notification: Notification, - notifications: IOSNotifications, + notifications: Notifications, data?: NativeIOSNotification ) { this._notification = notification; @@ -65,8 +64,8 @@ export default class IOSNotification { getNativeModule(notifications).complete(notificationId, fetchResult); }; - if (notifications.shouldAutoComplete) { - complete(notifications.backgroundFetchResult.noData); + if (notifications.ios.shouldAutoComplete) { + complete(notifications.ios.backgroundFetchResult.noData); } else { this._complete = complete; } diff --git a/src/modules/notifications/Notification.js b/src/modules/notifications/Notification.js index 9eec6b21..5d7bae0e 100644 --- a/src/modules/notifications/Notification.js +++ b/src/modules/notifications/Notification.js @@ -57,7 +57,7 @@ export default class Notification { ); this._ios = new IOSNotification( this, - notifications.ios, + notifications, nativeNotification && nativeNotification.ios ); From 0d0f85d2170a2554c4b2245ab1126ea364cf60a1 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Wed, 15 Aug 2018 11:22:02 +0100 Subject: [PATCH 16/17] Add complete getter for IOSNotification --- src/modules/notifications/IOSNotification.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/notifications/IOSNotification.js b/src/modules/notifications/IOSNotification.js index cc1dbf9b..d465ba77 100644 --- a/src/modules/notifications/IOSNotification.js +++ b/src/modules/notifications/IOSNotification.js @@ -102,6 +102,10 @@ export default class IOSNotification { return this._threadIdentifier; } + get complete(): CompletionHandler { + return this._complete; + } + /** * * @param identifier From 5689d702e4ecd036470fa1672dba87c5c5cab407 Mon Sep 17 00:00:00 2001 From: Ryan Grey Date: Wed, 15 Aug 2018 11:51:35 +0100 Subject: [PATCH 17/17] Update ts/flow types CompletionHandler is no longer passed as a param to listener. Instead it exists on the IOSNotification class. --- src/index.d.ts | 3 ++- src/modules/notifications/index.js | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index a477009b..ed5216ac 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1168,7 +1168,7 @@ declare module 'react-native-firebase' { ): () => any; onNotificationDisplayed( - listener: (notification: Notification, done: CompletionHandler) => any + listener: (notification: Notification) => any ): () => any; onNotificationOpened( @@ -1473,6 +1473,7 @@ declare module 'react-native-firebase' { hasAction?: boolean; launchImage?: string; threadIdentifier?: string; + complete?: CompletionHandler; addAttachment( identifier: string, diff --git a/src/modules/notifications/index.js b/src/modules/notifications/index.js index 101fd14c..ec33c181 100644 --- a/src/modules/notifications/index.js +++ b/src/modules/notifications/index.js @@ -12,9 +12,7 @@ import AndroidAction from './AndroidAction'; import AndroidChannel from './AndroidChannel'; import AndroidChannelGroup from './AndroidChannelGroup'; import AndroidNotifications from './AndroidNotifications'; -import IOSNotifications, { - type BackgroundFetchResultValue, -} from './IOSNotifications'; +import IOSNotifications from './IOSNotifications'; import AndroidRemoteInput from './AndroidRemoteInput'; import Notification from './Notification'; import { @@ -36,9 +34,7 @@ import type { Schedule, } from './types'; -type CompletionHandler = BackgroundFetchResultValue => void; - -type OnNotification = (Notification, CompletionHandler) => any; +type OnNotification = Notification => any; type OnNotificationObserver = { next: OnNotification,