Skip to content

Commit 34152ed

Browse files
MonchiLinmikehardy
andauthored
feat(messaging): Adding support for Firebase Messaging via Expo config plugin. (#7369)
* feat(messaging): Adding support for Firebase Messaging via Expo config plugin. * feat(messaging): Adding expo plugin entry. * perf(messaging): Add NS `xmlns:tools to handle boundary conditions. * chore(messaging): typo. * chore: rebase to main, bump dependencies that moved after posting the PR --------- Co-authored-by: Mike Hardy <[email protected]>
1 parent afa6364 commit 34152ed

File tree

10 files changed

+216
-2
lines changed

10 files changed

+216
-2
lines changed

packages/messaging/app.plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./plugin/build');

packages/messaging/package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"scripts": {
99
"build": "genversion --semi lib/version.js",
1010
"build:clean": "rimraf android/build && rimraf ios/build",
11-
"prepare": "yarn run build"
11+
"build:plugin": "rimraf plugin/build && tsc --build plugin",
12+
"lint:plugin": "eslint plugin/src/*",
13+
"prepare": "yarn run build && yarn run build:plugin"
1214
},
1315
"repository": {
1416
"type": "git",
@@ -22,7 +24,16 @@
2224
"messaging"
2325
],
2426
"peerDependencies": {
25-
"@react-native-firebase/app": "18.6.2"
27+
"@react-native-firebase/app": "18.6.2",
28+
"expo": ">=47.0.0"
29+
},
30+
"devDependencies": {
31+
"expo": "^49.0.20"
32+
},
33+
"peerDependenciesMeta": {
34+
"expo": {
35+
"optional": true
36+
}
2637
},
2738
"publishConfig": {
2839
"access": "public"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { describe, expect, it, jest } from '@jest/globals';
2+
import { setFireBaseMessagingAndroidManifest } from '../src/android/setupFirebaseNotifationIcon';
3+
import { ExpoConfig } from '@expo/config-types';
4+
import expoConfigExample from './fixtures/expo-config-example';
5+
import manifestApplicationExample from './fixtures/application-example';
6+
import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest';
7+
8+
describe('Config Plugin Android Tests', function () {
9+
it('applies changes to app/src/main/AndroidManifest.xml with color', async function () {
10+
const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExample));
11+
const manifestApplication: ManifestApplication = JSON.parse(
12+
JSON.stringify(manifestApplicationExample),
13+
);
14+
setFireBaseMessagingAndroidManifest(config, manifestApplication);
15+
expect(manifestApplication['meta-data']).toContainEqual({
16+
$: {
17+
'android:name': 'com.google.firebase.messaging.default_notification_icon',
18+
'android:resource': '@drawable/notification_icon',
19+
},
20+
});
21+
expect(manifestApplication['meta-data']).toContainEqual({
22+
$: {
23+
'android:name': 'com.google.firebase.messaging.default_notification_color',
24+
'android:resource': '@color/notification_icon_color',
25+
'tools:replace': 'android:resource',
26+
},
27+
});
28+
});
29+
30+
it('applies changes to app/src/main/AndroidManifest.xml without color', async function () {
31+
const config = JSON.parse(JSON.stringify(expoConfigExample));
32+
const manifestApplication: ManifestApplication = JSON.parse(
33+
JSON.stringify(manifestApplicationExample),
34+
);
35+
config.notification!.color = undefined;
36+
setFireBaseMessagingAndroidManifest(config, manifestApplication);
37+
expect(manifestApplication['meta-data']).toContainEqual({
38+
$: {
39+
'android:name': 'com.google.firebase.messaging.default_notification_icon',
40+
'android:resource': '@drawable/notification_icon',
41+
},
42+
});
43+
expect(manifestApplication['meta-data']).not.toContainEqual({
44+
$: {
45+
'android:name': 'com.google.firebase.messaging.default_notification_icon',
46+
'android:resource': '@drawable/notification_icon_color',
47+
'tools:replace': 'android:resource',
48+
},
49+
});
50+
});
51+
52+
it('applies changes to app/src/main/AndroidManifest.xml without notification', async function () {
53+
const warnSpy = jest.spyOn(console, 'warn');
54+
const config: ExpoConfig = JSON.parse(JSON.stringify(expoConfigExample));
55+
const manifestApplication: ManifestApplication = JSON.parse(
56+
JSON.stringify(manifestApplicationExample),
57+
);
58+
config.notification = undefined;
59+
setFireBaseMessagingAndroidManifest(config, manifestApplication);
60+
expect(warnSpy).toHaveBeenCalled();
61+
warnSpy.mockRestore();
62+
});
63+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest';
2+
3+
/**
4+
* @type {import('"@expo/config-plugins/build/android/Manifest"').ManifestApplication}
5+
*/
6+
const manifestApplicationExample: ManifestApplication = {
7+
$: {
8+
'android:name': '',
9+
},
10+
};
11+
12+
export default manifestApplicationExample;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ExpoConfig } from '@expo/config-types';
2+
3+
/**
4+
* @type {import('@expo/config-types').ExpoConfig}
5+
*/
6+
const expoConfigExample: ExpoConfig = {
7+
name: 'FirebaseMessagingTest',
8+
slug: 'fire-base-messaging-test',
9+
notification: {
10+
icon: 'IconAsset',
11+
color: '#1D172D',
12+
},
13+
};
14+
15+
export default expoConfigExample;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { withExpoPluginFirebaseNotification } from './setupFirebaseNotifationIcon';
2+
3+
export { withExpoPluginFirebaseNotification };
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ConfigPlugin, withAndroidManifest } from '@expo/config-plugins';
2+
import { ManifestApplication } from '@expo/config-plugins/build/android/Manifest';
3+
import { ExpoConfig } from '@expo/config-types';
4+
5+
/**
6+
* Determine whether a ManifestApplication has an attribute.
7+
*/
8+
const hasMetaData = (application: ManifestApplication, metaData: string) => {
9+
return application['meta-data']?.some(item => item['$']['android:name'] === metaData);
10+
};
11+
12+
/**
13+
* Create `com.google.firebase.messaging.default_notification_icon` and `com.google.firebase.messaging.default_notification_color`
14+
*/
15+
export const withExpoPluginFirebaseNotification: ConfigPlugin = config => {
16+
return withAndroidManifest(config, async config => {
17+
// Add NS `xmlns:tools to handle boundary conditions.
18+
config.modResults.manifest.$ = {
19+
...config.modResults.manifest.$,
20+
'xmlns:tools': 'http://schemas.android.com/tools',
21+
};
22+
23+
const application = config.modResults.manifest.application![0];
24+
setFireBaseMessagingAndroidManifest(config, application);
25+
return config;
26+
});
27+
};
28+
29+
export function setFireBaseMessagingAndroidManifest(
30+
config: ExpoConfig,
31+
application: ManifestApplication,
32+
) {
33+
// If the notification object is not defined, print a friendly warning
34+
if (!config.notification) {
35+
// This warning is important because the notification icon can only use pure white on Android. By default, the system uses the app icon as the notification icon, but the app icon is usually not pure white, so you need to set the notification icon
36+
// eslint-disable-next-line no-console
37+
console.warn(
38+
'For Android 8.0 and above, it is necessary to set the notification icon to ensure correct display. Otherwise, the notification will not show the correct icon. For more information, visit https://docs.expo.dev/versions/latest/config/app/#notification',
39+
);
40+
return config;
41+
}
42+
43+
// Defensive code
44+
application['meta-data'] ??= [];
45+
46+
const metaData = application['meta-data'];
47+
48+
if (
49+
config.notification.icon &&
50+
!hasMetaData(application, 'com.google.firebase.messaging.default_notification_icon')
51+
) {
52+
// Expo will automatically create '@drawable/notification_icon' resource if you specify config.notification.icon.
53+
metaData.push({
54+
$: {
55+
'android:name': 'com.google.firebase.messaging.default_notification_icon',
56+
'android:resource': '@drawable/notification_icon',
57+
},
58+
});
59+
}
60+
61+
if (
62+
config.notification.color &&
63+
!hasMetaData(application, 'com.google.firebase.messaging.default_notification_color')
64+
) {
65+
metaData.push({
66+
$: {
67+
'android:name': 'com.google.firebase.messaging.default_notification_color',
68+
'android:resource': '@color/notification_icon_color',
69+
// @react-native-firebase/messaging will automatically configure the notification color from the 'firebase.json' file, setting 'tools:replace' = 'android:resource' to overwrite it.
70+
// @ts-ignore
71+
'tools:replace': 'android:resource',
72+
},
73+
});
74+
}
75+
76+
return application;
77+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ConfigPlugin, withPlugins, createRunOncePlugin } from '@expo/config-plugins';
2+
import { withExpoPluginFirebaseNotification } from './android';
3+
4+
/**
5+
* A config plugin for configuring `@react-native-firebase/app`
6+
*/
7+
const withRnFirebaseApp: ConfigPlugin = config => {
8+
return withPlugins(config, [
9+
// iOS
10+
11+
// Android
12+
withExpoPluginFirebaseNotification,
13+
]);
14+
};
15+
16+
const pak = require('@react-native-firebase/messaging/package.json');
17+
export default createRunOncePlugin(withRnFirebaseApp, pak.name, pak.version);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "@tsconfig/node-lts/tsconfig",
3+
"compilerOptions": {
4+
"outDir": "build",
5+
"rootDir": "src",
6+
"declaration": true
7+
},
8+
"include": ["./src"]
9+
}

yarn.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5054,8 +5054,14 @@ __metadata:
50545054
"@react-native-firebase/messaging@npm:18.6.2, @react-native-firebase/messaging@workspace:packages/messaging":
50555055
version: 0.0.0-use.local
50565056
resolution: "@react-native-firebase/messaging@workspace:packages/messaging"
5057+
dependencies:
5058+
expo: "npm:^49.0.20"
50575059
peerDependencies:
50585060
"@react-native-firebase/app": 18.6.2
5061+
expo: ">=47.0.0"
5062+
peerDependenciesMeta:
5063+
expo:
5064+
optional: true
50595065
languageName: unknown
50605066
linkType: soft
50615067

0 commit comments

Comments
 (0)