diff --git a/index.js b/index.js index f3a20458a62a610e2afd7d26adf6093106ecd003..75db8e7692eaeba9a6bd5565ba011e9451d78d56 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,20 @@ import {NativeModules, DeviceEventEmitter, Platform} from 'react-native'; -const eventsMap = { - refreshToken: 'FCMTokenRefreshed', - notification: 'FCMNotificationReceived' -}; +export const FCMEvent = { + RefreshToken: 'FCMTokenRefreshed', + Notification: 'FCMNotificationReceived' +} + +export const RemoteNotificationResult = { + NewData: 'UIBackgroundFetchResultNewData', + NoData: 'UIBackgroundFetchResultNoData', + ResultFailed: 'UIBackgroundFetchResultFailed' +} + +export const WillPresentNotificationResult = { + All: 'UNNotificationPresentationOptionAll', + None: 'UNNotificationPresentationOptionNone' +} const RNFIRMessaging = NativeModules.RNFIRMessaging; @@ -69,13 +80,57 @@ FCM.getBadgeNumber = () => { return RNFIRMessaging.getBadgeNumber(); } + +function finish(result){ + if(Platform.OS !== 'ios'){ + return; + } + if(!this._finishCalled && this._completionHandlerId){ + this._finishCalled = true; + switch(this.notification_event_type){ + case 'remote_notification': + result = result || RemoteNotificationResult.NoData; + if(!Object.values(RemoteNotificationResult).includes(result)){ + throw new Error(`Invalid RemoteNotificationResult, use import {RemoteNotificationResult} from 'react-native-fcm' to avoid typo`); + } + RNFIRMessaging.finishRemoteNotification(this._completionHandlerId, result); + return; + case 'notification_response': + RNFIRMessaging.finishNotificationResponse(this._completionHandlerId); + return; + case 'will_present_notification': + result = result || (this.show_in_foreground ? WillPresentNotificationResult.All : WillPresentNotificationResult.None); + if(!Object.values(WillPresentNotificationResult).includes(result)){ + throw new Error(`Invalid WillPresentNotificationResult, make sure you use import {WillPresentNotificationResult} from 'react-native-fcm' to avoid typo`); + } + RNFIRMessaging.finishWillPresentNotification(this._completionHandlerId, result); + return; + default: + return; + } + } +} + FCM.on = (event, callback) => { - const nativeEvent = eventsMap[event]; - if (!nativeEvent) { - throw new Error('FCM event must be "refreshToken" or "notification"'); + if (!Object.values(FCMEvent).includes(event)) { + throw new Error(`Invalid FCM event subscription, use import {FCMEvent} from 'react-native-fcm' to avoid typo`); }; - - return DeviceEventEmitter.addListener(nativeEvent, callback); + + if(event === FCMEvent.Notification){ + return DeviceEventEmitter.addListener(event, async(data)=>{ + data.finish = finish; + try{ + await callback(); + } catch(err){ + console.error('Notification handler err', err) + throw err; + } + if(!data._finishCalled){ + data.finish(); + } + }) + } + return DeviceEventEmitter.addListener(event, callback); }; FCM.subscribeToTopic = (topic) => { @@ -90,4 +145,4 @@ FCM.send = (senderId, payload) => { RNFIRMessaging.send(senderId, payload); }; -module.exports = FCM; +export default FCM; diff --git a/ios/RNFIRMessaging.h b/ios/RNFIRMessaging.h index e469d24a193fc8385cb3b380b5ceb3528532f36f..80f03c8228a6b0a6eb53c556c65499e865d5cc1b 100644 --- a/ios/RNFIRMessaging.h +++ b/ios/RNFIRMessaging.h @@ -5,12 +5,18 @@ #import - -extern NSString *const FCMNotificationReceived; +@import UserNotifications; @interface RNFIRMessaging : NSObject @property (nonatomic, assign) bool connectedToFCM; +#if !TARGET_OS_TV ++ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler; ++ (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; ++ (void)didReceiveNotificationResponse:(nonnull UNNotificationResponse *)response withCompletionHandler:(nonnull void (^)())completionHandler; ++ (void)willPresentNotification:(nonnull UNNotification *)notification withCompletionHandler:(nonnull void (^)(UNNotificationPresentationOptions))completionHandler; +#endif + @end diff --git a/ios/RNFIRMesssaging.m b/ios/RNFIRMesssaging.m index c51eb07132d1b6ced3ebdb05f1a2dd8a4acf0050..6e9ecb20aa288f58910c1bc2d1d1d8b288fd6aef 100644 --- a/ios/RNFIRMesssaging.m +++ b/ios/RNFIRMesssaging.m @@ -18,6 +18,10 @@ #endif +typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result); +typedef void (^RCTWillPresentNotificationCallback)(UNNotificationPresentationOptions result); +typedef void (^RCTNotificationResponseCallback)(); + NSString *const FCMNotificationReceived = @"FCMNotificationReceived"; @implementation RCTConvert (NSCalendarUnit) @@ -115,6 +119,21 @@ RCT_ENUM_CONVERTER(NSCalendarUnit, notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"badge"]]; return notification; } + +RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ + @"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData), + @"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData), + @"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed), + }), UIBackgroundFetchResultNoData, integerValue) + +RCT_ENUM_CONVERTER(UNNotificationPresentationOptions, (@{ + @"UNNotificationPresentationOptionAll": @(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound), + @"UNNotificationPresentationOptionNone": @(UNNotificationPresentationOptionNone)}), UIBackgroundFetchResultNoData, integerValue) + +@end + +@interface RNFIRMessaging () + @property (nonatomic, strong) NSMutableDictionary *notificationCallbacks; @end @implementation RNFIRMessaging @@ -123,6 +142,34 @@ RCT_EXPORT_MODULE() @synthesize bridge = _bridge; ++ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(RCTRemoteNotificationCallback)completionHandler { + NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: userInfo]; + [data setValue:@"remote_notification" forKey:@"notification_event_type"]; + [data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"]; + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}]; +} + ++ (void)didReceiveLocalNotification:(UILocalNotification *)notification { + NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.userInfo]; + [data setValue:@"local_notification" forKey:@"notification_event_type"]; + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data]; +} + ++ (void)didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(RCTNotificationResponseCallback)completionHandler +{ + NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: response.notification.request.content.userInfo]; + [data setValue:@"notification_response" forKey:@"notification_event_type"]; + [data setValue:@YES forKey:@"opened_from_tray"]; + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}]; +} + ++ (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(RCTWillPresentNotificationCallback)completionHandler +{ + NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.request.content.userInfo]; + [data setValue:@"will_present_notification" forKey:@"notification_event_type"]; + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}]; +} + - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -377,16 +424,52 @@ RCT_EXPORT_METHOD(send:(NSString*)senderId withPayload:(NSDictionary *)message) [[FIRMessaging messaging]sendMessage:imMessage to:receiver withMessageID:messageID timeToLive:ttl]; } +RCT_EXPORT_METHOD(finishRemoteNotification: (NSString *)completionHandlerId fetchResult:(UIBackgroundFetchResult)result){ + RCTRemoteNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId]; + if (!completionHandler) { + RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); + return; + } + completionHandler(result); + [self.notificationCallbacks removeObjectForKey:completionHandlerId]; +} + +RCT_EXPORT_METHOD(finishWillPresentNotification: (NSString *)completionHandlerId fetchResult:(UNNotificationPresentationOptions)result){ + RCTWillPresentNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId]; + if (!completionHandler) { + RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); + return; + } + completionHandler(result); + [self.notificationCallbacks removeObjectForKey:completionHandlerId]; +} + +RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId){ + RCTNotificationResponseCallback completionHandler = self.notificationCallbacks[completionHandlerId]; + if (!completionHandler) { + RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); + return; + } + completionHandler(); + [self.notificationCallbacks removeObjectForKey:completionHandlerId]; +} + - (void)handleNotificationReceived:(NSNotification *)notification { - if([notification.userInfo valueForKey:@"opened_from_tray"] == nil){ - NSMutableDictionary *data = [[NSMutableDictionary alloc]initWithDictionary: notification.userInfo]; - [data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"]; - [_bridge.eventDispatcher sendDeviceEventWithName:FCMNotificationReceived body:data]; - }else{ - [_bridge.eventDispatcher sendDeviceEventWithName:FCMNotificationReceived body:notification.userInfo]; + id completionHandler = notification.userInfo[@"completionHandler"]; + NSMutableDictionary* data = notification.userInfo[@"data"]; + if(completionHandler != nil){ + NSString *completionHandlerId = [[NSUUID UUID] UUIDString]; + if (!self.notificationCallbacks) { + // Lazy initialization + self.notificationCallbacks = [NSMutableDictionary dictionary]; + } + self.notificationCallbacks[completionHandlerId] = completionHandler; + data[@"_completionHandlerId"] = completionHandlerId; } + [_bridge.eventDispatcher sendDeviceEventWithName:FCMNotificationReceived body:data]; + } - (void)sendDataMessageFailure:(NSNotification *)notification