Skip to content

Commit ccd0781

Browse files
authored
feat: Add support for broadcast push notifications (#163)
1 parent 93b09ce commit ccd0781

File tree

7 files changed

+78
-18
lines changed

7 files changed

+78
-18
lines changed

doc/notification.markdown

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,24 @@ The properties below are sent alongside the notification as configuration and do
163163

164164
#### notification.topic
165165

166-
_Required_: The destination topic for the notification.
166+
_Required_: The destination topic for the notification. If you want to set a `pushtotalk` push type, the `topic` must use your app’s bundle ID with `.voip-ptt` appended to the end. If you want to set a `liveactivity` push type, the `topic` must use your app’s bundle ID with `.push-type.liveactivity` appended to the end.
167167

168168
#### notification.id
169169

170170
A UUID to identify the notification with APNS. If an `id` is not supplied, APNS will generate one automatically. If an error occurs the response will contain the `id`. This property populates the `apns-id` header.
171171

172+
#### notification.collapseId
173+
174+
Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
175+
176+
#### notification.requestId
177+
178+
An optional custom request identifier that’s returned back in the response. The request identifier must be encoded as a UUID string.
179+
180+
#### notification.channelId
181+
182+
The channel ID is a base64-encoded string that identifies the channel to publish the payload. The channel ID is generated by sending channel creation request to APNs.
183+
172184
#### notification.expiry
173185

174186
A UNIX timestamp when the notification should expire. If the notification cannot be delivered to the device, APNS will retry until it expires. An expiry of `0` indicates that the notification expires immediately, therefore no retries will be attempted.
@@ -186,12 +198,8 @@ Provide one of the following values:
186198

187199
(Required when delivering notifications to devices running iOS 13 and later, or watchOS 6 and later. Ignored on earlier system versions.)
188200

189-
The type of the notification. The value of this header is `alert` or `background`. Specify `alert` when the delivery of your notification displays an alert, plays a sound, or badges your app's icon. Specify `background` for silent notifications that do not interact with the user.
201+
The type of the notification. The value of this header is `alert`, `background`, `pushtotalk`, or `liveactivity`. Specify `alert` when the delivery of your notification displays an alert, plays a sound, or badges your app's icon. Specify `background` for silent notifications that do not interact with the user. Specify `pushtotalk` for notifications that provide information about updates to your application’s push to talk services. Specify `liveactivity` for live activities.
190202

191203
The value of this header must accurately reflect the contents of your notification's payload. If there is a mismatch, or if the header is missing on required systems, APNs may delay the delivery of the notification or drop it altogether.
192204

193-
#### notification.collapseId
194-
195-
Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
196-
197-
[pl]:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html "Local and Push Notification Programming Guide: Apple Push Notification Service"
205+
[pl]:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html "Local and Push Notification Programming Guide: Apple Push Notification Service"

index.d.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export class MultiProvider extends EventEmitter {
167167
shutdown(callback?: () => void): void;
168168
}
169169

170-
export type NotificationPushType = 'background' | 'alert' | 'voip';
170+
export type NotificationPushType = 'background' | 'alert' | 'voip' | 'pushtotalk' | 'liveactivity';
171171

172172
export interface NotificationAlertOptions {
173173
title?: string;
@@ -201,6 +201,18 @@ export class Notification {
201201
* The UNIX timestamp representing when the notification should expire. This does not contribute to the 2048 byte payload size limit. An expiry of 0 indicates that the notification expires immediately.
202202
*/
203203
public expiry: number;
204+
/**
205+
* Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
206+
*/
207+
public collapseId: string;
208+
/**
209+
* Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
210+
*/
211+
public requestId: string;
212+
/**
213+
* An optional custom request identifier that’s returned back in the response. The request identifier must be encoded as a UUID string.
214+
*/
215+
public channelId: string;
204216
/**
205217
* Provide one of the following values:
206218
*
@@ -209,9 +221,14 @@ export class Notification {
209221
* - 5 - The push message is sent at a time that conserves power on the device receiving it.
210222
*/
211223
public priority: number;
212-
213-
public collapseId: string;
224+
/**
225+
* The type of the notification.
226+
*/
214227
public pushType: NotificationPushType;
228+
229+
/**
230+
* An app-specific identifier for grouping related notifications.
231+
*/
215232
public threadId: string;
216233

217234
/**

lib/notification/index.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Notification.prototype = require('./apsProperties');
5656
'staleDate',
5757
'event',
5858
'contentState',
59-
'dismissalDate'
59+
'dismissalDate',
6060
].forEach(propName => {
6161
const methodName = 'set' + propName[0].toUpperCase() + propName.slice(1);
6262
Notification.prototype[methodName] = function (value) {
@@ -76,6 +76,18 @@ Notification.prototype.headers = function headers() {
7676
headers['apns-id'] = this.id;
7777
}
7878

79+
if (this.collapseId) {
80+
headers['apns-collapse-id'] = this.collapseId;
81+
}
82+
83+
if (this.requestId) {
84+
headers['apns-request-id'] = this.requestId;
85+
}
86+
87+
if (this.channelId) {
88+
headers['apns-channel-id'] = this.channelId;
89+
}
90+
7991
if (this.expiry >= 0) {
8092
headers['apns-expiration'] = this.expiry;
8193
}
@@ -84,10 +96,6 @@ Notification.prototype.headers = function headers() {
8496
headers['apns-topic'] = this.topic;
8597
}
8698

87-
if (this.collapseId) {
88-
headers['apns-collapse-id'] = this.collapseId;
89-
}
90-
9199
if (this.pushType) {
92100
headers['apns-push-type'] = this.pushType;
93101
}

test/client.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,11 @@ describe('Client', () => {
352352
const result = await client.write(mockNotification, mockDevice);
353353
// Should not happen, but if it does, the promise should resolve with an error
354354
expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
355-
expect(result.error.message.startsWith('Unexpected error processing APNs response: Unexpected token')).to.equal(true);
355+
expect(
356+
result.error.message.startsWith(
357+
'Unexpected error processing APNs response: Unexpected token'
358+
)
359+
).to.equal(true);
356360
};
357361
await runRequestWithInternalServerError();
358362
await runRequestWithInternalServerError();

test/multiclient.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,11 @@ describe('MultiClient', () => {
373373
const result = await client.write(mockNotification, mockDevice);
374374
// Should not happen, but if it does, the promise should resolve with an error
375375
expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
376-
expect(result.error.message.startsWith('Unexpected error processing APNs response: Unexpected token')).to.equal(true);
376+
expect(
377+
result.error.message.startsWith(
378+
'Unexpected error processing APNs response: Unexpected token'
379+
)
380+
).to.equal(true);
377381
};
378382
await runRequestWithInternalServerError();
379383
await runRequestWithInternalServerError();

test/notification/apsProperties.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@ describe('Notification', function () {
367367
describe('subtitleLocKey', function () {
368368
it('sets the aps.alert.subtitle-loc-key property', function () {
369369
note.subtitleLocKey = 'Warning';
370-
expect(compiledOutput()).to.have.nested.deep.property('aps.alert.subtitle-loc-key', 'Warning');
370+
expect(compiledOutput()).to.have.nested.deep.property(
371+
'aps.alert.subtitle-loc-key',
372+
'Warning'
373+
);
371374
});
372375

373376
context('alert is already an object', function () {

test/notification/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,22 @@ describe('Notification', function () {
169169
});
170170
});
171171

172+
context('requestId is set', function () {
173+
it('contains the apns-request-id header', function () {
174+
note.requestId = 'io.apn.request';
175+
176+
expect(note.headers()).to.have.property('apns-request-id', 'io.apn.request');
177+
});
178+
});
179+
180+
context('channelId is set', function () {
181+
it('contains the apns-request-id header', function () {
182+
note.channelId = 'io.apn.channel';
183+
184+
expect(note.headers()).to.have.property('apns-channel-id', 'io.apn.channel');
185+
});
186+
});
187+
172188
context('pushType is set', function () {
173189
it('contains the apns-push-type header', function () {
174190
note.pushType = 'alert';

0 commit comments

Comments
 (0)