From 6c6c0db07ae074c87c3733843bfc46ac0944cdfc Mon Sep 17 00:00:00 2001 From: Alexander Friedl Date: Fri, 5 Feb 2021 22:14:00 +0100 Subject: [PATCH] Fix handling of loc-args and title-loc-args in sendAPN --- README.md | 12 ++--- src/sendAPN.js | 19 +++++-- test/send/sendAPN.js | 122 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e568b33..6ff4ea5 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,8 @@ const data = { clickAction: '', // gcm for android. In ios, category will be used if not supplied locKey: '', // gcm, apn titleLocKey: '', // gcm, apn - locArgs: undefined, // gcm, apn - titleLocArgs: undefined, // gcm, apn + locArgs: undefined, // gcm, apn. Expected format: Stringified Array + titleLocArgs: undefined, // gcm, apn. Expected format: Stringified Array retries: 1, // gcm, apn encoding: '', // apn badge: 2, // gcm for ios, apn @@ -290,9 +290,9 @@ The following parameters are used to create a GCM message. See https://developer title: data.title, // Android, iOS (Watch) body: data.body, // Android, iOS icon: data.icon, // Android - image: data.image, // Android - style: data.style, // Android - picture: data.picture, // Android + image: data.image, // Android + style: data.style, // Android + picture: data.picture, // Android sound: data.sound, // Android, iOS badge: data.badge, // iOS tag: data.tag, // Android @@ -302,7 +302,7 @@ The following parameters are used to create a GCM message. See https://developer body_loc_args: data.locArgs, // Android, iOS title_loc_key: data.titleLocKey, // Android, iOS title_loc_args: data.titleLocArgs, // Android, iOS - android_channel_id: data.android_channel_id, // Android + android_channel_id: data.android_channel_id, // Android }, } ``` diff --git a/src/sendAPN.js b/src/sendAPN.js index e94ab51..5798482 100644 --- a/src/sendAPN.js +++ b/src/sendAPN.js @@ -20,6 +20,15 @@ const getPropValueOrUndefinedIfIsSilent = (propName, data) => R.prop(propName) )(data); +const toJSONorUndefined = R.tryCatch(JSON.parse, R.always(undefined)); + +const alertLocArgsToJSON = R.evolve({ + alert: { + 'title-loc-args': toJSONorUndefined, + 'loc-args': toJSONorUndefined, + }, +}); + const getDefaultAlert = (data) => ({ title: data.title, body: data.body, @@ -32,13 +41,15 @@ const getDefaultAlert = (data) => ({ action: data.action, }); -const pushDataWithDefaultAlert = (data) => +const alertOrDefault = (data) => R.when( R.propSatisfies(R.isNil, 'alert'), - R.assoc('alert', getDefaultAlert(data)), - data + R.assoc('alert', getDefaultAlert(data)) ); +const getParsedAlertOrDefault = (data) => + R.pipe(alertOrDefault(data), alertLocArgsToJSON)(data); + class APN { constructor(settings) { try { @@ -66,7 +77,7 @@ class APN { sound: getPropValueOrUndefinedIfIsSilent('sound', data), alert: getPropValueOrUndefinedIfIsSilent( 'alert', - pushDataWithDefaultAlert(data) + getParsedAlertOrDefault(data) ), topic: data.topic, category: data.category || data.clickAction, diff --git a/test/send/sendAPN.js b/test/send/sendAPN.js index e6d9b62..2fcbde8 100644 --- a/test/send/sendAPN.js +++ b/test/send/sendAPN.js @@ -71,6 +71,16 @@ function sendOkMethod() { expect(message.aps.sound).to.eql(data.sound); expect(message.aps.alert.title).to.eql(data.title); expect(message.aps.alert.body).to.equal(data.body); + expect(message.aps.alert).to.eql({ + body: data.body, + title: data.title, + action: undefined, + 'launch-image': undefined, + 'title-loc-key': undefined, + 'title-loc-args': undefined, + 'loc-key': undefined, + 'loc-args': undefined, + }); expect(message.priority).to.equal(10); expect(message.payload).to.eql(data.custom); return Promise.resolve({ @@ -453,6 +463,118 @@ describe('push-notifications-apn', () => { }); }); + describe('parse title-loc-args and loc-args', () => { + describe('when valid string args are passed in alert object', () => { + before(() => { + sendMethod = sinon.stub( + apn.Provider.prototype, + 'send', + (message, _regIds) => { + expect(_regIds).to.be.instanceOf(Array); + _regIds.forEach((regId) => expect(regIds).to.include(regId)); + expect(message).to.be.instanceOf(apn.Notification); + expect(message.aps.alert['title-loc-args']).to.deep.equal([ + { a: 1 }, + ]); + expect(message.aps.alert['loc-args']).to.deep.equal([{ b: 2 }]); + return Promise.resolve({ + sent: _regIds, + }); + } + ); + }); + + after(() => { + sendMethod.restore(); + }); + + it('should parse the stringified arrays to JSON', (done) => { + const locArgsData = { + ...data, + alert: { + 'title-loc-args': '[{"a":1}]', + 'loc-args': '[{"b":2}]', + }, + }; + pn.send(regIds, locArgsData, (err, results) => + testSuccess(err, results, done) + ); + }); + }); + + describe('when valid string args are passed in top-level data', () => { + before(() => { + sendMethod = sinon.stub( + apn.Provider.prototype, + 'send', + (message, _regIds) => { + expect(_regIds).to.be.instanceOf(Array); + _regIds.forEach((regId) => expect(regIds).to.include(regId)); + expect(message).to.be.instanceOf(apn.Notification); + expect(message.aps.alert['title-loc-args']).to.deep.equal([ + { a: 1 }, + ]); + expect(message.aps.alert['loc-args']).to.deep.equal([{ b: 2 }]); + return Promise.resolve({ + sent: _regIds, + }); + } + ); + }); + + after(() => { + sendMethod.restore(); + }); + + it('should parse the stringified arrays to JSON', (done) => { + const locArgsData = { + ...data, + titleLocArgs: '[{"a":1}]', + locArgs: '[{"b":2}]', + }; + pn.send(regIds, locArgsData, (err, results) => + testSuccess(err, results, done) + ); + }); + }); + + describe("when args can't be parsed to JSON", () => { + before(() => { + sendMethod = sinon.stub( + apn.Provider.prototype, + 'send', + (message, _regIds) => { + expect(_regIds).to.be.instanceOf(Array); + _regIds.forEach((regId) => expect(regIds).to.include(regId)); + expect(message).to.be.instanceOf(apn.Notification); + expect(message.aps.alert['title-loc-args']).to.be.undefined(); + expect(message.aps.alert['loc-args']).to.be.undefined(); + return Promise.resolve({ + sent: _regIds, + }); + } + ); + }); + + after(() => { + sendMethod.restore(); + }); + + it('should leave the input as is', (done) => { + const invalidLocArgsData = { + ...data, + alert: { + 'title-loc-args': '[{a:1}]', + 'loc-args': '[{b:2}]', + }, + }; + pn.send(regIds, invalidLocArgsData, (err, results) => + testSuccess(err, results, done) + ); + }); + }); + }); + describe('send push notifications failure (no response message)', () => { before(() => { sendMethod = sendFailureMethod1();