478 lines
18 KiB
Objective-C
478 lines
18 KiB
Objective-C
/**
|
|
* 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 <UserNotifications/UserNotifications.h>
|
|
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
|
|
|
|
static NSString *const kLocalNotificationReceived = @"LocalNotificationReceived";
|
|
static NSString *const kRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
|
|
static NSString *const kRegisterUserNotificationSettings = @"RegisterUserNotificationSettings";
|
|
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<NSString *, id> *details = [self NSDictionary:json];
|
|
BOOL isSilent = [RCTConvert BOOL:details[@"isSilent"]];
|
|
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.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
|
|
#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:kLocalNotificationReceived
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(handleRemoteNotificationReceived:)
|
|
name:RCTRemoteNotificationReceived
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(handleRegisterUserNotificationSettings:)
|
|
name:kRegisterUserNotificationSettings
|
|
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
|
|
{
|
|
if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) {
|
|
[RCTSharedApplication() registerForRemoteNotifications];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kRegisterUserNotificationSettings
|
|
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: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];
|
|
}
|
|
|
|
- (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(kErrorUnableToRequestPermissions, 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<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)
|
|
{
|
|
if ([UNUserNotificationCenter class]) {
|
|
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
|
[center removeAllDeliveredNotifications];
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(removeDeliveredNotifications:(NSArray<NSString *> *)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<UNNotification *> *_Nonnull notifications) {
|
|
NSMutableArray<NSDictionary *> *formattedNotifications = [NSMutableArray new];
|
|
|
|
for (UNNotification *notification in notifications) {
|
|
[formattedNotifications addObject:RCTFormatUNNotification(notification)];
|
|
}
|
|
callback(@[formattedNotifications]);
|
|
}];
|
|
}
|
|
}
|
|
|
|
#else //TARGET_OS_TV
|
|
|
|
- (NSArray<NSString *> *)supportedEvents
|
|
{
|
|
return @[];
|
|
}
|
|
|
|
#endif //TARGET_OS_TV
|
|
|
|
@end
|