diff --git a/README.md b/README.md index 10c8e58..0408443 100644 --- a/README.md +++ b/README.md @@ -578,13 +578,28 @@ pushNotifications.send(tokens, notifications, (error, result) => { }); ``` -`fcm_notification` - object that will be passed to +`fcm_notification` - object that will be **merged** with the notification fields. This allows you to override specific notification properties (like `channelId`, `ttl`, etc.) without duplicating standard fields like `title` and `body`. + +For example, to set a channel ID for Android: + +```js +const data = { + title: "My Title", + body: "My Message", + fcm_notification: { + channelId: "my-channel-id", + }, + custom: { id: 123 }, +}; +``` + +The `fcm_notification` object will be passed to ```js - new gcm.Message({ ..., notification: data.fcm_notification }) + new gcm.Message({ ..., notification: { ...builtNotification, ...data.fcm_notification } }) ``` -Fcm object that will be sent to provider ([Fcm message format](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#Message)) : +FCM object that will be sent to provider ([FCM message format](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#Message)) : ```json { diff --git a/src/utils/fcmMessage.js b/src/utils/fcmMessage.js index f5cfd56..58050c2 100644 --- a/src/utils/fcmMessage.js +++ b/src/utils/fcmMessage.js @@ -27,7 +27,9 @@ class FcmMessage { } static buildAndroidMessage(params, options) { - const message = buildGcmMessage(params, options); + // Mark as FCM so buildGcmMessage doesn't pollute custom data + const fcmOptions = { ...options, fcm: true }; + const message = buildGcmMessage(params, fcmOptions); const androidMessage = message.toJson(); diff --git a/src/utils/tools.js b/src/utils/tools.js index 5bb7eda..68b7a71 100644 --- a/src/utils/tools.js +++ b/src/utils/tools.js @@ -86,7 +86,7 @@ const containsValidRecipients = (obj) => { }; const buildGcmNotification = (data) => { - const notification = data.fcm_notification || { + const notification = { title: data.title, body: data.body, icon: data.icon, @@ -106,6 +106,11 @@ const buildGcmNotification = (data) => { notification_count: data.notificationCount || data.badge, }; + // Merge with fcm_notification overrides if provided + if (data.fcm_notification) { + return { ...notification, ...data.fcm_notification }; + } + return notification; }; @@ -125,11 +130,15 @@ const buildGcmMessage = (data, options) => { }; } - custom.title = custom.title || data.title; - custom.message = custom.message || data.body; - custom.sound = custom.sound || data.sound; - custom.icon = custom.icon || data.icon; - custom.msgcnt = custom.msgcnt || data.badge; + // Only add notification fields to custom data for GCM (not FCM) + // FCM uses separate notification and data fields + if (!options.fcm) { + custom.title = custom.title || data.title; + custom.message = custom.message || data.body; + custom.sound = custom.sound || data.sound; + custom.icon = custom.icon || data.icon; + custom.msgcnt = custom.msgcnt || data.badge; + } if (options.phonegap === true && data.contentAvailable) { custom["content-available"] = 1; } diff --git a/test/send/sendFCM.js b/test/send/sendFCM.js index 91bb560..d317e0b 100644 --- a/test/send/sendFCM.js +++ b/test/send/sendFCM.js @@ -76,4 +76,68 @@ describe("push-notifications-fcm", () => { .catch(done); }); }); + + describe("send push notifications with custom data", () => { + const customDataMessage = { + title: "Notification Title", + body: "Notification Body", + custom: { + userId: "12345", + actionId: "action-001", + deepLink: "app://section/item", + }, + }; + + let customDataSendMethod; + + function sendCustomDataMethod() { + return sinon.stub( + fbMessaging.prototype, + "sendEachForMulticast", + function sendFCMWithCustomData(firebaseMessage) { + const { custom } = customDataMessage; + + // Verify custom data is preserved in top-level data field + expect(firebaseMessage.data).to.deep.equal(custom); + + // Verify custom data does NOT pollute the notification + // Note: normalizeDataParams converts all values to strings (FCM requirement) + expect(firebaseMessage.android.data).to.deep.equal(custom); + expect(firebaseMessage.android.data).to.not.have.property("title"); + expect(firebaseMessage.android.data).to.not.have.property("body"); + + // Verify notification has proper fields (separate from data) + expect(firebaseMessage.android.notification).to.include({ + title: customDataMessage.title, + body: customDataMessage.body, + }); + + return Promise.resolve({ + successCount: 1, + failureCount: 0, + responses: [{ error: null }], + }); + } + ); + } + + before(() => { + customDataSendMethod = sendCustomDataMethod(); + }); + + after(() => { + customDataSendMethod.restore(); + }); + + it("custom data should be preserved and not mixed with notification fields", (done) => { + pn.send(regIds, customDataMessage) + .then((results) => { + expect(results).to.be.an("array"); + expect(results[0].method).to.equal("fcm"); + expect(results[0].success).to.equal(1); + done(); + }) + .catch(done); + }); + }); }); diff --git a/test/send/sendGCM.js b/test/send/sendGCM.js index 6f344ea..1f3b2cc 100644 --- a/test/send/sendGCM.js +++ b/test/send/sendGCM.js @@ -788,6 +788,47 @@ describe("push-notifications-gcm", () => { }); }); + describe("fcm_notification partial override (merge)", () => { + before(() => { + sendMethod = sinon.stub(gcm.Sender.prototype, "send", (message, recipients, retries, cb) => { + expect(recipients).to.be.instanceOf(Object); + expect(message).to.be.instanceOf(gcm.Message); + // Verify that title and body from main data are preserved + expect(message.params.notification.title).to.equal(data.title); + expect(message.params.notification.body).to.equal(data.body); + // Verify that fcm_notification overrides are applied + expect(message.params.notification.color).to.equal("#FF0000"); + // Verify custom data is present + expect(message.params.data.sender).to.equal(data.custom.sender); + + cb(null, { + multicast_id: "abc", + success: recipients.registrationTokens.length, + failure: 0, + results: recipients.registrationTokens.map((token) => ({ + message_id: "", + registration_id: token, + error: null, + })), + }); + }); + }); + + after(() => { + sendMethod.restore(); + }); + + it("should merge fcm_notification overrides with base notification", (done) => { + const androidData = { + ...data, + fcm_notification: { + color: "#FF0000", // Override only the color + }, + }; + pn.send(regIds, androidData, (err, results) => testSuccess(err, results, done)); + }); + }); + describe("send push notifications in phonegap-push compatibility mode", () => { const pushPhoneGap = new PN({ gcm: {