/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import #import #import #import "RCTPushNotificationPlugins.h" NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; static NSString *const kLocalNotificationReceived = @"LocalNotificationReceived"; static NSString *const kRemoteNotificationsRegistered = @"RemoteNotificationsRegistered"; static NSString *const kRemoteNotificationRegistrationFailed = @"RemoteNotificationRegistrationFailed"; static NSString *const kErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS"; #if !TARGET_OS_TV @implementation RCTConvert (NSCalendarUnit) RCT_ENUM_CONVERTER(NSCalendarUnit, (@{ @"year": @(NSCalendarUnitYear), @"month": @(NSCalendarUnitMonth), @"week": @(NSCalendarUnitWeekOfYear), @"day": @(NSCalendarUnitDay), @"hour": @(NSCalendarUnitHour), @"minute": @(NSCalendarUnitMinute) }), 0, integerValue) @end @interface RCTPushNotificationManager () @property (nonatomic, strong) NSMutableDictionary *remoteNotificationCallbacks; @end @implementation RCTConvert (UILocalNotification) + (UILocalNotification *)UILocalNotification:(id)json { NSDictionary *details = [self NSDictionary:json]; BOOL isSilent = [RCTConvert BOOL:details[@"isSilent"]]; UILocalNotification *notification = [UILocalNotification new]; notification.alertTitle = [RCTConvert NSString:details[@"alertTitle"]]; notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; notification.category = [RCTConvert NSString:details[@"category"]]; notification.repeatInterval = [RCTConvert NSCalendarUnit:details[@"repeatInterval"]]; if (details[@"applicationIconBadgeNumber"]) { notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"applicationIconBadgeNumber"]]; } if (!isSilent) { notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; } return notification; } RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ @"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData), @"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData), @"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed), }), UIBackgroundFetchResultNoData, integerValue) @end #else @interface RCTPushNotificationManager () @end #endif //TARGET_OS_TV / TARGET_OS_UIKITFORMAC @implementation RCTPushNotificationManager #if !TARGET_OS_TV && !TARGET_OS_UIKITFORMAC static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notification) { NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; if (notification.fireDate) { NSDateFormatter *formatter = [NSDateFormatter new]; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; formattedLocalNotification[@"fireDate"] = fireDateString; } formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); formattedLocalNotification[@"applicationIconBadgeNumber"] = @(notification.applicationIconBadgeNumber); formattedLocalNotification[@"category"] = RCTNullIfNil(notification.category); formattedLocalNotification[@"soundName"] = RCTNullIfNil(notification.soundName); formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(notification.userInfo)); formattedLocalNotification[@"remote"] = @NO; return formattedLocalNotification; } API_AVAILABLE(ios(10.0)) static NSDictionary *RCTFormatUNNotification(UNNotification *notification) { NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; UNNotificationContent *content = notification.request.content; formattedNotification[@"identifier"] = notification.request.identifier; if (notification.date) { NSDateFormatter *formatter = [NSDateFormatter new]; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; NSString *dateString = [formatter stringFromDate:notification.date]; formattedNotification[@"date"] = dateString; } formattedNotification[@"title"] = RCTNullIfNil(content.title); formattedNotification[@"body"] = RCTNullIfNil(content.body); formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); formattedNotification[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); formattedNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); return formattedNotification; } #endif //TARGET_OS_TV / TARGET_OS_UIKITFORMAC RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } #if !TARGET_OS_TV && !TARGET_OS_UIKITFORMAC - (void)startObserving { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleLocalNotificationReceived:) name:kLocalNotificationReceived object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationReceived:) name:RCTRemoteNotificationReceived object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationsRegistered:) name:kRemoteNotificationsRegistered object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationRegistrationError:) name:kRemoteNotificationRegistrationFailed object:nil]; } - (void)stopObserving { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (NSArray *)supportedEvents { return @[@"localNotificationReceived", @"remoteNotificationReceived", @"remoteNotificationsRegistered", @"remoteNotificationRegistrationError"]; } + (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings { } + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSMutableString *hexString = [NSMutableString string]; NSUInteger deviceTokenLength = deviceToken.length; const unsigned char *bytes = reinterpret_cast(deviceToken.bytes); for (NSUInteger i = 0; i < deviceTokenLength; i++) { [hexString appendFormat:@"%02x", bytes[i]]; } [[NSNotificationCenter defaultCenter] postNotificationName:kRemoteNotificationsRegistered object:self userInfo:@{@"deviceToken" : [hexString copy]}]; } + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[NSNotificationCenter defaultCenter] postNotificationName:kRemoteNotificationRegistrationFailed object:self userInfo:@{@"error": error}]; } + (void)didReceiveRemoteNotification:(NSDictionary *)notification { NSDictionary *userInfo = @{@"notification": notification}; [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived object:self userInfo:userInfo]; } + (void)didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(RCTRemoteNotificationCallback)completionHandler { NSDictionary *userInfo = @{@"notification": notification, @"completionHandler": completionHandler}; [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived object:self userInfo:userInfo]; } + (void)didReceiveLocalNotification:(UILocalNotification *)notification { [[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationReceived object:self userInfo:RCTFormatLocalNotification(notification)]; } - (void)handleLocalNotificationReceived:(NSNotification *)notification { [self sendEventWithName:@"localNotificationReceived" body:notification.userInfo]; } - (void)handleRemoteNotificationReceived:(NSNotification *)notification { NSMutableDictionary *remoteNotification = [NSMutableDictionary dictionaryWithDictionary:notification.userInfo[@"notification"]]; RCTRemoteNotificationCallback completionHandler = notification.userInfo[@"completionHandler"]; NSString *notificationId = [[NSUUID UUID] UUIDString]; remoteNotification[@"notificationId"] = notificationId; remoteNotification[@"remote"] = @YES; if (completionHandler) { if (!self.remoteNotificationCallbacks) { // Lazy initialization self.remoteNotificationCallbacks = [NSMutableDictionary dictionary]; } self.remoteNotificationCallbacks[notificationId] = completionHandler; } [self sendEventWithName:@"remoteNotificationReceived" body:remoteNotification]; } - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification { [self sendEventWithName:@"remoteNotificationsRegistered" body:notification.userInfo]; } - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification { NSError *error = notification.userInfo[@"error"]; NSDictionary *errorDetails = @{ @"message": error.localizedDescription, @"code": @(error.code), @"details": error.userInfo, }; [self sendEventWithName:@"remoteNotificationRegistrationError" body:errorDetails]; } RCT_EXPORT_METHOD(onFinishRemoteNotification:(NSString *)notificationId fetchResult:(NSString *)fetchResult) { UIBackgroundFetchResult result = [RCTConvert UIBackgroundFetchResult:fetchResult]; RCTRemoteNotificationCallback completionHandler = self.remoteNotificationCallbacks[notificationId]; if (!completionHandler) { RCTLogError(@"There is no completion handler with notification id: %@", notificationId); return; } completionHandler(result); [self.remoteNotificationCallbacks removeObjectForKey:notificationId]; } /** * Update the application icon badge number on the home screen */ RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(double)number) { RCTSharedApplication().applicationIconBadgeNumber = number; } /** * Get the current application icon badge number on the home screen */ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) { callback(@[@(RCTSharedApplication().applicationIconBadgeNumber)]); } RCT_EXPORT_METHOD(requestPermissions:(JS::NativePushNotificationManagerIOS::SpecRequestPermissionsPermission &)permissions resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { if (RCTRunningInAppExtension()) { reject(kErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(@"Requesting push notifications is currently unavailable in an app extension")); return; } // Add a listener to make sure that startObserving has been called [self addListener:@"remoteNotificationsRegistered"]; UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions.alert()) { types |= UIUserNotificationTypeAlert; } if (permissions.badge()) { types |= UIUserNotificationTypeBadge; } if (permissions.sound()) { types |= UIUserNotificationTypeSound; } [UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:types completionHandler:^(BOOL granted, NSError *_Nullable error) { if (error != NULL) { reject(@"-1", @"Error - Push authorization request failed.", error); } else { [RCTSharedApplication() registerForRemoteNotifications]; [UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { resolve(RCTPromiseResolveValueForUNNotificationSettings(settings)); }]; } }]; } RCT_EXPORT_METHOD(abandonPermissions) { [RCTSharedApplication() unregisterForRemoteNotifications]; } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { if (RCTRunningInAppExtension()) { callback(@[RCTSettingsDictForUNNotificationSettings(NO, NO, NO)]); return; } [UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { callback(@[RCTPromiseResolveValueForUNNotificationSettings(settings)]); }]; } static inline NSDictionary *RCTPromiseResolveValueForUNNotificationSettings(UNNotificationSettings* _Nonnull settings) { return RCTSettingsDictForUNNotificationSettings(settings.alertSetting == UNNotificationSettingEnabled, settings.badgeSetting == UNNotificationSettingEnabled, settings.soundSetting == UNNotificationSettingEnabled); } static inline NSDictionary *RCTSettingsDictForUNNotificationSettings(BOOL alert, BOOL badge, BOOL sound) { return @{@"alert": @(alert), @"badge": @(badge), @"sound": @(sound)}; } RCT_EXPORT_METHOD(presentLocalNotification:(JS::NativePushNotificationManagerIOS::Notification &)notification) { NSMutableDictionary *notificationDict = [NSMutableDictionary new]; notificationDict[@"alertTitle"] = notification.alertTitle(); notificationDict[@"alertBody"] = notification.alertBody(); notificationDict[@"alertAction"] = notification.alertAction(); notificationDict[@"userInfo"] = notification.userInfo(); notificationDict[@"category"] = notification.category(); notificationDict[@"repeatInterval"] = notification.repeatInterval(); if (notification.fireDate()) { notificationDict[@"fireDate"] = @(*notification.fireDate()); } if (notification.applicationIconBadgeNumber()) { notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); } if (notification.isSilent()) { notificationDict[@"isSilent"] = @(*notification.isSilent()); } [RCTSharedApplication() presentLocalNotificationNow:[RCTConvert UILocalNotification:notificationDict]]; } RCT_EXPORT_METHOD(scheduleLocalNotification:(JS::NativePushNotificationManagerIOS::Notification &)notification) { NSMutableDictionary *notificationDict = [NSMutableDictionary new]; notificationDict[@"alertTitle"] = notification.alertTitle(); notificationDict[@"alertBody"] = notification.alertBody(); notificationDict[@"alertAction"] = notification.alertAction(); notificationDict[@"userInfo"] = notification.userInfo(); notificationDict[@"category"] = notification.category(); notificationDict[@"repeatInterval"] = notification.repeatInterval(); if (notification.fireDate()) { notificationDict[@"fireDate"] = @(*notification.fireDate()); } if (notification.applicationIconBadgeNumber()) { notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); } if (notification.isSilent()) { notificationDict[@"isSilent"] = @(*notification.isSilent()); } [RCTSharedApplication() scheduleLocalNotification:[RCTConvert UILocalNotification:notificationDict]]; } RCT_EXPORT_METHOD(cancelAllLocalNotifications) { [RCTSharedApplication() cancelAllLocalNotifications]; } RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo) { for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) { __block BOOL matchesAll = YES; NSDictionary *notificationInfo = notification.userInfo; // Note: we do this with a loop instead of just `isEqualToDictionary:` // because we only require that all specified userInfo values match the // notificationInfo values - notificationInfo may contain additional values // which we don't care about. [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { if (![notificationInfo[key] isEqual:obj]) { matchesAll = NO; *stop = YES; } }]; if (matchesAll) { [RCTSharedApplication() cancelLocalNotification:notification]; } } } RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { NSMutableDictionary *initialNotification = [self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy]; UILocalNotification *initialLocalNotification = self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; if (initialNotification) { initialNotification[@"remote"] = @YES; resolve(initialNotification); } else if (initialLocalNotification) { resolve(RCTFormatLocalNotification(initialLocalNotification)); } else { resolve((id)kCFNull); } } RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTResponseSenderBlock)callback) { NSArray *scheduledLocalNotifications = RCTSharedApplication().scheduledLocalNotifications; NSMutableArray *formattedScheduledLocalNotifications = [NSMutableArray new]; for (UILocalNotification *notification in scheduledLocalNotifications) { [formattedScheduledLocalNotifications addObject:RCTFormatLocalNotification(notification)]; } callback(@[formattedScheduledLocalNotifications]); } RCT_EXPORT_METHOD(removeAllDeliveredNotifications) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeAllDeliveredNotifications]; } RCT_EXPORT_METHOD(removeDeliveredNotifications:(NSArray *)identifiers) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeDeliveredNotificationsWithIdentifiers:identifiers]; } RCT_EXPORT_METHOD(getDeliveredNotifications:(RCTResponseSenderBlock)callback) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getDeliveredNotificationsWithCompletionHandler:^(NSArray *_Nonnull notifications) { NSMutableArray *formattedNotifications = [NSMutableArray new]; for (UNNotification *notification in notifications) { [formattedNotifications addObject:RCTFormatUNNotification(notification)]; } callback(@[formattedNotifications]); }]; } #else //TARGET_OS_TV / TARGET_OS_UIKITFORMAC RCT_EXPORT_METHOD(onFinishRemoteNotification:(NSString *)notificationId fetchResult:(NSString *)fetchResult) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(double)number) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(requestPermissions:(JS::NativePushNotificationManagerIOS::SpecRequestPermissionsPermission &)permissions resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(abandonPermissions) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(presentLocalNotification:(JS::NativePushNotificationManagerIOS::Notification &)notification) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(scheduleLocalNotification:(JS::NativePushNotificationManagerIOS::Notification &)notification) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(cancelAllLocalNotifications) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTResponseSenderBlock)callback) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(removeAllDeliveredNotifications) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(removeDeliveredNotifications:(NSArray *)identifiers) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } RCT_EXPORT_METHOD(getDeliveredNotifications:(RCTResponseSenderBlock)callback) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } - (NSArray *)supportedEvents { return @[]; } #endif //TARGET_OS_TV / TARGET_OS_UIKITFORMAC - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } @end Class RCTPushNotificationManagerCls(void) { return RCTPushNotificationManager.class; }