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