Skip to content

Commit

Permalink
fix: FCM push notification payload incompatible with Parse Android SDK (
Browse files Browse the repository at this point in the history
  • Loading branch information
mman committed May 15, 2024
1 parent 92f86bd commit 4274b7f
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 46 deletions.
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

0 comments on commit 4274b7f

Please sign in to comment.