Skip to content

Commit

Permalink
v9 (#39)
Browse files Browse the repository at this point in the history
* Full code cleanup. Remove tarn. Require node v12

* Move tests to github actions

* Expose env key

* Tests

* Remove dotenv from test command

* Remove wercker from readme

* Cleanup var naming

* Add publish action

* Docs

* Remove instances of async/await for slight perf benefit

* let -> const

* Remove old note

* Remove old lint rule

* Update lint ecma version

* Docs

* Update author email
  • Loading branch information
AndrewBarba authored Oct 30, 2020
1 parent 9eb8b1a commit d6cda80
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 882 deletions.
15 changes: 3 additions & 12 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,9 @@
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2019,
"sourceType": "module"
},
"rules": {
"require-atomic-updates": [
0,
"error"
]
}
}
}
20 changes: 20 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Publish

on:
release:
types: [published]

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x

- run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > .npmrc
npm publish
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test

on:
push:
branches-ignore:
- main

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [12.x, 14.x]

steps:
- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- run: yarn install --frozen-lockfile

- run: yarn lint

- run: yarn test
env:
APNS_PUSH_TOKEN: ${{ secrets.APNS_PUSH_TOKEN }}
APNS_SIGNING_KEY: ${{ secrets.APNS_SIGNING_KEY }}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

---

## [9.0.0](https://github.com/AndrewBarba/apns2/releases/tag/9.0.0)

1. Full code cleanup
2. Removes tarn
3. Requires Node v12 or newer
4. Rename `destroy()` to `close()`

## [8.5.0](https://github.com/AndrewBarba/apns2/releases/tag/8.5.0)

1. Fix TypeScript typings
Expand Down
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# APNS2

