react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
Jonathan Stanton f4dbf37ba8 PushNotificationIOS requestPermission promisified
Summary:
**Motivation**
Today it's hard to build a good flow around requesting permissions if we don't know when the user rejects the push notification permission.

With this PR I wrap `PushNotificationIOS#requestPermission` in a promise. The promise will return with the updated permissions when the user accepts, rejects or has previously rejected the permission.

An example flow of how an app should handle push notifications with the change proposed:
1) Show user an explanation of push notification permissions with a button to enable,
2) User presses the enable push notifications button,
3) If the user accepts -> take them into the app,
4) if the user rejects -> explain how to enable permission in the settings app.
5) My app will now store some state about how it has asked permissions for push notifications so that the next time the user is taken through this flow they will go straight to step 4.

Without this change we could listen to the `register` event that PushNotificationIOS fires on the success sc
Closes https://github.com/facebook/react-native/pull/7900

Differential Revision: D3387424

Pulled By: nicklockwood

fbshipit-source-id: e27df41e83216e4e2a14d1e42c6b26e72236f48c
2016-06-03 16:28:30 -07:00

309 lines
11 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 "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
#define UIUserNotificationTypeNone UIRemoteNotificationTypeNone
#define UIUserNotificationType UIRemoteNotificationType
#endif
NSString *const RCTLocalNotificationReceived = @"LocalNotificationReceived";
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
NSString *const RCTRegisterUserNotificationSettings = @"RegisterUserNotificationSettings";
NSString *const RCTErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS";
@interface RCTPushNotificationManager ()
@property (nonatomic, copy) RCTPromiseResolveBlock requestPermissionsResolveBlock;
@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"]];
if (details[@"applicationIconBadgeNumber"]) {
notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"applicationIconBadgeNumber"]];
}
return notification;
}
@end
@implementation RCTPushNotificationManager
RCT_EXPORT_MODULE()
- (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(handleRemoteNotificationsRegistered:)
name:RCTRemoteNotificationsRegistered
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRegisterUserNotificationSettings:)
name:RCTRegisterUserNotificationSettings
object:nil];
}
- (void)stopObserving
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (NSArray<NSString *> *)supportedEvents
{
return @[@"localNotificationReceived",
@"remoteNotificationReceived",
@"remoteNotificationsRegistered"];
}
// TODO: Once all JS call sites for popInitialNotification have
// been removed we can get rid of this
- (NSDictionary<NSString *, id> *)constantsToExport
{
NSDictionary<NSString *, id> *initialNotification =
[self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy];
return @{@"initialNotification": RCTNullIfNil(initialNotification)};
}
+ (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings
{
if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) {
[[UIApplication sharedApplication] 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)didReceiveRemoteNotification:(NSDictionary *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived
object:self
userInfo:notification];
}
+ (void)didReceiveLocalNotification:(UILocalNotification *)notification
{
NSMutableDictionary *details = [NSMutableDictionary new];
if (notification.alertBody) {
details[@"alertBody"] = notification.alertBody;
}
if (notification.userInfo) {
details[@"userInfo"] = RCTJSONClean(notification.userInfo);
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived
object:self
userInfo:details];
}
- (void)handleLocalNotificationReceived:(NSNotification *)notification
{
[self sendEventWithName:@"localNotificationReceived" body:notification.userInfo];
}
- (void)handleRemoteNotificationReceived:(NSNotification *)notification
{
[self sendEventWithName:@"remoteNotificationReceived" body:notification.userInfo];
}
- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
{
[self sendEventWithName:@"remoteNotificationsRegistered" body:notification.userInfo];
}
- (void)handleRegisterUserNotificationSettings:(NSNotification *)notification
{
if (self.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),
};
self.requestPermissionsResolveBlock(notificationTypes);
self.requestPermissionsResolveBlock = nil;
}
/**
* 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 (self.requestPermissionsResolveBlock != nil) {
RCTLogError(@"Cannot call requestPermissions twice before the first has returned.");
return;
}
self.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;
}
UIApplication *app = RCTSharedApplication();
if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) {
UIUserNotificationSettings *notificationSettings =
[UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil];
[app registerUserNotificationSettings:notificationSettings];
} else {
[app registerForRemoteNotificationTypes:(NSUInteger)types];
}
}
RCT_EXPORT_METHOD(abandonPermissions)
{
[RCTSharedApplication() unregisterForRemoteNotifications];
}
RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
{
if (RCTRunningInAppExtension()) {
callback(@[@{@"alert": @NO, @"badge": @NO, @"sound": @NO}]);
return;
}
NSUInteger types = 0;
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
types = [RCTSharedApplication() currentUserNotificationSettings].types;
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
types = [RCTSharedApplication() enabledRemoteNotificationTypes];
#endif
}
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 [UIApplication sharedApplication].scheduledLocalNotifications) {
__block BOOL matchesAll = YES;
NSDictionary *notificationInfo = notification.userInfo;
[userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if (![notificationInfo[key] isEqual:obj]) {
matchesAll = NO;
*stop = YES;
}
}];
if (matchesAll) {
[[UIApplication sharedApplication] cancelLocalNotification:notification];
}
}
}
RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject)
{
NSDictionary<NSString *, id> *initialNotification =
[self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy];
resolve(RCTNullIfNil(initialNotification));
}
@end