|
| 1 | +// Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | +#include <TargetConditionals.h> |
| 5 | + |
| 6 | +#import <UserNotifications/UserNotifications.h> |
| 7 | + |
| 8 | +#import "FLTFirebaseMessagingPlugin.h" |
| 9 | +#import "UserAgent.h" |
| 10 | + |
| 11 | +#import "Firebase/Firebase.h" |
| 12 | + |
| 13 | +#if TARGET_OS_OSX || (defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0) |
| 14 | +@interface FLTFirebaseMessagingPlugin () <FIRMessagingDelegate> |
| 15 | +@end |
| 16 | +#endif |
| 17 | + |
| 18 | +static FlutterError *getFlutterError(NSError *error) { |
| 19 | + if (error == nil) return nil; |
| 20 | + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", (long)error.code] |
| 21 | + message:error.domain |
| 22 | + details:error.localizedDescription]; |
| 23 | +} |
| 24 | + |
| 25 | +static NSObject<FlutterPluginRegistrar> *_registrar; |
| 26 | + |
| 27 | +@implementation FLTFirebaseMessagingPlugin { |
| 28 | + FlutterMethodChannel *_channel; |
| 29 | + NSDictionary *_launchNotification; |
| 30 | + BOOL _resumingFromBackground; |
| 31 | +} |
| 32 | + |
| 33 | ++ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { |
| 34 | + _registrar = registrar; |
| 35 | + FlutterMethodChannel *channel = |
| 36 | + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_messaging" |
| 37 | + binaryMessenger:[registrar messenger]]; |
| 38 | + FLTFirebaseMessagingPlugin *instance = |
| 39 | + [[FLTFirebaseMessagingPlugin alloc] initWithChannel:channel]; |
| 40 | +// TODO(cbenhagen): Enable for macOS when https://github.com/flutter/flutter/issues/41471 is done. |
| 41 | +#if TARGET_OS_IPHONE |
| 42 | + [registrar addApplicationDelegate:instance]; |
| 43 | +#endif |
| 44 | + [registrar addMethodCallDelegate:instance channel:channel]; |
| 45 | + |
| 46 | + SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); |
| 47 | + if ([FIRApp respondsToSelector:sel]) { |
| 48 | + [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +- (instancetype)initWithChannel:(FlutterMethodChannel *)channel { |
| 53 | + self = [super init]; |
| 54 | + |
| 55 | + if (self) { |
| 56 | + _channel = channel; |
| 57 | + _resumingFromBackground = NO; |
| 58 | + if (![FIRApp appNamed:@"__FIRAPP_DEFAULT"]) { |
| 59 | + NSLog(@"Configuring the default Firebase app..."); |
| 60 | + [FIRApp configure]; |
| 61 | + NSLog(@"Configured the default Firebase app %@.", [FIRApp defaultApp].name); |
| 62 | + } |
| 63 | + [FIRMessaging messaging].delegate = self; |
| 64 | + } |
| 65 | + return self; |
| 66 | +} |
| 67 | + |
| 68 | +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { |
| 69 | + NSString *method = call.method; |
| 70 | + if ([@"requestNotificationPermissions" isEqualToString:method]) { |
| 71 | + NSDictionary *arguments = call.arguments; |
| 72 | + if (@available(macOS 10.14, iOS 10.0, *)) { |
| 73 | + UNAuthorizationOptions authOptions = 0; |
| 74 | + NSNumber *provisional = arguments[@"provisional"]; |
| 75 | + if ([arguments[@"sound"] boolValue]) { |
| 76 | + authOptions |= UNAuthorizationOptionSound; |
| 77 | + } |
| 78 | + if ([arguments[@"alert"] boolValue]) { |
| 79 | + authOptions |= UNAuthorizationOptionAlert; |
| 80 | + } |
| 81 | + if ([arguments[@"badge"] boolValue]) { |
| 82 | + authOptions |= UNAuthorizationOptionBadge; |
| 83 | + } |
| 84 | + |
| 85 | + NSNumber *isAtLeastVersion12; |
| 86 | + if (@available(macOS 10.14, iOS 12, *)) { |
| 87 | + isAtLeastVersion12 = [NSNumber numberWithBool:YES]; |
| 88 | + if ([provisional boolValue]) authOptions |= UNAuthorizationOptionProvisional; |
| 89 | + } else { |
| 90 | + isAtLeastVersion12 = [NSNumber numberWithBool:NO]; |
| 91 | + } |
| 92 | + |
| 93 | + [[UNUserNotificationCenter currentNotificationCenter] |
| 94 | + requestAuthorizationWithOptions:authOptions |
| 95 | + completionHandler:^(BOOL granted, NSError *_Nullable error) { |
| 96 | + if (error) { |
| 97 | + result(getFlutterError(error)); |
| 98 | + return; |
| 99 | + } |
| 100 | + // This works for iOS >= 10. See |
| 101 | + // [UIApplication:didRegisterUserNotificationSettings:notificationSettings] |
| 102 | + // for ios < 10. |
| 103 | + [[UNUserNotificationCenter currentNotificationCenter] |
| 104 | + getNotificationSettingsWithCompletionHandler:^( |
| 105 | + UNNotificationSettings *_Nonnull settings) { |
| 106 | + NSDictionary *settingsDictionary = @{ |
| 107 | + @"sound" : [NSNumber numberWithBool:settings.soundSetting == |
| 108 | + UNNotificationSettingEnabled], |
| 109 | + @"badge" : [NSNumber numberWithBool:settings.badgeSetting == |
| 110 | + UNNotificationSettingEnabled], |
| 111 | + @"alert" : [NSNumber numberWithBool:settings.alertSetting == |
| 112 | + UNNotificationSettingEnabled], |
| 113 | + @"provisional" : |
| 114 | + [NSNumber numberWithBool:granted && [provisional boolValue] && |
| 115 | + isAtLeastVersion12], |
| 116 | + }; |
| 117 | + [self->_channel invokeMethod:@"onIosSettingsRegistered" |
| 118 | + arguments:settingsDictionary]; |
| 119 | + }]; |
| 120 | + result([NSNumber numberWithBool:granted]); |
| 121 | + }]; |
| 122 | + |
| 123 | +#if TARGET_OS_IPHONE |
| 124 | + [[UIApplication sharedApplication] registerForRemoteNotifications]; |
| 125 | +#else |
| 126 | + [[NSApplication sharedApplication] registerForRemoteNotifications]; |
| 127 | +#endif |
| 128 | + } else { |
| 129 | +#if TARGET_OS_IPHONE |
| 130 | + UIUserNotificationType notificationTypes = 0; |
| 131 | + if ([arguments[@"sound"] boolValue]) { |
| 132 | + notificationTypes |= UIUserNotificationTypeSound; |
| 133 | + } |
| 134 | + if ([arguments[@"alert"] boolValue]) { |
| 135 | + notificationTypes |= UIUserNotificationTypeAlert; |
| 136 | + } |
| 137 | + if ([arguments[@"badge"] boolValue]) { |
| 138 | + notificationTypes |= UIUserNotificationTypeBadge; |
| 139 | + } |
| 140 | + |
| 141 | + UIUserNotificationSettings *settings = |
| 142 | + [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil]; |
| 143 | + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; |
| 144 | + |
| 145 | + [[UIApplication sharedApplication] registerForRemoteNotifications]; |
| 146 | + result([NSNumber numberWithBool:YES]); |
| 147 | +#else |
| 148 | + NSRemoteNotificationType notificationTypes = 0; |
| 149 | + if ([arguments[@"sound"] boolValue]) { |
| 150 | + notificationTypes |= NSRemoteNotificationTypeSound; |
| 151 | + } |
| 152 | + if ([arguments[@"alert"] boolValue]) { |
| 153 | + notificationTypes |= NSRemoteNotificationTypeAlert; |
| 154 | + } |
| 155 | + if ([arguments[@"badge"] boolValue]) { |
| 156 | + notificationTypes |= NSRemoteNotificationTypeBadge; |
| 157 | + } |
| 158 | + |
| 159 | + [[NSApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes]; |
| 160 | + result([NSNumber numberWithBool:YES]); |
| 161 | +#endif |
| 162 | + } |
| 163 | + } else if ([@"configure" isEqualToString:method]) { |
| 164 | + [FIRMessaging messaging].shouldEstablishDirectChannel = true; |
| 165 | +#if TARGET_OS_IPHONE |
| 166 | + [[UIApplication sharedApplication] registerForRemoteNotifications]; |
| 167 | +#else |
| 168 | + [[NSApplication sharedApplication] |
| 169 | + registerForRemoteNotificationTypes:NSRemoteNotificationTypeSound | |
| 170 | + NSRemoteNotificationTypeAlert | |
| 171 | + NSRemoteNotificationTypeBadge]; |
| 172 | +#endif |
| 173 | + if (_launchNotification != nil) { |
| 174 | + [_channel invokeMethod:@"onLaunch" arguments:_launchNotification]; |
| 175 | + } |
| 176 | + result(nil); |
| 177 | + } else if ([@"subscribeToTopic" isEqualToString:method]) { |
| 178 | + NSString *topic = call.arguments; |
| 179 | + [[FIRMessaging messaging] subscribeToTopic:topic |
| 180 | + completion:^(NSError *error) { |
| 181 | + result(getFlutterError(error)); |
| 182 | + }]; |
| 183 | + } else if ([@"unsubscribeFromTopic" isEqualToString:method]) { |
| 184 | + NSString *topic = call.arguments; |
| 185 | + [[FIRMessaging messaging] unsubscribeFromTopic:topic |
| 186 | + completion:^(NSError *error) { |
| 187 | + result(getFlutterError(error)); |
| 188 | + }]; |
| 189 | + } else if ([@"getToken" isEqualToString:method]) { |
| 190 | + [[FIRInstanceID instanceID] |
| 191 | + instanceIDWithHandler:^(FIRInstanceIDResult *_Nullable instanceIDResult, |
| 192 | + NSError *_Nullable error) { |
| 193 | + if (error != nil) { |
| 194 | + NSLog(@"getToken, error fetching instanceID: %@", error); |
| 195 | + result(nil); |
| 196 | + } else { |
| 197 | + result(instanceIDResult.token); |
| 198 | + } |
| 199 | + }]; |
| 200 | + } else if ([@"deleteInstanceID" isEqualToString:method]) { |
| 201 | + [[FIRInstanceID instanceID] deleteIDWithHandler:^void(NSError *_Nullable error) { |
| 202 | + if (error.code != 0) { |
| 203 | + NSLog(@"deleteInstanceID, error: %@", error); |
| 204 | + result([NSNumber numberWithBool:NO]); |
| 205 | + } else { |
| 206 | +#if TARGET_OS_IPHONE |
| 207 | + [[UIApplication sharedApplication] unregisterForRemoteNotifications]; |
| 208 | +#else |
| 209 | + [[NSApplication sharedApplication] unregisterForRemoteNotifications]; |
| 210 | +#endif |
| 211 | + result([NSNumber numberWithBool:YES]); |
| 212 | + } |
| 213 | + }]; |
| 214 | + } else if ([@"autoInitEnabled" isEqualToString:method]) { |
| 215 | + BOOL value = [[FIRMessaging messaging] isAutoInitEnabled]; |
| 216 | + result([NSNumber numberWithBool:value]); |
| 217 | + } else if ([@"setAutoInitEnabled" isEqualToString:method]) { |
| 218 | + NSNumber *value = call.arguments; |
| 219 | + [FIRMessaging messaging].autoInitEnabled = value.boolValue; |
| 220 | + result(nil); |
| 221 | + } else { |
| 222 | + result(FlutterMethodNotImplemented); |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | +#if TARGET_OS_OSX || (defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0) |
| 227 | +// Receive data message on iOS 10 devices while app is in the foreground. |
| 228 | +- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { |
| 229 | + [self didReceiveRemoteNotification:remoteMessage.appData]; |
| 230 | +} |
| 231 | +#endif |
| 232 | + |
| 233 | +- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo { |
| 234 | + if (_resumingFromBackground) { |
| 235 | + [_channel invokeMethod:@"onResume" arguments:userInfo]; |
| 236 | + } else { |
| 237 | + [_channel invokeMethod:@"onMessage" arguments:userInfo]; |
| 238 | + } |
| 239 | +} |
| 240 | + |
| 241 | +#pragma mark - AppDelegate |
| 242 | +#if TARGET_OS_IPHONE |
| 243 | +- (BOOL)application:(UIApplication *)application |
| 244 | + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
| 245 | + if (launchOptions != nil) { |
| 246 | + _launchNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; |
| 247 | + } |
| 248 | + return YES; |
| 249 | +} |
| 250 | + |
| 251 | +- (void)applicationDidEnterBackground:(UIApplication *)application { |
| 252 | + _resumingFromBackground = YES; |
| 253 | +} |
| 254 | + |
| 255 | +- (void)applicationDidBecomeActive:(UIApplication *)application { |
| 256 | + _resumingFromBackground = NO; |
| 257 | + // Clears push notifications from the notification center, with the |
| 258 | + // side effect of resetting the badge count. We need to clear notifications |
| 259 | + // because otherwise the user could tap notifications in the notification |
| 260 | + // center while the app is in the foreground, and we wouldn't be able to |
| 261 | + // distinguish that case from the case where a message came in and the |
| 262 | + // user dismissed the notification center without tapping anything. |
| 263 | + // TODO(goderbauer): Revisit this behavior once we provide an API for managing |
| 264 | + // the badge number, or if we add support for running Dart in the background. |
| 265 | + // Setting badgeNumber to 0 is a no-op (= notifications will not be cleared) |
| 266 | + // if it is already 0, |
| 267 | + // therefore the next line is setting it to 1 first before clearing it again |
| 268 | + // to remove all |
| 269 | + // notifications. |
| 270 | + application.applicationIconBadgeNumber = 1; |
| 271 | + application.applicationIconBadgeNumber = 0; |
| 272 | +} |
| 273 | + |
| 274 | +- (BOOL)application:(UIApplication *)application |
| 275 | + didReceiveRemoteNotification:(NSDictionary *)userInfo |
| 276 | + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { |
| 277 | + [self didReceiveRemoteNotification:userInfo]; |
| 278 | + completionHandler(UIBackgroundFetchResultNoData); |
| 279 | + return YES; |
| 280 | +} |
| 281 | + |
| 282 | +- (void)application:(UIApplication *)application |
| 283 | + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { |
| 284 | +#ifdef DEBUG |
| 285 | + [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeSandbox]; |
| 286 | +#else |
| 287 | + [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeProd]; |
| 288 | +#endif |
| 289 | + |
| 290 | + [_channel invokeMethod:@"onToken" arguments:[FIRMessaging messaging].FCMToken]; |
| 291 | +} |
| 292 | + |
| 293 | +// This will only be called for iOS < 10. For iOS >= 10, we make this call when we request |
| 294 | +// permissions. |
| 295 | +- (void)application:(UIApplication *)application |
| 296 | + didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { |
| 297 | + NSDictionary *settingsDictionary = @{ |
| 298 | + @"sound" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeSound], |
| 299 | + @"badge" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeBadge], |
| 300 | + @"alert" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeAlert], |
| 301 | + @"provisional" : [NSNumber numberWithBool:NO], |
| 302 | + }; |
| 303 | + [_channel invokeMethod:@"onIosSettingsRegistered" arguments:settingsDictionary]; |
| 304 | +} |
| 305 | + |
| 306 | +- (void)messaging:(nonnull FIRMessaging *)messaging |
| 307 | + didReceiveRegistrationToken:(nonnull NSString *)fcmToken { |
| 308 | + [_channel invokeMethod:@"onToken" arguments:fcmToken]; |
| 309 | +} |
| 310 | + |
| 311 | +- (void)messaging:(FIRMessaging *)messaging |
| 312 | + didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage { |
| 313 | + [_channel invokeMethod:@"onMessage" arguments:remoteMessage.appData]; |
| 314 | +} |
| 315 | + |
| 316 | +#else |
| 317 | +- (void)applicationDidFinishLaunching:(NSNotification *)notification { |
| 318 | + if (notification != nil) { |
| 319 | + _launchNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; |
| 320 | + } |
| 321 | +} |
| 322 | + |
| 323 | +- (void)applicationDidEnterBackground:(NSApplication *)application { |
| 324 | + _resumingFromBackground = YES; |
| 325 | +} |
| 326 | + |
| 327 | +- (void)applicationDidBecomeActive:(NSApplication *)application { |
| 328 | + _resumingFromBackground = NO; |
| 329 | +} |
| 330 | + |
| 331 | +- (BOOL)application:(NSApplication *)application |
| 332 | + didReceiveRemoteNotification:(NSDictionary *)userInfo { |
| 333 | + [self didReceiveRemoteNotification:userInfo]; |
| 334 | + return YES; |
| 335 | +} |
| 336 | + |
| 337 | +- (void)messaging:(nonnull FIRMessaging *)messaging |
| 338 | + didReceiveRegistrationToken:(nonnull NSString *)fcmToken { |
| 339 | + [_channel invokeMethod:@"onToken" arguments:fcmToken]; |
| 340 | +} |
| 341 | + |
| 342 | +- (void)messaging:(FIRMessaging *)messaging |
| 343 | + didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage { |
| 344 | + [_channel invokeMethod:@"onMessage" arguments:remoteMessage.appData]; |
| 345 | +} |
| 346 | +#endif |
| 347 | + |
| 348 | +@end |
0 commit comments