[![wercker status](https://app.wercker.com/status/0e705662e5c35d51a971764fe3e27814/s/master 'wercker status')](https://app.wercker.com/project/byKey/0e705662e5c35d51a971764fe3e27814)
[![npm version](https://badge.fury.io/js/apns2.svg)](https://badge.fury.io/js/apns2)
[![Twitter](https://img.shields.io/badge/twitter-@andrew_barba-blue.svg?style=flat)](http://twitter.com/andrew_barba)

Node client for connecting to Apple's Push Notification Service using the new HTTP/2 protocol with JSON web tokens.

> Now uses the native `http2` module in Node.js v10.16 or later
---

## Create Client
Expand All @@ -17,7 +14,7 @@ Create an APNS client using a signing key:
```javascript
const { APNS } = require('apns2')

let client = new APNS({
const client = new APNS({
team: `TFLP87PW54`,
keyId: `123ABC456`,
signingKey: fs.readFileSync(`${__dirname}/path/to/auth.p8`),
Expand All @@ -34,7 +31,7 @@ Send a basic notification with message:
```javascript
const { BasicNotification } = require('apns2')

let bn = new BasicNotification(deviceToken, 'Hello, World')
const bn = new BasicNotification(deviceToken, 'Hello, World')

try {
await client.send(bn)
Expand All @@ -48,7 +45,7 @@ Send a basic notification with message and options:
```javascript
const { BasicNotification } = require('apns2')

let bn = new BasicNotification(deviceToken, 'Hello, World', {
const bn = new BasicNotification(deviceToken, 'Hello, World', {
badge: 4,
data: {
userId: user.getUserId
Expand All @@ -69,7 +66,7 @@ Send a silent notification using `content-available` key:
```javascript
const { SilentNotification } = require('apns2')

let sn = new SilentNotification(deviceToken)
const sn = new SilentNotification(deviceToken)

try {
await client.send(sn)
Expand All @@ -87,7 +84,7 @@ Send multiple notifications concurrently:
```javascript
const { BasicNotification } = require('apns2')

let notifications = [
const notifications = [
new BasicNotification(deviceToken1, 'Hello, World'),
new BasicNotification(deviceToken2, 'Hello, World')
]
Expand All @@ -106,7 +103,7 @@ For complete control over the push notification packet use the base `Notificatio
```javascript
const { Notification } = require('apns2')

let notification = new Notification(deviceToken, {
const notification = new Notification(deviceToken, {
aps: { ... }
})

Expand Down Expand Up @@ -141,22 +138,22 @@ client.on(Errors.error, (err) => {
})
```

## Destroy
## Close Connections

If you need to close connections to Apple's APNS servers in order to allow the Node process to exit, you can tear down the APNS client:

```javascript
await client.destroy()
await client.close()
```

Once a client is destroyed you will not be able to use it again. Instead you should instantiate a new client with `new APNS()`.
Once a client is closed you will not be able to use it again. Instead you should instantiate a new client with `new APNS()`.

## Environments

By default the APNS client connects to the production push notification server. This is identical to passing in the options:

```javascript
let client = new APNS({
const client = new APNS({
host: 'api.push.apple.com',
port: 443,
...
Expand All @@ -166,12 +163,12 @@ let client = new APNS({
To connect to the development push notification server, pass the options:

```javascript
let client = new APNS({
const client = new APNS({
host: 'api.development.push.apple.com'
...
})
```

## Requirements

`apns2` requires Node.js v10.16 or later
`apns2` requires Node.js v12.14 or later
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export class APNS extends EventEmitter {
constructor(options: APNSOptions)
send(notification: Notification): Promise<Notification | ResponseError>
sendMany(notifications: Notification[]): Promise<(Notification | ResponseError)[]>
destroy(): Promise<void>
close(): Promise<void>
}

export class Notification {
Expand Down
104 changes: 23 additions & 81 deletions lib/apns.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const { EventEmitter } = require('events')
const { Pool } = require('tarn')
const jwt = require('jsonwebtoken')
const Http2Client = require('./http2-client')
const Errors = require('./errors')
Expand All @@ -13,12 +12,6 @@ const SilentNotification = require('./notifications/silent-notification')
*/
const API_VERSION = 3

/**
* @const
* @desc Number of connections to open up with apns API
*/
const MAX_CONNECTIONS = 10

/**
* @const
* @desc Default host to send request
Expand Down Expand Up @@ -57,15 +50,7 @@ class APNS extends EventEmitter {
* @param {Int} [options.port]
* @param {Int} [options.connections]
*/
constructor({
team,
keyId,
signingKey,
defaultTopic = null,
host = HOST,
port = PORT,
connections = MAX_CONNECTIONS
}) {
constructor({ team, keyId, signingKey, defaultTopic = null, host = HOST, port = PORT }) {
if (!team) throw new Error(`team is required`)
if (!keyId) throw new Error(`keyId is required`)
if (!signingKey) throw new Error(`signingKey is required`)
Expand All @@ -74,7 +59,7 @@ class APNS extends EventEmitter {
this._keyId = keyId
this._signingKey = signingKey
this._defaultTopic = defaultTopic
this._clients = this._createClientPool({ host, port, connections })
this._client = new Http2Client(host, port)
this._interval = setInterval(() => this._resetSigningToken(), RESET_TOKEN_INTERVAL).unref()
this.on(Errors.expiredProviderToken, () => this._resetSigningToken())
}
Expand All @@ -84,37 +69,28 @@ class APNS extends EventEmitter {
* @param {Array<Notification>|Notification} notifications
* @return {Promise}
*/
async send(notifications) {
if (Array.isArray(notifications)) {
console.warn('#send(Array<Notification>) is deprecated. Please use #sendMany()') // eslint-disable-line no-console
return this.sendMany(notifications)
} else {
return this._sendOne(notifications)
}
send(notification) {
return this._sendOne(notification)
}

/**
* @method sendMany
* @param {Array<Notification>} notifications
* @return {Promise}
*/
async sendMany(notifications) {
let promises = notifications.map(async (notification) => {
try {
return await this._sendOne(notification)
} catch (error) {
return { error }
}
sendMany(notifications) {
const promises = notifications.map((notification) => {
return this._sendOne(notification).catch((error) => ({ error }))
})
return Promise.all(promises)
}

/**
* @method destroy
* @method close
* @return {Promise}
*/
async destroy() {
return this._clients.destroy()
close() {
return this._client.close()
}

/**
Expand All @@ -123,8 +99,9 @@ class APNS extends EventEmitter {
* @param {Notification} notification
* @return {Promise}
*/
async _sendOne(notification) {
let options = {
_sendOne(notification) {
const options = {
method: 'POST',
path: `/${API_VERSION}/device/${encodeURIComponent(notification.deviceToken)}`,
headers: {
authorization: `bearer ${this._getSigningToken()}`,
Expand All @@ -144,47 +121,12 @@ class APNS extends EventEmitter {
options.headers['apns-collapse-id'] = notification.collapseId
}

let client = await this._acquireClient()
this._releaseClient(client)

let body = JSON.stringify(notification.APNSOptions())
let res = await client.post(options, body)
return this._handleServerResponse(res, notification)
}

/**
* @private
* @method _createClientPool
* @param {String} host
* @param {Number} port
* @return {Pool}
*/
_createClientPool({ host, port, connections }) {
return new Pool({
create: () => new Http2Client(host, port).connect(),
validate: (client) => client.ready,
destroy: (client) => client.destroy(),
min: 0,
max: connections
})
}

/**
* @private
* @method _acquireClient
* @return {Promise}
*/
async _acquireClient() {
return this._clients.acquire().promise
}

/**
* @private
* @method _acquireClient
* @return {Promise}
*/
_releaseClient(client) {
return this._clients.release(client)
return this._client
.request({
...options,
body: JSON.stringify(notification.APNSOptions())
})
.then((res) => this._handleServerResponse(res, notification))
}

/**
Expand All @@ -193,7 +135,7 @@ class APNS extends EventEmitter {
* @param {ServerResponse} res
* @return {Promise}
*/
async _handleServerResponse(res, notification) {
_handleServerResponse(res, notification) {
if (res.statusCode === 200) {
return notification
}
Expand Down Expand Up @@ -225,14 +167,14 @@ class APNS extends EventEmitter {
return this._token
}

let claims = {
const claims = {
iss: this._team,
iat: parseInt(Date.now() / 1000)
}

let key = this._signingKey
const key = this._signingKey

let options = {
const options = {
algorithm: SIGNING_ALGORITHM,
header: {
alg: SIGNING_ALGORITHM,
Expand Down
Loading

0 comments on commit d6cda80

Please sign in to comment.