mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 09:35:48 +00:00
d03f9b7442
Summary: Resolves #13012 RCTPushNotificationManager uses startObserving to register for RCTRegisterUserNotificationSettings. According to the docs, the startObserving method won't be called until somebody subscribes to NotificationManagerIOS. This means there is a scenario when the developer can call requestPermissions without subscribing to notifications first, but since RCTPushNotificationManager relies on NSNotificationCenter subscribtion, the result will never be returned. When requesting permissions the promise will resolve: `PushNotificationIOS.requestPermissions().then(console.log);` without the need for calling `PushNotificationIOS.addEventListener()` first. Closes https://github.com/facebook/react-native/pull/13263 Differential Revision: D4851767 Pulled By: javache fbshipit-source-id: 2be8621e072ae1086014594bc986ca5590b5eb61
476 lines
18 KiB
Objective-C
476 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 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<NSString *, id> *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];
|
|
[self startObservingRegistrationNotifications];
|
|
}
|
|
|
|
- (void)startObservingRegistrationNotifications
|
|
{
|
|
[[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<NSString *> *)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);
|
|
_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;
|
|
}
|
|
|
|
[self startObservingRegistrationNotifications];
|
|
_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
|