diff --git a/native/ios/Comm/CommIOSNotifications/CommIOSNotifications.mm b/native/ios/Comm/CommIOSNotifications/CommIOSNotifications.mm index 786b105c7..341ebfe4a 100644 --- a/native/ios/Comm/CommIOSNotifications/CommIOSNotifications.mm +++ b/native/ios/Comm/CommIOSNotifications/CommIOSNotifications.mm @@ -1,268 +1,347 @@ #import "CommIOSNotifications.h" #import "CommIOSNotificationsBridgeQueue.h" #import "Logger.h" #import #import #import #import #import /* Internal Constants */ NSString *const CommIOSNotificationsRegistered = @"CommIOSNotificationsRegistered"; NSString *const CommIOSNotificationsRegistrationFailed = @"CommIOSNotificationsRegistrationFailed"; NSString *const CommIOSNotificationsReceivedForeground = @"CommIOSNotificationsReceivedForeground"; NSString *const CommIOSNotificationsOpened = @"CommIOSNotificationsOpened"; @implementation CommIOSNotifications RCT_EXPORT_MODULE() @synthesize bridge = _bridge; - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; [CommIOSNotificationsBridgeQueue sharedInstance].openedRemoteNotification = [_bridge.launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; } - (void)stopObserving { _hasListeners = NO; } - (void)startObserving { _hasListeners = YES; } - (instancetype)init { self = [super init]; if (!self) { return self; } _hasListeners = NO; _remoteNotificationCallbacks = [NSMutableDictionary dictionary]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleNotificationsRegistered:) name:CommIOSNotificationsRegistered object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleNotificationsRegistrationFailed:) name:CommIOSNotificationsRegistrationFailed object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleNotificationReceivedForeground:) name:CommIOSNotificationsReceivedForeground object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleNotificationOpened:) name:CommIOSNotificationsOpened object:nil]; return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (NSArray *)supportedEvents { return @[ @"remoteNotificationsRegistered", @"remoteNotificationsRegistrationFailed", @"notificationReceivedForeground", @"notificationOpened" ]; } /* Public methods */ + (void)didRegisterForRemoteNotificationsWithDeviceToken:(id)deviceToken { NSString *token = [deviceToken isKindOfClass:[NSString class]] ? deviceToken : [self deviceTokenToString:deviceToken]; [NSNotificationCenter.defaultCenter postNotificationName:CommIOSNotificationsRegistered object:self userInfo:@{@"deviceToken" : token}]; } + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [NSNotificationCenter.defaultCenter postNotificationName:CommIOSNotificationsRegistrationFailed object:self userInfo:@{ @"code" : [NSNumber numberWithInteger:error.code], @"domain" : error.domain, @"localizedDescription" : error.localizedDescription }]; } + (void)didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler: (void (^)(UIBackgroundFetchResult))completionHandler { NSDictionary *notifInfo = @{ @"notification" : notification, @"completionHandler" : completionHandler }; UIApplicationState state = [UIApplication sharedApplication].applicationState; if (!CommIOSNotificationsBridgeQueue.sharedInstance.jsReady) { [CommIOSNotificationsBridgeQueue.sharedInstance putNotification:notifInfo]; return; } if (state == UIApplicationStateActive) { [NSNotificationCenter.defaultCenter postNotificationName:CommIOSNotificationsReceivedForeground object:self userInfo:notifInfo]; } else if (state == UIApplicationStateInactive) { [NSNotificationCenter.defaultCenter postNotificationName:CommIOSNotificationsOpened object:self userInfo:notifInfo]; } } + (void)clearNotificationFromNotificationsCenter:(NSString *)notificationId completionHandler: (void (^)(UIBackgroundFetchResult)) completionHandler { [UNUserNotificationCenter.currentNotificationCenter getDeliveredNotificationsWithCompletionHandler:^( NSArray *_Nonnull notifications) { for (UNNotification *notif in notifications) { if ([notificationId isEqual:notif.request.content.userInfo[@"id"]]) { NSArray *identifiers = [NSArray arrayWithObjects:notif.request.identifier, nil]; [UNUserNotificationCenter.currentNotificationCenter removeDeliveredNotificationsWithIdentifiers:identifiers]; } } if (completionHandler) { dispatch_async(dispatch_get_main_queue(), ^{ completionHandler(UIBackgroundFetchResultNewData); }); } }]; return; } /* JavaScript Events */ - (void)handleNotificationsRegistered:(NSNotification *)notification { if (!_hasListeners) { return; } [self sendEventWithName:@"remoteNotificationsRegistered" body:notification.userInfo]; } - (void)handleNotificationsRegistrationFailed:(NSNotification *)notification { if (!_hasListeners) { return; } [self sendEventWithName:@"remoteNotificationsRegistrationFailed" body:notification.userInfo]; } - (void)handleNotifInfo:(NSDictionary *)notifInfo withName:(NSString *)name { NSDictionary *notification = notifInfo[@"notification"]; NSString *notifID = notification[@"id"]; RCTRemoteNotificationCallback completionHandler = notifInfo[@"completionHandler"]; if (completionHandler && notifID) { self.remoteNotificationCallbacks[notifID] = completionHandler; } NSDictionary *jsReadableNotification = [CommIOSNotifications parseNotificationToJSReadableObject:notification withRequestIdentifier:nil]; if (!jsReadableNotification) { return; } [self sendEventWithName:name body:jsReadableNotification]; } - (void)handleNotificationReceivedForeground:(NSNotification *)sysNotif { if (!_hasListeners) { return; } [self handleNotifInfo:sysNotif.userInfo withName:@"notificationReceivedForeground"]; } - (void)handleNotificationOpened:(NSNotification *)sysNotif { if (!_hasListeners) { return; } [self handleNotifInfo:sysNotif.userInfo withName:@"notificationOpened"]; } /* Helper methods */ + (NSString *)deviceTokenToString:(NSData *)deviceToken { NSMutableString *result = [NSMutableString string]; const unsigned char *bytes = (const unsigned char *)deviceToken.bytes; for (NSUInteger i = 0; i < deviceToken.length; i++) { [result appendFormat:@"%02x", bytes[i]]; } return [result copy]; } + (NSDictionary *)parseNotificationToJSReadableObject: (NSDictionary *)notification withRequestIdentifier:(NSString *)identifier { NSMutableDictionary *jsReadableNotification = [[NSMutableDictionary alloc] init]; static NSArray *obligatoryJSNotificationKeys = @[ @"id", @"threadID" ]; static NSArray *optionalJSNotificationKeys = @[ @"body", @"title", @"messageInfos", @"prefix" ]; for (NSString *key in obligatoryJSNotificationKeys) { if (!notification[key]) { comm::Logger::log( "Received malformed notification missing key: " + std::string([key UTF8String])); return nil; } jsReadableNotification[key] = notification[key]; } for (NSString *key in optionalJSNotificationKeys) { if (notification[key]) { jsReadableNotification[key] = notification[key]; } } if (notification[@"aps"] && notification[@"aps"][@"alert"]) { jsReadableNotification[@"message"] = notification[@"aps"][@"alert"]; } if (identifier) { jsReadableNotification[@"identifier"] = identifier; } return jsReadableNotification; } +/* + React Native exported methods +*/ +RCT_EXPORT_METHOD(requestPermissions) { + UNAuthorizationOptions options = UNAuthorizationOptionAlert + + UNAuthorizationOptionSound + UNAuthorizationOptionBadge; + void (^authorizationRequestCompletionHandler)(BOOL, NSError *) = + ^(BOOL granted, NSError *_Nullable error) { + if (granted && + [UIApplication instancesRespondToSelector:@selector + (registerForRemoteNotifications)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] registerForRemoteNotifications]; + }); + return; + } + + NSDictionary *errorInfo = @{}; + if (error) { + errorInfo = @{ + @"code" : [NSNumber numberWithInteger:error.code], + @"domain" : error.domain, + @"localizedDescription" : error.localizedDescription + }; + } + + [NSNotificationCenter.defaultCenter + postNotificationName:CommIOSNotificationsRegistrationFailed + object:self + userInfo:errorInfo]; + }; + [UNUserNotificationCenter.currentNotificationCenter + requestAuthorizationWithOptions:options + completionHandler:authorizationRequestCompletionHandler]; +} + +RCT_EXPORT_METHOD(consumeBackgroundQueue) { + CommIOSNotificationsBridgeQueue.sharedInstance.jsReady = YES; + + // Push background notifications to JS + [CommIOSNotificationsBridgeQueue.sharedInstance + processNotifications:^(NSDictionary *notifInfo) { + NSDictionary *notification = notifInfo[@"notification"]; + RCTRemoteNotificationCallback completionHandler = + notifInfo[@"completionHandler"]; + [CommIOSNotifications didReceiveRemoteNotification:notification + fetchCompletionHandler:completionHandler]; + }]; + + // Push opened remote notifications + NSDictionary *openedRemoteNotification = [_bridge.launchOptions + objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; + if (openedRemoteNotification) { + CommIOSNotificationsBridgeQueue.sharedInstance.openedRemoteNotification = + nil; + NSDictionary *notifInfo = @{@"notification" : openedRemoteNotification}; + [NSNotificationCenter.defaultCenter + postNotificationName:CommIOSNotificationsOpened + object:self + userInfo:notifInfo]; + } +} + +RCT_EXPORT_METHOD(checkPermissions + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) { + __block NSDictionary *permissions; + [UNUserNotificationCenter.currentNotificationCenter + getNotificationSettingsWithCompletionHandler:^( + UNNotificationSettings *_Nonnull settings) { + permissions = @{ + @"badge" : @(settings.badgeSetting == UNNotificationSettingEnabled), + @"sound" : @(settings.soundSetting == UNNotificationSettingEnabled), + @"alert" : @(settings.alertSetting == UNNotificationSettingEnabled) + }; + resolve(permissions); + }]; +} + @end