react-native-firebase/ios/RNFirebase/messaging/RNFirebaseMessaging.m

286 lines
10 KiB
Objective-C

#import "RNFirebaseMessaging.h"
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
@import UserNotifications;
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <FirebaseMessaging/FirebaseMessaging.h>
#import <FirebaseInstanceID/FIRInstanceID.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
#import <React/RCTUtils.h>
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@import UserNotifications;
#endif
@implementation RNFirebaseMessaging
static RNFirebaseMessaging *theRNFirebaseMessaging = nil;
static bool jsReady = FALSE;
static NSString* initialToken = nil;
+ (nonnull instancetype)instance {
return theRNFirebaseMessaging;
}
RCT_EXPORT_MODULE()
- (id)init {
self = [super init];
if (self != nil) {
NSLog(@"Setting up RNFirebaseMessaging instance");
[self configure];
}
return self;
}
- (void)configure {
// Set as delegate for FIRMessaging
[FIRMessaging messaging].delegate = self;
// Establish Firebase managed data channel
[FIRMessaging messaging].shouldEstablishDirectChannel = YES;
// Set static instance for use from AppDelegate
theRNFirebaseMessaging = self;
}
// *******************************************************
// ** Start AppDelegate methods
// ** iOS 8/9 Only
// *******************************************************
// Listen for permission response
- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
if (notificationSettings.types == UIUserNotificationTypeNone) {
if (_permissionRejecter) {
_permissionRejecter(@"messaging/permission_error", @"Failed to grant permission", nil);
}
} else if (_permissionResolver) {
_permissionResolver(nil);
}
_permissionRejecter = nil;
_permissionResolver = nil;
}
// Listen for FCM data messages that arrive as a remote notification
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
NSDictionary *message = [self parseUserInfo:userInfo];
[self sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
// *******************************************************
// ** Start FIRMessagingDelegate methods
// ** iOS 8+
// *******************************************************
// Listen for FCM tokens
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken {
NSLog(@"Received new FCM token: %@", fcmToken);
[self sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:fcmToken];
}
// Listen for data messages in the foreground
- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[self sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set [Messaging messaging].shouldEstablishDirectChannel to YES.
- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[self sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// *******************************************************
// ** Finish FIRMessagingDelegate methods
// *******************************************************
// ** Start React Module methods **
RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
resolve([[FIRInstanceID instanceID] token]);
}
RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (RCTRunningInAppExtension()) {
reject(@"messaging/request-permission-unavailable", @"requestPermission is not supported in App Extensions", nil);
return;
}
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UIUserNotificationType types = (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:types categories:nil]];
// We set the promise for usage by the AppDelegate callback which listens
// for the result of the permission request
self.permissionRejecter = reject;
self.permissionResolver = resolve;
});
} else {
if (@available(iOS 10.0, *)) {
// For iOS 10 display notification (sent via APNS)
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
resolve(nil);
} else {
reject(@"messaging/permission_error", @"Failed to grant permission", error);
}
}];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() registerForRemoteNotifications];
});
}
// Non Web SDK methods
RCT_EXPORT_METHOD(hasPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
dispatch_async(dispatch_get_main_queue(), ^{
resolve(@([RCTSharedApplication() currentUserNotificationSettings].types != UIUserNotificationTypeNone));
});
} else {
if (@available(iOS 10.0, *)) {
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
resolve(@(settings.alertSetting == UNNotificationSettingEnabled));
}];
}
}
}
RCT_EXPORT_METHOD(sendMessage:(NSDictionary *) message
resolve:(RCTPromiseResolveBlock) resolve
reject:(RCTPromiseRejectBlock) reject) {
if (!message[@"to"]) {
reject(@"messaging/invalid-message", @"The supplied message is missing a 'to' field", nil);
}
NSString *to = message[@"to"];
NSString *messageId = message[@"messageId"];
NSNumber *ttl = message[@"ttl"];
NSDictionary *data = message[@"data"];
[[FIRMessaging messaging] sendMessage:data to:to withMessageID:messageId timeToLive:[ttl intValue]];
// TODO: Listen for send success / errors
resolve(nil);
}
RCT_EXPORT_METHOD(subscribeToTopic:(NSString*) topic
resolve:(RCTPromiseResolveBlock) resolve
reject:(RCTPromiseRejectBlock) reject) {
[[FIRMessaging messaging] subscribeToTopic:topic];
resolve(nil);
}
RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic
resolve:(RCTPromiseResolveBlock) resolve
reject:(RCTPromiseRejectBlock) reject) {
[[FIRMessaging messaging] unsubscribeFromTopic:topic];
resolve(nil);
}
RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
jsReady = TRUE;
resolve(nil);
if (initialToken) {
[self sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:initialToken];
}
}
// ** Start internals **
// Because of the time delay between the app starting and the bridge being initialised
// we catch any events that are received before the JS is ready to receive them
- (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body {
if (emitter.bridge && jsReady) {
[RNFirebaseUtil sendJSEvent:emitter name:name body:body];
} else {
if ([name isEqualToString:MESSAGING_TOKEN_REFRESHED]) {
initialToken = body;
} else {
// TODO: Is this even possible?
NSLog(@"Received Remote Message before the bridge is ready");
}
}
}
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *appData = remoteMessage.appData;
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in appData) {
if ([k1 isEqualToString:@"collapse_key"]) {
message[@"collapseKey"] = appData[@"collapse_key"];
} else if ([k1 isEqualToString:@"from"]) {
message[@"from"] = appData[k1];
} else if ([k1 isEqualToString:@"notification"]) {
// Ignore for messages
} else {
// Assume custom data key
data[k1] = appData[k1];
}
}
message[@"data"] = data;
return message;
}
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
// Ignore notification section
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
|| [k1 isEqualToString:@"google.c.a.c_l"]
|| [k1 isEqualToString:@"google.c.a.e"]
|| [k1 isEqualToString:@"google.c.a.udt"]) {
// Ignore known keys
} else {
// Assume custom data
data[k1] = userInfo[k1];
}
}
message[@"data"] = data;
return message;
}
- (NSArray<NSString *> *)supportedEvents {
return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED];
}
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end
#else
@implementation RNFirebaseMessaging
@end
#endif