Skip to content

Commit c0c5054

Browse files
committed
feat(messaging, android): notification delegation APIs, firebase.json feature toggle
with Android Q+ and current Google Play Services, android does notification delegation by default now for REST v1 API FCM, but this causes react-native-firebase message handlers to fail Implement a feature toggle in AndroidManifest.xml via firebase.json that defaults to false so most users won't be affected Implement the programmatic APIs to dynamically set and fetch current state for the feature as well
1 parent dc47364 commit c0c5054

File tree

12 files changed

+218
-7
lines changed

12 files changed

+218
-7
lines changed

.spellcheck.dict.txt

+1
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ uri-scheme
215215
userData
216216
utils
217217
Utils
218+
v1
218219
v15
219220
v5
220221
v6

docs/messaging/usage/index.md

+10
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ If you require `remote notification` on Expo, you can also add this to your Expo
6464
}
6565
```
6666

67+
## Android - Google Play Notification Delegation
68+
69+
If you use the REST v1 APIs (used by the Firebase admin SDKs) and your app is running on Android Q+ with current Google Play services, Google implemented "Notification Delegation" for messages. Notification delegation is not currently compatible with react-native-firebase. Specifically, if your notifications are delegated via proxy to Play Services, then your messaging listeners will not be called.
70+
71+
To work around this incompatibility, react-native-firebase disables notification delegation by default currently, using the `AndroidManifest.xml` method listed as one of the options described here: <https://firebase.google.com/docs/cloud-messaging/android/message-priority#proxy>.
72+
73+
You may re-enable notification delegation if your use case requires it and you can accept the messaging listener methods not executing for delegated messages by altering the firebase.json setting `messaging_android_notification_delegation_enabled` to `true`.
74+
75+
You may also use the new messaging APIs to get and set the notification delegation state for the app, as desired.
76+
6777
# What does it do
6878

6979
React Native Firebase provides native integration of Firebase Cloud Messaging (FCM) for both Android & iOS. FCM is a cost

packages/app/firebase-schema.json

+5-6
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,7 @@
5656
"app_log_level": {
5757
"description": "Set the log level across all modules. Only applies to iOS currently. Can be 'error', 'warn', 'info', 'debug'.\n Logs messages at the configured level or lower.\n Note that if an app is running from AppStore, it will never log above info even if level is set to a higher (more verbose) setting",
5858
"type": "string",
59-
"enum": [
60-
"error",
61-
"warn",
62-
"info",
63-
"debug"
64-
]
59+
"enum": ["error", "warn", "info", "debug"]
6560
},
6661
"app_check_token_auto_refresh": {
6762
"description": "If this flag is disabled then Firebase App Check will not periodically auto-refresh the app check token.\n This is useful for opt-in-first data flows, for example when dealing with GDPR compliance. \nIf unset it will default to the SDK-wide data collection default enabled setting. This may be overridden dynamically in Javascript.",
@@ -100,6 +95,10 @@
10095
"type": "number",
10196
"minimum": 0
10297
},
98+
"messaging_android_notification_delegation_enabled": {
99+
"description": "On Android Q+ and current Play Services, your FCM may be delegated which disables firebase message listeners. Disabled by default. You may re-enable if necessary",
100+
"type": "boolean"
101+
},
103102
"messaging_android_notification_channel_id": {
104103
"description": "On Android, any message which displays a Notification use a default Notification Channel (created by FCM called `Miscellaneous`). This channel contains basic notification settings which may not be appropriate for your application. You can change what Channel is used by updating the `messaging_android_notification_channel_id` property.",
105104
"type": "string"

packages/messaging/android/build.gradle

+8-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ String deliveryMetricsExportEnabled = 'false'
7171
// If nothing is defined, data collection defaults to true
7272
String dataCollectionEnabled = 'true'
7373

74+
String notificationDelegationEnabled = 'false'
75+
7476
if (rootProject.ext && rootProject.ext.firebaseJson) {
7577
/* groovylint-disable-next-line NoDef, VariableTypeRequired */
7678
def rnfbConfig = rootProject.ext.firebaseJson
@@ -92,6 +94,10 @@ if (rootProject.ext && rootProject.ext.firebaseJson) {
9294
}
9395
notificationChannelId = rnfbConfig.getStringValue('messaging_android_notification_channel_id', defaultNotificationChannelId)
9496
notificationColor = rnfbConfig.getStringValue('messaging_android_notification_color', defaultNotificationColor)
97+
98+
if (rnfbConfig.isFlagEnabled('messaging_android_notification_delegation_enabled', false)) {
99+
notificationDelegationEnabled = 'true'
100+
}
95101
}
96102

97103
android {
@@ -106,7 +112,8 @@ android {
106112
firebaseJsonDeliveryMetricsExportEnabled: deliveryMetricsExportEnabled,
107113
firebaseJsonAutoInitEnabled: dataCollectionEnabled,
108114
firebaseJsonNotificationChannelId: notificationChannelId,
109-
firebaseJsonNotificationColor: notificationColor
115+
firebaseJsonNotificationColor: notificationColor,
116+
firebaseJsonNotificationDelegationEnabled: notificationDelegationEnabled
110117
]
111118
}
112119

packages/messaging/android/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
<meta-data
3131
android:name="firebase_messaging_auto_init_enabled"
3232
android:value="${firebaseJsonAutoInitEnabled}"/>
33+
<meta-data
34+
android:name="firebase_messaging_notification_delegation_enabled"
35+
android:value="${firebaseJsonNotificationDelegationEnabled}"/>
3336
<meta-data
3437
android:name="com.google.firebase.messaging.default_notification_channel_id"
3538
android:value="${firebaseJsonNotificationChannelId}" />

packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingModule.java

+39
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,42 @@ public void setAutoInitEnabled(Boolean enabled, Promise promise) {
120120
});
121121
}
122122

123+
@ReactMethod
124+
public void isNotificationDelegationEnabled(Promise promise) {
125+
Tasks.call(
126+
getExecutor(),
127+
() -> {
128+
FirebaseMessaging.getInstance().isNotificationDelegationEnabled();
129+
return null;
130+
})
131+
.addOnCompleteListener(
132+
task -> {
133+
if (task.isSuccessful()) {
134+
promise.resolve(task.getResult());
135+
} else {
136+
rejectPromiseWithExceptionMap(promise, task.getException());
137+
}
138+
});
139+
}
140+
141+
@ReactMethod
142+
public void setNotificationDelegationEnabled(Boolean enabled, Promise promise) {
143+
Tasks.call(
144+
getExecutor(),
145+
() -> {
146+
FirebaseMessaging.getInstance().setNotificationDelegationEnabled(enabled);
147+
return null;
148+
})
149+
.addOnCompleteListener(
150+
task -> {
151+
if (task.isSuccessful()) {
152+
promise.resolve(FirebaseMessaging.getInstance().isNotificationDelegationEnabled());
153+
} else {
154+
rejectPromiseWithExceptionMap(promise, task.getException());
155+
}
156+
});
157+
}
158+
123159
@ReactMethod
124160
public void getToken(String appName, String senderId, Promise promise) {
125161
FirebaseMessaging messagingInstance =
@@ -247,6 +283,9 @@ public Map<String, Object> getConstants() {
247283
constants.put(
248284
"isDeliveryMetricsExportToBigQueryEnabled",
249285
FirebaseMessaging.getInstance().deliveryMetricsExportToBigQueryEnabled());
286+
constants.put(
287+
"isNotificationDelegationEnabled",
288+
FirebaseMessaging.getInstance().isNotificationDelegationEnabled());
250289
return constants;
251290
}
252291

packages/messaging/e2e/messaging.e2e.js

+49
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,28 @@ describe('messaging()', function () {
445445
});
446446
});
447447

448+
describe('setNotificationDelegationEnabled()', function () {
449+
afterEach(async function () {
450+
await firebase.messaging().setNotificationDelegationEnabled(false);
451+
});
452+
453+
it('throws if enabled is not a boolean', function () {
454+
try {
455+
firebase.messaging().setNotificationDelegationEnabled(123);
456+
return Promise.reject(new Error('Did not throw Error.'));
457+
} catch (e) {
458+
e.message.should.containEql("'enabled' expected a boolean value");
459+
return Promise.resolve();
460+
}
461+
});
462+
463+
it('sets the value', async function () {
464+
should.equal(firebase.messaging().isNotificationDelegationEnabled, false);
465+
await firebase.messaging().setNotificationDelegationEnabled(true);
466+
should.equal(firebase.messaging().isNotificationDelegationEnabled, true);
467+
});
468+
});
469+
448470
describe('isSupported()', function () {
449471
it('should return "true" if the device or browser supports Firebase Messaging', async function () {
450472
// For android, when the play services are available, it will return "true"
@@ -897,6 +919,33 @@ describe('messaging()', function () {
897919
});
898920
});
899921

922+
describe('setNotificationDelegationEnabled()', function () {
923+
afterEach(async function () {
924+
const { getMessaging, setNotificationDelegationEnabled } = messagingModular;
925+
await setNotificationDelegationEnabled(getMessaging(), false);
926+
});
927+
928+
it('throws if enabled is not a boolean', function () {
929+
const { getMessaging, setNotificationDelegationEnabled } = messagingModular;
930+
try {
931+
setNotificationDelegationEnabled(getMessaging(), 123);
932+
return Promise.reject(new Error('Did not throw Error.'));
933+
} catch (e) {
934+
e.message.should.containEql("'enabled' expected a boolean value");
935+
return Promise.resolve();
936+
}
937+
});
938+
939+
it('sets the value', async function () {
940+
const { getMessaging, setNotificationDelegationEnabled, isNotificationDelegationEnabled } =
941+
messagingModular;
942+
943+
should.equal(isNotificationDelegationEnabled(getMessaging()), false);
944+
await setNotificationDelegationEnabled(getMessaging(), true);
945+
should.equal(isNotificationDelegationEnabled(getMessaging()), true);
946+
});
947+
});
948+
900949
describe('isSupported()', function () {
901950
it('should return "true" if the device or browser supports Firebase Messaging', async function () {
902951
const { isSupported, getMessaging } = messagingModular;

packages/messaging/lib/index.d.ts

+34
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,40 @@ export namespace FirebaseMessagingTypes {
11451145
* @param enabled A boolean value to enable or disable exporting of message delivery metrics to BigQuery.
11461146
*/
11471147
setDeliveryMetricsExportToBigQuery(enabled: boolean): Promise<void>;
1148+
1149+
/**
1150+
* Sets whether remote notification delegation to Google Play Services is enabled or disabled.
1151+
*
1152+
* The value is false by default. Set this to true to allow remote notification delegation.
1153+
*
1154+
* Warning: this will disable notification handlers on Android, and on iOS it has no effect
1155+
*
1156+
*
1157+
* #### Example
1158+
*
1159+
* ```js
1160+
* // Enable delegation of remote notifications to Google Play Services
1161+
* await firebase.messaging().setNotificationDelegationEnabled(true);
1162+
* ```
1163+
*
1164+
* @param enabled A boolean value to enable or disable remote notification delegation to Google Play Services.
1165+
*/
1166+
setNotificationDelegationEnabled(enabled: boolean): Promise<void>;
1167+
1168+
/**
1169+
* Gets whether remote notification delegation to Google Play Services is enabled or disabled.
1170+
*
1171+
* #### Example
1172+
*
1173+
* ```js
1174+
* // Determine if delegation of remote notifications to Google Play Services is enabled
1175+
* const delegationEnabled = await firebase.messaging().isNotificationDelegationEnabled();
1176+
* ```
1177+
*
1178+
* @returns enabled A boolean value indicatign if remote notification delegation to Google Play Services is enabled.
1179+
*/
1180+
istNotificationDelegationEnabled(): Promise<boolean>;
1181+
11481182
/**
11491183
* Checks if all required APIs exist in the browser.
11501184
*

packages/messaging/lib/index.js

+23
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class FirebaseMessagingModule extends FirebaseModule {
6060
this.native.isRegisteredForRemoteNotifications != null
6161
? this.native.isRegisteredForRemoteNotifications
6262
: true;
63+
this._isNotificationDelegationEnabled =
64+
this.native.isNotificationDelegationEnabled != null
65+
? this.native.isNotificationDelegationEnabled
66+
: false;
6367

6468
AppRegistry.registerHeadlessTask('ReactNativeFirebaseMessagingHeadlessTask', () => {
6569
if (!backgroundMessageHandler) {
@@ -121,6 +125,10 @@ class FirebaseMessagingModule extends FirebaseModule {
121125
return this._isRegisteredForRemoteNotifications;
122126
}
123127

128+
get isNotificationDelegationEnabled() {
129+
return this._isNotificationDelegationEnabled;
130+
}
131+
124132
get isDeliveryMetricsExportToBigQueryEnabled() {
125133
return this._isDeliveryMetricsExportToBigQueryEnabled;
126134
}
@@ -460,6 +468,21 @@ class FirebaseMessagingModule extends FirebaseModule {
460468
return this.native.setDeliveryMetricsExportToBigQuery(enabled);
461469
}
462470

471+
setNotificationDelegationEnabled(enabled) {
472+
if (!isBoolean(enabled)) {
473+
throw new Error(
474+
"firebase.messaging().setNotificationDelegationEnabled(*) 'enabled' expected a boolean value.",
475+
);
476+
}
477+
478+
this._isNotificationDelegationEnabled = enabled;
479+
if (isIOS) {
480+
return;
481+
}
482+
483+
return this.native.setNotificationDelegationEnabled(enabled);
484+
}
485+
463486
async isSupported() {
464487
if (Platform.isAndroid) {
465488
playServicesAvailability = firebase.utils().playServicesAvailability;

packages/messaging/lib/modular/index.d.ts

+23
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ export function isAutoInitEnabled(messaging: Messaging): boolean;
9999
*/
100100
export function setAutoInitEnabled(messaging: Messaging, enabled: boolean): Promise<void>;
101101

102+
/**
103+
* Sets whether remote notification delegation to Google Play Services is enabled or disabled.
104+
*
105+
* The value is false by default. Set this to true to allow remote notification delegation.
106+
*
107+
* Warning: this will disable notification handlers on Android, and on iOS it has no effect
108+
*
109+
* @param messaging - Messaging instance.
110+
* @param enabled A boolean value to enable or disable remote notification delegation to Google Play Services.
111+
*/
112+
export function setNotificationDelegationEnabled(
113+
messaging: Messaging,
114+
enabled: boolean,
115+
): Promise<void>;
116+
117+
/**
118+
* Gets whether remote notification delegation to Google Play Services is enabled or disabled.
119+
*
120+
* @param messaging - Messaging instance.
121+
* @returns enabled A boolean value indicatign if remote notification delegation to Google Play Services is enabled.
122+
*/
123+
export function istNotificationDelegationEnabled(messaging: Messaging): Promise<boolean>;
124+
102125
/**
103126
* When a notification from FCM has triggered the application to open from a quit state,
104127
* this method will return a `RemoteMessage` containing the notification data, or `null` if

packages/messaging/lib/modular/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,28 @@ export function isDeliveryMetricsExportToBigQueryEnabled(messaging) {
317317
return messaging.isDeliveryMetricsExportToBigQueryEnabled;
318318
}
319319

320+
/**
321+
* Returns a boolean whether message delegation is enabled. Android only,
322+
* always returns false on iOS
323+
* @param {Messaging} messaging - Messaging instance.
324+
* @returns {boolean}
325+
*/
326+
export function isNotificationDelegationEnabled(messaging) {
327+
return messaging.isNotificationDelegationEnabled;
328+
}
329+
330+
/**
331+
* Sets whether message notification delegation is enabled or disabled.
332+
* The value is false by default. Set this to true to allow delegation of notification to Google Play Services.
333+
* Note if true message handlers will not function on Android, and it has no effect on iOS
334+
* @param {Messaging} messaging - Messaging instance.
335+
* @param {boolean} enabled - A boolean value to enable or disable delegation of messages to Google Play Services.
336+
* @returns {Promise<void>}
337+
*/
338+
export function setNotificationDelegationEnabled(messaging, enabled) {
339+
return messaging.setNotificationDelegationEnabled(enabled);
340+
}
341+
320342
/**
321343
* Checks if all required APIs exist in the browser.
322344
* @param {Messaging} messaging - Messaging instance.

tests/firebase.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"messaging_android_notification_channel_id": "",
2121
"messaging_android_notification_color": "@color/hotpink",
2222
"messaging_ios_auto_register_for_remote_messages": false,
23+
"messaging_android_notification_delegation_enabled": true,
2324

2425
"analytics_auto_collection_enabled": true,
2526
"analytics_collection_deactivated": false,

0 commit comments

Comments
 (0)