Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[firebase_messaging] Add macOS support #1989

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ that enable Flutter apps to use [Firebase](https://firebase.google.com/) service
| [firebase_database][database_pub] | ![pub package][database_badge] | [Firebase Realtime Database][database_product] | [`firebase_database`][database_code] | | |
| [firebase_dynamic_links][dynamic_links_pub] | ![pub package][dynamic_links_badge] | [Firebase Dynamic Links][dynamic_links_product] | [`firebase_dynamic_links`][dynamic_links_code] | | |
| [firebase_in_app_messaging][in_app_messaging_pub] | ![pub package][in_app_messaging_badge] | [Firebase In-App Messaging][in_app_messaging_product] | [`firebase_in_app_messaging`][in_app_messaging_code] | | |
| [firebase_messaging][messaging_pub] | ![pub package][messaging_badge] | [Firebase Cloud Messaging][messaging_product] | [`firebase_messaging`][messaging_code] | | |
| [firebase_messaging][messaging_pub] | ![pub package][messaging_badge] | [Firebase Cloud Messaging][messaging_product] | [`firebase_messaging`][messaging_code] | | macOS |
| [firebase_ml_vision][ml_vision_pub] | ![pub package][ml_vision_badge] | [Firebase ML Kit][ml_vision_product] | [`firebase_ml_vision`][ml_vision_code] | | |
| [firebase_performance][performance_pub] | ![pub package][performance_badge] | [Firebase Performance Monitoring][performance_product] | [`firebase_performance`][performance_code] | | |
| [firebase_remote_config][remote_config_pub] | ![pub package][remote_config_badge] | [Firebase Remote Config][remote_config_product] | [`firebase_remote_config`][remote_config_code] | | |
Expand Down
4 changes: 4 additions & 0 deletions packages/firebase_messaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 6.0.17

* Add macOS support

## 6.0.16

* Update lower bound of dart dependency to 2.0.0.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <TargetConditionals.h>

#import <UserNotifications/UserNotifications.h>

#import "FLTFirebaseMessagingPlugin.h"

#import "Firebase/Firebase.h"

NSString *const kGCMMessageIDKey = @"gcm.message_id";

#if TARGET_OS_OSX || (defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0)
@interface FLTFirebaseMessagingPlugin () <FIRMessagingDelegate>
@end
#endif

static FlutterError *getFlutterError(NSError *error) {
if (error == nil) return nil;
return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", (long)error.code]
message:error.domain
details:error.localizedDescription];
}

static NSObject<FlutterPluginRegistrar> *_registrar;

@implementation FLTFirebaseMessagingPlugin {
FlutterMethodChannel *_channel;
NSDictionary *_launchNotification;
BOOL _resumingFromBackground;
}

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
_registrar = registrar;
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_messaging"
binaryMessenger:[registrar messenger]];
FLTFirebaseMessagingPlugin *instance =
[[FLTFirebaseMessagingPlugin alloc] initWithChannel:channel];
// TODO(cbenhagen): Enable for macOS when https://github.com/flutter/flutter/issues/41471 is done.
#if TARGET_OS_IPHONE
[registrar addApplicationDelegate:instance];
#endif
[registrar addMethodCallDelegate:instance channel:channel];

SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:");
if ([FIRApp respondsToSelector:sel]) {
[FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION];
}
}

- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {
self = [super init];

if (self) {
_channel = channel;
_resumingFromBackground = NO;
if (![FIRApp appNamed:@"__FIRAPP_DEFAULT"]) {
NSLog(@"Configuring the default Firebase app...");
[FIRApp configure];
NSLog(@"Configured the default Firebase app %@.", [FIRApp defaultApp].name);
}
[FIRMessaging messaging].delegate = self;
}
return self;
}

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *method = call.method;
if ([@"requestNotificationPermissions" isEqualToString:method]) {
NSDictionary *arguments = call.arguments;
if (@available(macOS 10.14, iOS 10.0, *)) {
UNAuthorizationOptions authOptions = 0;
NSNumber *provisional = arguments[@"provisional"];
if ([arguments[@"sound"] boolValue]) {
authOptions |= UNAuthorizationOptionSound;
}
if ([arguments[@"alert"] boolValue]) {
authOptions |= UNAuthorizationOptionAlert;
}
if ([arguments[@"badge"] boolValue]) {
authOptions |= UNAuthorizationOptionBadge;
}

NSNumber *isAtLeastVersion12;
if (@available(macOS 10.14, iOS 12, *)) {
isAtLeastVersion12 = [NSNumber numberWithBool:YES];
if ([provisional boolValue]) authOptions |= UNAuthorizationOptionProvisional;
} else {
isAtLeastVersion12 = [NSNumber numberWithBool:NO];
}

[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:authOptions
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (error) {
result(getFlutterError(error));
return;
}
// This works for iOS >= 10. See
// [UIApplication:didRegisterUserNotificationSettings:notificationSettings]
// for ios < 10.
[[UNUserNotificationCenter currentNotificationCenter]
getNotificationSettingsWithCompletionHandler:^(
UNNotificationSettings *_Nonnull settings) {
NSDictionary *settingsDictionary = @{
@"sound" : [NSNumber numberWithBool:settings.soundSetting ==
UNNotificationSettingEnabled],
@"badge" : [NSNumber numberWithBool:settings.badgeSetting ==
UNNotificationSettingEnabled],
@"alert" : [NSNumber numberWithBool:settings.alertSetting ==
UNNotificationSettingEnabled],
@"provisional" :
[NSNumber numberWithBool:granted && [provisional boolValue] &&
isAtLeastVersion12],
};
[self->_channel invokeMethod:@"onIosSettingsRegistered"
arguments:settingsDictionary];
}];
result([NSNumber numberWithBool:granted]);
}];
#if TARGET_OS_IPHONE
[[UIApplication sharedApplication] registerForRemoteNotifications];
#else
[[NSApplication sharedApplication] registerForRemoteNotifications];
#endif
} else {
#if TARGET_OS_IPHONE
UIUserNotificationType notificationTypes = 0;
if ([arguments[@"sound"] boolValue]) {
notificationTypes |= UIUserNotificationTypeSound;
}
if ([arguments[@"alert"] boolValue]) {
notificationTypes |= UIUserNotificationTypeAlert;
}
if ([arguments[@"badge"] boolValue]) {
notificationTypes |= UIUserNotificationTypeBadge;
}

UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];

[[UIApplication sharedApplication] registerForRemoteNotifications];
result([NSNumber numberWithBool:YES]);
#else
NSRemoteNotificationType notificationTypes = 0;
if ([arguments[@"sound"] boolValue]) {
notificationTypes |= NSRemoteNotificationTypeSound;
}
if ([arguments[@"alert"] boolValue]) {
notificationTypes |= NSRemoteNotificationTypeAlert;
}
if ([arguments[@"badge"] boolValue]) {
notificationTypes |= NSRemoteNotificationTypeBadge;
}

[[NSApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes];
result([NSNumber numberWithBool:YES]);
#endif
}
} else if ([@"configure" isEqualToString:method]) {
[FIRMessaging messaging].shouldEstablishDirectChannel = true;
#if TARGET_OS_IPHONE
[[UIApplication sharedApplication] registerForRemoteNotifications];
#else
[[NSApplication sharedApplication] unregisterForRemoteNotifications];
#endif
if (_launchNotification != nil && _launchNotification[kGCMMessageIDKey]) {
[_channel invokeMethod:@"onLaunch" arguments:_launchNotification];
}
result(nil);
} else if ([@"subscribeToTopic" isEqualToString:method]) {
NSString *topic = call.arguments;
[[FIRMessaging messaging] subscribeToTopic:topic
completion:^(NSError *error) {
result(getFlutterError(error));
}];
} else if ([@"unsubscribeFromTopic" isEqualToString:method]) {
NSString *topic = call.arguments;
[[FIRMessaging messaging] unsubscribeFromTopic:topic
completion:^(NSError *error) {
result(getFlutterError(error));
}];
} else if ([@"getToken" isEqualToString:method]) {
[[FIRInstanceID instanceID]
instanceIDWithHandler:^(FIRInstanceIDResult *_Nullable instanceIDResult,
NSError *_Nullable error) {
if (error != nil) {
NSLog(@"getToken, error fetching instanceID: %@", error);
result(nil);
} else {
result(instanceIDResult.token);
}
}];
} else if ([@"deleteInstanceID" isEqualToString:method]) {
[[FIRInstanceID instanceID] deleteIDWithHandler:^void(NSError *_Nullable error) {
if (error.code != 0) {
NSLog(@"deleteInstanceID, error: %@", error);
result([NSNumber numberWithBool:NO]);
} else {
#if TARGET_OS_IPHONE
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
#else
[[NSApplication sharedApplication] unregisterForRemoteNotifications];
#endif
result([NSNumber numberWithBool:YES]);
}
}];
} else if ([@"autoInitEnabled" isEqualToString:method]) {
BOOL value = [[FIRMessaging messaging] isAutoInitEnabled];
result([NSNumber numberWithBool:value]);
} else if ([@"setAutoInitEnabled" isEqualToString:method]) {
NSNumber *value = call.arguments;
[FIRMessaging messaging].autoInitEnabled = value.boolValue;
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
}

