diff --git a/spec/APNS.spec.js b/spec/APNS.spec.js index ba9ac4f..fade9b9 100644 --- a/spec/APNS.spec.js +++ b/spec/APNS.spec.js @@ -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 = { diff --git a/spec/FCM.spec.js b/spec/FCM.spec.js index 4b48144..c5e67bc 100644 --- a/spec/FCM.spec.js +++ b/spec/FCM.spec.js @@ -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', () => { @@ -87,7 +85,7 @@ describe('FCM', () => { const requestData = { data: { - alert: 'alert', + alert: { body: 'alert', title: 'title' } }, notification: { title: 'I am a title', @@ -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); }); @@ -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); }); @@ -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); }); @@ -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); }); }); @@ -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', () => { @@ -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, @@ -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', () => { @@ -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, @@ -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', () => { @@ -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, @@ -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); }); }); diff --git a/src/FCM.js b/src/FCM.js index e6bd52e..4838cd4 100644 --- a/src/FCM.js +++ b/src/FCM.js @@ -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; @@ -237,7 +241,8 @@ function _APNSToFCMPayload(requestData) { return apnsPayload; } -function _GCMToFCMPayload(requestData, timeStamp) { +function _GCMToFCMPayload(requestData, pushId, timeStamp) { + const androidPayload = { android: { priority: 'high', @@ -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']) { @@ -281,10 +290,11 @@ 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; } @@ -292,7 +302,7 @@ function payloadConverter(requestData, pushType, timeStamp) { 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, @@ -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,