Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the ability to manage channels and send broadcasts #164

Open
wants to merge 80 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
b35fbb8
add TS missing properties
cbaker6 Dec 29, 2024
71f2cf1
feat: Add the ability to use different http methods
cbaker6 Dec 30, 2024
0b4022f
Merge branch 'master' into useBroadcastPath
cbaker6 Dec 30, 2024
49b5bf5
lint
cbaker6 Dec 30, 2024
5e83b2a
Merge branch 'useBroadcastPath' of https://github.com/cbaker6/node-ap…
cbaker6 Dec 30, 2024
e5bf411
test v2 as provider
cbaker6 Dec 30, 2024
5b029b7
replace original client with V2
cbaker6 Dec 30, 2024
755aeec
lint
cbaker6 Dec 30, 2024
058070c
create manageBroadcastSession
cbaker6 Dec 31, 2024
8136a8c
nit
cbaker6 Dec 31, 2024
3d348fc
nit
cbaker6 Dec 31, 2024
a1263c7
add channels method
cbaker6 Dec 31, 2024
36b3f35
nit
cbaker6 Dec 31, 2024
8ec017d
update typescript
cbaker6 Dec 31, 2024
53393f0
make JS match TS
cbaker6 Jan 1, 2025
6cf1299
delete non channel related headers when necessary
cbaker6 Jan 1, 2025
5193a14
Merge branch 'master' into useBroadcastPath
cbaker6 Jan 1, 2025
c21e682
use async/await
cbaker6 Jan 2, 2025
b7a5bfe
Merge branch 'useBroadcastPath' of https://github.com/cbaker6/node-ap…
cbaker6 Jan 2, 2025
dee2882
lint
cbaker6 Jan 2, 2025
576a62d
fix code on older versions of node
cbaker6 Jan 2, 2025
d006ded
add initial tests for manageBroadcastSession
cbaker6 Jan 2, 2025
6318b98
throw error if reached connectionRetryLimit
cbaker6 Jan 2, 2025
bcd1364
improve usage of connectionRetryLimit, defaults to 2 instead of 10
cbaker6 Jan 2, 2025
446c8f2
manageBroadcast -> manageChannels
cbaker6 Jan 2, 2025
2f814c9
make code for connectionRetryLimit match documentation
cbaker6 Jan 2, 2025
b280af9
convert provider tests to async/await
cbaker6 Jan 4, 2025
551e686
lint
cbaker6 Jan 4, 2025
46c3df2
add more provider tests
cbaker6 Jan 4, 2025
7202f41
add more notification tests
cbaker6 Jan 4, 2025
58e6a86
retry on http codes 408, 429, 500, 502, 503, 504
cbaker6 Jan 4, 2025
56d7337
add proxy test
cbaker6 Jan 4, 2025
4905239
add some client tests
cbaker6 Jan 5, 2025
e0bb26d
fix manage channels test
cbaker6 Jan 10, 2025
5519fa4
more tests
cbaker6 Jan 10, 2025
2f2027d
improve logging pings
cbaker6 Jan 10, 2025
8b75d4e
revert
cbaker6 Jan 10, 2025
db39674
add ping tests
cbaker6 Jan 11, 2025
12d7968
more logger coverage
cbaker6 Jan 11, 2025
84a0736
modify tests for older node
cbaker6 Jan 11, 2025
b6c149b
remove unnecessary check
cbaker6 Jan 11, 2025
a561daa
make shutdown async
cbaker6 Jan 13, 2025
4ddf509
lint
cbaker6 Jan 13, 2025
af39226
test unref
cbaker6 Jan 13, 2025
77f117d
try without JSON error test
cbaker6 Jan 13, 2025
854338e
xit some tests for now
cbaker6 Jan 13, 2025
07a8afe
modify logger tests to run on older node
cbaker6 Jan 13, 2025
b092680
run json tests
cbaker6 Jan 13, 2025
5f8f975
lint
cbaker6 Jan 13, 2025
8a926c3
test
cbaker6 Jan 13, 2025
694b04d
remove close connection tests
cbaker6 Jan 13, 2025
39027cf
don't retry on status 500
cbaker6 Jan 13, 2025
d4da764
pass reject in closure
cbaker6 Jan 13, 2025
55adff1
force proxy to close
cbaker6 Jan 13, 2025
7727d1e
nit
cbaker6 Jan 13, 2025
85bedf6
improve 500 errors
cbaker6 Jan 13, 2025
1efd838
more tests
cbaker6 Jan 14, 2025
20fc514
reduce use of callbacks
cbaker6 Jan 14, 2025
c04aa4e
add back proxy test
cbaker6 Jan 14, 2025
3c05075
fix manage channel proxy test
cbaker6 Jan 14, 2025
14e50f1
comment nits
cbaker6 Jan 14, 2025
354242d
increase coverage
cbaker6 Jan 14, 2025
065e88c
add destroySession test
cbaker6 Jan 14, 2025
4445430
nit
cbaker6 Jan 14, 2025
c2776f8
add docs for new features
cbaker6 Jan 15, 2025
4347a05
doc nits
cbaker6 Jan 15, 2025
70f0390
nit
cbaker6 Jan 15, 2025
6712dd8
add additional proxy option to typescript
cbaker6 Jan 15, 2025
821973b
Merge branch 'useBroadcastPath' of https://github.com/cbaker6/node-ap…
cbaker6 Jan 15, 2025
7590bce
remove topic in broadcast docs
cbaker6 Jan 15, 2025
932a79d
doc nits
cbaker6 Jan 15, 2025
9813ce8
Remove unnecessary code
cbaker6 Jan 15, 2025
b6b1a6f
allow experation header in manageChannels
cbaker6 Jan 15, 2025
209794a
Merge branch 'useBroadcastPath' of https://github.com/cbaker6/node-ap…
cbaker6 Jan 15, 2025
1ac618e
support 201 and 204 status codes
cbaker6 Jan 16, 2025
78e1d03
remove old socketError not available after node 8.x
cbaker6 Jan 16, 2025
ab6ece8
expose the rest of config params to TS
cbaker6 Jan 18, 2025
6b604c1
Update Provider documentation
cbaker6 Jan 18, 2025
a584c1a
doc nits
cbaker6 Jan 19, 2025
0482ac3
mocker uses default method parameter
cbaker6 Jan 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 118 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ A Node.js module for interfacing with the Apple Push Notification service.
- [Connecting through an HTTP proxy](#connecting-through-an-http-proxy)
- [Using a pool of http/2 connections](#using-a-pool-of-http2-connections)
- [Sending a notification](#sending-a-notification)
- [Managing channels](#manage-channels)
- [Sending a broadcast notification](#sending-a-broadcast-notification)

# Features

Expand All @@ -36,7 +38,7 @@ $ npm install @parse/node-apn --save

# Quick Start

This readme is a brief introduction, please refer to the full [documentation](doc/apn.markdown) in `doc/` for more details.
This readme is a brief introduction; please refer to the full [documentation](doc/apn.markdown) in `doc/` for more details.

If you have previously used v1.x and wish to learn more about what's changed in v2.0, please see [What's New](doc/whats-new.markdown)

Expand All @@ -59,15 +61,19 @@ var options = {
production: false
};

var apnProvider = new apn.Provider(options);
const apnProvider = new apn.Provider(options);
```

By default, the provider will connect to the sandbox unless the environment variable `NODE_ENV=production` is set.

For more information about configuration options consult the [provider documentation](doc/provider.markdown).
For more information about configuration options, consult the [provider documentation](doc/provider.markdown).

Help with preparing the key and certificate files for connection can be found in the [wiki][certificateWiki]

⚠️ You should only create one `Provider` per-process for each certificate/key pair you have. You do not need to create a new `Provider` for each notification. If you are only sending notifications to one app, there is no need for more than one `Provider`.

If you are constantly creating `Provider` instances in your app, make sure to call `Provider.shutdown()` when you are done with each provider to release its resources and memory.

### Connecting through an HTTP proxy

If you need to connect through an HTTP proxy, you simply need to provide the `proxy: {host, port}` option when creating the provider. For example:
Expand All @@ -86,7 +92,7 @@ var options = {
production: false
};

var apnProvider = new apn.Provider(options);
const apnProvider = new apn.Provider(options);
```

The provider will first send an HTTP CONNECT request to the specified proxy in order to establish an HTTP tunnel. Once established, it will create a new secure connection to the Apple Push Notification provider API through the tunnel.
Expand All @@ -111,11 +117,11 @@ var options = {
production: false
};

var apnProvider = new apn.MultiProvider(options);
const apnProvider = new apn.MultiProvider(options);
```

## Sending a notification
To send a notification you will first need a device token from your app as a string
To send a notification, you will first need a device token from your app as a string.

```javascript
let deviceToken = "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7"
Expand All @@ -124,7 +130,7 @@ let deviceToken = "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da1
Create a notification object, configuring it with the relevant parameters (See the [notification documentation](doc/notification.markdown) for more details.)

```javascript
var note = new apn.Notification();
let note = new apn.Notification();

note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
Expand All @@ -137,29 +143,32 @@ note.topic = "<your-app-bundle-id>";
Send the notification to the API with `send`, which returns a promise.

```javascript
apnProvider.send(note, deviceToken).then( (result) => {
try {
const result = apnProvider.send(note, deviceToken);
// see documentation for an explanation of result
});
} catch(error) {
// Handle error...
}
```

This will result in the the following notification payload being sent to the device
This will result in the following notification payload being sent to the device.

```json
{"messageFrom":"John Appelseed","aps":{"badge":3,"sound":"ping.aiff","alert":"\uD83D\uDCE7 \u2709 You have a new message"}}
```

Create a Live Activity notification object, configuring it with the relevant parameters (See the [notification documentation](doc/notification.markdown) for more details.)
Create a Live Activity notification object and configure it with the relevant parameters (See the [notification documentation](doc/notification.markdown) for more details.)

```javascript
var note = new apn.Notification();
let note = new apn.Notification();

note.topic = "<your-app-bundle-id>.push-type.liveactivity";
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.pushType = "liveactivity",
note.badge = 3;
note.sound = "ping.aiff";
note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
note.payload = {'messageFrom': 'John Appleseed'};
note.topic = "<your-app-bundle-id>";
note.pushType = "liveactivity",
note.relevanceScore = 75,
note.timestamp = Math.floor(Date.now() / 1000); // Current time
note.staleDate = Math.floor(Date.now() / 1000) + (8 * 3600); // Expires 8 hour from now.
Expand All @@ -170,18 +179,107 @@ note.contentState = {}
Send the notification to the API with `send`, which returns a promise.

```javascript
apnProvider.send(note, deviceToken).then( (result) => {
// see documentation for an explanation of result
});
try {
const result = await apnProvider.send(note, deviceToken);
// see the documentation for an explanation of the result
} catch (error) {
// Handle error...
}
```

This will result in the the following notification payload being sent to the device
This will result in the following notification payload being sent to the device.


```json
{"messageFrom":"John Appleseed","aps":{"badge":3,"sound":"ping.aiff","alert":"\uD83D\uDCE7 \u2709 You have a new message", "relevance-score":75,"timestamp":1683129662,"stale-date":1683216062,"event":"update","content-state":{}}}
```

You should only create one `Provider` per-process for each certificate/key pair you have. You do not need to create a new `Provider` for each notification. If you are only sending notifications to one app then there is no need for more than one `Provider`.
## Manage Channels
Starting in iOS 18 and iPadOS 18 Live Activities can be used to broadcast push notifications over channels. To do so, you will need your apps' `bundleId`.

If you are constantly creating `Provider` instances in your app, make sure to call `Provider.shutdown()` when you are done with each provider to release its resources and memory.
```javascript
let bundleId = "com.node.apn";
```

Create a notification object, configuring it with the relevant parameters (See the [notification documentation](doc/notification.markdown) for more details.)

```javascript
let note = new apn.Notification();

note.requestId = "0309F412-AA57-46A8-9AC6-B5AECA8C4594"; // Optional
note.payload = {'message-storage-policy': '1', 'push-type': 'liveactivity'}; // Required
```

Create a channel with `manageChannels` and the `create` action, which returns a promise.

```javascript
try {
const result = await apnProvider.manageChannels(note, bundleId, 'create');
// see the documentation for an explanation of the result
} catch (error) {
// Handle error...
}
```

If the channel is created successfully, the result will look like the following:
```javascript
{
apns-request-id: '0309F412-AA57-46A8-9AC6-B5AECA8C4594',
apns-channel-id: 'dHN0LXNyY2gtY2hubA==' // The new channel
}
```

Similarly, `manageChannels` has additional `action`s that allow you to `read`, `readAll`, and `delete` channels. The `read` and `delete` actions require similar information to the `create` example above, with the exception that they require `note.channelId` to be populated. To request all active channel id's, you can use the `readAll` action:

```javascript
try {
const result = await apnProvider.manageChannels(note, bundleId, 'readAll');
// see the documentation for an explanation of the result
} catch (error) {
// Handle error...
}
```

After the promise is fulfilled, `result` will look like the following:

```javascript
{
apns-request-id: 'some id value',
channels: ['dHN0LXNyY2gtY2hubA==', 'eCN0LXNyY2gtY2hubA==' ...] // A list of active channels
}
```

Further information about managing channels can be found in [Apple's documentation](https://developer.apple.com/documentation/usernotifications/sending-channel-management-requests-to-apns).

## Sending A Broadcast Notification
Starting in iOS 18 and iPadOS 18, after a channel is created using `manageChannels`, broadcast push notifications can be sent to any device subscribed to the respective `channelId` created for a `bundleId`. A broadcast notification looks similar to a standard Live Activity notification mentioned above but requires `note.channelId` to be populated. An example is below:

```javascript
let note = new apn.Notification();

note.channelId = "dHN0LXNyY2gtY2hubA=="; // Required
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.pushType = "liveactivity",
note.badge = 3;
note.sound = "ping.aiff";
note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
note.payload = {'messageFrom': 'John Appleseed'};
note.relevanceScore = 75,
note.timestamp = Math.floor(Date.now() / 1000); // Current time
note.staleDate = Math.floor(Date.now() / 1000) + (8 * 3600); // Expires 8 hour from now.
note.event = "update"
note.contentState = {}
```

Send the broadcast notification to the API with `broadcast`, which returns a promise.

```javascript
try {
const result = await apnProvider.broadcast(note, bundleId);
// see documentation for an explanation of result
} catch (error) {
// Handle error...
}
```

Further information about broadcast notifications can be found in [Apple's documentation](https://developer.apple.com/documentation/usernotifications/sending-broadcast-push-notification-requests-to-apns).
4 changes: 2 additions & 2 deletions doc/provider.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ When the returned `Promise` resolves, its value will be an Object containing two

An array of device tokens to which the notification was successfully sent and accepted by Apple.

Being `sent` does **not** guaranteed the notification will be _delivered_, other unpredictable factors - including whether the device is reachable - can ultimately prevent delivery.
Being `sent` does **not** guarantee the notification will be _delivered_, other unpredictable factors - including whether the device is reachable - can ultimately prevent delivery.

#### failed

Expand Down Expand Up @@ -103,7 +103,7 @@ If you wish to send notifications containing emoji or other multi-byte character

Indicate to node-apn that it should close all open connections when the queue of pending notifications is fully drained. This will allow your application to terminate.

**Note:** If notifications are pushed after the connection has started, an error will be thrown.
**Note:** If notifications are pushed after the shutdown has started, an error will be thrown.

[provider-api]: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
[provider-auth-tokens]: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1
Expand Down
Loading
Loading