react-native-macos/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm

595 строки
24 KiB
Plaintext

/*
* 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 <React/RCTPushNotificationManager.h>
#import <UserNotifications/UserNotifications.h>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTUtils.h>
#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 () <NativePushNotificationManagerIOSSpec>
@property (nonatomic, strong) NSMutableDictionary *remoteNotificationCallbacks;
@end
@implementation RCTConvert (UILocalNotification)
+ (UILocalNotification *)UILocalNotification:(id)json
{
NSDictionary<NSString *, id> *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 () <NativePushNotificationManagerIOSSpec>
@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<NSString *> *)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<const unsigned char *>(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<NSString *, id> *)userInfo)
{
for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) {
__block BOOL matchesAll = YES;
NSDictionary<NSString *, id> *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<NSString *, id> *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<UILocalNotification *> *scheduledLocalNotifications = RCTSharedApplication().scheduledLocalNotifications;
NSMutableArray<NSDictionary *> *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<NSString *> *)identifiers)
{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center removeDeliveredNotificationsWithIdentifiers:identifiers];
}
RCT_EXPORT_METHOD(getDeliveredNotifications:(RCTResponseSenderBlock)callback)
{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> *_Nonnull notifications) {
NSMutableArray<NSDictionary *> *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<NSString *, id> *)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<NSString *> *)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<NSString *> *)supportedEvents
{
return @[];
}
#endif // TARGET_OS_UIKITFORMAC
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativePushNotificationManagerIOSSpecJSI>(params);
}
@end
Class RCTPushNotificationManagerCls(void) {
return RCTPushNotificationManager.class;
}