Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,13 +580,27 @@ 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
new gcm.Message({ ..., notification: data.fcm_notification })
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: { ...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
{
Expand Down
4 changes: 3 additions & 1 deletion src/utils/fcmMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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();

Expand Down
21 changes: 15 additions & 6 deletions src/utils/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const containsValidRecipients = (obj) => {
};

const buildGcmNotification = (data) => {
const notification = data.fcm_notification || {
const notification = {
title: data.title,
body: data.body,
icon: data.icon,
Expand All @@ -107,6 +107,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;
};

Expand All @@ -126,11 +131,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;
}
Expand Down
64 changes: 64 additions & 0 deletions test/send/sendFCM.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,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);
});
});
});
47 changes: 47 additions & 0 deletions test/send/sendGCM.js
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,53 @@ 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: {
Expand Down