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 9292eb2d..5788201f 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java @@ -304,7 +304,7 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A public void onReceive(Context context, Intent intent) { if (getReactApplicationContext().hasActiveCatalystInstance()) { String token = FirebaseInstanceId.getInstance().getToken(); - Log.d(TAG, "Received new token: " + token); + Log.d(TAG, "Received new FCM token: " + token); Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token); } diff --git a/ios/RNFirebase/RNFirebaseUtil.h b/ios/RNFirebase/RNFirebaseUtil.h index 8191c387..9a4a49f2 100644 --- a/ios/RNFirebase/RNFirebaseUtil.h +++ b/ios/RNFirebase/RNFirebaseUtil.h @@ -10,8 +10,8 @@ + (FIRApp *)getApp:(NSString *)appDisplayName; + (NSString *)getAppName:(NSString *)appDisplayName; + (NSString *)getAppDisplayName:(NSString *)appName; -+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body; -+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(NSDictionary *)body; ++ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body; ++ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body; @end diff --git a/ios/RNFirebase/RNFirebaseUtil.m b/ios/RNFirebase/RNFirebaseUtil.m index c97bb636..2c53fbca 100644 --- a/ios/RNFirebase/RNFirebaseUtil.m +++ b/ios/RNFirebase/RNFirebaseUtil.m @@ -24,7 +24,7 @@ static NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT"; return appName; } -+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body { ++ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { @try { // TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233 // until a better solution comes around @@ -36,7 +36,7 @@ static NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT"; } } -+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(NSDictionary *)body { ++ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body { // Add the appName to the body NSMutableDictionary *newBody = [body mutableCopy]; newBody[@"appName"] = [RNFirebaseUtil getAppDisplayName:app.name]; diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.h b/ios/RNFirebase/messaging/RNFirebaseMessaging.h index c493eabf..2bf10470 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.h +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.h @@ -9,8 +9,11 @@ @interface RNFirebaseMessaging : RCTEventEmitter -#if !TARGET_OS_TV ++ (_Nonnull instancetype)instance; +#if !TARGET_OS_TV +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; #endif @end diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index 0bffc7d6..f2a7e2c5 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -11,32 +11,155 @@ #import #import -@interface RNFirebaseMessaging () +// 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 () @end +#endif @implementation RNFirebaseMessaging +static RNFirebaseMessaging *theRNFirebaseMessaging = nil; + ++ (nonnull instancetype)instance { + return theRNFirebaseMessaging; +} + RCT_EXPORT_MODULE() - (id)init { self = [super init]; if (self != nil) { NSLog(@"Setting up RNFirebaseMessaging instance"); - [self initialiseMessaging]; + [self configure]; } return self; } -- (void)initialiseMessaging { +- (void)configure { + // Set as delegate for FIRMessaging + [FIRMessaging messaging].delegate = self; + // 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 + static dispatch_once_t once; + dispatch_once(&once, ^{ + theRNFirebaseMessaging = self; + }); } - (void)dealloc { } +// ** AppDelegate methods ** + +// Listen for background messages +- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { + BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); + + // TODO: Format data before send + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:userInfo]; +} + +// Listen for background messages +- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); + + // TODO: Format data before send + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:userInfo]; +} + +// ** UNUserNotificationCenterDelegate methods ** +#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 { + NSDictionary *userInfo = notification.request.content.userInfo; + + // TODO: Format data before send + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:userInfo]; + + // TODO: Change this to your preferred presentation option + completionHandler(UNNotificationPresentationOptionNone); +} + +// 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 *userInfo = response.notification.request.content.userInfo; + + // TODO: Format data before send + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:userInfo]; + + completionHandler(); +} + +#endif + +// ** FIRMessagingDelegate methods ** + +// Listen for FCM tokens +- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { + NSLog(@"Received new FCM token: %@", fcmToken); + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:fcmToken]; +} + +// Listen for data messages in the foreground +- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage { + NSDictionary *appData = remoteMessage.appData; + + NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + for (id k1 in appData) { + if ([k1 isEqualToString:@"collapse_key"]) { + message[@"collapseKey"] = appData[@"collapse_key"]; + } else if ([k1 isEqualToString:@"from"]) { + message[@"from"] = appData[@"from"]; + } else if ([k1 isEqualToString:@"notification"]) { + NSDictionary *n = appData[@"notification"]; + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; + for (id k2 in appData[@"notification"]) { + if ([k2 isEqualToString:@"badge"]) { + notification[@"badge"] = n[@"badge"]; + } else if ([k2 isEqualToString:@"body"]) { + notification[@"body"] = n[@"body"]; + } else if ([k2 isEqualToString:@"sound"]) { + notification[@"sound"] = n[@"sound"]; + } else if ([k2 isEqualToString:@"title"]) { + notification[@"title"] = n[@"title"]; + } else { + NSLog(@"Unknown notification key: %@", k2); + } + } + message[@"notification"] = notification; + } else { + data[k1] = appData[k1]; + } + } + message[@"data"] = data; + message[@"openedFromTray"] = @(false); + + [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; +} + // ** Start React Module methods ** RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve([[FIRInstanceID instanceID] token]); @@ -56,7 +179,6 @@ RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RC // TODO: Is there something we can listen for? resolve(@{@"status":@"unknown"}); } else { - // iOS 10 or later #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // For iOS 10 display notification (sent via APNS) UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; @@ -81,12 +203,12 @@ RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromise resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); } -RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ - UILocalNotification *localUserInfo = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; - if (localUserInfo) { - resolve([[localUserInfo userInfo] copy]); +RCT_EXPORT_METHOD(getInitialMessage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ + NSDictionary *notification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + if (notification) { + resolve([notification copy]); } else { - resolve([[self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]); + resolve(nil); } } diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 5f149eea..daf2cf06 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -5,6 +5,25 @@ @implementation RNFirebaseNotifications RCT_EXPORT_MODULE(); +RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ + UILocalNotification *localUserInfo = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; + if (localUserInfo) { + // TODO: Proper format + resolve([[localUserInfo userInfo] copy]); + } else { + resolve(nil); + } +} + +- (NSArray *)supportedEvents { + return @[]; +} + ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + @end #else diff --git a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m index 49716fe4..d266a798 100644 --- a/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m +++ b/tests/ios/ReactNativeFirebaseDemo/AppDelegate.m @@ -12,12 +12,14 @@ #import #import #import +#import @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [FIRApp configure]; + NSURL *jsCodeLocation; jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; @@ -36,4 +38,13 @@ return YES; } +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { + [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo]; +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + @end