fix(messaging,ios): keep original UNUserNotificationCenter dele… (#3427)

* fix(messaging,ios): keep original UNUserNotificationCenter delegate

Keeps a reference to any original UNUserNotificationCenter delegates that are set before we replace the delegate with out own implementation. Internally we will also call the original delegate if our code does not already handle the delegate call. This should keep compatibility with other RN modules that set the delegate.

* v6.4.1-alpha.0

* Revert "v6.4.1-alpha.0"

This reverts commit b355a86a

* feat: automatically register with APNs

* docs: typos

* fix: forward delegate call to FIRAuth

Fixes / supersedes #3425

* fix(messaging): add activity check to getInitialNotification (#3495)

* fix(messaging): add activity check to getInitialNotification

* fix(messaging): add activity check to getInitialNotification

* Update .spellcheck.dict.txt

Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>

Co-authored-by: Elliot Hesp <elliot.hesp@gmail.com>
This commit is contained in:
Mike Diarmid
2020-04-22 12:49:52 +01:00
committed by GitHub
parent 4f61188784
commit a800cdbc81
16 changed files with 3928 additions and 6173 deletions

View File

@@ -6,7 +6,7 @@ Analytics
analytics
APIs
APIs.
APNS
APNs
async
auth
Auth
@@ -123,3 +123,4 @@ v6
VSCode
Wix
Xcode
lifecycle

View File

@@ -164,6 +164,9 @@ function App() {
}
```
The call to `getInitialNotification` should happen within a React lifecycle method after mounting (e.g. `componentDidMount` or `useEffect`).
If it's called too soon (e.g. within a class constructor or global scope), the notification data may not be available.
# Notifee - Advanced Notifications
FCM provides support for displaying basic notifications to users with minimal integration required. If however you require

View File

@@ -22,8 +22,8 @@ yarn add @react-native-firebase/messaging
cd ios/ && pod install
```
> iOS requires further configuration steps to be carried out before you can start receiving and sending
> messages through Firebase. Read the documentation on how to [setup iOS with Firebase Messaging](/messaging/usage/ios-setup).
> iOS requires further configuration before you can start receiving and sending
> messages through Firebase. Read the documentation on how to [setup iOS with Firebase Cloud Messaging](/messaging/usage/ios-setup).
If you're using an older version of React Native without auto-linking support, or wish to integrate into an existing project,
you can follow the manual installation steps for [iOS](/messaging/usage/installation/ios) and [Android](/messaging/usage/installation/android).
@@ -40,31 +40,13 @@ The module also provides basic support for displaying local notifications, to le
# Usage
Before receiving messages from the FCM service, you must first register your device and to use the service on iOS, you need to request the explicit users
permission to accept incoming messages.
## Registering devices with FCM
Before devices can receive and send messages via FCM, they must be registered. The library exposes a `registerDeviceForRemoteMessages`
method which should be called early on in your apps life-cycle. It is recommended that this method is called on every app
boot:
```js
import messaging from '@react-native-firebase/messaging';
async function registerAppWithFCM() {
await messaging().registerDeviceForRemoteMessages();
}
```
## iOS - Requesting permissions
iOS prevents messages from being delivered to devices unless you have received explicit permission from the user. This includes
messages which include data payloads and/or notification payloads.
iOS prevents messages containing notification (or 'alert') payloads from being displayed unless you have received explicit permission from the user.
> To learn more about local notifications, view the [Notifications](/messaging/notifications) documentation.
The module provides a `requestPermission` method which triggers a native permission dialog requesting the user's permission:
This module provides a `requestPermission` method which triggers a native permission dialog requesting the user's permission:
```js
import messaging from '@react-native-firebase/messaging';
@@ -306,6 +288,32 @@ messaging()
Messaging can be further configured to provide more control over how FCM is handled internally within your application.
## Auto Registration (iOS)
React Native Firebase Messaging automatically registers the device with APNs to receive remote messages. If you need
to manually control registration you can disable this via the `firebase.json` file:
```json
// <projectRoot>/firebase.json
{
"react-native": {
"messaging_ios_auto_register_for_remote_messages": false,
}
}
```
Once auto-registration is disabled you must manually call `registerDeviceForRemoteMessages` in your JavaScript code as
early as possible in your application startup;
```js
import messaging from '@react-native-firebase/messaging';
async function registerAppWithFCM() {
await messaging().registerDeviceForRemoteMessages();
}
```
## Auto initialization
Firebase generates an Instance ID, which FCM uses to generate a registration token and which Analytics uses for data collection.

View File

@@ -8,8 +8,8 @@ There are also a number of prerequisites which are required to be able to enable
- You must have an active [Apple Developer Account](https://developer.apple.com/membercenter/index.action).
- You must have a physical iOS device to receive messages.
- Firebase Cloud Messaging integrates with the [Apple Push Notification Service (APNS)](https://developer.apple.com/notifications/),
however APNS only works with real devices.
- Firebase Cloud Messaging integrates with the [Apple Push Notification service (APNs)](https://developer.apple.com/notifications/),
however APNs only works with real devices.
# Configuring your app
@@ -55,10 +55,10 @@ Now ensure that both the "Background fetch" and the "Remote notifications" sub-m
![Enabling the sub-modes](https://images.prismic.io/invertase/3a618574-dd9f-4478-9f39-9834d142b2e5_xcode-background-modes-check.gif?auto=compress,format)
# Linking APNS with FCM
# Linking APNs with FCM
Even though FCM a has limited capability to work without linking with APNS, the below steps are strongly recommended
to ensure the library works as expected. Without linking APNS, your device will not receive messages when in the background
Even though FCM a has limited capability to work without linking with APNs, the below steps are strongly recommended
to ensure the library works as expected. Without linking APNs, your device will not receive messages when in the background
or when quit.
A few steps are required:
@@ -75,11 +75,11 @@ tab on the account sidebar:
## 1. Registering a key
A key can be generated which gives the FCM full access over the Apple Push Notification (APNS) service. On the "Keys" menu item,
register a new key. The name of the key can be anything, however you must ensure the "Apple Push Notification (APNS)" service
A key can be generated which gives the FCM full access over the Apple Push Notification service (APNs). On the "Keys" menu item,
register a new key. The name of the key can be anything, however you must ensure the APNs service
is enabled:
![Enable "Apple Push Notification (APNS)"](https://images.prismic.io/invertase/01fefe19-132f-4b88-8c17-9dc40357e4ce_apple-key.png?auto=format)
![Enable "Apple Push Notification (APNs)"](https://images.prismic.io/invertase/01fefe19-132f-4b88-8c17-9dc40357e4ce_apple-key.png?auto=format)
Click "Continue" & then "Save". Once saved, you will be presented with a screen displaying the private "Key ID" & the ability
to download the key. Copy the ID, and download the file to your local machine:

View File

@@ -67,8 +67,8 @@ date: 2020-04-03
- This fixes an issue where FCM would throw a `"The operation couldnt be completed"` error ([`#2657`](https://github.com/invertase/react-native-firebase/issues/2657))
- `iOS`: direct FCM connection is now fixed.
- When the app was in the foreground, `data-only` messages were not coming through, they are now.
- `iOS`: when running debug build, the APNS token will be registered with FCM as a `"sandbox"` key type
- `iOS`: the original APNS swizzling we implemented was not functioning correctly with `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
- `iOS`: when running debug build, the APNs token will be registered with FCM as a `"sandbox"` key type
- `iOS`: the original APNs swizzling we implemented was not functioning correctly with `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
- We added additional logic whereby this is executed in all scenarios (foreground/background/quit) and replaces a deprecated Apple API.
- This fixes issues with `data-only` messages not being handled by the device
- `iOS`: any custom `FIRMessagingDelegate` methods you add to your `AppDelegate.m` will now also be called internally by React Native Firebase messaging.

9909
docs/typedoc.json vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -83,7 +83,8 @@
if (firebaseJsonRaw == nil) {
return @"{}";
}
return firebaseJsonRaw;
NSData *data = [[NSData alloc] initWithBase64EncodedString:firebaseJsonRaw options:0];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];;
}
@end

