Skip to content

Commit

Permalink
feat: Add support for broadcast push notifications (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbaker6 authored Dec 28, 2024
1 parent 93b09ce commit ccd0781
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 18 deletions.
22 changes: 15 additions & 7 deletions doc/notification.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,24 @@ The properties below are sent alongside the notification as configuration and do

#### notification.topic

_Required_: The destination topic for the notification.
_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.

#### notification.id

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.

#### notification.collapseId

Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.

#### notification.requestId

An optional custom request identifier that’s returned back in the response. The request identifier must be encoded as a UUID string.

#### notification.channelId

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.

#### notification.expiry

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.
Expand All @@ -186,12 +198,8 @@ Provide one of the following values:

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

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.
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.

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.

#### notification.collapseId

Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.

[pl]:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html "Local and Push Notification Programming Guide: Apple Push Notification Service"
[pl]:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html "Local and Push Notification Programming Guide: Apple Push Notification Service"
23 changes: 20 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class MultiProvider extends EventEmitter {
shutdown(callback?: () => void): void;
}

export type NotificationPushType = 'background' | 'alert' | 'voip';
export type NotificationPushType = 'background' | 'alert' | 'voip' | 'pushtotalk' | 'liveactivity';

export interface NotificationAlertOptions {
title?: string;
Expand Down Expand Up @@ -201,6 +201,18 @@ export class Notification {
* 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.
*/
public expiry: number;
/**
* Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
*/
public collapseId: string;
/**
* Multiple notifications with same collapse identifier are displayed to the user as a single notification. The value should not exceed 64 bytes.
*/
public requestId: string;
/**
* An optional custom request identifier that’s returned back in the response. The request identifier must be encoded as a UUID string.
*/
public channelId: string;
/**
* Provide one of the following values:
*
Expand All @@ -209,9 +221,14 @@ export class Notification {
* - 5 - The push message is sent at a time that conserves power on the device receiving it.
*/
public priority: number;

public collapseId: string;
/**
* The type of the notification.
*/
public pushType: NotificationPushType;

/**
* An app-specific identifier for grouping related notifications.
*/
public threadId: string;

/**
Expand Down
18 changes: 13 additions & 5 deletions lib/notification/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Notification.prototype = require('./apsProperties');
'staleDate',
'event',
'contentState',
'dismissalDate'
'dismissalDate',
].forEach(propName => {
const methodName = 'set' + propName[0].toUpperCase() + propName.slice(1);
Notification.prototype[methodName] = function (value) {
Expand All @@ -76,6 +76,18 @@ Notification.prototype.headers = function headers() {
headers['apns-id'] = this.id;
}

if (this.collapseId) {
headers['apns-collapse-id'] = this.collapseId;
}

if (this.requestId) {
headers['apns-request-id'] = this.requestId;
}

if (this.channelId) {
headers['apns-channel-id'] = this.channelId;
}

if (this.expiry >= 0) {
headers['apns-expiration'] = this.expiry;
}
Expand All @@ -84,10 +96,6 @@ Notification.prototype.headers = function headers() {
headers['apns-topic'] = this.topic;
}

if (this.collapseId) {
headers['apns-collapse-id'] = this.collapseId;
}

if (this.pushType) {
headers['apns-push-type'] = this.pushType;
}
Expand Down
6 changes: 5 additions & 1 deletion test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ describe('Client', () => {
const result = await client.write(mockNotification, mockDevice);
// Should not happen, but if it does, the promise should resolve with an error
expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
expect(result.error.message.startsWith('Unexpected error processing APNs response: Unexpected token')).to.equal(true);
expect(
result.error.message.startsWith(
'Unexpected error processing APNs response: Unexpected token'
)
).to.equal(true);
};
await runRequestWithInternalServerError();
await runRequestWithInternalServerError();
Expand Down
6 changes: 5 additions & 1 deletion test/multiclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,11 @@ describe('MultiClient', () => {
const result = await client.write(mockNotification, mockDevice);
// Should not happen, but if it does, the promise should resolve with an error
expect(result.device).to.equal(MOCK_DEVICE_TOKEN);
expect(result.error.message.startsWith('Unexpected error processing APNs response: Unexpected token')).to.equal(true);
expect(
result.error.message.startsWith(
'Unexpected error processing APNs response: Unexpected token'
)
).to.equal(true);
};
await runRequestWithInternalServerError();
await runRequestWithInternalServerError();
Expand Down
5 changes: 4 additions & 1 deletion test/notification/apsProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,10 @@ describe('Notification', function () {
describe('subtitleLocKey', function () {
it('sets the aps.alert.subtitle-loc-key property', function () {
note.subtitleLocKey = 'Warning';
expect(compiledOutput()).to.have.nested.deep.property('aps.alert.subtitle-loc-key', 'Warning');
expect(compiledOutput()).to.have.nested.deep.property(
'aps.alert.subtitle-loc-key',
'Warning'
);
});

context('alert is already an object', function () {
Expand Down
16 changes: 16 additions & 0 deletions test/notification/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@ describe('Notification', function () {
});
});

context('requestId is set', function () {
it('contains the apns-request-id header', function () {
note.requestId = 'io.apn.request';

expect(note.headers()).to.have.property('apns-request-id', 'io.apn.request');
});
});

context('channelId is set', function () {
it('contains the apns-request-id header', function () {
note.channelId = 'io.apn.channel';

expect(note.headers()).to.have.property('apns-channel-id', 'io.apn.channel');
});
});

context('pushType is set', function () {
it('contains the apns-push-type header', function () {
note.pushType = 'alert';
Expand Down

0 comments on commit ccd0781

Please sign in to comment.