Skip to content

Commit

Permalink
fix: APN notification topic not composed based on push type (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbaker6 authored Jan 1, 2025
1 parent 3cb34c4 commit 9905c34
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 15 deletions.
105 changes: 105 additions & 0 deletions spec/APNS.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,111 @@ describe('APNS', () => {
done();
});

it('generating notification prioritizes header information from notification data', async () => {
const data = {
'id': 'hello',
'requestId': 'world',
'channelId': 'foo',
'topic': 'bundle',
'expiry': 20,
'collapseId': 'collapse',
'pushType': 'alert',
'priority': 7,
};
const id = 'foo';
const requestId = 'hello';
const channelId = 'world';
const topic = 'bundleId';
const expirationTime = 1454571491354;
const collapseId = "collapseIdentifier";
const pushType = "background";
const priority = 5;

const notification = APNS._generateNotification(data, { id: id, requestId: requestId, channelId: channelId, topic: topic, expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority });
expect(notification.id).toEqual(data.id);
expect(notification.requestId).toEqual(data.requestId);
expect(notification.channelId).toEqual(data.channelId);
expect(notification.topic).toEqual(data.topic);
expect(notification.expiry).toEqual(data.expiry);
expect(notification.collapseId).toEqual(data.collapseId);
expect(notification.pushType).toEqual(data.pushType);
expect(notification.priority).toEqual(data.priority);
expect(Object.keys(notification.payload).length).toBe(0);
});

it('generating notification does not override default notification info when header info is missing', async () => {
const data = {};
const topic = 'bundleId';
const collapseId = "collapseIdentifier";
const pushType = "background";

const notification = APNS._generateNotification(data, { topic: topic, collapseId: collapseId, pushType: pushType });
expect(notification.topic).toEqual(topic);
expect(notification.expiry).toEqual(-1);
expect(notification.collapseId).toEqual(collapseId);
expect(notification.pushType).toEqual(pushType);
expect(notification.priority).toEqual(10);
});

it('generating notification updates topic properly', async () => {
const data = {};
const topic = 'bundleId';
const pushType = "liveactivity";

const notification = APNS._generateNotification(data, { topic: topic, pushType: pushType });
expect(notification.topic).toEqual(topic + '.push-type.liveactivity');
expect(notification.pushType).toEqual(pushType);
});

it('defaults to original topic', async () => {
const topic = 'bundleId';
const pushType = 'alert';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic);
});

it('updates topic based on location pushType', async () => {
const topic = 'bundleId';
const pushType = 'location';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.location-query');
});

it('updates topic based on voip pushType', async () => {
const topic = 'bundleId';
const pushType = 'voip';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.voip');
});

it('updates topic based on complication pushType', async () => {
const topic = 'bundleId';
const pushType = 'complication';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.complication');
});

it('updates topic based on complication pushType', async () => {
const topic = 'bundleId';
const pushType = 'fileprovider';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.pushkit.fileprovider');
});

it('updates topic based on liveactivity pushType', async () => {
const topic = 'bundleId';
const pushType = 'liveactivity';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.push-type.liveactivity');
});

it('updates topic based on pushtotalk pushType', async () => {
const topic = 'bundleId';
const pushType = 'pushtotalk';
const updatedTopic = APNS._determineTopic(topic, pushType);
expect(updatedTopic).toEqual(topic + '.voip-ptt');
});

it('can choose providers for device with valid appIdentifier', (done) => {
const appIdentifier = 'topic';
// Mock providers
Expand Down
68 changes: 53 additions & 15 deletions src/APNS.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class APNS {
static _createProvider(apnsArgs) {
// if using certificate, then topic must be defined
if (!APNS._validateAPNArgs(apnsArgs)) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is mssing for %j', apnsArgs);
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is missing for %j', apnsArgs);
}

const provider = new apn.Provider(apnsArgs);
Expand Down Expand Up @@ -213,6 +213,16 @@ export class APNS {
case 'threadId':
notification.setThreadId(coreData.threadId);
break;
case 'id':
case 'collapseId':
case 'channelId':
case 'requestId':
case 'pushType':
case 'topic':
case 'expiry':
case 'priority':
// Header information is skipped and added later.
break;
default:
payload[key] = coreData[key];
break;
Expand All @@ -221,32 +231,60 @@ export class APNS {

notification.payload = payload;

notification.topic = headers.topic;
notification.expiry = Math.round(headers.expirationTime / 1000);
notification.collapseId = headers.collapseId;
// Update header information if necessary.
notification.id = coreData.id ?? headers.id;
notification.collapseId = coreData.collapseId ?? headers.collapseId;
notification.requestId = coreData.requestId ?? headers.requestId;
notification.channelId = coreData.channelId ?? headers.channelId;
// 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.
notification.pushType = 'alert';
if (headers.pushType) {
notification.pushType = headers.pushType;
}
if (headers.priority) {
// 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.
notification.priority = headers.priority
const pushType = coreData.pushType ?? headers.pushType ?? 'alert';
notification.pushType = pushType;
const topic = coreData.topic ?? APNS._determineTopic(headers.topic, pushType);
notification.topic = topic;
let expiry = notification.expiry;
if (headers.expirationTime) {
expiry = Math.round(headers.expirationTime / 1000);
}
notification.expiry = coreData.expiry ?? expiry;
// 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.
notification.priority = coreData.priority ?? headers.priority ?? notification.priority;

return notification;
}

/**
* Updates the topic based on the pushType.
*
* @param {String} topic The current topic to append additional information to for required provider
* @param {any} pushType The current push type of the notification
* @returns {String} Returns the updated topic
*/
static _determineTopic(topic, pushType) {
switch(pushType) {
case 'location':
return topic + '.location-query';
case 'voip':
return topic + '.voip';
case 'complication':
return topic + '.complication';
case 'fileprovider':
return topic + '.pushkit.fileprovider';
case 'liveactivity':
return topic + '.push-type.liveactivity';
case 'pushtotalk':
return topic + '.voip-ptt';
default:
return topic;
}
}

/**
* Choose appropriate providers based on device appIdentifier.
*
* @param {String} appIdentifier appIdentifier for required provider
* @returns {Array} Returns Array with appropriate providers
*/
_chooseProviders(appIdentifier) {
// If the device we need to send to does not have appIdentifier, any provider could be a qualified provider
/*if (!appIdentifier || appIdentifier === '') {
return this.providers.map((provider) => provider.index);
}*/

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

0 comments on commit 9905c34

Please sign in to comment.