Skip to content

fix: FCM push notification payload incompatible with Parse Android SDK #238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
May 15, 2024
Merged
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
42 changes: 42 additions & 0 deletions spec/APNS.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,48 @@ describe('APNS', () => {
done();
});

it('can generate APNS notification with nested alert dictionary', (done) => {
//Mock request data
let data = {
'alert': { body: 'alert', title: 'title' },
'badge': 100,
'sound': 'test',
'content-available': 1,
'mutable-content': 1,
'targetContentIdentifier': 'window1',
'interruptionLevel': 'passive',
'category': 'INVITE_CATEGORY',
'threadId': 'a-thread-id',
'key': 'value',
'keyAgain': 'valueAgain'
};
let expirationTime = 1454571491354;
let collapseId = "collapseIdentifier";

let pushType = "alert";
let priority = 5;
let notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority });

expect(notification.aps.alert).toEqual({ body: 'alert', title: 'title' });
expect(notification.aps.badge).toEqual(data.badge);
expect(notification.aps.sound).toEqual(data.sound);
expect(notification.aps['content-available']).toEqual(1);
expect(notification.aps['mutable-content']).toEqual(1);
expect(notification.aps['target-content-id']).toEqual('window1');
expect(notification.aps['interruption-level']).toEqual('passive');
expect(notification.aps.category).toEqual(data.category);
expect(notification.aps['thread-id']).toEqual(data.threadId);
expect(notification.payload).toEqual({
'key': 'value',
'keyAgain': 'valueAgain'
});
expect(notification.expiry).toEqual(Math.round(expirationTime / 1000));
expect(notification.collapseId).toEqual(collapseId);
expect(notification.pushType).toEqual(pushType);
expect(notification.priority).toEqual(priority);
done();
});

