/** * 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"; @implementation RCTConvert (UILocalNotification) + (UILocalNotification *)UILocalNotification:(id)json { NSDictionary *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 { RCTPromiseResolveBlock _requestPermissionsResolveBlock; } 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; } RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } - (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 *)supportedEvents { return @[@"localNotificationReceived", @"remoteNotificationReceived", @"remoteNotificationsRegistered"]; } + (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 { [[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived object:self userInfo:RCTFormatLocalNotification(notification)]; } - (void)handleLocalNotificationReceived:(NSNotification *)notification { [self sendEventWithName:@"localNotificationReceived" body:notification.userInfo]; } - (void)handleRemoteNotificationReceived:(NSNotification *)notification { NSMutableDictionary *userInfo = [notification.userInfo mutableCopy]; userInfo[@"remote"] = @YES; [self sendEventWithName:@"remoteNotificationReceived" body:userInfo]; } - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification { [self sendEventWithName:@"remoteNotificationsRegistered" body:notification.userInfo]; } - (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; } /** * 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; } _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; // 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) { [[UIApplication sharedApplication] 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 = [UIApplication sharedApplication].scheduledLocalNotifications; NSMutableArray *formattedScheduledLocalNotifications = [NSMutableArray new]; for (UILocalNotification *notification in scheduledLocalNotifications) { [formattedScheduledLocalNotifications addObject:RCTFormatLocalNotification(notification)]; } callback(@[formattedScheduledLocalNotifications]); } @end