#if TARGET_OS_OSX || (defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0)
// Received data message on iOS 10 devices while app is in the foreground.
// Only invoked if method swizzling is enabled.
- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[self didReceiveRemoteNotification:remoteMessage.appData];
}

// Received data message on iOS 10 devices while app is in the foreground.
// Only invoked if method swizzling is disabled and UNUserNotificationCenterDelegate has been
// registered in AppDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
NS_AVAILABLE_IOS(10.0) {
NSDictionary *userInfo = notification.request.content.userInfo;
// Check to key to ensure we only handle messages from Firebase
if (userInfo[kGCMMessageIDKey]) {
[[FIRMessaging messaging] appDidReceiveMessage:userInfo];
[_channel invokeMethod:@"onMessage" arguments:userInfo];
completionHandler(UNNotificationPresentationOptionNone);
}
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(10.0) {
NSDictionary *userInfo = response.notification.request.content.userInfo;
// Check to key to ensure we only handle messages from Firebase
if (userInfo[kGCMMessageIDKey]) {
[_channel invokeMethod:@"onResume" arguments:userInfo];
completionHandler();
}
}

#endif

- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
if (_resumingFromBackground) {
[_channel invokeMethod:@"onResume" arguments:userInfo];
} else {
[_channel invokeMethod:@"onMessage" arguments:userInfo];
}
}

#pragma mark - AppDelegate

#if TARGET_OS_IPHONE
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (launchOptions != nil) {
_launchNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
}
return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
_resumingFromBackground = YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
_resumingFromBackground = NO;
// Clears push notifications from the notification center, with the
// side effect of resetting the badge count. We need to clear notifications
// because otherwise the user could tap notifications in the notification
// center while the app is in the foreground, and we wouldn't be able to
// distinguish that case from the case where a message came in and the
// user dismissed the notification center without tapping anything.
// TODO(goderbauer): Revisit this behavior once we provide an API for managing
// the badge number, or if we add support for running Dart in the background.
// Setting badgeNumber to 0 is a no-op (= notifications will not be cleared)
// if it is already 0,
// therefore the next line is setting it to 1 first before clearing it again
// to remove all
// notifications.
application.applicationIconBadgeNumber = 1;
application.applicationIconBadgeNumber = 0;
}

- (BOOL)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[self didReceiveRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNoData);
return YES;
}

- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
#ifdef DEBUG
[[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeSandbox];
#else
[[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeProd];
#endif

[_channel invokeMethod:@"onToken" arguments:[FIRMessaging messaging].FCMToken];
}

// This will only be called for iOS < 10. For iOS >= 10, we make this call when we request
// permissions.
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
NSDictionary *settingsDictionary = @{
@"sound" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeSound],
@"badge" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeBadge],
@"alert" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeAlert],
@"provisional" : [NSNumber numberWithBool:NO],
};
[_channel invokeMethod:@"onIosSettingsRegistered" arguments:settingsDictionary];
}

- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveRegistrationToken:(nonnull NSString *)fcmToken {
[_channel invokeMethod:@"onToken" arguments:fcmToken];
}

- (void)messaging:(FIRMessaging *)messaging
didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[_channel invokeMethod:@"onMessage" arguments:remoteMessage.appData];
}

#else
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
if (notification != nil) {
_launchNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey];
}
}

- (void)applicationDidEnterBackground:(NSApplication *)application {
_resumingFromBackground = YES;
}

- (void)applicationDidBecomeActive:(NSApplication *)application {
_resumingFromBackground = NO;
}

- (BOOL)application:(NSApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo {
[self didReceiveRemoteNotification:userInfo];
return YES;
}

- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveRegistrationToken:(nonnull NSString *)fcmToken {
[_channel invokeMethod:@"onToken" arguments:fcmToken];
}

- (void)messaging:(FIRMessaging *)messaging
didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[_channel invokeMethod:@"onMessage" arguments:remoteMessage.appData];
}
#endif

@end
6 changes: 6 additions & 0 deletions packages/firebase_messaging/example/macos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/

# Xcode-related
**/xcuserdata/
Loading