From b86d51f36b59fa0bb1b5e76b8a85dfc6d554ca7a Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 16 Feb 2018 09:47:46 +0000 Subject: [PATCH 01/13] [fcm] Start considering multiple events --- ios/RNFirebase/RNFirebaseEvents.h | 5 +++ .../messaging/RNFirebaseMessaging.m | 38 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ios/RNFirebase/RNFirebaseEvents.h b/ios/RNFirebase/RNFirebaseEvents.h index 26ce5f3e..ac40e86d 100644 --- a/ios/RNFirebase/RNFirebaseEvents.h +++ b/ios/RNFirebase/RNFirebaseEvents.h @@ -38,6 +38,11 @@ static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed"; // TODO: Remove static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received"; +// Notifications +static NSString *const NOTIFICATIONS_NOTIFICATION_CLICKED = @"notifications_notification_clicked"; +static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed"; +static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received"; + // AdMob static NSString *const ADMOB_INTERSTITIAL_EVENT = @"interstitial_event"; static NSString *const ADMOB_REWARDED_VIDEO_EVENT = @"rewarded_video_event"; diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 14454ef0..8198a8b7 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -113,11 +113,34 @@ RCT_EXPORT_MODULE() - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { + + NSString *event; + UNNotificationPresentationOptions options; NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; - - [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; - - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; + + if (isFCM || isScheduled) { + // If background + if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // display notification + options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else { + // don't show notification + options = UNNotificationPresentationOptionNone; + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } + } else { + // display notification + options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; + // no event + } + + if (event) { + [RNFirebaseUtil sendJSEvent:self name:event body:message]; + } + completionHandler(options); } // Handle notification messages after display notification is tapped by the user. @@ -129,10 +152,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler { #endif NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true]; - - [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; - - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; + + [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message]; + completionHandler(); } #endif From a244f17853fdabc62e7e723aff266cbf59d0b17f Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 18 Feb 2018 16:02:31 +0000 Subject: [PATCH 02/13] [fcm] More iOS event separation --- .../messaging/RNFirebaseMessaging.m | 9 +- lib/modules/notifications/index.js | 83 +++++++++++++++++-- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 8198a8b7..7e916620 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -119,9 +119,9 @@ RCT_EXPORT_MODULE() NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; if (isFCM || isScheduled) { - // If background + // If app is in the background if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // display notification + // display the notification options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; // notification_displayed event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; @@ -132,7 +132,8 @@ RCT_EXPORT_MODULE() event = NOTIFICATIONS_NOTIFICATION_RECEIVED; } } else { - // display notification + // Triggered by `notifications().displayNotification(notification)` + // Display the notification options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; // no event } @@ -482,7 +483,7 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId } - (NSArray *)supportedEvents { - return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED]; + return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED, NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; } + (BOOL)requiresMainQueueSetup diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 17857fbd..57aed391 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -32,7 +32,11 @@ export type Schedule = { repeatInterval?: 'minute' | 'hour' | 'day' | 'week', }; -const NATIVE_EVENTS = ['notifications_notification_received']; +const NATIVE_EVENTS = [ + 'notifications_notification_clicked', + 'notifications_notification_displayed', + 'notifications_notification_received', +]; export const MODULE_NAME = 'RNFirebaseNotifications'; export const NAMESPACE = 'notifications'; @@ -65,7 +69,25 @@ export default class Notifications extends ModuleBase { SharedEventEmitter.addListener( // sub to internal native event - this fans out to - // public event name: onMessage + // public event name: onNotificationClicked + 'notifications_notification_clicked', + (notification: Notification) => { + SharedEventEmitter.emit('onNotificationClicked', notification); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotificationDisplayed + 'notifications_notification_displayed', + (notification: Notification) => { + SharedEventEmitter.emit('onNotificationDisplayed', notification); + } + ); + + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotification 'notifications_notification_received', (notification: Notification) => { SharedEventEmitter.emit('onNotification', notification); @@ -73,12 +95,16 @@ export default class Notifications extends ModuleBase { ); } + /** + * Cancel all notifications + * @returns {*} + */ cancelAllNotifications(): Promise { return getNativeModule(this).cancelAllNotifications(); } /** - * Cancel a local notification by id. + * Cancel a notification by id. * @param id * @returns {*} */ @@ -90,7 +116,7 @@ export default class Notifications extends ModuleBase { } /** - * Display a local notification + * Display a notification * @param notification * @returns {*} */ @@ -131,7 +157,6 @@ export default class Notifications extends ModuleBase { ); } - // TODO: iOS finish getLogger(this).info('Creating onNotification listener'); SharedEventEmitter.addListener('onNotification', listener); @@ -141,6 +166,52 @@ export default class Notifications extends ModuleBase { }; } + onNotificationClicked( + nextOrObserver: OnNotification | OnNotificationObserver + ): () => any { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotificationClicked failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotificationClicked listener'); + SharedEventEmitter.addListener('onNotificationClicked', listener); + + return () => { + getLogger(this).info('Removing onNotificationClicked listener'); + SharedEventEmitter.removeListener('onNotificationClicked', listener); + }; + } + + onNotificationDisplayed( + nextOrObserver: OnNotification | OnNotificationObserver + ): () => any { + let listener; + if (isFunction(nextOrObserver)) { + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotificationDisplayed listener'); + SharedEventEmitter.addListener('onNotificationDisplayed', listener); + + return () => { + getLogger(this).info('Removing onNotificationDisplayed listener'); + SharedEventEmitter.removeListener('onNotificationDisplayed', listener); + }; + } + /** * Remove all delivered notifications. * @returns {*} @@ -162,7 +233,7 @@ export default class Notifications extends ModuleBase { } /** - * + * Schedule a notification * @param notification * @returns {*} */ From 831eec82f740868f82cdff974496df20128ebf81 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 18 Feb 2018 17:09:13 +0000 Subject: [PATCH 03/13] [messaging][notifications] Move all notifications from messaging to notifications --- .../messaging/RNFirebaseMessaging.h | 2 - .../messaging/RNFirebaseMessaging.m | 195 +--------------- .../notifications/RNFirebaseNotifications.h | 6 +- .../notifications/RNFirebaseNotifications.m | 220 +++++++++++++++++- .../ios/ReactNativeFirebaseDemo/AppDelegate.m | 6 +- 5 files changed, 235 insertions(+), 194 deletions(-) diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.h b/ios/RNFirebase/messaging/RNFirebaseMessaging.h index ea6ed75f..2aa44131 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.h +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.h @@ -15,8 +15,6 @@ @property _Nullable RCTPromiseResolveBlock permissionResolver; #if !TARGET_OS_TV -- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; -- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; - (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; #endif diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 7e916620..19782e02 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -11,14 +11,10 @@ #import #import -// For iOS 10 we need to implement UNUserNotificationCenterDelegate to receive display -// notifications via APNS #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @import UserNotifications; -@interface RNFirebaseMessaging () -#else -@interface RNFirebaseMessaging () #endif +@interface RNFirebaseMessaging () @property (nonatomic, strong) NSMutableDictionary *callbackHandlers; @end @@ -48,12 +44,7 @@ RCT_EXPORT_MODULE() // Establish Firebase managed data channel [FIRMessaging messaging].shouldEstablishDirectChannel = YES; - - // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter -#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - [UNUserNotificationCenter currentNotificationCenter].delegate = self; -#endif - + // Set static instance for use from AppDelegate theRNFirebaseMessaging = self; @@ -66,25 +57,6 @@ RCT_EXPORT_MODULE() // ** iOS 8/9 Only // ******************************************************* -// Listen for background messages -- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { - BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground]; - - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; -} - -// Listen for background messages -- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground]; - - [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; - - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; -} - // Listen for permission response - (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { if (notificationSettings.types == UIUserNotificationTypeNone) { @@ -103,68 +75,6 @@ RCT_EXPORT_MODULE() // ******************************************************* -// ******************************************************* -// ** Start UNUserNotificationCenterDelegate methods -// ** iOS 10+ -// ******************************************************* - -#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -// Handle incoming notification messages while app is in the foreground. -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - willPresentNotification:(UNNotification *)notification - withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { - - NSString *event; - UNNotificationPresentationOptions options; - NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; - - if (isFCM || isScheduled) { - // If app is in the background - if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // display the notification - options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; - } else { - // don't show notification - options = UNNotificationPresentationOptionNone; - // notification_received - event = NOTIFICATIONS_NOTIFICATION_RECEIVED; - } - } else { - // Triggered by `notifications().displayNotification(notification)` - // Display the notification - options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; - // no event - } - - if (event) { - [RNFirebaseUtil sendJSEvent:self name:event body:message]; - } - completionHandler(options); -} - -// Handle notification messages after display notification is tapped by the user. -- (void)userNotificationCenter:(UNUserNotificationCenter *)center -didReceiveNotificationResponse:(UNNotificationResponse *)response -#if defined(__IPHONE_11_0) - withCompletionHandler:(void(^)(void))completionHandler { -#else - withCompletionHandler:(void(^)())completionHandler { -#endif - NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true]; - - [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message]; - completionHandler(); -} - -#endif - -// ******************************************************* -// ** Finish UNUserNotificationCenterDelegate methods -// ******************************************************* - - // ******************************************************* // ** Start FIRMessagingDelegate methods // ** iOS 8+ @@ -228,20 +138,16 @@ RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RC // Non Web SDK methods +// TODO: Move to notifications RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_main_queue(), ^{ resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); }); } +// TODO: Remove RCT_EXPORT_METHOD(getInitialMessage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ - NSDictionary *notification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - if (notification) { - NSDictionary *message = [self parseUserInfo:notification messageType:@"InitialMessage" clickAction:nil openedFromTray:true]; - resolve(message); - } else { - resolve(nil); - } + resolve(nil); } RCT_EXPORT_METHOD(hasPermission: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { @@ -273,6 +179,7 @@ RCT_EXPORT_METHOD(sendMessage: (NSDictionary *) message [[FIRMessaging messaging] sendMessage:data to:to withMessageID:messageId timeToLive:[ttl intValue]]; } +// TODO: Move to notifications RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { dispatch_async(dispatch_get_main_queue(), ^{ [RCTSharedApplication() setApplicationIconBadgeNumber:number]; @@ -395,95 +302,8 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId return message; } -- (NSDictionary*)parseUNNotification:(UNNotification *)notification - messageType:(NSString *)messageType - openedFromTray:(bool)openedFromTray { - NSDictionary *userInfo = notification.request.content.userInfo; - NSString *clickAction = notification.request.content.categoryIdentifier; - - return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray]; -} - -- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo - messageType:(NSString *) messageType - clickAction:(NSString *) clickAction - openedFromTray:(bool)openedFromTray { - NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; - NSMutableDictionary *notif = [[NSMutableDictionary alloc] init]; - NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; - - for (id k1 in userInfo) { - if ([k1 isEqualToString:@"aps"]) { - NSDictionary *aps = userInfo[k1]; - for (id k2 in aps) { - if ([k2 isEqualToString:@"alert"]) { - NSDictionary *alert = aps[k2]; - for (id k3 in alert) { - if ([k3 isEqualToString:@"body"]) { - notif[@"body"] = alert[k3]; - } else if ([k3 isEqualToString:@"loc-args"]) { - notif[@"bodyLocalizationArgs"] = alert[k3]; - } else if ([k3 isEqualToString:@"loc-key"]) { - notif[@"bodyLocalizationKey"] = alert[k3]; - } else if ([k3 isEqualToString:@"subtitle"]) { - notif[@"subtitle"] = alert[k3]; - } else if ([k3 isEqualToString:@"title"]) { - notif[@"title"] = alert[k3]; - } else if ([k3 isEqualToString:@"title-loc-args"]) { - notif[@"titleLocalizationArgs"] = alert[k3]; - } else if ([k3 isEqualToString:@"title-loc-key"]) { - notif[@"titleLocalizationKey"] = alert[k3]; - } else { - NSLog(@"Unknown alert key: %@", k2); - } - } - } else if ([k2 isEqualToString:@"badge"]) { - notif[@"badge"] = aps[k2]; - } else if ([k2 isEqualToString:@"category"]) { - notif[@"clickAction"] = aps[k2]; - } else if ([k2 isEqualToString:@"sound"]) { - notif[@"sound"] = aps[k2]; - } else { - NSLog(@"Unknown aps key: %@", k2); - } - } - } else if ([k1 isEqualToString:@"gcm.message_id"]) { - message[@"messageId"] = userInfo[k1]; - } else if ([k1 isEqualToString:@"google.c.a.ts"]) { - message[@"sentTime"] = userInfo[k1]; - } else if ([k1 isEqualToString:@"gcm.n.e"] - || [k1 isEqualToString:@"gcm.notification.sound2"] - || [k1 isEqualToString:@"google.c.a.c_id"] - || [k1 isEqualToString:@"google.c.a.c_l"] - || [k1 isEqualToString:@"google.c.a.e"] - || [k1 isEqualToString:@"google.c.a.udt"]) { - // Ignore known keys - } else { - // Assume custom data - data[k1] = userInfo[k1]; - } - } - - if (!notif[@"clickAction"] && clickAction) { - notif[@"clickAction"] = clickAction; - } - - // Generate a message ID if one was not present in the notification - // This is used for resolving click handlers - if (!message[@"messageId"]) { - message[@"messageId"] = [[NSUUID UUID] UUIDString]; - } - message[@"messageType"] = messageType; - - message[@"data"] = data; - message[@"notification"] = notif; - message[@"openedFromTray"] = @(openedFromTray); - - return message; -} - - (NSArray *)supportedEvents { - return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED, NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; + return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED]; } + (BOOL)requiresMainQueueSetup @@ -497,3 +317,4 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId @implementation RNFirebaseMessaging @end #endif + diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.h b/ios/RNFirebase/notifications/RNFirebaseNotifications.h index 16298e70..56ca289b 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.h +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.h @@ -8,8 +8,12 @@ @interface RNFirebaseNotifications : RCTEventEmitter -#if !TARGET_OS_TV ++ (_Nonnull instancetype)instance; +#if !TARGET_OS_TV +- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; #endif @end diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 58f0fee4..abdcd338 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -2,15 +2,146 @@ #if __has_include() #import "RNFirebaseEvents.h" +#import "RNFirebaseUtil.h" #import +// For iOS 10 we need to implement UNUserNotificationCenterDelegate to receive display +// notifications via APNS #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @import UserNotifications; +@interface RNFirebaseNotifications () +#else +@interface RNFirebaseNotifications () #endif +@end @implementation RNFirebaseNotifications + +static RNFirebaseNotifications *theRNFirebaseNotifications = nil; + ++ (nonnull instancetype)instance { + return theRNFirebaseNotifications; +} + RCT_EXPORT_MODULE(); +- (id)init { + self = [super init]; + if (self != nil) { + NSLog(@"Setting up RNFirebaseNotifications instance"); + [self configure]; + } + return self; +} + +- (void)configure { + // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + [UNUserNotificationCenter currentNotificationCenter].delegate = self; +#endif + + // Set static instance for use from AppDelegate + theRNFirebaseNotifications = self; +} + +// ******************************************************* +// ** Start AppDelegate methods +// ** iOS 8/9 Only +// ******************************************************* + +- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification { + +} + +// Listen for background messages +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { + BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground]; + + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; +} + +// Listen for background messages +- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground]; + + // [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; + + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; +} + +// ******************************************************* +// ** Finish AppDelegate methods +// ******************************************************* + +// ******************************************************* +// ** Start UNUserNotificationCenterDelegate methods +// ** iOS 10+ +// ******************************************************* + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +// Handle incoming notification messages while app is in the foreground. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { + + UNNotificationTrigger *trigger = notification.request.trigger; + BOOL isFcm = trigger && [notification.request.trigger class] == [UNPushNotificationTrigger class]; + BOOL isScheduled = trigger && [notification.request.trigger class] == [UNCalendarNotificationTrigger class]; + + NSString *event; + UNNotificationPresentationOptions options; + NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; + + if (isFcm || isScheduled) { + // If app is in the background + if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // display the notification + options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else { + // don't show notification + options = UNNotificationPresentationOptionNone; + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } + } else { + // Triggered by `notifications().displayNotification(notification)` + // Display the notification + options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } + + if (event) { + [RNFirebaseUtil sendJSEvent:self name:event body:message]; + } + completionHandler(options); +} + +// Handle notification messages after display notification is tapped by the user. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response +#if defined(__IPHONE_11_0) + withCompletionHandler:(void(^)(void))completionHandler { +#else + withCompletionHandler:(void(^)())completionHandler { +#endif + NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true]; + + [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message]; + completionHandler(); +} + +#endif + +// ******************************************************* +// ** Finish UNUserNotificationCenterDelegate methods +// ******************************************************* + RCT_EXPORT_METHOD(cancelAllNotifications) { if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { [RCTSharedApplication() cancelAllLocalNotifications]; @@ -371,9 +502,96 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification return notification; } + +- (NSDictionary*)parseUNNotification:(UNNotification *)notification + messageType:(NSString *)messageType + openedFromTray:(bool)openedFromTray { + NSDictionary *userInfo = notification.request.content.userInfo; + NSString *clickAction = notification.request.content.categoryIdentifier; + + return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray]; +} + +- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo + messageType:(NSString *) messageType + clickAction:(NSString *) clickAction + openedFromTray:(bool)openedFromTray { + NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *notif = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + + for (id k1 in userInfo) { + if ([k1 isEqualToString:@"aps"]) { + NSDictionary *aps = userInfo[k1]; + for (id k2 in aps) { + if ([k2 isEqualToString:@"alert"]) { + NSDictionary *alert = aps[k2]; + for (id k3 in alert) { + if ([k3 isEqualToString:@"body"]) { + notif[@"body"] = alert[k3]; + } else if ([k3 isEqualToString:@"loc-args"]) { + notif[@"bodyLocalizationArgs"] = alert[k3]; + } else if ([k3 isEqualToString:@"loc-key"]) { + notif[@"bodyLocalizationKey"] = alert[k3]; + } else if ([k3 isEqualToString:@"subtitle"]) { + notif[@"subtitle"] = alert[k3]; + } else if ([k3 isEqualToString:@"title"]) { + notif[@"title"] = alert[k3]; + } else if ([k3 isEqualToString:@"title-loc-args"]) { + notif[@"titleLocalizationArgs"] = alert[k3]; + } else if ([k3 isEqualToString:@"title-loc-key"]) { + notif[@"titleLocalizationKey"] = alert[k3]; + } else { + NSLog(@"Unknown alert key: %@", k2); + } + } + } else if ([k2 isEqualToString:@"badge"]) { + notif[@"badge"] = aps[k2]; + } else if ([k2 isEqualToString:@"category"]) { + notif[@"clickAction"] = aps[k2]; + } else if ([k2 isEqualToString:@"sound"]) { + notif[@"sound"] = aps[k2]; + } else { + NSLog(@"Unknown aps key: %@", k2); + } + } + } else if ([k1 isEqualToString:@"gcm.message_id"]) { + message[@"messageId"] = userInfo[k1]; + } else if ([k1 isEqualToString:@"google.c.a.ts"]) { + message[@"sentTime"] = userInfo[k1]; + } else if ([k1 isEqualToString:@"gcm.n.e"] + || [k1 isEqualToString:@"gcm.notification.sound2"] + || [k1 isEqualToString:@"google.c.a.c_id"] + || [k1 isEqualToString:@"google.c.a.c_l"] + || [k1 isEqualToString:@"google.c.a.e"] + || [k1 isEqualToString:@"google.c.a.udt"]) { + // Ignore known keys + } else { + // Assume custom data + data[k1] = userInfo[k1]; + } + } + + if (!notif[@"clickAction"] && clickAction) { + notif[@"clickAction"] = clickAction; + } + + // Generate a message ID if one was not present in the notification + // This is used for resolving click handlers + if (!message[@"messageId"]) { + message[@"messageId"] = [[NSUUID UUID] UUIDString]; + } + message[@"messageType"] = messageType; + + message[@"data"] = data; + message[@"notification"] = notif; + message[@"openedFromTray"] = @(openedFromTray); + + return message; +} - (NSArray *)supportedEvents { - return @[NOTIFICATIONS_NOTIFICATION_RECEIVED]; + return @[NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; } + (BOOL)requiresMainQueueSetup diff --git a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m index d266a798..88cfe550 100644 --- a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m +++ b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m @@ -12,7 +12,7 @@ #import #import #import -#import +#import @implementation AppDelegate @@ -39,12 +39,12 @@ } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; + [[RNFirebaseNotifications instance] didReceiveRemoteNotification:userInfo]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; + [[RNFirebaseNotifications instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } @end From 303cb4c4288d7b8a3b3f0313a888f2d472ee55fa Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 22 Feb 2018 15:52:24 +0000 Subject: [PATCH 04/13] [notifications] Refactor for better support of separate messages --- ios/RNFirebase/RNFirebaseEvents.h | 2 +- .../messaging/RNFirebaseMessaging.h | 3 +- .../messaging/RNFirebaseMessaging.m | 79 ++++---- .../notifications/RNFirebaseNotifications.m | 153 ++++++++++----- lib/modules/messaging/Message.js | 14 -- lib/modules/messaging/index.js | 7 +- lib/modules/messaging/types.js | 3 - .../notifications/AndroidNotification.js | 180 ++++++------------ lib/modules/notifications/IOSNotification.js | 63 +++--- lib/modules/notifications/Notification.js | 39 ++-- lib/modules/notifications/index.js | 84 ++++---- lib/modules/notifications/types.js | 162 ++++++++++++++++ 12 files changed, 463 insertions(+), 326 deletions(-) create mode 100644 lib/modules/notifications/types.js diff --git a/ios/RNFirebase/RNFirebaseEvents.h b/ios/RNFirebase/RNFirebaseEvents.h index ac40e86d..938a8445 100644 --- a/ios/RNFirebase/RNFirebaseEvents.h +++ b/ios/RNFirebase/RNFirebaseEvents.h @@ -39,8 +39,8 @@ static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed"; static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received"; // Notifications -static NSString *const NOTIFICATIONS_NOTIFICATION_CLICKED = @"notifications_notification_clicked"; static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed"; +static NSString *const NOTIFICATIONS_NOTIFICATION_PRESSED = @"notifications_notification_pressed"; static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received"; // AdMob diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.h b/ios/RNFirebase/messaging/RNFirebaseMessaging.h index 2aa44131..4db95fa9 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.h +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.h @@ -15,7 +15,8 @@ @property _Nullable RCTPromiseResolveBlock permissionResolver; #if !TARGET_OS_TV -- (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; +- (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings; #endif @end diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 19782e02..8ce69e62 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -70,6 +70,12 @@ RCT_EXPORT_MODULE() _permissionResolver = nil; } +// Listen for FCM data messages that arrive as a remote notification +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { + NSDictionary *message = [self parseUserInfo:userInfo]; + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; +} + // ******************************************************* // ** Finish AppDelegate methods // ******************************************************* @@ -88,8 +94,15 @@ RCT_EXPORT_MODULE() // Listen for data messages in the foreground - (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage { - NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage openedFromTray:false]; + NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage]; + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; +} +// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground. +// To enable direct data messages, you can set [Messaging messaging].shouldEstablishDirectChannel to YES. +- (void)messaging:(nonnull FIRMessaging *)messaging +didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage { + NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage]; [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; } @@ -250,8 +263,7 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId // ** Start internals ** -- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage - openedFromTray:(bool)openedFromTray { +- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { NSDictionary *appData = remoteMessage.appData; NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; @@ -262,46 +274,46 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId } else if ([k1 isEqualToString:@"from"]) { message[@"from"] = appData[k1]; } else if ([k1 isEqualToString:@"notification"]) { - NSDictionary *notification = appData[k1]; - NSMutableDictionary *notif = [[NSMutableDictionary alloc] init]; - for (id k2 in notification) { - if ([k2 isEqualToString:@"badge"]) { - notif[@"badge"] = notification[k2]; - } else if ([k2 isEqualToString:@"body"]) { - notif[@"body"] = notification[k2]; - } else if ([k2 isEqualToString:@"body_loc_args"]) { - notif[@"bodyLocalizationArgs"] = notification[k2]; - } else if ([k2 isEqualToString:@"body_loc_key"]) { - notif[@"bodyLocalizationKey"] = notification[k2]; - } else if ([k2 isEqualToString:@"click_action"]) { - notif[@"clickAction"] = notification[k2]; - } else if ([k2 isEqualToString:@"sound"]) { - notif[@"sound"] = notification[k2]; - } else if ([k2 isEqualToString:@"subtitle"]) { - notif[@"subtitle"] = notification[k2]; - } else if ([k2 isEqualToString:@"title"]) { - notif[@"title"] = notification[k2]; - } else if ([k2 isEqualToString:@"title_loc_args"]) { - notif[@"titleLocalizationArgs"] = notification[k2]; - } else if ([k2 isEqualToString:@"title_loc_key"]) { - notif[@"titleLocalizationKey"] = notification[k2]; - } else { - NSLog(@"Unknown notification key: %@", k2); - } - } - message[@"notification"] = notif; + // Ignore for messages } else { // Assume custom data key data[k1] = appData[k1]; } } - message[@"messageType"] = @"RemoteMessage"; message[@"data"] = data; - message[@"openedFromTray"] = @(false); return message; } +- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo { + NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + + for (id k1 in userInfo) { + if ([k1 isEqualToString:@"aps"]) { + // Ignore notification section + } else if ([k1 isEqualToString:@"gcm.message_id"]) { + message[@"messageId"] = userInfo[k1]; + } else if ([k1 isEqualToString:@"google.c.a.ts"]) { + message[@"sentTime"] = userInfo[k1]; + } else if ([k1 isEqualToString:@"gcm.n.e"] + || [k1 isEqualToString:@"gcm.notification.sound2"] + || [k1 isEqualToString:@"google.c.a.c_id"] + || [k1 isEqualToString:@"google.c.a.c_l"] + || [k1 isEqualToString:@"google.c.a.e"] + || [k1 isEqualToString:@"google.c.a.udt"]) { + // Ignore known keys + } else { + // Assume custom data + data[k1] = userInfo[k1]; + } + } + + message[@"data"] = data; + + return message; +} + - (NSArray *)supportedEvents { return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED]; } @@ -317,4 +329,3 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId @implementation RNFirebaseMessaging @end #endif - diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index abdcd338..fb602149 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -2,6 +2,7 @@ #if __has_include() #import "RNFirebaseEvents.h" +#import "RNFirebaseMessaging.h" #import "RNFirebaseUtil.h" #import @@ -37,7 +38,7 @@ RCT_EXPORT_MODULE(); - (void)configure { // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - [UNUserNotificationCenter currentNotificationCenter].delegate = self; + // [UNUserNotificationCenter currentNotificationCenter].delegate = self; #endif // Set static instance for use from AppDelegate @@ -50,26 +51,78 @@ RCT_EXPORT_MODULE(); // ******************************************************* - (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification { - + NSString *event; + if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_PRESSED; + } else { + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } + + NSDictionary *message = [self parseUILocalNotification:notification]; + [RNFirebaseUtil sendJSEvent:self name:event body:message]; } // Listen for background messages - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { - BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground]; + // FCM Data messages come through here if they specify content-available=true + // Pass them over to the RNFirebaseMessaging handler instead + if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) { + [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; + return; + } - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; + NSString *event; + if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_PRESSED; + } else { + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } + + // TODO: Proper notification structure + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil]; + + [RNFirebaseUtil sendJSEvent:self name:event body:message]; } // Listen for background messages - (void)didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground]; + // FCM Data messages come through here if they specify content-available=true + // Pass them over to the RNFirebaseMessaging handler instead + if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) { + [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; + return; + } + NSString *event; + if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_PRESSED; + } else { + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } + + // TODO: Proper notification structure + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil]; + + // TODO: Callback handler // [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; - [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; + [RNFirebaseUtil sendJSEvent:self name:event body:message]; } // ******************************************************* @@ -93,7 +146,7 @@ RCT_EXPORT_MODULE(); NSString *event; UNNotificationPresentationOptions options; - NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; + NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification"]; if (isFcm || isScheduled) { // If app is in the background @@ -116,9 +169,7 @@ RCT_EXPORT_MODULE(); event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; } - if (event) { - [RNFirebaseUtil sendJSEvent:self name:event body:message]; - } + [RNFirebaseUtil sendJSEvent:self name:event body:message]; completionHandler(options); } @@ -130,9 +181,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response #else withCompletionHandler:(void(^)())completionHandler { #endif - NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true]; + NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse"]; - [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message]; + [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message]; completionHandler(); } @@ -200,7 +251,13 @@ RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecte NSDictionary *notification = [self parseUILocalNotification:localNotification]; resolve(notification); } else { - resolve(nil); + NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + if (remoteNotification) { + NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" clickAction:nil]; + resolve(message); + } else { + resolve(nil); + } } } @@ -485,7 +542,7 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification } ios[@"attachments"] = attachments; } - + if (localNotification.content.badge) { ios[@"badge"] = localNotification.content.badge; } @@ -504,21 +561,20 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification } - (NSDictionary*)parseUNNotification:(UNNotification *)notification - messageType:(NSString *)messageType - openedFromTray:(bool)openedFromTray { + messageType:(NSString *)messageType { NSDictionary *userInfo = notification.request.content.userInfo; NSString *clickAction = notification.request.content.categoryIdentifier; - return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray]; + return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction]; } - (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo messageType:(NSString *) messageType - clickAction:(NSString *) clickAction - openedFromTray:(bool)openedFromTray { - NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; - NSMutableDictionary *notif = [[NSMutableDictionary alloc] init]; + clickAction:(NSString *) clickAction { + + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; for (id k1 in userInfo) { if ([k1 isEqualToString:@"aps"]) { @@ -528,37 +584,42 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification NSDictionary *alert = aps[k2]; for (id k3 in alert) { if ([k3 isEqualToString:@"body"]) { - notif[@"body"] = alert[k3]; + notification[@"body"] = alert[k3]; } else if ([k3 isEqualToString:@"loc-args"]) { - notif[@"bodyLocalizationArgs"] = alert[k3]; + // TODO: What to do with this? + // notif[@"bodyLocalizationArgs"] = alert[k3]; } else if ([k3 isEqualToString:@"loc-key"]) { - notif[@"bodyLocalizationKey"] = alert[k3]; + // TODO: What to do with this? + // notif[@"bodyLocalizationKey"] = alert[k3]; } else if ([k3 isEqualToString:@"subtitle"]) { - notif[@"subtitle"] = alert[k3]; + notification[@"subtitle"] = alert[k3]; } else if ([k3 isEqualToString:@"title"]) { - notif[@"title"] = alert[k3]; + notification[@"title"] = alert[k3]; } else if ([k3 isEqualToString:@"title-loc-args"]) { - notif[@"titleLocalizationArgs"] = alert[k3]; + // TODO: What to do with this? + // notif[@"titleLocalizationArgs"] = alert[k3]; } else if ([k3 isEqualToString:@"title-loc-key"]) { - notif[@"titleLocalizationKey"] = alert[k3]; + // TODO: What to do with this? + // notif[@"titleLocalizationKey"] = alert[k3]; } else { NSLog(@"Unknown alert key: %@", k2); } } } else if ([k2 isEqualToString:@"badge"]) { - notif[@"badge"] = aps[k2]; + ios[@"badge"] = aps[k2]; } else if ([k2 isEqualToString:@"category"]) { - notif[@"clickAction"] = aps[k2]; + ios[@"category"] = aps[k2]; } else if ([k2 isEqualToString:@"sound"]) { - notif[@"sound"] = aps[k2]; + notification[@"sound"] = aps[k2]; } else { NSLog(@"Unknown aps key: %@", k2); } } } else if ([k1 isEqualToString:@"gcm.message_id"]) { - message[@"messageId"] = userInfo[k1]; + notification[@"notificationId"] = userInfo[k1]; } else if ([k1 isEqualToString:@"google.c.a.ts"]) { - message[@"sentTime"] = userInfo[k1]; + // TODO: What to do with this? + // message[@"sentTime"] = userInfo[k1]; } else if ([k1 isEqualToString:@"gcm.n.e"] || [k1 isEqualToString:@"gcm.notification.sound2"] || [k1 isEqualToString:@"google.c.a.c_id"] @@ -572,26 +633,17 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification } } - if (!notif[@"clickAction"] && clickAction) { - notif[@"clickAction"] = clickAction; - } + // TODO: What to do with this? + // message[@"messageType"] = messageType; - // Generate a message ID if one was not present in the notification - // This is used for resolving click handlers - if (!message[@"messageId"]) { - message[@"messageId"] = [[NSUUID UUID] UUIDString]; - } - message[@"messageType"] = messageType; + notification[@"data"] = data; + notification[@"ios"] = ios; - message[@"data"] = data; - message[@"notification"] = notif; - message[@"openedFromTray"] = @(openedFromTray); - - return message; + return notification; } - (NSArray *)supportedEvents { - return @[NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; + return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_PRESSED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; } + (BOOL)requiresMainQueueSetup @@ -605,4 +657,3 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification @implementation RNFirebaseNotifications @end #endif - diff --git a/lib/modules/messaging/Message.js b/lib/modules/messaging/Message.js index d789377e..38f3433f 100644 --- a/lib/modules/messaging/Message.js +++ b/lib/modules/messaging/Message.js @@ -11,9 +11,7 @@ import { } from './types'; import type Messaging from './'; import type { - MessageTypeType, NativeMessage, - Notification, PresentNotificationResultType, RemoteNotificationResultType, } from './types'; @@ -47,18 +45,6 @@ export default class Message { return this._message.messageId; } - get messageType(): ?MessageTypeType { - return this._message.messageType; - } - - get openedFromTray(): boolean { - return this._message.openedFromTray; - } - - get notification(): ?Notification { - return this._message.notification; - } - get sentTime(): ?number { return this._message.sentTime; } diff --git a/lib/modules/messaging/index.js b/lib/modules/messaging/index.js index b4b2abc8..f17d389a 100644 --- a/lib/modules/messaging/index.js +++ b/lib/modules/messaging/index.js @@ -55,8 +55,8 @@ export default class Messaging extends ModuleBase { // sub to internal native event - this fans out to // public event name: onMessage 'messaging_message_received', - (message: Message) => { - SharedEventEmitter.emit('onMessage', message); + (message: NativeMessage) => { + SharedEventEmitter.emit('onMessage', new Message(this, message)); } ); @@ -89,8 +89,7 @@ export default class Messaging extends ModuleBase { getLogger(this).info('Creating onMessage listener'); - const wrappedListener = async (nativeMessage: NativeMessage) => { - const message = new Message(this, nativeMessage); + const wrappedListener = async (message: Message) => { await listener(message); message.complete(); }; diff --git a/lib/modules/messaging/types.js b/lib/modules/messaging/types.js index 1909660b..3b58201a 100644 --- a/lib/modules/messaging/types.js +++ b/lib/modules/messaging/types.js @@ -51,9 +51,6 @@ export type NativeMessage = { data: { [string]: string }, from?: string, messageId: string, - messageType?: MessageTypeType, - openedFromTray: boolean, - notification?: Notification, sentTime?: number, to?: string, ttl?: number, diff --git a/lib/modules/notifications/AndroidNotification.js b/lib/modules/notifications/AndroidNotification.js index f2591707..c3305e0b 100644 --- a/lib/modules/notifications/AndroidNotification.js +++ b/lib/modules/notifications/AndroidNotification.js @@ -2,128 +2,29 @@ * @flow * AndroidNotification representation wrapper */ +import { Category } from './types'; import type Notification from './Notification'; - -type Lights = { - argb: number, - onMs: number, - offMs: number, -}; - -type Progress = { - max: number, - progress: number, - indeterminate: boolean, -}; - -type SmallIcon = { - icon: string, - level?: number, -}; - -export type NativeAndroidNotification = {| - // TODO actions: Action[], - autoCancel: boolean, - badgeIconType: BadgeIconTypeType, - category: CategoryType, - channelId: string, - clickAction?: string, - color: string, - colorized: boolean, - contentInfo: string, - defaults: DefaultsType[], - group: string, - groupAlertBehaviour: GroupAlertType, - groupSummary: boolean, - largeIcon: string, - lights: Lights, - localOnly: boolean, - number: number, - ongoing: boolean, - onlyAlertOnce: boolean, - people: string[], - priority: PriorityType, - progress: Progress, - // publicVersion: Notification, - remoteInputHistory: string[], - shortcutId: string, - showWhen: boolean, - smallIcon: SmallIcon, - sortKey: string, - // TODO: style: Style, - ticker: string, - timeoutAfter: number, - usesChronometer: boolean, - vibrate: number[], - visibility: VisibilityType, - when: number, -|}; - -export const BadgeIconType = { - Large: 2, - None: 0, - Small: 1, -}; - -export const Category = { - Alarm: 'alarm', - Call: 'call', - Email: 'email', - Error: 'err', - Event: 'event', - Message: 'msg', - Progress: 'progress', - Promo: 'promo', - Recommendation: 'recommendation', - Reminder: 'reminder', - Service: 'service', - Social: 'social', - Status: 'status', - System: 'system', - Transport: 'transport', -}; - -export const Defaults = { - All: -1, - Lights: 4, - Sound: 1, - Vibrate: 2, -}; - -export const GroupAlert = { - All: 0, - Children: 2, - Summary: 1, -}; - -export const Priority = { - Default: 0, - High: 1, - Low: -1, - Max: 2, - Min: -2, -}; - -export const Visibility = { - Private: 0, - Public: 1, - Secret: -1, -}; - -type BadgeIconTypeType = $Values; -type CategoryType = $Values; -type DefaultsType = $Values; -type GroupAlertType = $Values; -type PriorityType = $Values; -type VisibilityType = $Values; +import type { + BadgeIconTypeType, + CategoryType, + DefaultsType, + GroupAlertType, + Lights, + NativeAndroidNotification, + PriorityType, + Progress, + SmallIcon, + VisibilityType, +} from './types'; export default class AndroidNotification { + // TODO optional fields // TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh) _autoCancel: boolean; _badgeIconType: BadgeIconTypeType; _category: CategoryType; _channelId: string; - _clickAction: string; + _clickAction: string | void; _color: string; _colorized: boolean; _contentInfo: string; @@ -145,9 +46,7 @@ export default class AndroidNotification { _remoteInputHistory: string[]; _shortcutId: string; _showWhen: boolean; - _smallIcon: SmallIcon = { - icon: 'ic_launcher', - }; + _smallIcon: SmallIcon; _sortKey: string; // TODO: style: Style; // Need to figure out if this can work _ticker: string; @@ -167,9 +66,50 @@ export default class AndroidNotification { // fullScreenIntent: PendingIntent // sound.streamType - constructor(notification: Notification) { + constructor(notification: Notification, data?: NativeAndroidNotification) { this._notification = notification; - this._people = []; + + if (data) { + this._autoCancel = data.autoCancel; + this._badgeIconType = data.badgeIconType; + this._category = data.category; + this._channelId = data.channelId; + this._clickAction = data.clickAction; + this._color = data.color; + this._colorized = data.colorized; + this._contentInfo = data.contentInfo; + this._defaults = data.defaults; + this._group = data.group; + this._groupAlertBehaviour = data.groupAlertBehaviour; + this._groupSummary = data.groupSummary; + this._largeIcon = data.largeIcon; + this._lights = data.lights; + this._localOnly = data.localOnly; + this._number = data.number; + this._ongoing = data.ongoing; + this._onlyAlertOnce = data.onlyAlertOnce; + this._people = data.people; + this._priority = data.priority; + this._progress = data.progress; + // _publicVersion: Notification; + this._remoteInputHistory = data.remoteInputHistory; + this._shortcutId = data.shortcutId; + this._showWhen = data.showWhen; + this._smallIcon = data.smallIcon; + this._sortKey = data.sortKey; + this._ticker = data.ticker; + this._timeoutAfter = data.timeoutAfter; + this._usesChronometer = data.usesChronometer; + this._vibrate = data.vibrate; + this._visibility = data.visibility; + this._when = data.when; + } + + // Defaults + this._people = this._people || []; + this._smallIcon = this._smallIcon || { + icon: 'ic_launcher', + }; } /** @@ -506,7 +446,7 @@ export default class AndroidNotification { } build(): NativeAndroidNotification { - // TODO: Validation + // TODO: Validation of required fields if (!this._channelId) { throw new Error( 'AndroidNotification: Missing required `channelId` property' diff --git a/lib/modules/notifications/IOSNotification.js b/lib/modules/notifications/IOSNotification.js index d4ad7e11..5b1e563b 100644 --- a/lib/modules/notifications/IOSNotification.js +++ b/lib/modules/notifications/IOSNotification.js @@ -3,48 +3,37 @@ * IOSNotification representation wrapper */ import type Notification from './Notification'; - -type AttachmentOptions = {| - TypeHint: string, - ThumbnailHidden: boolean, - ThumbnailClippingRect: { - height: number, - width: number, - x: number, - y: number, - }, - ThumbnailTime: number, -|}; - -type Attachment = {| - identifier: string, - options?: AttachmentOptions, - url: string, -|}; - -export type NativeIOSNotification = {| - alertAction?: string, - attachments: Attachment[], - badge?: number, - category?: string, - hasAction?: boolean, - launchImage?: string, - threadIdentifier?: string, -|}; +import type { + Attachment, + AttachmentOptions, + NativeIOSNotification, +} from './types'; export default class IOSNotification { - _alertAction: string; // alertAction | N/A + _alertAction: string | void; // alertAction | N/A _attachments: Attachment[]; // N/A | attachments - _badge: number; // applicationIconBadgeNumber | badge - _category: string; - _hasAction: boolean; // hasAction | N/A - _launchImage: string; // alertLaunchImage | launchImageName + _badge: number | void; // applicationIconBadgeNumber | badge + _category: string | void; + _hasAction: boolean | void; // hasAction | N/A + _launchImage: string | void; // alertLaunchImage | launchImageName _notification: Notification; - _threadIdentifier: string; // N/A | threadIdentifier + _threadIdentifier: string | void; // N/A | threadIdentifier - constructor(notification: Notification) { - this._attachments = []; + constructor(notification: Notification, data?: NativeIOSNotification) { this._notification = notification; + + if (data) { + this._alertAction = data.alertAction; + this._attachments = data.attachments; + this._badge = data.badge; + this._category = data.category; + this._hasAction = data.hasAction; + this._launchImage = data.launchImage; + this._threadIdentifier = data.threadIdentifier; + } + + // Defaults + this._attachments = this._attachments || []; } /** @@ -128,7 +117,7 @@ export default class IOSNotification { } build(): NativeIOSNotification { - // TODO: Validation + // TODO: Validation of required fields return { alertAction: this._alertAction, diff --git a/lib/modules/notifications/Notification.js b/lib/modules/notifications/Notification.js index bcaae87b..50ec6b5d 100644 --- a/lib/modules/notifications/Notification.js +++ b/lib/modules/notifications/Notification.js @@ -7,21 +7,7 @@ import AndroidNotification from './AndroidNotification'; import IOSNotification from './IOSNotification'; import { generatePushID, isObject } from '../../utils'; -import type { NativeAndroidNotification } from './AndroidNotification'; -import type { NativeIOSNotification } from './IOSNotification'; -import type { Schedule } from './'; - -type NativeNotification = {| - android?: NativeAndroidNotification, - body: string, - data: { [string]: string }, - ios?: NativeIOSNotification, - notificationId: string, - schedule?: Schedule, - sound?: string, - subtitle?: string, - title: string, -|}; +import type { NativeNotification } from './types'; export default class Notification { // iOS 8/9 | 10+ | Android @@ -34,12 +20,23 @@ export default class Notification { _subtitle: string | void; // N/A | subtitle | subText _title: string; // alertTitle | title | contentTitle - constructor() { - this._android = new AndroidNotification(this); - this._data = {}; - this._ios = new IOSNotification(this); - // TODO: Is this the best way to generate an ID? - this._notificationId = generatePushID(); + constructor(data?: NativeNotification) { + this._android = new AndroidNotification(this, data && data.android); + this._ios = new IOSNotification(this, data && data.ios); + + if (data) { + this._body = data.body; + this._data = data.data; + // TODO: Is this the best way to generate an ID? + this._notificationId = data.notificationId; + this._sound = data.sound; + this._subtitle = data.subtitle; + this._title = data.title; + } + + // Defaults + this._data = this._data || {}; + this._notificationId = this._notificationId || generatePushID(); } get android(): AndroidNotification { diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 57aed391..6b9f3e24 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -15,26 +15,20 @@ import { GroupAlert, Priority, Visibility, -} from './AndroidNotification'; +} from './types'; import type App from '../core/app'; +import type { NativeNotification, Schedule } from './types'; -// TODO: Received notification type will be different from sent notification type OnNotification = Notification => any; type OnNotificationObserver = { next: OnNotification, }; -export type Schedule = { - exact?: boolean, - fireDate: number, - repeatInterval?: 'minute' | 'hour' | 'day' | 'week', -}; - const NATIVE_EVENTS = [ - 'notifications_notification_clicked', 'notifications_notification_displayed', + 'notifications_notification_pressed', 'notifications_notification_received', ]; @@ -69,10 +63,13 @@ export default class Notifications extends ModuleBase { SharedEventEmitter.addListener( // sub to internal native event - this fans out to - // public event name: onNotificationClicked - 'notifications_notification_clicked', - (notification: Notification) => { - SharedEventEmitter.emit('onNotificationClicked', notification); + // public event name: onNotificationPressed + 'notifications_notification_pressed', + (notification: NativeNotification) => { + SharedEventEmitter.emit( + 'onNotificationPressed', + new Notification(notification) + ); } ); @@ -80,8 +77,11 @@ export default class Notifications extends ModuleBase { // sub to internal native event - this fans out to // public event name: onNotificationDisplayed 'notifications_notification_displayed', - (notification: Notification) => { - SharedEventEmitter.emit('onNotificationDisplayed', notification); + (notification: NativeNotification) => { + SharedEventEmitter.emit( + 'onNotificationDisplayed', + new Notification(notification) + ); } ); @@ -89,8 +89,11 @@ export default class Notifications extends ModuleBase { // sub to internal native event - this fans out to // public event name: onNotification 'notifications_notification_received', - (notification: Notification) => { - SharedEventEmitter.emit('onNotification', notification); + (notification: NativeNotification) => { + SharedEventEmitter.emit( + 'onNotification', + new Notification(notification) + ); } ); } @@ -166,29 +169,6 @@ export default class Notifications extends ModuleBase { }; } - onNotificationClicked( - nextOrObserver: OnNotification | OnNotificationObserver - ): () => any { - let listener; - if (isFunction(nextOrObserver)) { - listener = nextOrObserver; - } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { - listener = nextOrObserver.next; - } else { - throw new Error( - 'Notifications.onNotificationClicked failed: First argument must be a function or observer object with a `next` function.' - ); - } - - getLogger(this).info('Creating onNotificationClicked listener'); - SharedEventEmitter.addListener('onNotificationClicked', listener); - - return () => { - getLogger(this).info('Removing onNotificationClicked listener'); - SharedEventEmitter.removeListener('onNotificationClicked', listener); - }; - } - onNotificationDisplayed( nextOrObserver: OnNotification | OnNotificationObserver ): () => any { @@ -212,6 +192,30 @@ export default class Notifications extends ModuleBase { }; } + onNotificationPressed( + nextOrObserver: OnNotification | OnNotificationObserver + ): () => any { + let listener: Notification => any; + if (isFunction(nextOrObserver)) { + // $FlowBug: Not coping with the overloaded method signature + listener = nextOrObserver; + } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { + listener = nextOrObserver.next; + } else { + throw new Error( + 'Notifications.onNotificationPressed failed: First argument must be a function or observer object with a `next` function.' + ); + } + + getLogger(this).info('Creating onNotificationPressed listener'); + SharedEventEmitter.addListener('onNotificationPressed', listener); + + return () => { + getLogger(this).info('Removing onNotificationPressed listener'); + SharedEventEmitter.removeListener('onNotificationPressed', listener); + }; + } + /** * Remove all delivered notifications. * @returns {*} diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js new file mode 100644 index 00000000..eec87988 --- /dev/null +++ b/lib/modules/notifications/types.js @@ -0,0 +1,162 @@ +/** + * @flow + */ + +export const BadgeIconType = { + Large: 2, + None: 0, + Small: 1, +}; + +export const Category = { + Alarm: 'alarm', + Call: 'call', + Email: 'email', + Error: 'err', + Event: 'event', + Message: 'msg', + Progress: 'progress', + Promo: 'promo', + Recommendation: 'recommendation', + Reminder: 'reminder', + Service: 'service', + Social: 'social', + Status: 'status', + System: 'system', + Transport: 'transport', +}; + +export const Defaults = { + All: -1, + Lights: 4, + Sound: 1, + Vibrate: 2, +}; + +export const GroupAlert = { + All: 0, + Children: 2, + Summary: 1, +}; + +export const Priority = { + Default: 0, + High: 1, + Low: -1, + Max: 2, + Min: -2, +}; + +export const Visibility = { + Private: 0, + Public: 1, + Secret: -1, +}; + +export type BadgeIconTypeType = $Values; +export type CategoryType = $Values; +export type DefaultsType = $Values; +export type GroupAlertType = $Values; +export type PriorityType = $Values; +export type VisibilityType = $Values; + +export type Lights = { + argb: number, + onMs: number, + offMs: number, +}; + +export type Progress = { + max: number, + progress: number, + indeterminate: boolean, +}; + +export type SmallIcon = { + icon: string, + level?: number, +}; + +export type NativeAndroidNotification = {| + // TODO actions: Action[], + autoCancel: boolean, + badgeIconType: BadgeIconTypeType, + category: CategoryType, + channelId: string, + clickAction?: string, + color: string, + colorized: boolean, + contentInfo: string, + defaults: DefaultsType[], + group: string, + groupAlertBehaviour: GroupAlertType, + groupSummary: boolean, + largeIcon: string, + lights: Lights, + localOnly: boolean, + number: number, + ongoing: boolean, + onlyAlertOnce: boolean, + people: string[], + priority: PriorityType, + progress: Progress, + // publicVersion: Notification, + remoteInputHistory: string[], + shortcutId: string, + showWhen: boolean, + smallIcon: SmallIcon, + sortKey: string, + // TODO: style: Style, + ticker: string, + timeoutAfter: number, + usesChronometer: boolean, + vibrate: number[], + visibility: VisibilityType, + when: number, +|}; + +export type AttachmentOptions = {| + TypeHint: string, + ThumbnailHidden: boolean, + ThumbnailClippingRect: { + height: number, + width: number, + x: number, + y: number, + }, + ThumbnailTime: number, +|}; + +export type Attachment = {| + identifier: string, + options?: AttachmentOptions, + url: string, +|}; + +export type NativeIOSNotification = {| + alertAction?: string, + attachments: Attachment[], + badge?: number, + category?: string, + hasAction?: boolean, + launchImage?: string, + threadIdentifier?: string, +|}; + +export type Schedule = { + exact?: boolean, + fireDate: number, + repeatInterval?: 'minute' | 'hour' | 'day' | 'week', +}; + +export type NativeNotification = {| + android?: NativeAndroidNotification, + body: string, + data: { [string]: string }, + ios?: NativeIOSNotification, + notificationId: string, + schedule?: Schedule, + sound?: string, + subtitle?: string, + title: string, +|}; From 8e84dd576b1c08568ccc0fa148f9f1e0528509df Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 22 Feb 2018 17:25:15 +0000 Subject: [PATCH 05/13] [notifications] Specific notificationPressed data type including action --- .../notifications/RNFirebaseNotifications.m | 114 ++++++++++-------- lib/modules/notifications/Notification.js | 5 + lib/modules/notifications/index.js | 42 ++++--- lib/modules/notifications/types.js | 5 + 4 files changed, 101 insertions(+), 65 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index fb602149..ed8b7fa5 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -38,7 +38,7 @@ RCT_EXPORT_MODULE(); - (void)configure { // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - // [UNUserNotificationCenter currentNotificationCenter].delegate = self; + [UNUserNotificationCenter currentNotificationCenter].delegate = self; #endif // Set static instance for use from AppDelegate @@ -51,20 +51,22 @@ RCT_EXPORT_MODULE(); // ******************************************************* - (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification { - NSString *event; - if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; - } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_PRESSED; - } else { - // notification_received - event = NOTIFICATIONS_NOTIFICATION_RECEIVED; - } + #if !defined(__IPHONE_10_0) || __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0 + NSString *event; + if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; + } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_PRESSED; + } else { + // notification_received + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + } - NSDictionary *message = [self parseUILocalNotification:notification]; - [RNFirebaseUtil sendJSEvent:self name:event body:message]; + NSDictionary *message = [self parseUILocalNotification:notification]; + [RNFirebaseUtil sendJSEvent:self name:event body:message]; + #endif } // Listen for background messages @@ -75,7 +77,7 @@ RCT_EXPORT_MODULE(); [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; return; } - + NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { // notification_displayed @@ -85,12 +87,17 @@ RCT_EXPORT_MODULE(); event = NOTIFICATIONS_NOTIFICATION_PRESSED; } else { // notification_received - event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + // On IOS 10, foreground notifications also go through willPresentNotification + // This prevents duplicate messages from hitting the JS app + #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + return; + #else + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + #endif } - - // TODO: Proper notification structure - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil]; - + + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" category:nil]; + [RNFirebaseUtil sendJSEvent:self name:event body:message]; } @@ -104,6 +111,7 @@ RCT_EXPORT_MODULE(); return; } + NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { // notification_displayed @@ -113,16 +121,19 @@ RCT_EXPORT_MODULE(); event = NOTIFICATIONS_NOTIFICATION_PRESSED; } else { // notification_received - event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + // On IOS 10, foreground notifications also go through willPresentNotification + // This prevents duplicate messages from hitting the JS app + #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + return; + #else + event = NOTIFICATIONS_NOTIFICATION_RECEIVED; + #endif } - - // TODO: Proper notification structure - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil]; - - // TODO: Callback handler - // [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; - + + NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" category:nil]; + [RNFirebaseUtil sendJSEvent:self name:event body:message]; + completionHandler(UIBackgroundFetchResultNoData); } // ******************************************************* @@ -181,7 +192,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response #else withCompletionHandler:(void(^)())completionHandler { #endif - NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse"]; + NSDictionary *message = [self parseUNNotificationResponse:response messageType:@"NotificationResponse"]; [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message]; completionHandler(); @@ -253,7 +264,7 @@ RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecte } else { NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (remoteNotification) { - NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" clickAction:nil]; + NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" category:nil]; resolve(message); } else { resolve(nil); @@ -560,17 +571,27 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification return notification; } +- (NSDictionary*)parseUNNotificationResponse:(UNNotificationResponse *)response + messageType:(NSString *)messageType { + NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; + NSDictionary *notification = [self parseUNNotification:response.notification messageType:messageType]; + notificationResponse[@"notification"] = notification; + notificationResponse[@"action"] = response.actionIdentifier; + + return notificationResponse; +} + - (NSDictionary*)parseUNNotification:(UNNotification *)notification messageType:(NSString *)messageType { NSDictionary *userInfo = notification.request.content.userInfo; - NSString *clickAction = notification.request.content.categoryIdentifier; + NSString *category = notification.request.content.categoryIdentifier; - return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction]; + return [self parseUserInfo:userInfo messageType:messageType category:category]; } - (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo messageType:(NSString *) messageType - clickAction:(NSString *) clickAction { + category:(NSString *) category { NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; @@ -585,22 +606,15 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification for (id k3 in alert) { if ([k3 isEqualToString:@"body"]) { notification[@"body"] = alert[k3]; - } else if ([k3 isEqualToString:@"loc-args"]) { - // TODO: What to do with this? - // notif[@"bodyLocalizationArgs"] = alert[k3]; - } else if ([k3 isEqualToString:@"loc-key"]) { - // TODO: What to do with this? - // notif[@"bodyLocalizationKey"] = alert[k3]; } else if ([k3 isEqualToString:@"subtitle"]) { notification[@"subtitle"] = alert[k3]; } else if ([k3 isEqualToString:@"title"]) { notification[@"title"] = alert[k3]; - } else if ([k3 isEqualToString:@"title-loc-args"]) { - // TODO: What to do with this? - // notif[@"titleLocalizationArgs"] = alert[k3]; - } else if ([k3 isEqualToString:@"title-loc-key"]) { - // TODO: What to do with this? - // notif[@"titleLocalizationKey"] = alert[k3]; + } else if ([k3 isEqualToString:@"loc-args"] + || [k3 isEqualToString:@"loc-key"] + || [k3 isEqualToString:@"title-loc-args"] + || [k3 isEqualToString:@"title-loc-key"]) { + // Ignore known keys } else { NSLog(@"Unknown alert key: %@", k2); } @@ -617,21 +631,23 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification } } else if ([k1 isEqualToString:@"gcm.message_id"]) { notification[@"notificationId"] = userInfo[k1]; - } else if ([k1 isEqualToString:@"google.c.a.ts"]) { - // TODO: What to do with this? - // message[@"sentTime"] = userInfo[k1]; } else if ([k1 isEqualToString:@"gcm.n.e"] || [k1 isEqualToString:@"gcm.notification.sound2"] || [k1 isEqualToString:@"google.c.a.c_id"] || [k1 isEqualToString:@"google.c.a.c_l"] || [k1 isEqualToString:@"google.c.a.e"] - || [k1 isEqualToString:@"google.c.a.udt"]) { + || [k1 isEqualToString:@"google.c.a.udt"] + || [k1 isEqualToString:@"google.c.a.ts"]) { // Ignore known keys } else { // Assume custom data data[k1] = userInfo[k1]; } } + + if (!ios[@"category"]) { + ios[@"category"] = category; + } // TODO: What to do with this? // message[@"messageType"] = messageType; diff --git a/lib/modules/notifications/Notification.js b/lib/modules/notifications/Notification.js index 50ec6b5d..1ad7d2cd 100644 --- a/lib/modules/notifications/Notification.js +++ b/lib/modules/notifications/Notification.js @@ -9,6 +9,11 @@ import { generatePushID, isObject } from '../../utils'; import type { NativeNotification } from './types'; +export type NotificationPressed = { + action: string, + notification: Notification, +}; + export default class Notification { // iOS 8/9 | 10+ | Android _android: AndroidNotification; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 6b9f3e24..60e1dc61 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -18,7 +18,12 @@ import { } from './types'; import type App from '../core/app'; -import type { NativeNotification, Schedule } from './types'; +import type { NotificationPressed } from './Notification'; +import type { + NativeNotification, + NativeNotificationPressed, + Schedule, +} from './types'; type OnNotification = Notification => any; @@ -26,6 +31,12 @@ type OnNotificationObserver = { next: OnNotification, }; +type OnNotificationPressed = NotificationPressed => any; + +type OnNotificationPressedObserver = { + next: OnNotificationPressed, +}; + const NATIVE_EVENTS = [ 'notifications_notification_displayed', 'notifications_notification_pressed', @@ -61,18 +72,6 @@ export default class Notifications extends ModuleBase { namespace: NAMESPACE, }); - SharedEventEmitter.addListener( - // sub to internal native event - this fans out to - // public event name: onNotificationPressed - 'notifications_notification_pressed', - (notification: NativeNotification) => { - SharedEventEmitter.emit( - 'onNotificationPressed', - new Notification(notification) - ); - } - ); - SharedEventEmitter.addListener( // sub to internal native event - this fans out to // public event name: onNotificationDisplayed @@ -85,6 +84,18 @@ export default class Notifications extends ModuleBase { } ); + SharedEventEmitter.addListener( + // sub to internal native event - this fans out to + // public event name: onNotificationPressed + 'notifications_notification_pressed', + (notificationPressed: NativeNotificationPressed) => { + SharedEventEmitter.emit('onNotificationPressed', { + action: notificationPressed.action, + notification: new Notification(notificationPressed.notification), + }); + } + ); + SharedEventEmitter.addListener( // sub to internal native event - this fans out to // public event name: onNotification @@ -193,11 +204,10 @@ export default class Notifications extends ModuleBase { } onNotificationPressed( - nextOrObserver: OnNotification | OnNotificationObserver + nextOrObserver: OnNotificationPressed | OnNotificationPressedObserver ): () => any { - let listener: Notification => any; + let listener; if (isFunction(nextOrObserver)) { - // $FlowBug: Not coping with the overloaded method signature listener = nextOrObserver; } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { listener = nextOrObserver.next; diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index eec87988..9c2abe10 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -160,3 +160,8 @@ export type NativeNotification = {| subtitle?: string, title: string, |}; + +export type NativeNotificationPressed = {| + action: string, + notification: NativeNotification, +|}; From 2838bbc0cde3fef451139ebaef972aba2ccb8494 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 23 Feb 2018 15:34:00 +0000 Subject: [PATCH 06/13] [notifications] Last part of iOS implementation --- .../messaging/RNFirebaseMessaging.m | 21 +- .../notifications/RNFirebaseNotifications.h | 3 +- .../notifications/RNFirebaseNotifications.m | 308 ++++++++++-------- lib/modules/messaging/index.js | 15 - lib/modules/notifications/index.js | 8 + 5 files changed, 185 insertions(+), 170 deletions(-) diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 8ce69e62..31365c80 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -150,19 +150,6 @@ RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RC } // Non Web SDK methods - -// TODO: Move to notifications -RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - dispatch_async(dispatch_get_main_queue(), ^{ - resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); - }); -} - -// TODO: Remove -RCT_EXPORT_METHOD(getInitialMessage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ - resolve(nil); -} - RCT_EXPORT_METHOD(hasPermission: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -192,13 +179,6 @@ RCT_EXPORT_METHOD(sendMessage: (NSDictionary *) message [[FIRMessaging messaging] sendMessage:data to:to withMessageID:messageId timeToLive:[ttl intValue]]; } -// TODO: Move to notifications -RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { - dispatch_async(dispatch_get_main_queue(), ^{ - [RCTSharedApplication() setApplicationIconBadgeNumber:number]; - }); -} - RCT_EXPORT_METHOD(subscribeToTopic: (NSString*) topic) { [[FIRMessaging messaging] subscribeToTopic:topic]; } @@ -329,3 +309,4 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId @implementation RNFirebaseMessaging @end #endif + diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.h b/ios/RNFirebase/notifications/RNFirebaseNotifications.h index 56ca289b..5871ba7b 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.h +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.h @@ -8,11 +8,11 @@ @interface RNFirebaseNotifications : RCTEventEmitter ++ (void)configure; + (_Nonnull instancetype)instance; #if !TARGET_OS_TV - (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; -- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; #endif @@ -24,3 +24,4 @@ #endif #endif + diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index ed8b7fa5..beb733a0 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -19,39 +19,60 @@ @implementation RNFirebaseNotifications 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 +// static NSMutableArray *pendingEvents = nil; +static NSDictionary *initialNotification = nil; + (nonnull instancetype)instance { return theRNFirebaseNotifications; } ++ (void)configure { + // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side + // pendingEvents = [[NSMutableArray alloc] init]; + theRNFirebaseNotifications = [[RNFirebaseNotifications alloc] init]; +} + RCT_EXPORT_MODULE(); - (id)init { self = [super init]; if (self != nil) { NSLog(@"Setting up RNFirebaseNotifications instance"); - [self configure]; + [self initialise]; } return self; } -- (void)configure { +- (void)initialise { // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter -#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - [UNUserNotificationCenter currentNotificationCenter].delegate = self; -#endif + #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + [UNUserNotificationCenter currentNotificationCenter].delegate = self; + #endif // Set static instance for use from AppDelegate theRNFirebaseNotifications = self; } +// PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side +// The bridge is initialised after the module is created +// When the bridge is set, check if we have any pending events to send, and send them +/* - (void)setValue:(nullable id)value forKey:(NSString *)key { + [super setValue:value forKey:key]; + if ([key isEqualToString:@"bridge"] && value) { + for (NSDictionary* event in pendingEvents) { + [RNFirebaseUtil sendJSEvent:self name:event[@"name"] body:event[@"body"]]; + } + [pendingEvents removeAllObjects]; + } +} */ + // ******************************************************* // ** Start AppDelegate methods // ** iOS 8/9 Only // ******************************************************* - -- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification { - #if !defined(__IPHONE_10_0) || __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0 +- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)localNotification { + if ([self isIOS89]) { NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { // notification_displayed @@ -64,41 +85,15 @@ RCT_EXPORT_MODULE(); event = NOTIFICATIONS_NOTIFICATION_RECEIVED; } - NSDictionary *message = [self parseUILocalNotification:notification]; - [RNFirebaseUtil sendJSEvent:self name:event body:message]; - #endif -} - -// Listen for background messages -- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { - // FCM Data messages come through here if they specify content-available=true - // Pass them over to the RNFirebaseMessaging handler instead - if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) { - [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; - return; + NSDictionary *notification = [self parseUILocalNotification:localNotification]; + if (event == NOTIFICATIONS_NOTIFICATION_PRESSED) { + notification = @{ + @"action": UNNotificationDefaultActionIdentifier, + @"notification": notification + }; + } + [self sendJSEvent:self name:event body:notification]; } - - NSString *event; - if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; - } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_PRESSED; - } else { - // notification_received - // On IOS 10, foreground notifications also go through willPresentNotification - // This prevents duplicate messages from hitting the JS app - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - return; - #else - event = NOTIFICATIONS_NOTIFICATION_RECEIVED; - #endif - } - - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" category:nil]; - - [RNFirebaseUtil sendJSEvent:self name:event body:message]; } // Listen for background messages @@ -111,28 +106,36 @@ RCT_EXPORT_MODULE(); return; } - NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { // notification_displayed event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; - } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_PRESSED; - } else { - // notification_received - // On IOS 10, foreground notifications also go through willPresentNotification - // This prevents duplicate messages from hitting the JS app - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - return; - #else + } else if ([self isIOS89]) { + if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + // notification_displayed + event = NOTIFICATIONS_NOTIFICATION_PRESSED; + } else { + // notification_received event = NOTIFICATIONS_NOTIFICATION_RECEIVED; - #endif + } + } else { + // On IOS 10: + // - foreground notifications also go through willPresentNotification + // - background notification presses also go through didReceiveNotificationResponse + // This prevents duplicate messages from hitting the JS app + return; } - NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" category:nil]; + NSDictionary *notification = [self parseUserInfo:userInfo]; + // For onPressed events, we set the default action name as iOS 8/9 has no concept of actions + if (event == NOTIFICATIONS_NOTIFICATION_PRESSED) { + notification = @{ + @"action": UNNotificationDefaultActionIdentifier, + @"notification": notification + }; + } - [RNFirebaseUtil sendJSEvent:self name:event body:message]; + [self sendJSEvent:self name:event body:notification]; completionHandler(UIBackgroundFetchResultNoData); } @@ -157,11 +160,12 @@ RCT_EXPORT_MODULE(); NSString *event; UNNotificationPresentationOptions options; - NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification"]; + NSDictionary *message = [self parseUNNotification:notification]; if (isFcm || isScheduled) { // If app is in the background - if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { + if (RCTSharedApplication().applicationState == UIApplicationStateBackground + || RCTSharedApplication().applicationState == UIApplicationStateInactive) { // display the notification options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; // notification_displayed @@ -180,7 +184,7 @@ RCT_EXPORT_MODULE(); event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; } - [RNFirebaseUtil sendJSEvent:self name:event body:message]; + [self sendJSEvent:self name:event body:message]; completionHandler(options); } @@ -192,9 +196,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response #else withCompletionHandler:(void(^)())completionHandler { #endif - NSDictionary *message = [self parseUNNotificationResponse:response messageType:@"NotificationResponse"]; + NSDictionary *message = [self parseUNNotificationResponse:response]; - [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message]; + [self sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message]; completionHandler(); } @@ -205,7 +209,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response // ******************************************************* RCT_EXPORT_METHOD(cancelAllNotifications) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { [RCTSharedApplication() cancelAllLocalNotifications]; } else { #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @@ -218,7 +222,7 @@ RCT_EXPORT_METHOD(cancelAllNotifications) { } RCT_EXPORT_METHOD(cancelNotification:(NSString*) notificationId) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) { NSDictionary *notificationInfo = notification.userInfo; if ([notificationId isEqualToString:[notificationInfo valueForKey:@"notificationId"]]) { @@ -238,7 +242,7 @@ RCT_EXPORT_METHOD(cancelNotification:(NSString*) notificationId) { RCT_EXPORT_METHOD(displayNotification:(NSDictionary*) notification resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:false]; [RCTSharedApplication() presentLocalNotificationNow:notif]; resolve(nil); @@ -248,33 +252,48 @@ RCT_EXPORT_METHOD(displayNotification:(NSDictionary*) notification [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if (!error) { resolve(nil); - }else{ + } else{ reject(@"notifications/display_notification_error", @"Failed to display notificaton", error); } }]; #endif } } + +RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); + }); +} -RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ - UILocalNotification *localNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; - if (localNotification) { - NSDictionary *notification = [self parseUILocalNotification:localNotification]; - resolve(notification); - } else { - NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - if (remoteNotification) { - NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" category:nil]; - resolve(message); +RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + if ([self isIOS89]) { + // With iOS 8/9 we rely on the launch options + UILocalNotification *localNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; + NSDictionary *notification; + if (localNotification) { + notification = [self parseUILocalNotification:localNotification]; + } else { + NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + if (remoteNotification) { + notification = [self parseUserInfo:remoteNotification]; + } + } + if (notification) { + resolve(@{@"action": UNNotificationDefaultActionIdentifier, @"notification": notification}); } else { resolve(nil); } + } else { + // With iOS 10+ we are able to intercept the didReceiveNotificationResponse message + // to get both the notification and the action + resolve(initialNotification); } } RCT_EXPORT_METHOD(getScheduledNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { NSMutableArray* notifications = [[NSMutableArray alloc] init]; for (UILocalNotification *notif in [RCTSharedApplication() scheduledLocalNotifications]){ NSDictionary *notification = [self parseUILocalNotification:notif]; @@ -296,7 +315,7 @@ RCT_EXPORT_METHOD(getScheduledNotifications:(RCTPromiseResolveBlock)resolve } RCT_EXPORT_METHOD(removeAllDeliveredNotifications) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { // No such functionality on iOS 8/9 } else { #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @@ -309,7 +328,7 @@ RCT_EXPORT_METHOD(removeAllDeliveredNotifications) { } RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { // No such functionality on iOS 8/9 } else { #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @@ -324,7 +343,7 @@ RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) { RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { + if ([self isIOS89]) { UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:true]; [RCTSharedApplication() scheduleLocalNotification:notif]; resolve(nil); @@ -334,13 +353,38 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if (!error) { resolve(nil); - }else{ + } else{ reject(@"notification/schedule_notification_error", @"Failed to schedule notificaton", error); } }]; #endif } } + +RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { + dispatch_async(dispatch_get_main_queue(), ^{ + [RCTSharedApplication() setApplicationIconBadgeNumber:number]; + }); +} + +// Because of the time delay between the app starting and the bridge being initialised +// we create a temporary instance of RNFirebaseNotifications. +// With this temporary instance, we cache any events to be sent as soon as the bridge is set on the module +- (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { + if (emitter.bridge) { + [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; + } else { + if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_PRESSED] && !initialNotification) { + initialNotification = body; + } + // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side + [pendingEvents addObject:@{@"name":name, @"body":body}]; + } +} + +- (BOOL)isIOS89 { + return floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max; +} - (UILocalNotification*) buildUILocalNotification:(NSDictionary *) notification withSchedule:(BOOL) withSchedule { @@ -518,33 +562,55 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification return notification; } - -- (NSDictionary*) parseUNNotificationRequest:(UNNotificationRequest *) localNotification { - NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; - - notification[@"notificationId"] = localNotification.identifier; - if (localNotification.content.body) { - notification[@"body"] = localNotification.content.body; +- (NSDictionary*)parseUNNotificationResponse:(UNNotificationResponse *)response { + NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; + NSDictionary *notification = [self parseUNNotification:response.notification]; + notificationResponse[@"notification"] = notification; + notificationResponse[@"action"] = response.actionIdentifier; + + return notificationResponse; +} + +- (NSDictionary*)parseUNNotification:(UNNotification *)notification { + return [self parseUNNotificationRequest:notification.request]; +} + +- (NSDictionary*) parseUNNotificationRequest:(UNNotificationRequest *) notificationRequest { + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; + + notification[@"notificationId"] = notificationRequest.identifier; + + if (notificationRequest.content.body) { + notification[@"body"] = notificationRequest.content.body; } - if (localNotification.content.userInfo) { - notification[@"data"] = localNotification.content.userInfo; + if (notificationRequest.content.userInfo) { + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + for (id k in notificationRequest.content.userInfo) { + if ([k isEqualToString:@"aps"] + || [k isEqualToString:@"gcm.message_id"]) { + // ignore as these are handled by the OS + } else { + data[k] = notificationRequest.content.userInfo[k]; + } + } + notification[@"data"] = data; } - if (localNotification.content.sound) { - notification[@"sound"] = localNotification.content.sound; + if (notificationRequest.content.sound) { + notification[@"sound"] = notificationRequest.content.sound; } - if (localNotification.content.subtitle) { - notification[@"subtitle"] = localNotification.content.subtitle; + if (notificationRequest.content.subtitle) { + notification[@"subtitle"] = notificationRequest.content.subtitle; } - if (localNotification.content.title) { - notification[@"title"] = localNotification.content.title; + if (notificationRequest.content.title) { + notification[@"title"] = notificationRequest.content.title; } NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; - if (localNotification.content.attachments) { + if (notificationRequest.content.attachments) { NSMutableArray *attachments = [[NSMutableArray alloc] init]; - for (UNNotificationAttachment *a in localNotification.content.attachments) { + for (UNNotificationAttachment *a in notificationRequest.content.attachments) { NSMutableDictionary *attachment = [[NSMutableDictionary alloc] init]; attachment[@"identifier"] = a.identifier; attachment[@"type"] = a.type; @@ -554,45 +620,25 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification ios[@"attachments"] = attachments; } - if (localNotification.content.badge) { - ios[@"badge"] = localNotification.content.badge; + if (notificationRequest.content.badge) { + ios[@"badge"] = notificationRequest.content.badge; } - if (localNotification.content.categoryIdentifier) { - ios[@"category"] = localNotification.content.categoryIdentifier; + if (notificationRequest.content.categoryIdentifier) { + ios[@"category"] = notificationRequest.content.categoryIdentifier; } - if (localNotification.content.launchImageName) { - ios[@"launchImage"] = localNotification.content.launchImageName; + if (notificationRequest.content.launchImageName) { + ios[@"launchImage"] = notificationRequest.content.launchImageName; } - if (localNotification.content.threadIdentifier) { - ios[@"threadIdentifier"] = localNotification.content.threadIdentifier; + if (notificationRequest.content.threadIdentifier) { + ios[@"threadIdentifier"] = notificationRequest.content.threadIdentifier; } notification[@"ios"] = ios; return notification; } - -- (NSDictionary*)parseUNNotificationResponse:(UNNotificationResponse *)response - messageType:(NSString *)messageType { - NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; - NSDictionary *notification = [self parseUNNotification:response.notification messageType:messageType]; - notificationResponse[@"notification"] = notification; - notificationResponse[@"action"] = response.actionIdentifier; - - return notificationResponse; -} - -- (NSDictionary*)parseUNNotification:(UNNotification *)notification - messageType:(NSString *)messageType { - NSDictionary *userInfo = notification.request.content.userInfo; - NSString *category = notification.request.content.categoryIdentifier; - return [self parseUserInfo:userInfo messageType:messageType category:category]; -} - -- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo - messageType:(NSString *) messageType - category:(NSString *) category { - +- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo { + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; @@ -644,13 +690,6 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification data[k1] = userInfo[k1]; } } - - if (!ios[@"category"]) { - ios[@"category"] = category; - } - - // TODO: What to do with this? - // message[@"messageType"] = messageType; notification[@"data"] = data; notification[@"ios"] = ios; @@ -673,3 +712,4 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification @implementation RNFirebaseNotifications @end #endif + diff --git a/lib/modules/messaging/index.js b/lib/modules/messaging/index.js index f17d389a..5b658afd 100644 --- a/lib/modules/messaging/index.js +++ b/lib/modules/messaging/index.js @@ -125,7 +125,6 @@ export default class Messaging extends ModuleBase { }; } - // TODO: Permission structure? requestPermission(): Promise { return getNativeModule(this).requestPermission(); } @@ -133,16 +132,6 @@ export default class Messaging extends ModuleBase { /** * NON WEB-SDK METHODS */ - getBadge(): Promise { - return getNativeModule(this).getBadge(); - } - - getInitialMessage(): Promise { - return getNativeModule(this) - .getInitialMessage() - .then(message => (message ? new Message(this, message) : null)); - } - hasPermission(): Promise { return getNativeModule(this).hasPermission(); } @@ -156,10 +145,6 @@ export default class Messaging extends ModuleBase { return getNativeModule(this).send(remoteMessage.build()); } - setBadge(badge: number): void { - getNativeModule(this).setBadge(badge); - } - subscribeToTopic(topic: string): void { getNativeModule(this).subscribeToTopic(topic); } diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 60e1dc61..688be880 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -143,6 +143,10 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).displayNotification(notification.build()); } + getBadge(): Promise { + return getNativeModule(this).getBadge(); + } + getInitialNotification(): Promise { return getNativeModule(this).getInitialNotification(); // TODO @@ -264,6 +268,10 @@ export default class Notifications extends ModuleBase { nativeNotification.schedule = schedule; return getNativeModule(this).scheduleNotification(nativeNotification); } + + setBadge(badge: number): void { + getNativeModule(this).setBadge(badge); + } } export const statics = { From c5778c3d0dfeb68666abe8ff38802787760cdc52 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 23 Feb 2018 16:11:59 +0000 Subject: [PATCH 07/13] [notifications] JS tidy up --- .../messaging/RNFirebaseMessaging.m | 61 ------ .../notifications/RNFirebaseNotifications.m | 2 +- lib/index.js | 1 - lib/modules/messaging/Message.js | 108 ---------- lib/modules/messaging/RemoteMessage.js | 58 ++++-- lib/modules/messaging/index.js | 31 +-- lib/modules/messaging/types.js | 40 +--- .../notifications/AndroidNotification.js | 188 +++++++++++++++--- lib/modules/notifications/IOSNotification.js | 28 +++ lib/modules/notifications/Notification.js | 26 ++- lib/modules/notifications/types.js | 56 +++--- .../ios/ReactNativeFirebaseDemo/AppDelegate.m | 4 - 12 files changed, 307 insertions(+), 296 deletions(-) delete mode 100644 lib/modules/messaging/Message.js diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 31365c80..4af1e1b3 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -14,10 +14,6 @@ #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @import UserNotifications; #endif -@interface RNFirebaseMessaging () -@property (nonatomic, strong) NSMutableDictionary *callbackHandlers; -@end - @implementation RNFirebaseMessaging @@ -47,9 +43,6 @@ RCT_EXPORT_MODULE() // Set static instance for use from AppDelegate theRNFirebaseMessaging = self; - - // Initialise callback handlers dictionary - _callbackHandlers = [NSMutableDictionary dictionary]; } // ******************************************************* @@ -187,60 +180,6 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) { [[FIRMessaging messaging] unsubscribeFromTopic:topic]; } -// Response handler methods - -RCT_EXPORT_METHOD(completeNotificationResponse: (NSString*) messageId) { - void(^callbackHandler)() = [_callbackHandlers objectForKey:messageId]; - if (!callbackHandler) { - NSLog(@"There is no callback handler for messageId: %@", messageId); - return; - } - callbackHandler(); - [_callbackHandlers removeObjectForKey:messageId]; -} - -RCT_EXPORT_METHOD(completePresentNotification: (NSString*) messageId - result: (NSString*) result) { - void(^callbackHandler)(UNNotificationPresentationOptions) = [_callbackHandlers objectForKey:messageId]; - if (!callbackHandler) { - NSLog(@"There is no callback handler for messageId: %@", messageId); - return; - } - UNNotificationPresentationOptions options; - if ([result isEqualToString:@"UNNotificationPresentationOptionAll"]) { - options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound; - } else if ([result isEqualToString:@"UNNotificationPresentationOptionNone"]) { - options = UNNotificationPresentationOptionNone; - } else { - NSLog(@"Invalid result for PresentNotification: %@", result); - return; - } - callbackHandler(options); - [_callbackHandlers removeObjectForKey:messageId]; -} - -RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId - result: (NSString*) result) { - void(^callbackHandler)(UIBackgroundFetchResult) = [_callbackHandlers objectForKey:messageId]; - if (!callbackHandler) { - NSLog(@"There is no callback handler for messageId: %@", messageId); - return; - } - UIBackgroundFetchResult fetchResult; - if ([result isEqualToString:@"UIBackgroundFetchResultNewData"]) { - fetchResult = UIBackgroundFetchResultNewData; - } else if ([result isEqualToString:@"UIBackgroundFetchResultNoData"]) { - fetchResult = UIBackgroundFetchResultNoData; - } else if ([result isEqualToString:@"UIBackgroundFetchResultFailed"]) { - fetchResult = UIBackgroundFetchResultFailed; - } else { - NSLog(@"Invalid result for PresentNotification: %@", result); - return; - } - callbackHandler(fetchResult); - [_callbackHandlers removeObjectForKey:messageId]; -} - // ** Start internals ** - (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index beb733a0..fda16c66 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -378,7 +378,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { initialNotification = body; } // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side - [pendingEvents addObject:@{@"name":name, @"body":body}]; + // [pendingEvents addObject:@{@"name":name, @"body":body}]; } } diff --git a/lib/index.js b/lib/index.js index 8d7a7cc2..6cd42dce 100644 --- a/lib/index.js +++ b/lib/index.js @@ -68,7 +68,6 @@ export type { default as WriteBatch } from './modules/firestore/WriteBatch'; /* * Export Messaging types */ -export type { default as Message } from './modules/messaging/Message'; export type { default as RemoteMessage, } from './modules/messaging/RemoteMessage'; diff --git a/lib/modules/messaging/Message.js b/lib/modules/messaging/Message.js deleted file mode 100644 index 38f3433f..00000000 --- a/lib/modules/messaging/Message.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @flow - * Message representation wrapper - */ -import { Platform } from 'react-native'; -import { getNativeModule } from '../../utils/native'; -import { - MessageType, - PresentNotificationResult, - RemoteNotificationResult, -} from './types'; -import type Messaging from './'; -import type { - NativeMessage, - PresentNotificationResultType, - RemoteNotificationResultType, -} from './types'; - -/** - * @class Message - */ -export default class Message { - _completed: boolean; - _messaging: Messaging; - _message: NativeMessage; - - constructor(messaging: Messaging, message: NativeMessage) { - this._messaging = messaging; - this._message = message; - } - - get collapseKey(): ?string { - return this._message.collapseKey; - } - - get data(): { [string]: string } { - return this._message.data; - } - - get from(): ?string { - return this._message.from; - } - - get messageId(): ?string { - return this._message.messageId; - } - - get sentTime(): ?number { - return this._message.sentTime; - } - - get to(): ?string { - return this._message.to; - } - - get ttl(): ?number { - return this._message.ttl; - } - - complete( - result?: PresentNotificationResultType | RemoteNotificationResultType - ): void { - if (Platform.OS === 'android') { - return; - } - - if (!this._completed) { - this._completed = true; - - switch (this.messageType) { - case MessageType.NotificationResponse: - getNativeModule(this._messaging).completeNotificationResponse( - this.messageId - ); - break; - - case MessageType.PresentNotification: - if ( - result && - !Object.values(PresentNotificationResult).includes(result) - ) { - throw new Error(`Invalid PresentNotificationResult: ${result}`); - } - getNativeModule(this._messaging).completePresentNotification( - this.messageId, - result || PresentNotificationResult.None - ); - break; - - case MessageType.RemoteNotificationHandler: - if ( - result && - !Object.values(RemoteNotificationResult).includes(result) - ) { - throw new Error(`Invalid RemoteNotificationResult: ${result}`); - } - getNativeModule(this._messaging).completeRemoteNotification( - this.messageId, - result || RemoteNotificationResult.NoData - ); - break; - - default: - break; - } - } - } -} diff --git a/lib/modules/messaging/RemoteMessage.js b/lib/modules/messaging/RemoteMessage.js index 15a8493f..e99de6ca 100644 --- a/lib/modules/messaging/RemoteMessage.js +++ b/lib/modules/messaging/RemoteMessage.js @@ -4,30 +4,64 @@ */ import { isObject, generatePushID } from './../../utils'; -type NativeRemoteMessage = { - collapseKey?: string, - data: { [string]: string }, - messageId: string, - messageType?: string, - to: string, - ttl: number, -}; +import type { + NativeInboundRemoteMessage, + NativeOutboundRemoteMessage, +} from './types'; export default class RemoteMessage { _collapseKey: string | void; _data: { [string]: string }; + _from: string | void; _messageId: string; _messageType: string | void; + _sentTime: number | void; _to: string; _ttl: number; - constructor() { - this._data = {}; + constructor(inboundMessage?: NativeInboundRemoteMessage) { + if (inboundMessage) { + this._collapseKey = inboundMessage.collapseKey; + this._data = inboundMessage.data; + this._from = inboundMessage.from; + this._messageId = inboundMessage.messageId; + this._sentTime = inboundMessage.sentTime; + } + // defaults + this._data = this._data || {}; // TODO: Is this the best way to generate an ID? - this._messageId = generatePushID(); + this._messageId = this._messageId || generatePushID(); this._ttl = 3600; } + get collapseKey(): ?string { + return this._collapseKey; + } + + get data(): { [string]: string } { + return this._data; + } + + get from(): ?string { + return this._from; + } + + get messageId(): ?string { + return this._messageId; + } + + get sentTime(): ?number { + return this._sentTime; + } + + get to(): ?string { + return this._to; + } + + get ttl(): ?number { + return this._ttl; + } + /** * * @param collapseKey @@ -83,7 +117,7 @@ export default class RemoteMessage { return this; } - build(): NativeRemoteMessage { + build(): NativeOutboundRemoteMessage { if (!this.data) { throw new Error('RemoteMessage: Missing required `data` property'); } else if (!this.messageId) { diff --git a/lib/modules/messaging/index.js b/lib/modules/messaging/index.js index 5b658afd..ad46838b 100644 --- a/lib/modules/messaging/index.js +++ b/lib/modules/messaging/index.js @@ -8,18 +8,12 @@ import { getLogger } from '../../utils/log'; import ModuleBase from '../../utils/ModuleBase'; import { getNativeModule } from '../../utils/native'; import { isFunction, isObject } from '../../utils'; -import Message from './Message'; import RemoteMessage from './RemoteMessage'; -import { - MessageType, - PresentNotificationResult, - RemoteNotificationResult, -} from './types'; import type App from '../core/app'; -import type { NativeMessage } from './types'; +import type { NativeInboundRemoteMessage } from './types'; -type OnMessage = Message => any; +type OnMessage = RemoteMessage => any; type OnMessageObserver = { next: OnMessage, @@ -55,8 +49,8 @@ export default class Messaging extends ModuleBase { // sub to internal native event - this fans out to // public event name: onMessage 'messaging_message_received', - (message: NativeMessage) => { - SharedEventEmitter.emit('onMessage', new Message(this, message)); + (message: NativeInboundRemoteMessage) => { + SharedEventEmitter.emit('onMessage', new RemoteMessage(message)); } ); @@ -75,7 +69,7 @@ export default class Messaging extends ModuleBase { } onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any { - let listener: Message => any; + let listener: RemoteMessage => any; if (isFunction(nextOrObserver)) { // $FlowBug: Not coping with the overloaded method signature listener = nextOrObserver; @@ -89,24 +83,20 @@ export default class Messaging extends ModuleBase { getLogger(this).info('Creating onMessage listener'); - const wrappedListener = async (message: Message) => { - await listener(message); - message.complete(); - }; - - SharedEventEmitter.addListener('onMessage', wrappedListener); + SharedEventEmitter.addListener('onMessage', listener); return () => { getLogger(this).info('Removing onMessage listener'); - SharedEventEmitter.removeListener('onMessage', wrappedListener); + SharedEventEmitter.removeListener('onMessage', listener); }; } onTokenRefresh( nextOrObserver: OnTokenRefresh | OnTokenRefreshObserver ): () => any { - let listener; + let listener: String => any; if (isFunction(nextOrObserver)) { + // $FlowBug: Not coping with the overloaded method signature listener = nextOrObserver; } else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) { listener = nextOrObserver.next; @@ -186,8 +176,5 @@ export default class Messaging extends ModuleBase { } export const statics = { - MessageType, - PresentNotificationResult, RemoteMessage, - RemoteNotificationResult, }; diff --git a/lib/modules/messaging/types.js b/lib/modules/messaging/types.js index 3b58201a..1d187e30 100644 --- a/lib/modules/messaging/types.js +++ b/lib/modules/messaging/types.js @@ -1,35 +1,6 @@ /** * @flow */ - -export const MessageType = { - InitialMessage: 'InitialMessage', - NotificationResponse: 'NotificationResponse', - PresentNotification: 'PresentNotification', - RemoteMessage: 'RemoteMessage', - RemoteNotification: 'RemoteNotification', - RemoteNotificationHandler: 'RemoteNotificationHandler', -}; - -export const PresentNotificationResult = { - All: 'UNNotificationPresentationOptionAll', - None: 'UNNotificationPresentationOptionNone', -}; - -export const RemoteNotificationResult = { - NewData: 'UIBackgroundFetchResultNewData', - NoData: 'UIBackgroundFetchResultNoData', - ResultFailed: 'UIBackgroundFetchResultFailed', -}; - -export type MessageTypeType = $Values; -export type PresentNotificationResultType = $Values< - typeof PresentNotificationResult ->; -export type RemoteNotificationResultType = $Values< - typeof RemoteNotificationResult ->; - export type Notification = { body: string, bodyLocalizationArgs?: string[], @@ -46,7 +17,7 @@ export type Notification = { titleLocalizationKey?: string, }; -export type NativeMessage = { +export type NativeInboundRemoteMessage = { collapseKey?: string, data: { [string]: string }, from?: string, @@ -55,3 +26,12 @@ export type NativeMessage = { to?: string, ttl?: number, }; + +export type NativeOutboundRemoteMessage = { + collapseKey?: string, + data: { [string]: string }, + messageId: string, + messageType?: string, + to: string, + ttl: number, +}; diff --git a/lib/modules/notifications/AndroidNotification.js b/lib/modules/notifications/AndroidNotification.js index c3305e0b..2b33d4d2 100644 --- a/lib/modules/notifications/AndroidNotification.js +++ b/lib/modules/notifications/AndroidNotification.js @@ -20,41 +20,41 @@ import type { export default class AndroidNotification { // TODO optional fields // TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh) - _autoCancel: boolean; - _badgeIconType: BadgeIconTypeType; - _category: CategoryType; + _autoCancel: boolean | void; + _badgeIconType: BadgeIconTypeType | void; + _category: CategoryType | void; _channelId: string; _clickAction: string | void; - _color: string; - _colorized: boolean; - _contentInfo: string; - _defaults: DefaultsType[]; - _group: string; - _groupAlertBehaviour: GroupAlertType; - _groupSummary: boolean; - _largeIcon: string; - _lights: Lights; - _localOnly: boolean; + _color: string | void; + _colorized: boolean | void; + _contentInfo: string | void; + _defaults: DefaultsType[] | void; + _group: string | void; + _groupAlertBehaviour: GroupAlertType | void; + _groupSummary: boolean | void; + _largeIcon: string | void; + _lights: Lights | void; + _localOnly: boolean | void; _notification: Notification; - _number: number; - _ongoing: boolean; - _onlyAlertOnce: boolean; + _number: number | void; + _ongoing: boolean | void; + _onlyAlertOnce: boolean | void; _people: string[]; - _priority: PriorityType; - _progress: Progress; + _priority: PriorityType | void; + _progress: Progress | void; // _publicVersion: Notification; - _remoteInputHistory: string[]; - _shortcutId: string; - _showWhen: boolean; + _remoteInputHistory: string[] | void; + _shortcutId: string | void; + _showWhen: boolean | void; _smallIcon: SmallIcon; - _sortKey: string; + _sortKey: string | void; // TODO: style: Style; // Need to figure out if this can work - _ticker: string; - _timeoutAfter: number; - _usesChronometer: boolean; - _vibrate: number[]; - _visibility: VisibilityType; - _when: number; + _ticker: string | void; + _timeoutAfter: number | void; + _usesChronometer: boolean | void; + _vibrate: number[] | void; + _visibility: VisibilityType | void; + _when: number | void; // android unsupported // content: RemoteViews @@ -112,6 +112,134 @@ export default class AndroidNotification { }; } + get autoCancel(): ?boolean { + return this._autoCancel; + } + + get badgeIconType(): ?BadgeIconTypeType { + return this._badgeIconType; + } + + get category(): ?CategoryType { + return this._category; + } + + get channelId(): string { + return this._channelId; + } + + get clickAction(): ?string { + return this._clickAction; + } + + get color(): ?string { + return this._color; + } + + get colorized(): ?boolean { + return this._colorized; + } + + get contentInfo(): ?string { + return this._contentInfo; + } + + get defaults(): ?(DefaultsType[]) { + return this._defaults; + } + + get group(): ?string { + return this._group; + } + + get groupAlertBehaviour(): ?GroupAlertType { + return this._groupAlertBehaviour; + } + + get groupSummary(): ?boolean { + return this._groupSummary; + } + + get largeIcon(): ?string { + return this._largeIcon; + } + + get lights(): ?Lights { + return this._lights; + } + + get localOnly(): ?boolean { + return this._localOnly; + } + + get number(): ?number { + return this._number; + } + + get ongoing(): ?boolean { + return this._ongoing; + } + + get onlyAlertOnce(): ?boolean { + return this._onlyAlertOnce; + } + + get people(): string[] { + return this._people; + } + + get priority(): ?PriorityType { + return this._priority; + } + + get progress(): ?Progress { + return this._progress; + } + + get remoteInputHistory(): ?(string[]) { + return this._remoteInputHistory; + } + + get shortcutId(): ?string { + return this._shortcutId; + } + + get showWhen(): ?boolean { + return this._showWhen; + } + + get smallIcon(): SmallIcon { + return this._smallIcon; + } + + get sortKey(): ?string { + return this._sortKey; + } + + get ticker(): ?string { + return this._ticker; + } + + get timeoutAfter(): ?number { + return this._timeoutAfter; + } + + get usesChronometer(): ?boolean { + return this._usesChronometer; + } + + get vibrate(): ?(number[]) { + return this._vibrate; + } + + get visibility(): ?VisibilityType { + return this._visibility; + } + + get when(): ?number { + return this._when; + } + /** * * @param person @@ -451,6 +579,10 @@ export default class AndroidNotification { throw new Error( 'AndroidNotification: Missing required `channelId` property' ); + } else if (!this._smallIcon) { + throw new Error( + 'AndroidNotification: Missing required `smallIcon` property' + ); } return { diff --git a/lib/modules/notifications/IOSNotification.js b/lib/modules/notifications/IOSNotification.js index 5b1e563b..b78e97bd 100644 --- a/lib/modules/notifications/IOSNotification.js +++ b/lib/modules/notifications/IOSNotification.js @@ -36,6 +36,34 @@ export default class IOSNotification { this._attachments = this._attachments || []; } + get alertAction(): ?string { + return this._alertAction; + } + + get attachments(): Attachment[] { + return this._attachments; + } + + get badge(): ?number { + return this._badge; + } + + get category(): ?string { + return this._category; + } + + get hasAction(): ?boolean { + return this._hasAction; + } + + get launchImage(): ?string { + return this._launchImage; + } + + get threadIdentifier(): ?string { + return this._threadIdentifier; + } + /** * * @param identifier diff --git a/lib/modules/notifications/Notification.js b/lib/modules/notifications/Notification.js index 1ad7d2cd..734c4441 100644 --- a/lib/modules/notifications/Notification.js +++ b/lib/modules/notifications/Notification.js @@ -32,7 +32,6 @@ export default class Notification { if (data) { this._body = data.body; this._data = data.data; - // TODO: Is this the best way to generate an ID? this._notificationId = data.notificationId; this._sound = data.sound; this._subtitle = data.subtitle; @@ -41,6 +40,7 @@ export default class Notification { // Defaults this._data = this._data || {}; + // TODO: Is this the best way to generate an ID? this._notificationId = this._notificationId || generatePushID(); } @@ -48,10 +48,34 @@ export default class Notification { return this._android; } + get body(): string { + return this._body; + } + + get data(): { [string]: string } { + return this._data; + } + get ios(): IOSNotification { return this._ios; } + get notificationId(): string { + return this._notificationId; + } + + get sound(): ?string { + return this._sound; + } + + get subtitle(): ?string { + return this._subtitle; + } + + get title(): string { + return this._title; + } + /** * * @param body diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index 9c2abe10..22760c16 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -79,40 +79,40 @@ export type SmallIcon = { export type NativeAndroidNotification = {| // TODO actions: Action[], - autoCancel: boolean, - badgeIconType: BadgeIconTypeType, - category: CategoryType, + autoCancel?: boolean, + badgeIconType?: BadgeIconTypeType, + category?: CategoryType, channelId: string, clickAction?: string, - color: string, - colorized: boolean, - contentInfo: string, - defaults: DefaultsType[], - group: string, - groupAlertBehaviour: GroupAlertType, - groupSummary: boolean, - largeIcon: string, - lights: Lights, - localOnly: boolean, - number: number, - ongoing: boolean, - onlyAlertOnce: boolean, + color?: string, + colorized?: boolean, + contentInfo?: string, + defaults?: DefaultsType[], + group?: string, + groupAlertBehaviour?: GroupAlertType, + groupSummary?: boolean, + largeIcon?: string, + lights?: Lights, + localOnly?: boolean, + number?: number, + ongoing?: boolean, + onlyAlertOnce?: boolean, people: string[], - priority: PriorityType, - progress: Progress, + priority?: PriorityType, + progress?: Progress, // publicVersion: Notification, - remoteInputHistory: string[], - shortcutId: string, - showWhen: boolean, + remoteInputHistory?: string[], + shortcutId?: string, + showWhen?: boolean, smallIcon: SmallIcon, - sortKey: string, + sortKey?: string, // TODO: style: Style, - ticker: string, - timeoutAfter: number, - usesChronometer: boolean, - vibrate: number[], - visibility: VisibilityType, - when: number, + ticker?: string, + timeoutAfter?: number, + usesChronometer?: boolean, + vibrate?: number[], + visibility?: VisibilityType, + when?: number, |}; export type AttachmentOptions = {| diff --git a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m index 88cfe550..81105040 100644 --- a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m +++ b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m @@ -38,10 +38,6 @@ return YES; } -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - [[RNFirebaseNotifications instance] didReceiveRemoteNotification:userInfo]; -} - - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [[RNFirebaseNotifications instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; From 28b6d2b69cc3e25cbe1c5bb0e685ad41cc5b9259 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 23 Feb 2018 16:14:19 +0000 Subject: [PATCH 08/13] [messaging] Correctly resolve the promise after sending a message --- ios/RNFirebase/messaging/RNFirebaseMessaging.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 4af1e1b3..5b92cdb1 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -170,6 +170,9 @@ RCT_EXPORT_METHOD(sendMessage: (NSDictionary *) message NSDictionary *data = message[@"data"]; [[FIRMessaging messaging] sendMessage:data to:to withMessageID:messageId timeToLive:[ttl intValue]]; + + // TODO: Listen for send success / errors + resolve(nil); } RCT_EXPORT_METHOD(subscribeToTopic: (NSString*) topic) { From 07bc258c0857a4965c23fbb29620ea44d42b3eaf Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 23 Feb 2018 16:36:59 +0000 Subject: [PATCH 09/13] [messaging] Add missing setTo method --- lib/modules/messaging/RemoteMessage.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/modules/messaging/RemoteMessage.js b/lib/modules/messaging/RemoteMessage.js index e99de6ca..2ed54228 100644 --- a/lib/modules/messaging/RemoteMessage.js +++ b/lib/modules/messaging/RemoteMessage.js @@ -107,6 +107,16 @@ export default class RemoteMessage { return this; } + /** + * + * @param to + * @returns {RemoteMessage} + */ + setTo(to: string): RemoteMessage { + this._to = to; + return this; + } + /** * * @param ttl From ee3b2932efe1f64606933c06f1c4e67026ebb892 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sat, 24 Feb 2018 11:32:14 +0000 Subject: [PATCH 10/13] [notifications] Rename `onPressed` to `onOpened` --- ios/RNFirebase/RNFirebaseEvents.h | 2 +- .../notifications/RNFirebaseNotifications.m | 87 +++++++++---------- lib/modules/notifications/Notification.js | 2 +- lib/modules/notifications/index.js | 68 +++++++-------- lib/modules/notifications/types.js | 2 +- 5 files changed, 77 insertions(+), 84 deletions(-) diff --git a/ios/RNFirebase/RNFirebaseEvents.h b/ios/RNFirebase/RNFirebaseEvents.h index 938a8445..bf7dd021 100644 --- a/ios/RNFirebase/RNFirebaseEvents.h +++ b/ios/RNFirebase/RNFirebaseEvents.h @@ -40,7 +40,7 @@ static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notificatio // Notifications static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed"; -static NSString *const NOTIFICATIONS_NOTIFICATION_PRESSED = @"notifications_notification_pressed"; +static NSString *const NOTIFICATIONS_NOTIFICATION_OPENED = @"notifications_notification_opened"; static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received"; // AdMob diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index fda16c66..1fe8a342 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -49,7 +49,7 @@ RCT_EXPORT_MODULE(); #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 [UNUserNotificationCenter currentNotificationCenter].delegate = self; #endif - + // Set static instance for use from AppDelegate theRNFirebaseNotifications = self; } @@ -75,18 +75,15 @@ RCT_EXPORT_MODULE(); if ([self isIOS89]) { NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { - // notification_displayed event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; } else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_PRESSED; + event = NOTIFICATIONS_NOTIFICATION_OPENED; } else { - // notification_received event = NOTIFICATIONS_NOTIFICATION_RECEIVED; } NSDictionary *notification = [self parseUILocalNotification:localNotification]; - if (event == NOTIFICATIONS_NOTIFICATION_PRESSED) { + if (event == NOTIFICATIONS_NOTIFICATION_OPENED) { notification = @{ @"action": UNNotificationDefaultActionIdentifier, @"notification": notification @@ -105,17 +102,14 @@ RCT_EXPORT_MODULE(); [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; return; } - + NSString *event; if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { - // notification_displayed event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; } else if ([self isIOS89]) { if (RCTSharedApplication().applicationState == UIApplicationStateInactive) { - // notification_displayed - event = NOTIFICATIONS_NOTIFICATION_PRESSED; + event = NOTIFICATIONS_NOTIFICATION_OPENED; } else { - // notification_received event = NOTIFICATIONS_NOTIFICATION_RECEIVED; } } else { @@ -127,8 +121,8 @@ RCT_EXPORT_MODULE(); } NSDictionary *notification = [self parseUserInfo:userInfo]; - // For onPressed events, we set the default action name as iOS 8/9 has no concept of actions - if (event == NOTIFICATIONS_NOTIFICATION_PRESSED) { + // For onOpened events, we set the default action name as iOS 8/9 has no concept of actions + if (event == NOTIFICATIONS_NOTIFICATION_OPENED) { notification = @{ @"action": UNNotificationDefaultActionIdentifier, @"notification": notification @@ -153,15 +147,15 @@ RCT_EXPORT_MODULE(); - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { - + UNNotificationTrigger *trigger = notification.request.trigger; BOOL isFcm = trigger && [notification.request.trigger class] == [UNPushNotificationTrigger class]; BOOL isScheduled = trigger && [notification.request.trigger class] == [UNCalendarNotificationTrigger class]; - + NSString *event; UNNotificationPresentationOptions options; NSDictionary *message = [self parseUNNotification:notification]; - + if (isFcm || isScheduled) { // If app is in the background if (RCTSharedApplication().applicationState == UIApplicationStateBackground @@ -183,7 +177,7 @@ RCT_EXPORT_MODULE(); // notification_displayed event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; } - + [self sendJSEvent:self name:event body:message]; completionHandler(options); } @@ -197,8 +191,8 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler { #endif NSDictionary *message = [self parseUNNotificationResponse:response]; - - [self sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message]; + + [self sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_OPENED body:message]; completionHandler(); } @@ -259,7 +253,7 @@ RCT_EXPORT_METHOD(displayNotification:(NSDictionary*) notification #endif } } - + RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_main_queue(), ^{ resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); @@ -360,13 +354,13 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification #endif } } - + RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { dispatch_async(dispatch_get_main_queue(), ^{ [RCTSharedApplication() setApplicationIconBadgeNumber:number]; }); } - + // Because of the time delay between the app starting and the bridge being initialised // we create a temporary instance of RNFirebaseNotifications. // With this temporary instance, we cache any events to be sent as soon as the bridge is set on the module @@ -374,14 +368,14 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { if (emitter.bridge) { [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; } else { - if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_PRESSED] && !initialNotification) { + if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_OPENED] && !initialNotification) { initialNotification = body; } // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side // [pendingEvents addObject:@{@"name":name, @"body":body}]; } } - + - (BOOL)isIOS89 { return floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max; } @@ -425,7 +419,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { NSNumber *fireDateNumber = schedule[@"fireDate"]; NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)]; localNotification.fireDate = fireDate; - + NSString *interval = schedule[@"repeatInterval"]; if (interval) { if ([interval isEqualToString:@"minute"]) { @@ -438,9 +432,9 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { localNotification.repeatInterval = NSCalendarUnitWeekday; } } - + } - + return localNotification; } @@ -470,7 +464,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { NSString *identifier = a[@"identifier"]; NSURL *url = [NSURL URLWithString:a[@"url"]]; NSDictionary *options = a[@"options"]; - + NSError *error; UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:url options:options error:&error]; if (attachment) { @@ -495,13 +489,13 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { content.threadIdentifier = ios[@"threadIdentifier"]; } } - + if (withSchedule) { NSDictionary *schedule = notification[@"schedule"]; NSNumber *fireDateNumber = schedule[@"fireDate"]; NSString *interval = schedule[@"repeatInterval"]; NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)]; - + NSCalendarUnit calendarUnit; if (interval) { if ([interval isEqualToString:@"minute"]) { @@ -517,7 +511,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { // Needs to match exactly to the secpmd calendarUnit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; } - + NSDateComponents *components = [[NSCalendar currentCalendar] components:calendarUnit fromDate:fireDate]; UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:interval]; return [UNNotificationRequest requestWithIdentifier:notification[@"notificationId"] content:content trigger:trigger]; @@ -528,7 +522,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { - (NSDictionary*) parseUILocalNotification:(UILocalNotification *) localNotification { NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; - + if (localNotification.alertBody) { notification[@"body"] = localNotification.alertBody; } @@ -541,7 +535,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { if (localNotification.alertTitle) { notification[@"title"] = localNotification.alertTitle; } - + NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; if (localNotification.alertAction) { ios[@"alertAction"] = localNotification.alertAction; @@ -559,28 +553,28 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { ios[@"launchImage"] = localNotification.alertLaunchImage; } notification[@"ios"] = ios; - + return notification; } - + - (NSDictionary*)parseUNNotificationResponse:(UNNotificationResponse *)response { NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init]; NSDictionary *notification = [self parseUNNotification:response.notification]; notificationResponse[@"notification"] = notification; notificationResponse[@"action"] = response.actionIdentifier; - + return notificationResponse; } - + - (NSDictionary*)parseUNNotification:(UNNotification *)notification { return [self parseUNNotificationRequest:notification.request]; } - + - (NSDictionary*) parseUNNotificationRequest:(UNNotificationRequest *) notificationRequest { NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; - + notification[@"notificationId"] = notificationRequest.identifier; - + if (notificationRequest.content.body) { notification[@"body"] = notificationRequest.content.body; } @@ -605,9 +599,9 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { if (notificationRequest.content.title) { notification[@"title"] = notificationRequest.content.title; } - + NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; - + if (notificationRequest.content.attachments) { NSMutableArray *attachments = [[NSMutableArray alloc] init]; for (UNNotificationAttachment *a in notificationRequest.content.attachments) { @@ -619,7 +613,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { } ios[@"attachments"] = attachments; } - + if (notificationRequest.content.badge) { ios[@"badge"] = notificationRequest.content.badge; } @@ -633,12 +627,12 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { ios[@"threadIdentifier"] = notificationRequest.content.threadIdentifier; } notification[@"ios"] = ios; - + return notification; } - (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo { - + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; NSMutableDictionary *ios = [[NSMutableDictionary alloc] init]; @@ -698,7 +692,7 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { } - (NSArray *)supportedEvents { - return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_PRESSED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; + return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_OPENED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; } + (BOOL)requiresMainQueueSetup @@ -712,4 +706,3 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { @implementation RNFirebaseNotifications @end #endif - diff --git a/lib/modules/notifications/Notification.js b/lib/modules/notifications/Notification.js index 734c4441..ad38e9e1 100644 --- a/lib/modules/notifications/Notification.js +++ b/lib/modules/notifications/Notification.js @@ -9,7 +9,7 @@ import { generatePushID, isObject } from '../../utils'; import type { NativeNotification } from './types'; -export type NotificationPressed = { +export type NotificationOpened = { action: string, notification: Notification, }; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 688be880..f116d4f6 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -18,10 +18,10 @@ import { } from './types'; import type App from '../core/app'; -import type { NotificationPressed } from './Notification'; +import type { NotificationOpened } from './Notification'; import type { NativeNotification, - NativeNotificationPressed, + NativeNotificationOpened, Schedule, } from './types'; @@ -31,15 +31,15 @@ type OnNotificationObserver = { next: OnNotification, }; -type OnNotificationPressed = NotificationPressed => any; +type OnNotificationOpened = NotificationOpened => any; -type OnNotificationPressedObserver = { - next: OnNotificationPressed, +type OnNotificationOpenedObserver = { + next: OnNotificationOpened, }; const NATIVE_EVENTS = [ 'notifications_notification_displayed', - 'notifications_notification_pressed', + 'notifications_notification_opened', 'notifications_notification_received', ]; @@ -86,12 +86,12 @@ export default class Notifications extends ModuleBase { SharedEventEmitter.addListener( // sub to internal native event - this fans out to - // public event name: onNotificationPressed - 'notifications_notification_pressed', - (notificationPressed: NativeNotificationPressed) => { - SharedEventEmitter.emit('onNotificationPressed', { - action: notificationPressed.action, - notification: new Notification(notificationPressed.notification), + // public event name: onNotificationOpened + 'notifications_notification_opened', + (notificationOpened: NativeNotificationOpened) => { + SharedEventEmitter.emit('OnNotificationOpened', { + action: notificationOpened.action, + notification: new Notification(notificationOpened.notification), }); } ); @@ -111,22 +111,22 @@ export default class Notifications extends ModuleBase { /** * Cancel all notifications - * @returns {*} */ - cancelAllNotifications(): Promise { - return getNativeModule(this).cancelAllNotifications(); + cancelAllNotifications(): void { + getNativeModule(this).cancelAllNotifications(); } /** * Cancel a notification by id. - * @param id - * @returns {*} + * @param notificationId */ - cancelNotification(notificationId: string): Promise { + cancelNotification(notificationId: string): void { if (!notificationId) { - return Promise.reject(new Error('Missing notificationId')); + throw new Error( + 'Notifications: cancelNotification expects a `notificationId`' + ); } - return getNativeModule(this).cancelNotification(notificationId); + getNativeModule(this).cancelNotification(notificationId); } /** @@ -207,8 +207,8 @@ export default class Notifications extends ModuleBase { }; } - onNotificationPressed( - nextOrObserver: OnNotificationPressed | OnNotificationPressedObserver + onNotificationOpened( + nextOrObserver: OnNotificationOpened | OnNotificationOpenedObserver ): () => any { let listener; if (isFunction(nextOrObserver)) { @@ -217,37 +217,37 @@ export default class Notifications extends ModuleBase { listener = nextOrObserver.next; } else { throw new Error( - 'Notifications.onNotificationPressed failed: First argument must be a function or observer object with a `next` function.' + 'Notifications.onNotificationOpened failed: First argument must be a function or observer object with a `next` function.' ); } - getLogger(this).info('Creating onNotificationPressed listener'); - SharedEventEmitter.addListener('onNotificationPressed', listener); + getLogger(this).info('Creating onNotificationOpened listener'); + SharedEventEmitter.addListener('onNotificationOpened', listener); return () => { - getLogger(this).info('Removing onNotificationPressed listener'); - SharedEventEmitter.removeListener('onNotificationPressed', listener); + getLogger(this).info('Removing onNotificationOpened listener'); + SharedEventEmitter.removeListener('onNotificationOpened', listener); }; } /** * Remove all delivered notifications. - * @returns {*} */ - removeAllDeliveredNotifications(): Promise { - return getNativeModule(this).removeAllDeliveredNotifications(); + removeAllDeliveredNotifications(): void { + getNativeModule(this).removeAllDeliveredNotifications(); } /** * Remove a delivered notification. * @param notificationId - * @returns {*} */ - removeDeliveredNotification(notificationId: string): Promise { + removeDeliveredNotification(notificationId: string): void { if (!notificationId) { - return Promise.reject(new Error('Missing notificationId')); + throw new Error( + 'Notifications: removeDeliveredNotification expects a `notificationId`' + ); } - return getNativeModule(this).removeDeliveredNotification(notificationId); + getNativeModule(this).removeDeliveredNotification(notificationId); } /** diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index 22760c16..f939d869 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -161,7 +161,7 @@ export type NativeNotification = {| title: string, |}; -export type NativeNotificationPressed = {| +export type NativeNotificationOpened = {| action: string, notification: NativeNotification, |}; From 7acace4ce68558411b0149ea51d2b29ad5ed74af Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 1 Mar 2018 08:39:24 +0000 Subject: [PATCH 11/13] [notifications][ios] Properly define attachment options --- .../notifications/RNFirebaseNotifications.m | 21 +++++++++++++++++-- lib/modules/notifications/IOSNotification.js | 10 ++++----- lib/modules/notifications/types.js | 16 +++++++------- lib/utils/apps.js | 12 +++++------ 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 1fe8a342..985bf812 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -463,10 +463,27 @@ RCT_EXPORT_METHOD(setBadge: (NSInteger) number) { for (NSDictionary *a in ios[@"attachments"]) { NSString *identifier = a[@"identifier"]; NSURL *url = [NSURL URLWithString:a[@"url"]]; - NSDictionary *options = a[@"options"]; + 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]; + } else if ([key isEqualToString:@"thumbnailHidden"]) { + attachmentOptions[UNNotificationAttachmentOptionsThumbnailHiddenKey] = options[key]; + } else if ([key isEqualToString:@"thumbnailClippingRect"]) { + attachmentOptions[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = options[key]; + } else if ([key isEqualToString:@"thumbnailTime"]) { + attachmentOptions[UNNotificationAttachmentOptionsThumbnailTimeKey] = options[key]; + } + } + } NSError *error; - UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:url options:options error:&error]; + UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:url options:attachmentOptions error:&error]; if (attachment) { [attachments addObject:attachment]; } else { diff --git a/lib/modules/notifications/IOSNotification.js b/lib/modules/notifications/IOSNotification.js index b78e97bd..cd1acaae 100644 --- a/lib/modules/notifications/IOSNotification.js +++ b/lib/modules/notifications/IOSNotification.js @@ -4,14 +4,14 @@ */ import type Notification from './Notification'; import type { - Attachment, - AttachmentOptions, + IOSAttachment, + IOSAttachmentOptions, NativeIOSNotification, } from './types'; export default class IOSNotification { _alertAction: string | void; // alertAction | N/A - _attachments: Attachment[]; // N/A | attachments + _attachments: IOSAttachment[]; // N/A | attachments _badge: number | void; // applicationIconBadgeNumber | badge _category: string | void; _hasAction: boolean | void; // hasAction | N/A @@ -40,7 +40,7 @@ export default class IOSNotification { return this._alertAction; } - get attachments(): Attachment[] { + get attachments(): IOSAttachment[] { return this._attachments; } @@ -74,7 +74,7 @@ export default class IOSNotification { addAttachment( identifier: string, url: string, - options?: AttachmentOptions + options?: IOSAttachmentOptions ): Notification { this._attachments.push({ identifier, diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index f939d869..87d4f806 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -115,27 +115,27 @@ export type NativeAndroidNotification = {| when?: number, |}; -export type AttachmentOptions = {| - TypeHint: string, - ThumbnailHidden: boolean, - ThumbnailClippingRect: { +export type IOSAttachmentOptions = {| + typeHint: string, + thumbnailHidden: boolean, + thumbnailClippingRect: { height: number, width: number, x: number, y: number, }, - ThumbnailTime: number, + thumbnailTime: number, |}; -export type Attachment = {| +export type IOSAttachment = {| identifier: string, - options?: AttachmentOptions, + options?: IOSAttachmentOptions, url: string, |}; export type NativeIOSNotification = {| alertAction?: string, - attachments: Attachment[], + attachments: IOSAttachment[], badge?: number, category?: string, hasAction?: boolean, diff --git a/lib/utils/apps.js b/lib/utils/apps.js index 253265e2..c61d7415 100644 --- a/lib/utils/apps.js +++ b/lib/utils/apps.js @@ -18,7 +18,7 @@ import type { const FirebaseCoreModule = NativeModules.RNFirebase; const APPS: { [string]: App } = {}; -const APP_MODULES: { [App]: { [string]: FirebaseModule } } = {}; +const APP_MODULES: { [string]: { [string]: FirebaseModule } } = {}; const DEFAULT_APP_NAME = '[DEFAULT]'; export default { @@ -49,8 +49,8 @@ export default { InstanceClass: Class ): () => FirebaseModule { return (): M => { - if (!APP_MODULES[app]) { - APP_MODULES[app] = {}; + if (!APP_MODULES[app._name]) { + APP_MODULES[app._name] = {}; } if ( @@ -62,11 +62,11 @@ export default { app.utils().checkPlayServicesAvailability(); } - if (!APP_MODULES[app][namespace]) { - APP_MODULES[app][namespace] = new InstanceClass(app, app.options); + if (!APP_MODULES[app._name][namespace]) { + APP_MODULES[app._name][namespace] = new InstanceClass(app, app.options); } - return APP_MODULES[app][namespace]; + return APP_MODULES[app._name][namespace]; }; }, From b9df2584029fcfd4a54bf2aba27ac84f92e49a7e Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 5 Mar 2018 08:28:13 +0000 Subject: [PATCH 12/13] [notifications] Android channel and channel group support --- .../RNFirebaseLocalMessagingHelper.java | 49 ---- .../messaging/RNFirebaseMessaging.java | 160 +--------- .../messaging/RNFirebaseMessagingPackage.java | 1 - .../messaging/RNFirebaseMessagingService.java | 17 +- .../RNFirebaseNotificationManager.java | 274 ++++++++++++------ .../RNFirebaseNotifications.java | 191 +++++++++++- lib/modules/messaging/RemoteMessage.js | 5 + lib/modules/messaging/types.js | 1 + lib/modules/notifications/AndroidChannel.js | 213 ++++++++++++++ .../notifications/AndroidChannelGroup.js | 57 ++++ .../notifications/AndroidNotifications.js | 94 ++++++ lib/modules/notifications/index.js | 26 +- lib/modules/notifications/types.js | 11 + .../MainApplication.java | 2 + tests/src/firebase.js | 50 +++- 15 files changed, 840 insertions(+), 311 deletions(-) delete mode 100644 android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java create mode 100644 lib/modules/notifications/AndroidChannel.js create mode 100644 lib/modules/notifications/AndroidChannelGroup.js create mode 100644 lib/modules/notifications/AndroidNotifications.js diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java deleted file mode 100644 index 366e1923..00000000 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.invertase.firebase.messaging; - -import android.app.AlarmManager; -import android.app.Application; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.content.SharedPreferences; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.HttpURLConnection; - -public class RNFirebaseLocalMessagingHelper { - private static final long DEFAULT_VIBRATION = 300L; - private static final String TAG = RNFirebaseLocalMessagingHelper.class.getSimpleName(); - private final static String PREFERENCES_KEY = "ReactNativeSystemNotification"; - private static boolean mIsForeground = false; //this is a hack - - private Context mContext; - private SharedPreferences sharedPreferences = null; - - public RNFirebaseLocalMessagingHelper(Application context) { - mContext = context; - sharedPreferences = mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE); - } - - public void setApplicationForeground(boolean foreground){ - mIsForeground = foreground; - } - -} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java index 7376e452..c133d0a5 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java @@ -1,16 +1,12 @@ package io.invertase.firebase.messaging; -import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; -import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; @@ -18,32 +14,20 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.RemoteMessage; -import com.google.firebase.messaging.RemoteMessage.Notification; import io.invertase.firebase.Utils; -import me.leolin.shortcutbadger.ShortcutBadger; import java.util.Map; -public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements ActivityEventListener { - private static final String BADGE_FILE = "BadgeCountFile"; - private static final String BADGE_KEY = "BadgeCount"; - +public class RNFirebaseMessaging extends ReactContextBaseJavaModule { private static final String TAG = "RNFirebaseMessaging"; - private SharedPreferences sharedPreferences = null; - public RNFirebaseMessaging(ReactApplicationContext context) { super(context); - context.addActivityEventListener(this); - - sharedPreferences = context.getSharedPreferences(BADGE_FILE, Context.MODE_PRIVATE); - LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); // Subscribe to message events @@ -73,24 +57,6 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A } // Non Web SDK methods - - @ReactMethod - public void getBadge(Promise promise) { - int badge = sharedPreferences.getInt(BADGE_KEY, 0); - Log.d(TAG, "Got badge count: " + badge); - promise.resolve(badge); - } - - @ReactMethod - public void getInitialMessage(Promise promise) { - if (getCurrentActivity() == null) { - promise.resolve(null); - } else { - WritableMap messageMap = parseIntentForMessage(getCurrentActivity().getIntent()); - promise.resolve(messageMap); - } - } - @ReactMethod public void hasPermission(Promise promise) { promise.resolve(true); @@ -132,19 +98,6 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A promise.resolve(null); } - @ReactMethod - public void setBadge(int badge) { - // Store the badge count for later retrieval - sharedPreferences.edit().putInt(BADGE_KEY, badge).apply(); - if (badge == 0) { - Log.d(TAG, "Remove badge count"); - ShortcutBadger.removeCount(this.getReactApplicationContext()); - } else { - Log.d(TAG, "Apply badge count: " + badge); - ShortcutBadger.applyCount(this.getReactApplicationContext(), badge); - } - } - @ReactMethod public void subscribeToTopic(String topic) { FirebaseMessaging.getInstance().subscribeToTopic(topic); @@ -155,60 +108,6 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); } - ////////////////////////////////////////////////////////////////////// - // Start ActivityEventListener methods - ////////////////////////////////////////////////////////////////////// - @Override - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { - // FCM functionality does not need this function - } - - @Override - public void onNewIntent(Intent intent) { - WritableMap messageMap = parseIntentForMessage(intent); - if (messageMap != null) { - Log.d(TAG, "onNewIntent called with new FCM message"); - Utils.sendEvent(getReactApplicationContext(), "messaging_message_received", messageMap); - } - } - ////////////////////////////////////////////////////////////////////// - // End ActivityEventListener methods - ////////////////////////////////////////////////////////////////////// - - private WritableMap parseIntentForMessage(Intent intent) { - // Check if FCM data exists - if (intent.getExtras() == null || !intent.hasExtra("google.message_id")) { - return null; - } - - Bundle extras = intent.getExtras(); - - WritableMap messageMap = Arguments.createMap(); - WritableMap dataMap = Arguments.createMap(); - - for (String key : extras.keySet()) { - if (key.equals("collapse_key")) { - messageMap.putString("collapseKey", extras.getString("collapse_key")); - } else if (key.equals("from")) { - messageMap.putString("from", extras.getString("from")); - } else if (key.equals("google.message_id")) { - messageMap.putString("messageId", extras.getString("google.message_id")); - } else if (key.equals("google.sent_time")) { - messageMap.putDouble("sentTime", extras.getLong("google.sent_time")); - } else if (key.equals("google.ttl")) { - messageMap.putDouble("ttl", extras.getDouble("google.ttl")); - } else if (key.equals("_fbSourceApplicationHasBeenSet")) { - // ignore known unneeded fields - } else { - dataMap.putString(key, extras.getString(key)); - } - } - messageMap.putMap("data", dataMap); - messageMap.putBoolean("openedFromTray", true); - - return messageMap; - } - private class MessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -246,67 +145,10 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A if (message.getMessageType() != null) { messageMap.putString("messageType", message.getMessageType()); } - - if (message.getNotification() != null) { - Notification notification = message.getNotification(); - - WritableMap notificationMap = Arguments.createMap(); - - if (notification.getBody() != null) { - notificationMap.putString("body", notification.getBody()); - } - if (notification.getBodyLocalizationArgs() != null) { - WritableArray bodyArgs = Arguments.createArray(); - for (String arg : notification.getBodyLocalizationArgs()) { - bodyArgs.pushString(arg); - } - notificationMap.putArray("bodyLocalizationArgs", bodyArgs); - } - if (notification.getBodyLocalizationKey() != null) { - notificationMap.putString("bodyLocalizationKey", notification.getBodyLocalizationKey()); - } - if (notification.getClickAction() != null) { - notificationMap.putString("clickAction", notification.getClickAction()); - } - if (notification.getColor() != null) { - notificationMap.putString("color", notification.getColor()); - } - if (notification.getIcon() != null) { - notificationMap.putString("icon", notification.getIcon()); - } - if (notification.getLink() != null) { - notificationMap.putString("link", notification.getLink().toString()); - } - if (notification.getSound() != null) { - notificationMap.putString("sound", notification.getSound()); - } - if (notification.getTag() != null) { - notificationMap.putString("tag", notification.getTag()); - } - if (notification.getTitle() != null) { - notificationMap.putString("title", notification.getTitle()); - } - if (notification.getTitleLocalizationArgs() != null) { - WritableArray titleArgs = Arguments.createArray(); - for (String arg : notification.getTitleLocalizationArgs()) { - titleArgs.pushString(arg); - } - notificationMap.putArray("titleLocalizationArgs", titleArgs); - } - if (notification.getTitleLocalizationKey() != null) { - notificationMap.putString("titleLocalizationKey", notification.getTitleLocalizationKey()); - } - - messageMap.putMap("notification", notificationMap); - } - - messageMap.putBoolean("openedFromTray", false); messageMap.putDouble("sentTime", message.getSentTime()); - if (message.getTo() != null) { messageMap.putString("to", message.getTo()); } - messageMap.putDouble("ttl", message.getTtl()); return messageMap; diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java index 1a88b657..6457c897 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingPackage.java @@ -1,7 +1,6 @@ package io.invertase.firebase.messaging; import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.UIManagerModule; diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingService.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingService.java index fd350e76..662ed5c5 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingService.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessagingService.java @@ -10,16 +10,25 @@ import com.google.firebase.messaging.RemoteMessage; public class RNFirebaseMessagingService extends FirebaseMessagingService { private static final String TAG = "RNFMessagingService"; public static final String MESSAGE_EVENT = "messaging-message"; + public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification"; @Override public void onMessageReceived(RemoteMessage message) { Log.d(TAG, "onMessageReceived event received"); - // Build an Intent to pass the token to the RN Application - Intent messageEvent = new Intent(MESSAGE_EVENT); - messageEvent.putExtra("message", message); + Intent event; + + if (message.getNotification() != null) { + // It's a notification, pass to the notification module + event = new Intent(REMOTE_NOTIFICATION_EVENT); + event.putExtra("notification", message); + } else { + // It's a data message, pass to the messaging module + event = new Intent(MESSAGE_EVENT); + event.putExtra("message", message); + } // Broadcast it so it is only available to the RN Application - LocalBroadcastManager.getInstance(this).sendBroadcast(messageEvent); + LocalBroadcastManager.getInstance(this).sendBroadcast(event); } } diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index 387727ba..b99be159 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -3,6 +3,8 @@ package io.invertase.firebase.notifications; import android.app.AlarmManager; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -21,6 +23,7 @@ import android.util.Log; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import org.json.JSONException; @@ -30,6 +33,7 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; +import java.util.List; import java.util.Map; import io.invertase.firebase.messaging.BundleJSONConverter; @@ -71,6 +75,42 @@ public class RNFirebaseNotificationManager { preferences.edit().remove(notificationId).apply(); } + public void createChannel(ReadableMap channelMap) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = parseChannelMap(channelMap); + notificationManager.createNotificationChannel(channel); + } + } + + public void createChannelGroup(ReadableMap channelGroupMap) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannelGroup channelGroup = parseChannelGroupMap(channelGroupMap); + notificationManager.createNotificationChannelGroup(channelGroup); + } + } + + public void createChannelGroups(ReadableArray channelGroupsArray) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + List channelGroups = new ArrayList<>(); + for (int i = 0; i < channelGroupsArray.size(); i++) { + NotificationChannelGroup channelGroup = parseChannelGroupMap(channelGroupsArray.getMap(i)); + channelGroups.add(channelGroup); + } + notificationManager.createNotificationChannelGroups(channelGroups); + } + } + + public void createChannels(ReadableArray channelsArray) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + List channels = new ArrayList<>(); + for (int i = 0; i < channelsArray.size(); i++) { + NotificationChannel channel = parseChannelMap(channelsArray.getMap(i)); + channels.add(channel); + } + notificationManager.createNotificationChannels(channels); + } + } + public void displayNotification(ReadableMap notification, Promise promise) { Bundle notificationBundle = Arguments.toBundle(notification); displayNotification(notificationBundle, promise); @@ -155,12 +195,14 @@ public class RNFirebaseNotificationManager { return; } - String channelId = notification.getString("channelId"); + Bundle android = notification.getBundle("android"); + + String channelId = android.getString("channelId"); String notificationId = notification.getString("notificationId"); NotificationCompat.Builder nb; // TODO: Change 27 to 'Build.VERSION_CODES.O_MR1' when using appsupport v27 - if (Build.VERSION.SDK_INT >= 27) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { nb = new NotificationCompat.Builder(context, channelId); } else { nb = new NotificationCompat.Builder(context); @@ -173,16 +215,8 @@ public class RNFirebaseNotificationManager { nb = nb.setExtras(notification.getBundle("data")); } if (notification.containsKey("sound")) { - String sound = notification.getString("sound"); - if (sound.equalsIgnoreCase("default")) { - nb = nb.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); - } else { - int soundResourceId = getResourceId("raw", sound); - if (soundResourceId == 0) { - soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.'))); - } - nb = nb.setSound(Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId)); - } + Uri sound = getSound(notification.getString("sound")); + nb = nb.setSound(sound); } if (notification.containsKey("subtitle")) { nb = nb.setSubText(notification.getString("subtitle")); @@ -191,125 +225,138 @@ public class RNFirebaseNotificationManager { nb = nb.setContentTitle(notification.getString("title")); } - if (notification.containsKey("autoCancel")) { - nb = nb.setAutoCancel(notification.getBoolean("autoCancel")); + if (android.containsKey("autoCancel")) { + nb = nb.setAutoCancel(android.getBoolean("autoCancel")); } - if (notification.containsKey("badgeIconType")) { - nb = nb.setBadgeIconType(notification.getInt("badgeIconType")); + if (android.containsKey("badgeIconType")) { + Double badgeIconType = android.getDouble("badgeIconType"); + nb = nb.setBadgeIconType(badgeIconType.intValue()); } - if (notification.containsKey("category")) { - nb = nb.setCategory(notification.getString("category")); + if (android.containsKey("category")) { + nb = nb.setCategory(android.getString("category")); } - if (notification.containsKey("color")) { - String color = notification.getString("color"); + if (android.containsKey("color")) { + String color = android.getString("color"); nb = nb.setColor(Color.parseColor(color)); } - if (notification.containsKey("colorized")) { - nb = nb.setColorized(notification.getBoolean("colorized")); + if (android.containsKey("colorized")) { + nb = nb.setColorized(android.getBoolean("colorized")); } - if (notification.containsKey("contentInfo")) { - nb = nb.setContentInfo(notification.getString("contentInfo")); + if (android.containsKey("contentInfo")) { + nb = nb.setContentInfo(android.getString("contentInfo")); } if (notification.containsKey("defaults")) { - int[] defaultsArray = notification.getIntArray("defaults"); + double[] defaultsArray = android.getDoubleArray("defaults"); int defaults = 0; - for (int d : defaultsArray) { - defaults |= d; + for (Double d : defaultsArray) { + defaults |= d.intValue(); } nb = nb.setDefaults(defaults); } - if (notification.containsKey("group")) { - nb = nb.setGroup(notification.getString("group")); + if (android.containsKey("group")) { + nb = nb.setGroup(android.getString("group")); } - if (notification.containsKey("groupAlertBehaviour")) { - nb = nb.setGroupAlertBehavior(notification.getInt("groupAlertBehaviour")); + if (android.containsKey("groupAlertBehaviour")) { + Double groupAlertBehaviour = android.getDouble("groupAlertBehaviour"); + nb = nb.setGroupAlertBehavior(groupAlertBehaviour.intValue()); } - if (notification.containsKey("groupSummary")) { - nb = nb.setGroupSummary(notification.getBoolean("groupSummary")); + if (android.containsKey("groupSummary")) { + nb = nb.setGroupSummary(android.getBoolean("groupSummary")); } - if (notification.containsKey("largeIcon")) { - Bitmap largeIcon = getBitmap(notification.getString("largeIcon")); + if (android.containsKey("largeIcon")) { + Bitmap largeIcon = getBitmap(android.getString("largeIcon")); if (largeIcon != null) { nb = nb.setLargeIcon(largeIcon); } } - if (notification.containsKey("lights")) { - Bundle lights = notification.getBundle("lights"); - nb = nb.setLights(lights.getInt("argb"), lights.getInt("onMs"), lights.getInt("offMs")); + if (android.containsKey("lights")) { + Bundle lights = android.getBundle("lights"); + Double argb = lights.getDouble("argb"); + Double onMs = lights.getDouble("onMs"); + Double offMs = lights.getDouble("offMs"); + nb = nb.setLights(argb.intValue(), onMs.intValue(), offMs.intValue()); } - if (notification.containsKey("localOnly")) { - nb = nb.setLocalOnly(notification.getBoolean("localOnly")); + if (android.containsKey("localOnly")) { + nb = nb.setLocalOnly(android.getBoolean("localOnly")); } - if (notification.containsKey("number")) { - nb = nb.setNumber(notification.getInt("number")); + if (android.containsKey("number")) { + Double number = android.getDouble("number"); + nb = nb.setNumber(number.intValue()); } - if (notification.containsKey("ongoing")) { - nb = nb.setOngoing(notification.getBoolean("ongoing")); + if (android.containsKey("ongoing")) { + nb = nb.setOngoing(android.getBoolean("ongoing")); } - if (notification.containsKey("onlyAlertOnce")) { - nb = nb.setOngoing(notification.getBoolean("onlyAlertOnce")); + if (android.containsKey("onlyAlertOnce")) { + nb = nb.setOngoing(android.getBoolean("onlyAlertOnce")); } - if (notification.containsKey("people")) { - String[] people = notification.getStringArray("people"); - for (String person : people) { - nb = nb.addPerson(person); + if (android.containsKey("people")) { + String[] people = android.getStringArray("people"); + if (people != null) { + for (String person : people) { + nb = nb.addPerson(person); + } } } - if (notification.containsKey("priority")) { - nb = nb.setPriority(notification.getInt("priority")); + if (android.containsKey("priority")) { + Double priority = android.getDouble("priority"); + nb = nb.setPriority(priority.intValue()); } - if (notification.containsKey("progress")) { - Bundle progress = notification.getBundle("lights"); - nb = nb.setProgress(progress.getInt("max"), progress.getInt("progress"), progress.getBoolean("indeterminate")); + if (android.containsKey("progress")) { + Bundle progress = android.getBundle("lights"); + Double max = progress.getDouble("max"); + Double progressI = progress.getDouble("progress"); + nb = nb.setProgress(max.intValue(), progressI.intValue(), progress.getBoolean("indeterminate")); } // TODO: Public version of notification - /* if (notification.containsKey("publicVersion")) { + /* if (android.containsKey("publicVersion")) { nb = nb.setPublicVersion(); } */ - if (notification.containsKey("remoteInputHistory")) { - nb = nb.setRemoteInputHistory(notification.getStringArray("remoteInputHistory")); + if (android.containsKey("remoteInputHistory")) { + nb = nb.setRemoteInputHistory(android.getStringArray("remoteInputHistory")); } - if (notification.containsKey("shortcutId")) { - nb = nb.setShortcutId(notification.getString("shortcutId")); + if (android.containsKey("shortcutId")) { + nb = nb.setShortcutId(android.getString("shortcutId")); } - if (notification.containsKey("showWhen")) { - nb = nb.setShowWhen(notification.getBoolean("showWhen")); + if (android.containsKey("showWhen")) { + nb = nb.setShowWhen(android.getBoolean("showWhen")); } - if (notification.containsKey("smallIcon")) { - Bundle smallIcon = notification.getBundle("smallIcon"); + if (android.containsKey("smallIcon")) { + Bundle smallIcon = android.getBundle("smallIcon"); int smallIconResourceId = getResourceId("mipmap", smallIcon.getString("icon")); if (smallIconResourceId == 0) { smallIconResourceId = getResourceId("drawable", smallIcon.getString("icon")); } if (smallIconResourceId != 0) { if (smallIcon.containsKey("level")) { - nb = nb.setSmallIcon(smallIconResourceId, smallIcon.getInt("level")); + Double level = smallIcon.getDouble("level"); + nb = nb.setSmallIcon(smallIconResourceId, level.intValue()); } else { nb = nb.setSmallIcon(smallIconResourceId); } } } - if (notification.containsKey("sortKey")) { - nb = nb.setSortKey(notification.getString("sortKey")); + if (android.containsKey("sortKey")) { + nb = nb.setSortKey(android.getString("sortKey")); } - if (notification.containsKey("ticker")) { - nb = nb.setTicker(notification.getString("ticker")); + if (android.containsKey("ticker")) { + nb = nb.setTicker(android.getString("ticker")); } - if (notification.containsKey("timeoutAfter")) { - nb = nb.setTimeoutAfter(notification.getLong("timeoutAfter")); + if (android.containsKey("timeoutAfter")) { + nb = nb.setTimeoutAfter(android.getLong("timeoutAfter")); } - if (notification.containsKey("usesChronometer")) { - nb = nb.setUsesChronometer(notification.getBoolean("usesChronometer")); + if (android.containsKey("usesChronometer")) { + nb = nb.setUsesChronometer(android.getBoolean("usesChronometer")); } - if (notification.containsKey("vibrate")) { - nb = nb.setVibrate(notification.getLongArray("vibrate")); + if (android.containsKey("vibrate")) { + nb = nb.setVibrate(android.getLongArray("vibrate")); } - if (notification.containsKey("visibility")) { - nb = nb.setVisibility(notification.getInt("visibility")); + if (android.containsKey("visibility")) { + Double visibility = android.getDouble("visibility"); + nb = nb.setVisibility(visibility.intValue()); } - if (notification.containsKey("when")) { - nb = nb.setWhen(notification.getLong("when")); + if (android.containsKey("when")) { + nb = nb.setWhen(android.getLong("when")); } // TODO: Big text / Big picture @@ -335,8 +382,8 @@ public class RNFirebaseNotificationManager { Intent intent = new Intent(context, intentClass); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtras(notification); - if (notification.containsKey("clickAction")) { - intent.setAction(notification.getString("clickAction")); + if (android.containsKey("clickAction")) { + intent.setAction(android.getString("clickAction")); } PendingIntent contentIntent = PendingIntent.getActivity(context, notificationId.hashCode(), intent, @@ -391,6 +438,71 @@ public class RNFirebaseNotificationManager { return context.getResources().getIdentifier(image, type, context.getPackageName()); } + private Uri getSound(String sound) { + if (sound.equalsIgnoreCase("default")) { + return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + } else { + int soundResourceId = getResourceId("raw", sound); + if (soundResourceId == 0) { + soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.'))); + } + return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId); + } + } + + private NotificationChannelGroup parseChannelGroupMap(ReadableMap channelGroupMap) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String groupId = channelGroupMap.getString("groupId"); + String name = channelGroupMap.getString("name"); + + return new NotificationChannelGroup(groupId, name); + } + return null; + } + + private NotificationChannel parseChannelMap(ReadableMap channelMap) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String channelId = channelMap.getString("channelId"); + String name = channelMap.getString("name"); + int importance = channelMap.getInt("importance"); + + NotificationChannel channel = new NotificationChannel(channelId, name, importance); + if (channelMap.hasKey("bypassDnd")) { + channel.setBypassDnd(channelMap.getBoolean("bypassDnd")); + } + if (channelMap.hasKey("description")) { + channel.setDescription(channelMap.getString("description")); + } + if (channelMap.hasKey("group")) { + channel.setGroup(channelMap.getString("group")); + } + if (channelMap.hasKey("lightColor")) { + String lightColor = channelMap.getString("lightColor"); + channel.setLightColor(Color.parseColor(lightColor)); + } + if (channelMap.hasKey("lockScreenVisibility")) { + channel.setLockscreenVisibility(channelMap.getInt("lockScreenVisibility")); + } + if (channelMap.hasKey("showBadge")) { + channel.setShowBadge(channelMap.getBoolean("showBadge")); + } + if (channelMap.hasKey("sound")) { + Uri sound = getSound(channelMap.getString("sound")); + channel.setSound(sound, null); + } + if (channelMap.hasKey("vibrationPattern")) { + ReadableArray vibrationArray = channelMap.getArray("vibrationPattern"); + long[] vibration = new long[]{}; + for (int i = 0; i < vibrationArray.size(); i++) { + vibration[i] = (long) vibrationArray.getDouble(i); + } + channel.setVibrationPattern(vibration); + } + return channel; + } + return null; + } + private void scheduleNotification(Bundle notification, Promise promise) { if (!notification.containsKey("notificationId")) { if (promise == null) { diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 7e20f365..dad5af46 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -1,38 +1,57 @@ package io.invertase.firebase.notifications; +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; +import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.google.firebase.messaging.RemoteMessage; import java.util.ArrayList; +import java.util.Map; import io.invertase.firebase.Utils; +import io.invertase.firebase.messaging.RNFirebaseMessagingService; +import me.leolin.shortcutbadger.ShortcutBadger; -public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements LifecycleEventListener { +public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener { + private static final String BADGE_FILE = "BadgeCountFile"; + private static final String BADGE_KEY = "BadgeCount"; private static final String TAG = "RNFirebaseNotifications"; + private SharedPreferences sharedPreferences = null; + private RNFirebaseNotificationManager notificationManager; public RNFirebaseNotifications(ReactApplicationContext context) { super(context); - notificationManager = new RNFirebaseNotificationManager(context.getApplicationContext()); + context.addActivityEventListener(this); context.addLifecycleEventListener(this); + notificationManager = new RNFirebaseNotificationManager(context.getApplicationContext()); + sharedPreferences = context.getSharedPreferences(BADGE_FILE, Context.MODE_PRIVATE); + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + // Subscribe to remote notification events + localBroadcastManager.registerReceiver(new RemoteNotificationReceiver(), + new IntentFilter(RNFirebaseMessagingService.REMOTE_NOTIFICATION_EVENT)); + // Subscribe to scheduled notification events localBroadcastManager.registerReceiver(new ScheduledNotificationReceiver(), new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT)); @@ -58,9 +77,22 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen notificationManager.displayNotification(notification, promise); } + @ReactMethod + public void getBadge(Promise promise) { + int badge = sharedPreferences.getInt(BADGE_KEY, 0); + Log.d(TAG, "Got badge count: " + badge); + promise.resolve(badge); + } + @ReactMethod public void getInitialNotification(Promise promise) { // TODO + if (getCurrentActivity() == null) { + promise.resolve(null); + } else { + WritableMap notificationOpenedMap = parseIntentForRemoteNotification(getCurrentActivity().getIntent()); + promise.resolve(notificationOpenedMap); + } } @ReactMethod @@ -83,11 +115,74 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen notificationManager.removeDeliveredNotification(notificationId); } + @ReactMethod + public void setBadge(int badge) { + // Store the badge count for later retrieval + sharedPreferences.edit().putInt(BADGE_KEY, badge).apply(); + if (badge == 0) { + Log.d(TAG, "Remove badge count"); + ShortcutBadger.removeCount(this.getReactApplicationContext()); + } else { + Log.d(TAG, "Apply badge count: " + badge); + ShortcutBadger.applyCount(this.getReactApplicationContext(), badge); + } + } + @ReactMethod public void scheduleNotification(ReadableMap notification, Promise promise) { notificationManager.scheduleNotification(notification, promise); } + ////////////////////////////////////////////////////////////////////// + // Start Android specific methods + ////////////////////////////////////////////////////////////////////// + @ReactMethod + public void createChannel(ReadableMap channelMap, Promise promise) { + notificationManager.createChannel(channelMap); + promise.resolve(null); + } + + @ReactMethod + public void createChannelGroup(ReadableMap channelGroupMap, Promise promise) { + notificationManager.createChannelGroup(channelGroupMap); + promise.resolve(null); + } + + @ReactMethod + public void createChannelGroup(ReadableArray channelGroupsArray, Promise promise) { + notificationManager.createChannelGroups(channelGroupsArray); + promise.resolve(null); + } + + @ReactMethod + public void createChannels(ReadableArray channelsArray, Promise promise) { + notificationManager.createChannels(channelsArray); + promise.resolve(null); + } + ////////////////////////////////////////////////////////////////////// + // End Android specific methods + ////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + // Start ActivityEventListener methods + ////////////////////////////////////////////////////////////////////// + @Override + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + // FCM functionality does not need this function + } + + @Override + public void onNewIntent(Intent intent) { + WritableMap notificationOpenedMap = parseIntentForRemoteNotification(intent); + if (notificationOpenedMap != null) { + Log.d(TAG, "onNewIntent called with new remote notification"); + Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenedMap); + } + } + ////////////////////////////////////////////////////////////////////// + // End ActivityEventListener methods + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// // Start LifecycleEventListener methods ////////////////////////////////////////////////////////////////////// @@ -109,10 +204,102 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen // End LifecycleEventListener methods ////////////////////////////////////////////////////////////////////// + + private WritableMap parseIntentForRemoteNotification(Intent intent) { + // Check if FCM data exists + if (intent.getExtras() == null || !intent.hasExtra("google.message_id")) { + return null; + } + + Bundle extras = intent.getExtras(); + + WritableMap notificationMap = Arguments.createMap(); + WritableMap dataMap = Arguments.createMap(); + + for (String key : extras.keySet()) { + if (key.equals("google.message_id")) { + notificationMap.putString("notificationId", extras.getString(key)); + } else if (key.equals("collapse_key") + || key.equals("from") + || key.equals("google.sent_time") + || key.equals("google.ttl") + || key.equals("_fbSourceApplicationHasBeenSet")) { + // ignore known unneeded fields + } else { + dataMap.putString(key, extras.getString(key)); + } + } + notificationMap.putMap("data", dataMap); + + WritableMap notificationOpenedMap = Arguments.createMap(); + notificationOpenedMap.putString("action", intent.getAction()); + notificationOpenedMap.putMap("notification", notificationMap); + + return notificationOpenedMap; + } + private WritableMap parseNotificationBundle(Bundle notification) { return Arguments.makeNativeMap(notification); } + private WritableMap parseRemoteMessage(RemoteMessage message) { + RemoteMessage.Notification notification = message.getNotification(); + + WritableMap notificationMap = Arguments.createMap(); + WritableMap dataMap = Arguments.createMap(); + + // Cross platform notification properties + notificationMap.putString("body", notification.getBody()); + if (message.getData() != null) { + for (Map.Entry e : message.getData().entrySet()) { + dataMap.putString(e.getKey(), e.getValue()); + } + } + notificationMap.putMap("data", dataMap); + if (message.getMessageId() != null) { + notificationMap.putString("notificationId", message.getMessageId()); + } + if (notification.getSound() != null) { + notificationMap.putString("sound", notification.getSound()); + } + if (notification.getTitle() != null) { + notificationMap.putString("title", notification.getTitle()); + } + + // Android specific notification properties + WritableMap androidMap = Arguments.createMap(); + if (notification.getClickAction() != null) { + androidMap.putString("clickAction", notification.getClickAction()); + } + if (notification.getColor() != null) { + androidMap.putString("color", notification.getColor()); + } + if (notification.getIcon() != null) { + WritableMap iconMap = Arguments.createMap(); + iconMap.putString("icon", notification.getIcon()); + androidMap.putMap("smallIcon", iconMap); + } + if (notification.getTag() != null) { + androidMap.putString("group", notification.getTag()); + } + + return notificationMap; + } + + private class RemoteNotificationReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (getReactApplicationContext().hasActiveCatalystInstance()) { + Log.d(TAG, "Received new remote notification"); + + RemoteMessage message = intent.getParcelableExtra("notification"); + WritableMap messageMap = parseRemoteMessage(message); + + Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap); + } + } + } + private class ScheduledNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/lib/modules/messaging/RemoteMessage.js b/lib/modules/messaging/RemoteMessage.js index 2ed54228..a518c875 100644 --- a/lib/modules/messaging/RemoteMessage.js +++ b/lib/modules/messaging/RemoteMessage.js @@ -25,6 +25,7 @@ export default class RemoteMessage { this._data = inboundMessage.data; this._from = inboundMessage.from; this._messageId = inboundMessage.messageId; + this._messageType = inboundMessage.messageType; this._sentTime = inboundMessage.sentTime; } // defaults @@ -50,6 +51,10 @@ export default class RemoteMessage { return this._messageId; } + get messageType(): ?string { + return this._messageType; + } + get sentTime(): ?number { return this._sentTime; } diff --git a/lib/modules/messaging/types.js b/lib/modules/messaging/types.js index 1d187e30..e2cbe647 100644 --- a/lib/modules/messaging/types.js +++ b/lib/modules/messaging/types.js @@ -22,6 +22,7 @@ export type NativeInboundRemoteMessage = { data: { [string]: string }, from?: string, messageId: string, + messageType?: string, sentTime?: number, to?: string, ttl?: number, diff --git a/lib/modules/notifications/AndroidChannel.js b/lib/modules/notifications/AndroidChannel.js new file mode 100644 index 00000000..5e3f6cf0 --- /dev/null +++ b/lib/modules/notifications/AndroidChannel.js @@ -0,0 +1,213 @@ +/** + * @flow + * AndroidChannel representation wrapper + */ +import type { ImportanceType, VisibilityType } from './types'; + +type NativeAndroidChannel = {| + bypassDnd?: boolean, + channelId: string, + description?: string, + group?: string, + importance: ImportanceType, + lightColor?: string, + lockScreenVisibility?: VisibilityType, + name: string, + showBadge?: boolean, + sound?: string, + vibrationPattern?: number[], +|}; + +export default class AndroidChannel { + _bypassDnd: boolean | void; + _channelId: string; + _description: string | void; + _group: string | void; + _importance: ImportanceType; + _lightColor: string | void; + _lockScreenVisibility: VisibilityType; + _name: string; + _showBadge: boolean | void; + _sound: string | void; + _vibrationPattern: number[] | void; + + get bypassDnd(): ?boolean { + return this._bypassDnd; + } + + get channelId(): string { + return this._channelId; + } + + get description(): ?string { + return this._description; + } + + get group(): ?string { + return this._group; + } + + get importance(): ImportanceType { + return this._importance; + } + + get lightColor(): ?string { + return this._lightColor; + } + + get lockScreenVisibility(): ?VisibilityType { + return this._lockScreenVisibility; + } + + get name(): string { + return this._name; + } + + get showBadge(): ?boolean { + return this._showBadge; + } + + get sound(): ?string { + return this._sound; + } + + get vibrationPattern(): ?(number[]) { + return this._vibrationPattern; + } + + /** + * + * @param bypassDnd + * @returns {AndroidChannel} + */ + setBypassDnd(bypassDnd: boolean): AndroidChannel { + this._bypassDnd = bypassDnd; + return this; + } + + /** + * + * @param channelId + * @returns {AndroidChannel} + */ + setChannelId(channelId: string): AndroidChannel { + this._channelId = channelId; + return this; + } + + /** + * + * @param description + * @returns {AndroidChannel} + */ + setDescription(description: string): AndroidChannel { + this._description = description; + return this; + } + + /** + * + * @param group + * @returns {AndroidChannel} + */ + setGroup(groupId: string): AndroidChannel { + this._group = groupId; + return this; + } + + /** + * + * @param importance + * @returns {AndroidChannel} + */ + setImportance(importance: ImportanceType): AndroidChannel { + this._importance = importance; + return this; + } + + /** + * + * @param lightColor + * @returns {AndroidChannel} + */ + setLightColor(lightColor: string): AndroidChannel { + this._lightColor = lightColor; + return this; + } + + /** + * + * @param lockScreenVisibility + * @returns {AndroidChannel} + */ + setLockScreenVisibility( + lockScreenVisibility: VisibilityType + ): AndroidChannel { + this._lockScreenVisibility = lockScreenVisibility; + return this; + } + + /** + * + * @param name + * @returns {AndroidChannel} + */ + setName(name: string): AndroidChannel { + this._name = name; + return this; + } + + /** + * + * @param showBadge + * @returns {AndroidChannel} + */ + setShowBadge(showBadge: boolean): AndroidChannel { + this._showBadge = showBadge; + return this; + } + + /** + * + * @param sound + * @returns {AndroidChannel} + */ + setSound(sound: string): AndroidChannel { + this._sound = sound; + return this; + } + + /** + * + * @param vibrationPattern + * @returns {AndroidChannel} + */ + setVibrationPattern(vibrationPattern: number[]): AndroidChannel { + this._vibrationPattern = vibrationPattern; + return this; + } + + build(): NativeAndroidChannel { + if (!this._channelId) { + throw new Error('AndroidChannel: Missing required `channelId` property'); + } else if (!this._importance) { + throw new Error('AndroidChannel: Missing required `importance` property'); + } else if (!this._name) { + throw new Error('AndroidChannel: Missing required `name` property'); + } + + return { + bypassDnd: this._bypassDnd, + channelId: this._channelId, + description: this._description, + group: this._group, + importance: this._importance, + lightColor: this._lightColor, + lockScreenVisibility: this._lockScreenVisibility, + name: this._name, + showBadge: this._showBadge, + sound: this._sound, + vibrationPattern: this._vibrationPattern, + }; + } +} diff --git a/lib/modules/notifications/AndroidChannelGroup.js b/lib/modules/notifications/AndroidChannelGroup.js new file mode 100644 index 00000000..fb37a527 --- /dev/null +++ b/lib/modules/notifications/AndroidChannelGroup.js @@ -0,0 +1,57 @@ +/** + * @flow + * AndroidChannelGroup representation wrapper + */ + +type NativeAndroidChannelGroup = {| + groupId: string, + name: string, +|}; + +export default class AndroidChannel { + _groupId: string; + _name: string; + + get groupId(): string { + return this._groupId; + } + + get name(): string { + return this._name; + } + + /** + * + * @param groupId + * @returns {AndroidChannel} + */ + setGroupId(groupId: string): AndroidChannel { + this._groupId = groupId; + return this; + } + + /** + * + * @param name + * @returns {AndroidChannel} + */ + setName(name: string): AndroidChannel { + this._name = name; + return this; + } + + build(): NativeAndroidChannelGroup { + if (!this._groupId) { + throw new Error( + 'AndroidChannelGroup: Missing required `groupId` property' + ); + } else if (!this._name) { + throw new Error('AndroidChannelGroup: Missing required `name` property'); + } + + return { + groupId: this._groupId, + name: this._name, + }; + } +} diff --git a/lib/modules/notifications/AndroidNotifications.js b/lib/modules/notifications/AndroidNotifications.js new file mode 100644 index 00000000..bd644a60 --- /dev/null +++ b/lib/modules/notifications/AndroidNotifications.js @@ -0,0 +1,94 @@ +/** + * @flow + * AndroidNotifications representation wrapper + */ +import { Platform } from 'react-native'; +import AndroidChannel from './AndroidChannel'; +import AndroidChannelGroup from './AndroidChannelGroup'; +import { getNativeModule } from '../../utils/native'; + +import type Notifications from './'; + +export default class AndroidNotifications { + _notifications: Notifications; + + constructor(notifications: Notifications) { + this._notifications = notifications; + } + + createChannel(channel: AndroidChannel): Promise { + if (Platform.OS === 'android') { + if (!(channel instanceof AndroidChannel)) { + throw new Error( + `AndroidNotifications:createChannel expects an 'AndroidChannel' but got type ${typeof channel}` + ); + } + return getNativeModule(this._notifications).createChannel( + channel.build() + ); + } + return Promise.resolve(); + } + + createChannelGroup(channelGroup: AndroidChannelGroup): Promise { + if (Platform.OS === 'android') { + if (!(channelGroup instanceof AndroidChannelGroup)) { + throw new Error( + `AndroidNotifications:createChannelGroup expects an 'AndroidChannelGroup' but got type ${typeof channelGroup}` + ); + } + return getNativeModule(this._notifications).createChannelGroup( + channelGroup.build() + ); + } + return Promise.resolve(); + } + + createChannelGroups(channelGroups: AndroidChannelGroup[]): Promise { + if (Platform.OS === 'android') { + if (!Array.isArray(channelGroups)) { + throw new Error( + `AndroidNotifications:createChannelGroups expects an 'Array' but got type ${typeof channelGroups}` + ); + } + const nativeChannelGroups = []; + for (let i = 0; i < channelGroups.length; i++) { + const channelGroup = channelGroups[i]; + if (!(channelGroup instanceof AndroidChannelGroup)) { + throw new Error( + `AndroidNotifications:createChannelGroups expects array items of type 'AndroidChannelGroup' but got type ${typeof channelGroup}` + ); + } + nativeChannelGroups.push(channelGroup.build()); + } + return getNativeModule(this._notifications).createChannelGroups( + nativeChannelGroups + ); + } + return Promise.resolve(); + } + + createChannels(channels: AndroidChannel[]): Promise { + if (Platform.OS === 'android') { + if (!Array.isArray(channels)) { + throw new Error( + `AndroidNotifications:createChannels expects an 'Array' but got type ${typeof channels}` + ); + } + const nativeChannels = []; + for (let i = 0; i < channels.length; i++) { + const channel = channels[i]; + if (!(channel instanceof AndroidChannel)) { + throw new Error( + `AndroidNotifications:createChannels expects array items of type 'AndroidChannel' but got type ${typeof channel}` + ); + } + nativeChannels.push(channel.build()); + } + return getNativeModule(this._notifications).createChannels( + nativeChannels + ); + } + return Promise.resolve(); + } +} diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index f116d4f6..649831a9 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -1,18 +1,22 @@ /** * @flow - * Messaging (FCM) representation wrapper + * Notifications representation wrapper */ import { SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; import ModuleBase from '../../utils/ModuleBase'; import { getNativeModule } from '../../utils/native'; import { isFunction, isObject } from '../../utils'; +import AndroidChannel from './AndroidChannel'; +import AndroidChannelGroup from './AndroidChannelGroup'; +import AndroidNotifications from './AndroidNotifications'; import Notification from './Notification'; import { BadgeIconType, Category, Defaults, GroupAlert, + Importance, Priority, Visibility, } from './types'; @@ -64,6 +68,8 @@ export const NAMESPACE = 'notifications'; * @class Notifications */ export default class Notifications extends ModuleBase { + _android: AndroidNotifications; + constructor(app: App) { super(app, { events: NATIVE_EVENTS, @@ -71,6 +77,7 @@ export default class Notifications extends ModuleBase { multiApp: false, namespace: NAMESPACE, }); + this._android = new AndroidNotifications(this); SharedEventEmitter.addListener( // sub to internal native event - this fans out to @@ -89,7 +96,7 @@ export default class Notifications extends ModuleBase { // public event name: onNotificationOpened 'notifications_notification_opened', (notificationOpened: NativeNotificationOpened) => { - SharedEventEmitter.emit('OnNotificationOpened', { + SharedEventEmitter.emit('onNotificationOpened', { action: notificationOpened.action, notification: new Notification(notificationOpened.notification), }); @@ -109,6 +116,10 @@ export default class Notifications extends ModuleBase { ); } + get android(): AndroidNotifications { + return this._android; + } + /** * Cancel all notifications */ @@ -148,9 +159,11 @@ export default class Notifications extends ModuleBase { } getInitialNotification(): Promise { - return getNativeModule(this).getInitialNotification(); - // TODO - // .then(notification => (notification ? new Notification(this, notification) : null)); + return getNativeModule(this) + .getInitialNotification() + .then( + notification => (notification ? new Notification(notification) : null) + ); } /** @@ -278,8 +291,11 @@ export const statics = { Android: { BadgeIconType, Category, + Channel: AndroidChannel, + ChannelGroup: AndroidChannelGroup, Defaults, GroupAlert, + Importance, Priority, Visibility, }, diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index 87d4f806..a9c9f107 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -39,6 +39,16 @@ export const GroupAlert = { Summary: 1, }; +export const Importance = { + Default: 3, + High: 4, + Low: 2, + Max: 5, + Min: 1, + None: 3, + Unspecified: -1000, +}; + export const Priority = { Default: 0, High: 1, @@ -57,6 +67,7 @@ export type BadgeIconTypeType = $Values; export type CategoryType = $Values; export type DefaultsType = $Values; export type GroupAlertType = $Values; +export type ImportanceType = $Values; export type PriorityType = $Values; export type VisibilityType = $Values; diff --git a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java index ae9de9cd..1c99a0a7 100644 --- a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java +++ b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java @@ -15,6 +15,7 @@ import io.invertase.firebase.firestore.RNFirebaseFirestorePackage; import io.invertase.firebase.instanceid.RNFirebaseInstanceIdPackage; import io.invertase.firebase.links.RNFirebaseLinksPackage; import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; +import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage; import io.invertase.firebase.perf.RNFirebasePerformancePackage; import io.invertase.firebase.storage.RNFirebaseStoragePackage; import com.oblador.vectoricons.VectorIconsPackage; @@ -51,6 +52,7 @@ public class MainApplication extends Application implements ReactApplication { new RNFirebaseInstanceIdPackage(), new RNFirebaseLinksPackage(), new RNFirebaseMessagingPackage(), + new RNFirebaseNotificationsPackage(), new RNFirebasePerformancePackage(), new RNFirebaseStoragePackage() ); diff --git a/tests/src/firebase.js b/tests/src/firebase.js index fcdad95f..5309ab69 100644 --- a/tests/src/firebase.js +++ b/tests/src/firebase.js @@ -7,14 +7,6 @@ import DatabaseContents from './tests/support/DatabaseContents'; RNfirebase.database.enableLogging(true); RNfirebase.firestore.enableLogging(true); -RNfirebase.messaging().onMessage(message => { - console.log('got new message: ', message); -}); - -RNfirebase.messaging().onTokenRefresh(token => { - console.log('got new token: ', token); -}); - const init = async () => { try { await RNfirebase.messaging().requestPermission(); @@ -22,9 +14,47 @@ const init = async () => { console.log('instanceid: ', instanceid); const token = await RNfirebase.messaging().getToken(); console.log('token: ', token); - const initialMessage = await RNfirebase.messaging().getInitialMessage(); - console.log('initial message: ', initialMessage); + const initialNotification = await RNfirebase.notifications().getInitialNotification(); + console.log('initialNotification: ', initialNotification); + + RNfirebase.messaging().onMessage(message => { + console.log('onMessage: ', message); + }); + RNfirebase.messaging().onTokenRefresh(deviceToken => { + dispatch(fcmTokenReceived(deviceToken)); + }); + RNfirebase.notifications().onNotification(notification => { + console.log('onNotification: ', notification); + }); + RNfirebase.notifications().onNotificationOpened(notification => { + console.log('onNotificationOpened: ', notification); + }); + RNfirebase.notifications().onNotificationDisplayed(notification => { + console.log('onNotificationDisplayed: ', notification); + }); // RNfirebase.instanceid().delete(); + const channel = new RNfirebase.notifications.Android.Channel(); + channel + .setChannelId('test') + .setName('test') + .setImportance(RNfirebase.notifications.Android.Importance.Max) + .setDescription('test channel'); + RNfirebase.notifications().android.createChannel(channel); + + const notification = new RNfirebase.notifications.Notification(); + notification + .setTitle('Test title') + .setBody('Test body') + .android.setChannelId('test') + .android.setPriority(RNfirebase.notifications.Android.Priority.Max); + const date = new Date(); + date.setMinutes(date.getMinutes() + 1); + setTimeout(() => { + RNfirebase.notifications().displayNotification(notification); + }, 5); + RNfirebase.notifications().scheduleNotification(notification, { + fireDate: date.getTime(), + }); } catch (error) { console.error('messaging init error:', error); } From 7b9269fec5c18714a09d3e4430366670d21375df Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 6 Mar 2018 18:20:49 +0000 Subject: [PATCH 13/13] [notifications] Rename `onNotificationOpened` to `onNotificationOpen` --- .../notifications/RNFirebaseNotifications.java | 18 +++++++++--------- lib/modules/notifications/Notification.js | 2 +- lib/modules/notifications/index.js | 14 +++++++------- lib/modules/notifications/types.js | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index dad5af46..2e9de173 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -90,8 +90,8 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen if (getCurrentActivity() == null) { promise.resolve(null); } else { - WritableMap notificationOpenedMap = parseIntentForRemoteNotification(getCurrentActivity().getIntent()); - promise.resolve(notificationOpenedMap); + WritableMap notificationOpenMap = parseIntentForRemoteNotification(getCurrentActivity().getIntent()); + promise.resolve(notificationOpenMap); } } @@ -173,10 +173,10 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen @Override public void onNewIntent(Intent intent) { - WritableMap notificationOpenedMap = parseIntentForRemoteNotification(intent); - if (notificationOpenedMap != null) { + WritableMap notificationOpenMap = parseIntentForRemoteNotification(intent); + if (notificationOpenMap != null) { Log.d(TAG, "onNewIntent called with new remote notification"); - Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenedMap); + Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenMap); } } ////////////////////////////////////////////////////////////////////// @@ -231,11 +231,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen } notificationMap.putMap("data", dataMap); - WritableMap notificationOpenedMap = Arguments.createMap(); - notificationOpenedMap.putString("action", intent.getAction()); - notificationOpenedMap.putMap("notification", notificationMap); + WritableMap notificationOpenMap = Arguments.createMap(); + notificationOpenMap.putString("action", intent.getAction()); + notificationOpenMap.putMap("notification", notificationMap); - return notificationOpenedMap; + return notificationOpenMap; } private WritableMap parseNotificationBundle(Bundle notification) { diff --git a/lib/modules/notifications/Notification.js b/lib/modules/notifications/Notification.js index ad38e9e1..37ccb2bc 100644 --- a/lib/modules/notifications/Notification.js +++ b/lib/modules/notifications/Notification.js @@ -9,7 +9,7 @@ import { generatePushID, isObject } from '../../utils'; import type { NativeNotification } from './types'; -export type NotificationOpened = { +export type NotificationOpen = { action: string, notification: Notification, }; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 649831a9..3f862a31 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -22,10 +22,10 @@ import { } from './types'; import type App from '../core/app'; -import type { NotificationOpened } from './Notification'; +import type { NotificationOpen } from './Notification'; import type { NativeNotification, - NativeNotificationOpened, + NativeNotificationOpen, Schedule, } from './types'; @@ -35,10 +35,10 @@ type OnNotificationObserver = { next: OnNotification, }; -type OnNotificationOpened = NotificationOpened => any; +type OnNotificationOpened = NotificationOpen => any; type OnNotificationOpenedObserver = { - next: OnNotificationOpened, + next: OnNotificationOpen, }; const NATIVE_EVENTS = [ @@ -95,10 +95,10 @@ export default class Notifications extends ModuleBase { // sub to internal native event - this fans out to // public event name: onNotificationOpened 'notifications_notification_opened', - (notificationOpened: NativeNotificationOpened) => { + (notificationOpen: NativeNotificationOpen) => { SharedEventEmitter.emit('onNotificationOpened', { - action: notificationOpened.action, - notification: new Notification(notificationOpened.notification), + action: notificationOpen.action, + notification: new Notification(notificationOpen.notification), }); } ); diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index a9c9f107..5a77c74a 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -172,7 +172,7 @@ export type NativeNotification = {| title: string, |}; -export type NativeNotificationOpened = {| +export type NativeNotificationOpen = {| action: string, notification: NativeNotification, |};