diff --git a/README.md b/README.md index 268552d..964f9d6 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,10 @@ The official Push Notification adapter for Parse Server. See [Parse Server Push - [Configure Parse Server](#configure-parse-server) - [Apple Push Options](#apple-push-options) - [Android Push Options](#android-push-options) + - [Firebase Cloud Messaging (FCM)](#firebase-cloud-messaging-fcm) - [Google Cloud Service Account Key](#google-cloud-service-account-key) - [Migration to FCM HTTP v1 API (June 2024)](#migration-to-fcm-http-v1-api-june-2024) + - [HTTP/1.1 Legacy Option](#http11-legacy-option) - [Expo Push Options](#expo-push-options) - [Bundled with Parse Server](#bundled-with-parse-server) - [Logging](#logging) @@ -110,6 +112,10 @@ android: { } ``` +### Firebase Cloud Messaging (FCM) + +This section contains some considerations when using FCM, regardless of the destination ecosystems the push notification is sent to. + #### Google Cloud Service Account Key The Firebase console allows to easily create and download a Google Cloud service account key JSON file with the required permissions. Instead of setting `firebaseServiceAccount` to the path of the JSON file, you can provide an object representing a Google Cloud service account key: @@ -139,6 +145,19 @@ android: { } ``` +#### HTTP/1.1 Legacy Option + +With the introduction of the FCM HTTP v1 API, support for HTTP/2 was added which provides faster throughput for push notifications. To use the older version HTTP/1.1 set `fcmEnableLegacyHttpTransport: true` in your push options. + +Example options: + +```js +android: { + firebaseServiceAccount: __dirname + '/firebase.json', + fcmEnableLegacyHttpTransport: true +} +``` + ### Expo Push Options Example options: diff --git a/package-lock.json b/package-lock.json index 5054bb9..8fadca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@parse/node-apn": "6.0.1", "@parse/node-gcm": "1.0.2", "expo-server-sdk": "3.10.0", - "firebase-admin": "12.2.0", + "firebase-admin": "12.3.0", "npmlog": "7.0.1", "parse": "5.2.0", "web-push": "3.6.7" @@ -224,12 +224,9 @@ } }, "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "engines": { - "node": ">=14" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.0.0.tgz", + "integrity": "sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==" }, "node_modules/@firebase/app-check-interop-types": { "version": "0.3.0", @@ -3479,19 +3476,17 @@ } }, "node_modules/firebase-admin": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.2.0.tgz", - "integrity": "sha512-R9xxENvPA/19XJ3mv0Kxfbz9kPXd9/HrM4083LZWOO0qAQGheRzcCQamYRe+JSrV2cdKXP3ZsfFGTYMrFM0pJg==", - "license": "Apache-2.0", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.0.tgz", + "integrity": "sha512-AKJcFbOZ7W8Fwcqh6Ba7FThXVoXwPdsf+E9vyjk5Z1vN1Z9mnTw88EQWfIsR91YglQ0KvWu1rvMhW65bcB4sog==", "dependencies": { - "@fastify/busboy": "^2.1.0", + "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^1.0.2", "@firebase/database-types": "^1.0.0", "@types/node": "^20.10.3", "farmhash-modern": "^1.1.0", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", - "long": "^5.2.3", "node-forge": "^1.3.1", "uuid": "^10.0.0" }, @@ -4958,7 +4953,8 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/lru-cache": { "version": "10.3.1", @@ -10202,9 +10198,9 @@ "dev": true }, "@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.0.0.tgz", + "integrity": "sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==" }, "@firebase/app-check-interop-types": { "version": "0.3.0", @@ -12606,11 +12602,11 @@ } }, "firebase-admin": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.2.0.tgz", - "integrity": "sha512-R9xxENvPA/19XJ3mv0Kxfbz9kPXd9/HrM4083LZWOO0qAQGheRzcCQamYRe+JSrV2cdKXP3ZsfFGTYMrFM0pJg==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.0.tgz", + "integrity": "sha512-AKJcFbOZ7W8Fwcqh6Ba7FThXVoXwPdsf+E9vyjk5Z1vN1Z9mnTw88EQWfIsR91YglQ0KvWu1rvMhW65bcB4sog==", "requires": { - "@fastify/busboy": "^2.1.0", + "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^1.0.2", "@firebase/database-types": "^1.0.0", "@google-cloud/firestore": "^7.7.0", @@ -12619,7 +12615,6 @@ "farmhash-modern": "^1.1.0", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", - "long": "^5.2.3", "node-forge": "^1.3.1", "uuid": "^10.0.0" }, @@ -13742,7 +13737,8 @@ "long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "optional": true }, "lru-cache": { "version": "10.3.1", diff --git a/package.json b/package.json index eb91d4d..0474440 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@parse/node-apn": "6.0.1", "@parse/node-gcm": "1.0.2", "expo-server-sdk": "3.10.0", - "firebase-admin": "12.2.0", + "firebase-admin": "12.3.0", "npmlog": "7.0.1", "parse": "5.2.0", "web-push": "3.6.7" diff --git a/spec/FCM.spec.js b/spec/FCM.spec.js index ce84b90..f86102d 100644 --- a/spec/FCM.spec.js +++ b/spec/FCM.spec.js @@ -1,6 +1,7 @@ import path from 'path'; import log from 'npmlog'; import FCM from '../src/FCM.js'; +import { getApps, deleteApp } from 'firebase-admin/app'; const testArgs = { firebaseServiceAccount: path.join( @@ -13,6 +14,10 @@ const testArgs = { }; describe('FCM', () => { + beforeEach(async () => { + getApps().forEach(app => deleteApp(app)); + }); + it('can initialize', () => { const fcm = new FCM(testArgs); expect(fcm).toBeDefined(); @@ -31,6 +36,37 @@ describe('FCM', () => { expect(spy).toHaveBeenCalledWith('parse-server-push-adapter FCM', 'invalid push payload'); }); + it('initializes with fcmEnableLegacyHttpTransport set to false by default', () => { + const fcm = new FCM(testArgs); + expect(fcm).toBeDefined(); + expect(fcm.sender).toBeDefined(); + expect(fcm.sender.useLegacyTransport).toEqual(false); + }); + + it('can initialize with fcmEnableLegacyHttpTransport set to false', () => { + const legacyHttpTransportArgs = { + ...testArgs, + fcmEnableLegacyHttpTransport: false + }; + + const fcm = new FCM(legacyHttpTransportArgs); + expect(fcm).toBeDefined(); + expect(fcm.sender).toBeDefined(); + expect(fcm.sender.useLegacyTransport).toEqual(false); + }); + + it('can initialize with fcmEnableLegacyHttpTransport set to true', () => { + const legacyHttpTransportArgs = { + ...testArgs, + fcmEnableLegacyHttpTransport: true + }; + + const fcm = new FCM(legacyHttpTransportArgs); + expect(fcm).toBeDefined(); + expect(fcm.sender).toBeDefined(); + expect(fcm.sender.useLegacyTransport).toEqual(true); + }); + it('can send successful FCM android request', async () => { const spyVerbose = spyOn(log, 'verbose').and.callFake(() => {}); const spyInfo = spyOn(log, 'info').and.callFake(() => {}); diff --git a/src/FCM.js b/src/FCM.js index 400e020..b6fc9b1 100644 --- a/src/FCM.js +++ b/src/FCM.js @@ -25,14 +25,26 @@ export default function FCM(args, pushType) { ); } + const fcmEnableLegacyHttpTransport = typeof args.fcmEnableLegacyHttpTransport === 'boolean' + ? args.fcmEnableLegacyHttpTransport + : false; + let app; if (getApps().length === 0) { app = initializeApp({ credential: cert(args.firebaseServiceAccount) }); } else { app = getApp(); } + this.sender = getMessaging(app); - this.pushType = pushType; // Push type is only used to remain backwards compatible with APNS and GCM + + if (fcmEnableLegacyHttpTransport) { + this.sender.enableLegacyHttpTransport(); + log.warn(LOG_PREFIX, 'Legacy HTTP/1.1 transport is enabled. This is a deprecated feature and support for this flag will be removed in the future.'); + } + + // Push type is only used to remain backwards compatible with APNS and GCM + this.pushType = pushType; } FCM.FCMRegistrationTokensMax = FCMRegistrationTokensMax;