it('sets push type to alert if not defined explicitly', (done) => {
//Mock request data
let data = {
Expand Down
49 changes: 18 additions & 31 deletions spec/FCM.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ describe('FCM', () => {
expect(payload.data.android).toEqual(requestData.rawPayload.android);
expect(payload.data.apns).toEqual(requestData.rawPayload.apns);
expect(payload.data.tokens).toEqual(['testToken']);
expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
});

it('can slice devices', () => {
Expand All @@ -87,7 +85,7 @@ describe('FCM', () => {

const requestData = {
data: {
alert: 'alert',
alert: { body: 'alert', title: 'title' }
},
notification: {
title: 'I am a title',
Expand All @@ -114,10 +112,10 @@ describe('FCM', () => {
expect(fcmPayload.android.ttl).toEqual(undefined);
expect(fcmPayload.android.notification).toEqual(requestData.notification);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
expect(fcmPayload.android.data['time']).toEqual(timeStampISOStr);
expect(fcmPayload.android.data['push_id']).toEqual(pushId);

const dataFromUser = fcmPayload.android.data;
const dataFromUser = JSON.parse(fcmPayload.android.data.data);
expect(dataFromUser).toEqual(requestData.data);
});

Expand Down Expand Up @@ -163,10 +161,10 @@ describe('FCM', () => {
);
expect(fcmPayload.android.notification).toEqual(requestData.notification);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
expect(fcmPayload.android.data['time']).toEqual(timeStampISOStr);
expect(fcmPayload.android.data['push_id']).toEqual(pushId);

const dataFromUser = fcmPayload.android.data;
const dataFromUser = JSON.parse(fcmPayload.android.data.data);
expect(dataFromUser).toEqual(requestData.data);
});

Expand Down Expand Up @@ -203,10 +201,10 @@ describe('FCM', () => {
expect(fcmPayload.android.ttl).toEqual(0);
expect(fcmPayload.android.notification).toEqual(requestData.notification);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
expect(fcmPayload.android.data['time']).toEqual(timeStampISOStr);
expect(fcmPayload.android.data['push_id']).toEqual(pushId);

const dataFromUser = fcmPayload.android.data;
const dataFromUser = JSON.parse(fcmPayload.android.data.data);
expect(dataFromUser).toEqual(requestData.data);
});

Expand Down Expand Up @@ -244,10 +242,10 @@ describe('FCM', () => {
expect(fcmPayload.android.ttl).toEqual(4 * 7 * 24 * 60 * 60);
expect(fcmPayload.android.notification).toEqual(requestData.notification);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
expect(fcmPayload.android.data['time']).toEqual(timeStampISOStr);
expect(fcmPayload.android.data['push_id']).toEqual(pushId);

const dataFromUser = fcmPayload.android.data;
const dataFromUser = JSON.parse(fcmPayload.android.data.data);
expect(dataFromUser).toEqual(requestData.data);
});
});
Expand Down Expand Up @@ -329,9 +327,6 @@ describe('FCM', () => {
expect(fcmPayload.apns.headers['apns-collapse-id']).toEqual(collapseId);
expect(fcmPayload.apns.headers['apns-push-type']).toEqual(pushType);
expect(fcmPayload.apns.headers['apns-priority']).toEqual(priority);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
});

it('sets push type to alert if not defined explicitly', () => {
Expand All @@ -348,9 +343,9 @@ describe('FCM', () => {
keyAgain: 'valueAgain',
};

// unused when generating apple payload, required by Parse Android SDK
const pushId = 'pushId';
const timeStamp = 1454538822113;
const timeStampISOStr = new Date(timeStamp).toISOString();

const payload = FCM.generateFCMPayload(
data,
Expand All @@ -362,8 +357,6 @@ describe('FCM', () => {
const fcmPayload = payload.data;

expect(fcmPayload.apns.headers['apns-push-type']).toEqual('alert');
expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
});

it('can generate APNS notification from raw data', () => {
Expand All @@ -389,9 +382,9 @@ describe('FCM', () => {
keyAgain: 'valueAgain',
};

// unused when generating apple payload, required by Parse Android SDK
const pushId = 'pushId';
const timeStamp = 1454538822113;
const timeStampISOStr = new Date(timeStamp).toISOString();

const payload = FCM.generateFCMPayload(
data,
Expand All @@ -417,9 +410,6 @@ describe('FCM', () => {
expect(fcmPayload.apns.payload.aps['thread-id']).toEqual('a-thread-id');
expect(fcmPayload.apns.payload.key).toEqual('value');
expect(fcmPayload.apns.payload.keyAgain).toEqual('valueAgain');

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
});

it('can generate an APNS notification with headers in data', () => {
Expand All @@ -433,16 +423,16 @@ describe('FCM', () => {
let data = {
expiration_time: expirationTime,
data: {
alert: 'alert',
alert: { body: 'alert', title: 'title' },
collapse_id: collapseId,
push_type: pushType,
priority: 6,
},
};

// unused when generating apple payload, required by Parse Android SDK
const pushId = 'pushId';
const timeStamp = 1454538822113;
const timeStampISOStr = new Date(timeStamp).toISOString();

const payload = FCM.generateFCMPayload(
data,
Expand All @@ -454,16 +444,13 @@ describe('FCM', () => {

const fcmPayload = payload.data;

expect(fcmPayload.apns.payload.aps.alert).toEqual({ body: 'alert' });
expect(fcmPayload.apns.payload.aps.alert).toEqual({ body: 'alert', title: 'title' });
expect(fcmPayload.apns.headers['apns-expiration']).toEqual(
Math.round(expirationTime / 1000),
);
expect(fcmPayload.apns.headers['apns-collapse-id']).toEqual(collapseId);
expect(fcmPayload.apns.headers['apns-push-type']).toEqual(pushType);
expect(fcmPayload.apns.headers['apns-priority']).toEqual(6);

expect(payload.time).toEqual(timeStampISOStr);
expect(payload['push_id']).toEqual(pushId);
});
});

Expand Down
38 changes: 23 additions & 15 deletions src/FCM.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,20 +178,24 @@ function _APNSToFCMPayload(requestData) {
apnsPayload['apns']['payload']['aps'] = coreData.aps;
break;
case 'alert':
if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
if (typeof coreData.alert == 'object') {
// When we receive a dictionary, use as is to remain
// compatible with how the APNS.js + node-apn work
apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert;
} else {
// When we receive a value, prepare `alert` dictionary
// and set its `body` property
apnsPayload['apns']['payload']['aps']['alert'] = {};
apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
}
// In APNS.js we set a body with the same value as alert in requestData.
// See L200 in APNS.spec.js
apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
break;
case 'title':
// Ensure the alert object exists before trying to assign the title
// title always goes into the nested `alert` dictionary
if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
apnsPayload['apns']['payload']['aps']['alert'] = {};
}
apnsPayload['apns']['payload']['aps']['alert']['title'] =
coreData.title;
apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title;
break;
case 'badge':
apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge;
Expand Down Expand Up @@ -237,7 +241,8 @@ function _APNSToFCMPayload(requestData) {
return apnsPayload;
}

function _GCMToFCMPayload(requestData, timeStamp) {
function _GCMToFCMPayload(requestData, pushId, timeStamp) {

const androidPayload = {
android: {
priority: 'high',
Expand All @@ -255,7 +260,11 @@ function _GCMToFCMPayload(requestData, timeStamp) {
delete requestData.data[key]
}
}
androidPayload.android.data = requestData.data;
androidPayload.android.data = {
push_id: pushId,
time: new Date(timeStamp).toISOString(),
data: JSON.stringify(requestData.data),
}
}

if (requestData['expiration_time']) {
Expand All @@ -281,18 +290,19 @@ function _GCMToFCMPayload(requestData, timeStamp) {
* If the key rawPayload is present in the requestData, a raw payload will be used. Otherwise, conversion is done.
* @param {Object} requestData The request body
* @param {String} pushType Either apple or android.
* @param {Number} timeStamp Used during GCM payload conversion for ttl
* @param {String} pushId Used during GCM payload conversion, required by Parse Android SDK.
* @param {Number} timeStamp Used during GCM payload conversion for ttl, required by Parse Android SDK.
* @returns {Object} A FCMv1-compatible payload.
*/
function payloadConverter(requestData, pushType, timeStamp) {
function payloadConverter(requestData, pushType, pushId, timeStamp) {
if (requestData.hasOwnProperty('rawPayload')) {
return requestData.rawPayload;
}

if (pushType === 'apple') {
return _APNSToFCMPayload(requestData);
} else if (pushType === 'android') {
return _GCMToFCMPayload(requestData, timeStamp);
return _GCMToFCMPayload(requestData, pushId, timeStamp);
} else {
throw new Parse.Error(
Parse.Error.PUSH_MISCONFIGURED,
Expand Down Expand Up @@ -320,12 +330,10 @@ function generateFCMPayload(
delete requestData['where'];

const payloadToUse = {
data: {},
push_id: pushId,
time: new Date(timeStamp).toISOString(),
data: {}
};

const fcmPayload = payloadConverter(requestData, pushType, timeStamp);
const fcmPayload = payloadConverter(requestData, pushType, pushId, timeStamp);
payloadToUse.data = {
...fcmPayload,
tokens: deviceTokens,
Expand Down
Loading