/* * Copyright (c) Meta Platforms, Inc. and 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 "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_UIKITFORMAC @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_UIKITFORMAC @implementation RCTPushNotificationManager #if !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; } 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_UIKITFORMAC RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } #if !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 { dispatch_async(dispatch_get_main_queue(), ^{ [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, NO, NO, NO, UNAuthorizationStatusNotDetermined) ]); 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, settings.criticalAlertSetting == UNNotificationSettingEnabled, settings.lockScreenSetting == UNNotificationSettingEnabled, settings.notificationCenterSetting == UNNotificationSettingEnabled, settings.authorizationStatus); } static inline NSDictionary *RCTSettingsDictForUNNotificationSettings( BOOL alert, BOOL badge, BOOL sound, BOOL critical, BOOL lockScreen, BOOL notificationCenter, UNAuthorizationStatus authorizationStatus) { return @{ @"alert" : @(alert), @"badge" : @(badge), @"sound" : @(sound), @"critical" : @(critical), @"lockScreen" : @(lockScreen), @"notificationCenter" : @(notificationCenter), @"authorizationStatus" : @(authorizationStatus) }; } 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()); if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { notificationDict[@"soundName"] = notification.soundName(); } } [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()); if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { notificationDict[@"soundName"] = notification.soundName(); } } [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 ]); }]; } RCT_EXPORT_METHOD(getAuthorizationStatus : (RCTResponseSenderBlock)callback) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *_Nonnull settings) { callback(@[ @(settings.authorizationStatus) ]); }]; } #else // 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)); } RCT_EXPORT_METHOD(getAuthorizationStatus : (RCTResponseSenderBlock)callback) { RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd)); } - (NSArray *)supportedEvents { return @[]; } #endif // 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; }