/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #import "RCTPushNotificationManager.h" #import #import #import #import #import NSString *const RCTLocalNotificationReceived = @"LocalNotificationReceived"; NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered"; NSString *const RCTRegisterUserNotificationSettings = @"RegisterUserNotificationSettings"; NSString *const RCTErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS"; NSString *const RCTErrorRemoteNotificationRegistrationFailed = @"E_FAILED_TO_REGISTER_FOR_REMOTE_NOTIFICATIONS"; #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]; UILocalNotification *notification = [UILocalNotification new]; notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; 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"]]; } return notification; } RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ @"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData), @"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData), @"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed), }), UIBackgroundFetchResultNoData, integerValue) @end #endif //TARGET_OS_TV @implementation RCTPushNotificationManager { RCTPromiseResolveBlock _requestPermissionsResolveBlock; } #if !TARGET_OS_TV 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; } 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 RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } #if !TARGET_OS_TV - (void)startObserving { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleLocalNotificationReceived:) name:RCTLocalNotificationReceived object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationReceived:) name:RCTRemoteNotificationReceived object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRegisterUserNotificationSettings:) name:RCTRegisterUserNotificationSettings object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationsRegistered:) name:RCTRemoteNotificationsRegistered object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRemoteNotificationRegistrationError:) name:RCTErrorRemoteNotificationRegistrationFailed object:nil]; } - (void)stopObserving { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (NSArray *)supportedEvents { return @[@"localNotificationReceived", @"remoteNotificationReceived", @"remoteNotificationsRegistered", @"remoteNotificationRegistrationError"]; } + (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings { if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) { [RCTSharedApplication() registerForRemoteNotifications]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTRegisterUserNotificationSettings object:self userInfo:@{@"notificationSettings": notificationSettings}]; } } + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSMutableString *hexString = [NSMutableString string]; NSUInteger deviceTokenLength = deviceToken.length; const unsigned char *bytes = deviceToken.bytes; for (NSUInteger i = 0; i < deviceTokenLength; i++) { [hexString appendFormat:@"%02x", bytes[i]]; } [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered object:self userInfo:@{@"deviceToken" : [hexString copy]}]; } + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[NSNotificationCenter defaultCenter] postNotificationName:RCTErrorRemoteNotificationRegistrationFailed 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:RCTLocalNotificationReceived 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]; } - (void)handleRegisterUserNotificationSettings:(NSNotification *)notification { if (_requestPermissionsResolveBlock == nil) { return; } UIUserNotificationSettings *notificationSettings = notification.userInfo[@"notificationSettings"]; NSDictionary *notificationTypes = @{ @"alert": @((notificationSettings.types & UIUserNotificationTypeAlert) > 0), @"sound": @((notificationSettings.types & UIUserNotificationTypeSound) > 0), @"badge": @((notificationSettings.types & UIUserNotificationTypeBadge) > 0), }; _requestPermissionsResolveBlock(notificationTypes); // Clean up listener added in requestPermissions [self removeListeners:1]; _requestPermissionsResolveBlock = nil; } RCT_EXPORT_METHOD(onFinishRemoteNotification:(NSString *)notificationId fetchResult:(UIBackgroundFetchResult)result) { 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:(NSInteger)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:(NSDictionary *)permissions resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { if (RCTRunningInAppExtension()) { reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(@"Requesting push notifications is currently unavailable in an app extension")); return; } if (_requestPermissionsResolveBlock != nil) { RCTLogError(@"Cannot call requestPermissions twice before the first has returned."); return; } // Add a listener to make sure that startObserving has been called [self addListener:@"remoteNotificationsRegistered"]; _requestPermissionsResolveBlock = resolve; UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions) { if ([RCTConvert BOOL:permissions[@"alert"]]) { types |= UIUserNotificationTypeAlert; } if ([RCTConvert BOOL:permissions[@"badge"]]) { types |= UIUserNotificationTypeBadge; } if ([RCTConvert BOOL:permissions[@"sound"]]) { types |= UIUserNotificationTypeSound; } } else { types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; } UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; [RCTSharedApplication() registerUserNotificationSettings:notificationSettings]; } RCT_EXPORT_METHOD(abandonPermissions) { [RCTSharedApplication() unregisterForRemoteNotifications]; } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { if (RCTRunningInAppExtension()) { callback(@[@{@"alert": @NO, @"badge": @NO, @"sound": @NO}]); return; } NSUInteger types = [RCTSharedApplication() currentUserNotificationSettings].types; callback(@[@{ @"alert": @((types & UIUserNotificationTypeAlert) > 0), @"badge": @((types & UIUserNotificationTypeBadge) > 0), @"sound": @((types & UIUserNotificationTypeSound) > 0), }]); } RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) { [RCTSharedApplication() presentLocalNotificationNow:notification]; } RCT_EXPORT_METHOD(scheduleLocalNotification:(UILocalNotification *)notification) { [RCTSharedApplication() scheduleLocalNotification:notification]; } 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) { if ([UNUserNotificationCenter class]) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeAllDeliveredNotifications]; } } RCT_EXPORT_METHOD(removeDeliveredNotifications:(NSArray *)identifiers) { if ([UNUserNotificationCenter class]) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeDeliveredNotificationsWithIdentifiers:identifiers]; } } RCT_EXPORT_METHOD(getDeliveredNotifications:(RCTResponseSenderBlock)callback) { if ([UNUserNotificationCenter class]) { 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 - (NSArray *)supportedEvents { return @[]; } #endif //TARGET_OS_TV @end