Skip to content

Commit 9905c34

Browse files
authored
fix: APN notification topic not composed based on push type (#347)
1 parent 3cb34c4 commit 9905c34

File tree

2 files changed

+158
-15
lines changed

2 files changed

+158
-15
lines changed

spec/APNS.spec.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,111 @@ describe('APNS', () => {
322322
done();
323323
});
324324

325+
it('generating notification prioritizes header information from notification data', async () => {
326+
const data = {
327+
'id': 'hello',
328+
'requestId': 'world',
329+
'channelId': 'foo',
330+
'topic': 'bundle',
331+
'expiry': 20,
332+
'collapseId': 'collapse',
333+
'pushType': 'alert',
334+
'priority': 7,
335+
};
336+
const id = 'foo';
337+
const requestId = 'hello';
338+
const channelId = 'world';
339+
const topic = 'bundleId';
340+
const expirationTime = 1454571491354;
341+
const collapseId = "collapseIdentifier";
342+
const pushType = "background";
343+
const priority = 5;
344+
345+
const notification = APNS._generateNotification(data, { id: id, requestId: requestId, channelId: channelId, topic: topic, expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority });
346+
expect(notification.id).toEqual(data.id);
347+
expect(notification.requestId).toEqual(data.requestId);
348+
expect(notification.channelId).toEqual(data.channelId);
349+
expect(notification.topic).toEqual(data.topic);
350+
expect(notification.expiry).toEqual(data.expiry);
351+
expect(notification.collapseId).toEqual(data.collapseId);
352+
expect(notification.pushType).toEqual(data.pushType);
353+
expect(notification.priority).toEqual(data.priority);
354+
expect(Object.keys(notification.payload).length).toBe(0);
355+
});
356+
357+
it('generating notification does not override default notification info when header info is missing', async () => {
358+
const data = {};
359+
const topic = 'bundleId';
360+
const collapseId = "collapseIdentifier";
361+
const pushType = "background";
362+
363+
const notification = APNS._generateNotification(data, { topic: topic, collapseId: collapseId, pushType: pushType });
364+
expect(notification.topic).toEqual(topic);
365+
expect(notification.expiry).toEqual(-1);
366+
expect(notification.collapseId).toEqual(collapseId);
367+
expect(notification.pushType).toEqual(pushType);
368+
expect(notification.priority).toEqual(10);
369+
});
370+
371+
it('generating notification updates topic properly', async () => {
372+
const data = {};
373+
const topic = 'bundleId';
374+
const pushType = "liveactivity";
375+
376+
const notification = APNS._generateNotification(data, { topic: topic, pushType: pushType });
377+
expect(notification.topic).toEqual(topic + '.push-type.liveactivity');
378+
expect(notification.pushType).toEqual(pushType);
379+
});
380+
381+
it('defaults to original topic', async () => {
382+
const topic = 'bundleId';
383+
const pushType = 'alert';
384+
const updatedTopic = APNS._determineTopic(topic, pushType);
385+
expect(updatedTopic).toEqual(topic);
386+
});
387+
388+
it('updates topic based on location pushType', async () => {
389+
const topic = 'bundleId';
390+
const pushType = 'location';
391+
const updatedTopic = APNS._determineTopic(topic, pushType);
392+
expect(updatedTopic).toEqual(topic + '.location-query');
393+
});
394+
395+
it('updates topic based on voip pushType', async () => {
396+
const topic = 'bundleId';
397+
const pushType = 'voip';
398+
const updatedTopic = APNS._determineTopic(topic, pushType);
399+
expect(updatedTopic).toEqual(topic + '.voip');
400+
});
401+
402+
it('updates topic based on complication pushType', async () => {
403+
const topic = 'bundleId';
404+
const pushType = 'complication';
405+
const updatedTopic = APNS._determineTopic(topic, pushType);
406+
expect(updatedTopic).toEqual(topic + '.complication');
407+
});
408+
409+
it('updates topic based on complication pushType', async () => {
410+
const topic = 'bundleId';
411+
const pushType = 'fileprovider';
412+
const updatedTopic = APNS._determineTopic(topic, pushType);
413+
expect(updatedTopic).toEqual(topic + '.pushkit.fileprovider');
414+
});
415+
416+
it('updates topic based on liveactivity pushType', async () => {
417+
const topic = 'bundleId';
418+
const pushType = 'liveactivity';
419+
const updatedTopic = APNS._determineTopic(topic, pushType);
420+
expect(updatedTopic).toEqual(topic + '.push-type.liveactivity');
421+
});
422+
423+
it('updates topic based on pushtotalk pushType', async () => {
424+
const topic = 'bundleId';
425+
const pushType = 'pushtotalk';
426+
const updatedTopic = APNS._determineTopic(topic, pushType);
427+
expect(updatedTopic).toEqual(topic + '.voip-ptt');
428+
});
429+
325430
it('can choose providers for device with valid appIdentifier', (done) => {
326431
const appIdentifier = 'topic';
327432
// Mock providers

src/APNS.js

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class APNS {
151151
static _createProvider(apnsArgs) {
152152
// if using certificate, then topic must be defined
153153
if (!APNS._validateAPNArgs(apnsArgs)) {
154-
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is mssing for %j', apnsArgs);
154+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is missing for %j', apnsArgs);
155155
}
156156

157157
const provider = new apn.Provider(apnsArgs);
@@ -213,6 +213,16 @@ export class APNS {
213213
case 'threadId':
214214
notification.setThreadId(coreData.threadId);
215215
break;
216+
case 'id':
217+
case 'collapseId':
218+
case 'channelId':
219+
case 'requestId':
220+
case 'pushType':
221+
case 'topic':
222+
case 'expiry':
223+
case 'priority':
224+
// Header information is skipped and added later.
225+
break;
216226
default:
217227
payload[key] = coreData[key];
218228
break;
@@ -221,32 +231,60 @@ export class APNS {
221231

222232
notification.payload = payload;
223233

224-
notification.topic = headers.topic;
225-
notification.expiry = Math.round(headers.expirationTime / 1000);
226-
notification.collapseId = headers.collapseId;
234+
// Update header information if necessary.
235+
notification.id = coreData.id ?? headers.id;
236+
notification.collapseId = coreData.collapseId ?? headers.collapseId;
237+
notification.requestId = coreData.requestId ?? headers.requestId;
238+
notification.channelId = coreData.channelId ?? headers.channelId;
227239
// set alert as default push type. If push type is not set notifications are not delivered to devices running iOS 13, watchOS 6 and later.
228-
notification.pushType = 'alert';
229-
if (headers.pushType) {
230-
notification.pushType = headers.pushType;
231-
}
232-
if (headers.priority) {
233-
// if headers priority is not set 'node-apn' defaults it to 5 which is min. required value for background pushes to launch the app in background.
234-
notification.priority = headers.priority
240+
const pushType = coreData.pushType ?? headers.pushType ?? 'alert';
241+
notification.pushType = pushType;
242+
const topic = coreData.topic ?? APNS._determineTopic(headers.topic, pushType);
243+
notification.topic = topic;
244+
let expiry = notification.expiry;
245+
if (headers.expirationTime) {
246+
expiry = Math.round(headers.expirationTime / 1000);
235247
}
248+
notification.expiry = coreData.expiry ?? expiry;
249+
// if headers priority is not set 'node-apn' defaults it to notification's default value. Required value for background pushes to launch the app in background.
250+
notification.priority = coreData.priority ?? headers.priority ?? notification.priority;
251+
236252
return notification;
237253
}
238254

255+
/**
256+
* Updates the topic based on the pushType.
257+
*
258+
* @param {String} topic The current topic to append additional information to for required provider
259+
* @param {any} pushType The current push type of the notification
260+
* @returns {String} Returns the updated topic
261+
*/
262+
static _determineTopic(topic, pushType) {
263+
switch(pushType) {
264+
case 'location':
265+
return topic + '.location-query';
266+
case 'voip':
267+
return topic + '.voip';
268+
case 'complication':
269+
return topic + '.complication';
270+
case 'fileprovider':
271+
return topic + '.pushkit.fileprovider';
272+
case 'liveactivity':
273+
return topic + '.push-type.liveactivity';
274+
case 'pushtotalk':
275+
return topic + '.voip-ptt';
276+
default:
277+
return topic;
278+
}
279+
}
280+
239281
/**
240282
* Choose appropriate providers based on device appIdentifier.
241283
*
242284
* @param {String} appIdentifier appIdentifier for required provider
243285
* @returns {Array} Returns Array with appropriate providers
244286
*/
245287
_chooseProviders(appIdentifier) {
246-
// If the device we need to send to does not have appIdentifier, any provider could be a qualified provider
247-
/*if (!appIdentifier || appIdentifier === '') {
248-
return this.providers.map((provider) => provider.index);
249-
}*/
250288

251289
// Otherwise we try to match the appIdentifier with topic on provider
252290
const qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic);

0 commit comments

Comments
 (0)