View File

@@ -19,6 +19,7 @@ package io.invertase.firebase.messaging;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat;
@@ -55,23 +56,29 @@ public class ReactNativeFirebaseMessagingModule extends ReactNativeFirebaseModul
initialNotification = null;
return;
} else {
Intent intent = getCurrentActivity().getIntent();
Activity activity = getCurrentActivity();
if (intent != null && intent.getExtras() != null) {
// messageId can be either one...
String messageId = intent.getExtras().getString("google.message_id");
if (messageId == null) messageId = intent.getExtras().getString("message_id");
if (activity != null) {
Intent intent = activity.getIntent();
// only handle non-consumed initial notifications
if (messageId != null && initialNotificationMap.get(messageId) == null) {
RemoteMessage remoteMessage = ReactNativeFirebaseMessagingReceiver.notifications.get(messageId);
if (intent != null && intent.getExtras() != null) {
// messageId can be either one...
String messageId = intent.getExtras().getString("google.message_id");
if (messageId == null) messageId = intent.getExtras().getString("message_id");
if (remoteMessage != null) {
promise.resolve(ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage));
initialNotificationMap.put(messageId, true);
return;
// only handle non-consumed initial notifications
if (messageId != null && initialNotificationMap.get(messageId) == null) {
RemoteMessage remoteMessage = ReactNativeFirebaseMessagingReceiver.notifications.get(messageId);
if (remoteMessage != null) {
promise.resolve(ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage));
initialNotificationMap.put(messageId, true);
return;
}
}
}
} else {
Log.w(TAG, "Attempt to call getInitialNotification failed. The current activity is not ready, try calling the method later in the React lifecycle.");
}
}

View File

@@ -100,6 +100,13 @@
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
if ([[FIRAuth auth] canHandleNotification:userInfo]) {
completionHandler(UIBackgroundFetchResultNoData);
return;
}
#endif
if (userInfo[@"gcm.message_id"]) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// TODO add support in a later version for calling completion handler directly from JS when user JS code complete

View File

@@ -18,6 +18,7 @@
#import <React/RCTRootView.h>
#import <React/RCTConvert.h>
#import <RNFBApp/RNFBRCTEventEmitter.h>
#import <RNFBApp/RNFBJSON.h>
#import "RNFBMessaging+AppDelegate.h"
#import "RNFBMessaging+NSNotificationCenter.h"
@@ -124,6 +125,12 @@
) {
rctRootView = (RCTRootView *) [UIApplication sharedApplication].delegate.window.rootViewController.view;
}
#if !(TARGET_IPHONE_SIMULATOR)
if ([[RNFBJSON shared] getBooleanValue:@"messaging_ios_auto_register_for_remote_messages" defaultValue:YES]) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
#endif
if (notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey]) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
@@ -137,6 +144,8 @@
// application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless registerForRemoteNotifications
// was called early during app initialization - we call it here in this scenario as the user can only call this via JS, at which point
// it'd be too late resulting in the app being terminated.
// called irregardless of `messaging_ios_auto_register_for_remote_messages` as this is most likely an app launching
// as a result of a remote notification - so has been registered previously
[[UIApplication sharedApplication] registerForRemoteNotifications];
#endif
} else {

View File

@@ -21,7 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface RNFBMessagingUNUserNotificationCenter : NSObject <UNUserNotificationCenterDelegate>
@property NSDictionary* initialNotification;
@property NSDictionary *initialNotification;
@property(nonatomic, nullable, weak) id <UNUserNotificationCenterDelegate> originalDelegate;
+ (_Nonnull instancetype)sharedInstance;

View File

@@ -21,6 +21,11 @@
#import "RNFBMessaging+UNUserNotificationCenter.h"
@implementation RNFBMessagingUNUserNotificationCenter
struct {
unsigned int willPresentNotification:1;
unsigned int didReceiveNotificationResponse:1;
unsigned int openSettingsForNotification:1;
} originalDelegateRespondsTo;
+ (instancetype)sharedInstance {
static dispatch_once_t once;
@@ -38,6 +43,12 @@
dispatch_once(&once, ^{
RNFBMessagingUNUserNotificationCenter *strongSelf = weakSelf;
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
if (center.delegate != nil) {
_originalDelegate = center.delegate;
originalDelegateRespondsTo.openSettingsForNotification = (unsigned int) [_originalDelegate respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)];
originalDelegateRespondsTo.willPresentNotification = (unsigned int) [_originalDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)];
originalDelegateRespondsTo.didReceiveNotificationResponse = (unsigned int) [_originalDelegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)];
}
center.delegate = strongSelf;
});
}
@@ -64,6 +75,10 @@
// TODO in a later version allow customising completion options in JS code
completionHandler(UNNotificationPresentationOptionNone);
} else if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
[_originalDelegate userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler];
} else {
completionHandler(UNNotificationPresentationOptionNone);
}
}
@@ -74,6 +89,16 @@
[[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_notification_opened" body:notificationDict];
_initialNotification = notificationDict;
completionHandler();
} else if (_originalDelegate != nil && originalDelegateRespondsTo.didReceiveNotificationResponse) {
[_originalDelegate userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
} else {
completionHandler();
}
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification {
if (_originalDelegate != nil && originalDelegateRespondsTo.openSettingsForNotification) {
[_originalDelegate userNotificationCenter:center openSettingsForNotification:notification];
}
}

View File

@@ -108,14 +108,14 @@ export namespace FirebaseMessagingTypes {
notification?: Notification;
/**
* Whether the iOS APNS message was configured as a background update notification.
* Whether the iOS APNs message was configured as a background update notification.
*
* @platform ios iOS
*/
contentAvailable?: boolean;
/**
* Whether the iOS APNS `mutable-content` property on the message was set
* Whether the iOS APNs `mutable-content` property on the message was set
* allowing the app to modify the notification via app extensions.
*
* @platform ios iOS
@@ -678,9 +678,8 @@ export namespace FirebaseMessagingTypes {
registerForRemoteNotifications(): Promise<void>;
/**
* On iOS, if your app wants to receive remote messages from FCM (via APNS), you must explicitly register
* this request with APNS. For example if you want to display alerts, play sounds
* or perform other user-facing actions (via the Notification library), you must call this method.
* On iOS, if your app wants to receive remote messages from FCM (via APNs), you must explicitly register
* with APNs if auto-registration has been disabled.
*
* > You can safely call this method on Android without platform checks. It's a no-op on Android and will promise resolve `void`.
*
@@ -746,7 +745,7 @@ export namespace FirebaseMessagingTypes {
unregisterDeviceForRemoteMessages(): Promise<void>;
/**
* On iOS, it is possible to get the users APNS token. This may be required if you want to send messages to your
* On iOS, it is possible to get the users APNs token. This may be required if you want to send messages to your
* iOS devices without using the FCM service.
*
* > You can safely call this method on Android without platform checks. It's a no-op on Android and will promise resolve `null`.
@@ -757,7 +756,7 @@ export namespace FirebaseMessagingTypes {
* const apnsToken = await firebase.messaging().getAPNSToken();
*
* if (apnsToken) {
* console.log('User APNS Token:', apnsToken);
* console.log('User APNs Token:', apnsToken);
* }
* ```
*
@@ -967,5 +966,14 @@ namespace ReactNativeFirebase {
messaging_android_headless_task_timeout?: number;
messaging_android_notification_channel_id?: string;
messaging_android_notification_color?: string;
/**
* Whether RNFirebase Messaging automatically calls `[[UIApplication sharedApplication] registerForRemoteNotifications];`
* automatically on app launch (recommended) - defaults to true.
*
* If set to false; make sure to call `firebase.messaging().registerDeviceForRemoteMessages()`
* early on in your app startup - otherwise you will NOT receive remote messages/notifications
* in your app.
*/
messaging_ios_auto_register_for_remote_messages?: boolean;
}
}

View File

@@ -264,6 +264,15 @@ class FirebaseMessagingModule extends FirebaseModule {
if (isAndroid) {
return Promise.resolve();
}
const autoRegister = this.firebaseJson['messaging_ios_auto_register_for_remote_messages'];
if (autoRegister === undefined || autoRegister === true) {
// eslint-disable-next-line no-console
console.warn(
`Usage of "messaging().registerDeviceForRemoteMessages()" is not required. You only need to register if auto-registration is disabled in your 'firebase.json' configuration file via the 'messaging_ios_auto_register_for_remote_messages' property.`,
);
}
this._isRegisteredForRemoteNotifications = true;
return this.native.registerForRemoteNotifications();
}

View File

@@ -20,6 +20,7 @@
"messaging_android_headless_task_timeout": 30000,
"messaging_android_notification_channel_id": "",
"messaging_android_notification_color": "@color/hotpink",
"messaging_ios_auto_register_for_remote_messages": true,
"ml_vision_label_model": true,
"ml_vision_image_label_model": true,