Skip to content

Commit

Permalink
Drop support for older versions of Node, API cleanup, Tarn connection…
Browse files Browse the repository at this point in the history
… pool
  • Loading branch information
AndrewBarba committed May 3, 2018
1 parent 7318b53 commit 2323031
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 576 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Change Log
==========

`apns2` follows [Semantic Versioning](http://semver.org/)

---

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

1. Remove support for Node versions less than v8.10
2. High-performance connection pool using [tarn](https://github.com/vincit/tarn.js)
3. More friendly require API, see README for updated usage

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

1. Fix Typescript definitions

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

1. Introduces support for the native `http2` module in Node.js v8.4.0 or later with fall back to `node-spdy` in earlier versions of Node.js.

To use the new `http2` library you must start your node process with `node --expose-http2` and apns2 will automatically use the native module. Later versions of Node.js may expose the native module without the need for a command line flag. In this case, apns2 will automatically use the native module without any additional steps on your end.
20 changes: 0 additions & 20 deletions Makefile

This file was deleted.

64 changes: 29 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ APNS2

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 v8.4.0 when exposed with `--expose-http2`
> On earlier versions of Node.js we fallback to the `node-spdy` module
> Now uses the native `http2` module in Node.js v8.10 or later
---

Expand All @@ -17,14 +15,14 @@ Node client for connecting to Apple's Push Notification Service using the new HT
Create an APNS client using a signing key:

```javascript
const APNS = require('apns2');
const { APNS } = require('apns2')

let client = new APNS({
team: `TFLP87PW54`,
keyId: `123ABC456`,
signingKey: fs.readFileSync(`${__dirname}/path/to/auth.p8`),
defaultTopic: `com.tablelist.Tablelist`
});
})
```

## Sending Notifications
Expand All @@ -34,50 +32,50 @@ let client = new APNS({
Send a basic notification with message:

```javascript
const { BasicNotification } = APNS;
const { BasicNotification } = require('apns2')

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

client.send(bn).then(() => {
// sent successfully
}).catch(err => {
console.error(err.reason);
});
console.error(err.reason)
})
```

Send a basic notification with message and options:

```javascript
const { BasicNotification } = APNS;
const { BasicNotification } = require('apns2')

let bn = new BasicNotification(deviceToken, 'Hello, World', {
badge: 4,
data: {
userId: user.getUserId
}
});
})

client.send(bn).then(() => {
// sent successfully
}).catch(err => {
console.error(err.reason);
});
console.error(err.reason)
})
```

#### Silent

Send a silent notification using `content-available` key:

```javascript
const { SilentNotification } = APNS;
const { SilentNotification } = require('apns2')

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

client.send(sn).then(() => {
// sent successfully
}).catch(err => {
console.error(err.reason);
});
console.error(err.reason)
})
```

Note: [Apple recommends](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/TheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH107-SW1) that no options other than the `content-available` flag be sent in order for a notification to truly be silent and wake up your app in the background. Therefore this class does not accept any additional options in the constructor.
Expand All @@ -87,17 +85,17 @@ Note: [Apple recommends](https://developer.apple.com/library/ios/documentation/N
For complete control over the push notification packet use the base `Notification` class:

```javascript
const { Notification } = APNS;
const { Notification } = require('apns2')

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

client.send(notification).then(() => {
// sent successfully
}).catch(err => {
console.error(err.reason);
});
console.error(err.reason)
})
```

Available options can be found at [APNS Payload Options](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1)
Expand All @@ -109,19 +107,19 @@ All errors are defined in `./lib/errors.js` and come directly from [APNS Table 8
You can easily listen for these errors by attaching an error handler to the APNS client:

```javascript
const errors = APNS.errors;
const { Errors } = require('apns2')

// Listen for a specific error
client.on(errors.badDeviceToken, err => {
client.on(Errors.badDeviceToken, err => {
// Handle accordingly...
// Perhaps delete token from your database
console.error(err.reason, err.statusCode, err.notification.deviceToken);
});
console.error(err.reason, err.statusCode, err.notification.deviceToken)
})

// Listen for any error
client.on(errors.error, err => {
console.error(err.reason, err.statusCode, err.notification.deviceToken);
});
client.on(Errors.error, err => {
console.error(err.reason, err.statusCode, err.notification.deviceToken)
})
```

## Environments
Expand All @@ -133,7 +131,7 @@ let client = new APNS({
host: 'api.push.apple.com',
port: 443,
...
});
})
```

To connect to the development push notification server, pass the options:
Expand All @@ -142,13 +140,9 @@ To connect to the development push notification server, pass the options:
let client = new APNS({
host: 'api.development.push.apple.com'
...
});
})
```

## Requirements

`apns2` requires Node.js v6

#### Native http2

To use the new built in `http2` library in Node.js v8.4.0 you must start your node process with `node --expose-http2`. apns2 will automatically pick up the native module and use it instead of `node-spdy`.
`apns2` requires Node.js v8.10 or later
116 changes: 47 additions & 69 deletions lib/apns.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
const _ = require('lodash')
const apnsErrors = require('./errors')
const EventEmitter = require('events').EventEmitter
const jwt = require('jsonwebtoken')
const Promise = require('bluebird')

// Notifications
const { EventEmitter } = require('events')
const { Pool } = require('tarn')
const jwt = require('jsonwebtoken')
const Http2Client = require('./http2-client')
const Errors = require('./errors')
const Notification = require('./notifications/notification')
const BasicNotification = require('./notifications/basic-notification')
const SilentNotification = require('./notifications/silent-notification')

// HTTP/2 Client
const HTTP2Client = (() => {
try {
// try native module
return require('./http2-client')
} catch(err) {
// else fallback to spdy
return require('./spdy-client')
}
})()

/**
* @const
* @desc APNS version
Expand All @@ -30,13 +19,13 @@ const API_VERSION = 3
* @const
* @desc Number of connections to open up with apns API
*/
const MAX_CONNECTIONS = 8
const MAX_CONNECTIONS = 10

/**
* @const
* @desc Max notifications to send concurrently
*/
const CONCURRENCY = 32
const CONCURRENCY = 64

/**
* @const
Expand Down Expand Up @@ -67,38 +56,6 @@ const RESET_TOKEN_INTERVAL = 20 * 60 * 1000
*/
class APNS extends EventEmitter {

/**
* @static
* @prop {Class} Notification
*/
static get Notification() {
return Notification
}

/**
* @static
* @prop {Class} BasicNotification
*/
static get BasicNotification() {
return BasicNotification
}

/**
* @static
* @prop {Class} SilentNotification
*/
static get SilentNotification() {
return SilentNotification
}

/**
* @static
* @prop {Object} errors
*/
static get errors() {
return apnsErrors
}

/**
* @constructor
* @param {Object} options
Expand All @@ -119,10 +76,35 @@ class APNS extends EventEmitter {
this._signingKey = signingKey
this._defaultTopic = defaultTopic
this._concurrency = concurrency
this._clients = _.times(connections, () => new HTTP2Client(host, port))
this._clientIndex = 0
this._clients = this._createClientPool({ host, port, connections })
this._interval = setInterval(() => this._resetSigningToken(), RESET_TOKEN_INTERVAL).unref()
this.on(apnsErrors.expiredProviderToken, () => this._resetSigningToken())
this.on(Errors.expiredProviderToken, () => this._resetSigningToken())
}

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

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

/**
Expand All @@ -142,17 +124,6 @@ class APNS extends EventEmitter {
})
}

/**
* @private
* @param {HTTP2Client} _nextClient
*/
get _nextClient() {
let client = this._clients[this._clientIndex]
let nextIndex = this._clientIndex + 1
this._clientIndex = nextIndex < this._clients.length ? nextIndex : 0
return client
}

/**
* @private
* @method _send
Expand All @@ -179,7 +150,8 @@ class APNS extends EventEmitter {

let body = JSON.stringify(notification.APNSOptions())

return this._nextClient.post(options, body)
return this._acquireClient()
.then(client => client.post(options, body))
.then(res => this._handleServerResponse(res, notification))
}

Expand All @@ -199,14 +171,14 @@ class APNS extends EventEmitter {
try {
json = JSON.parse(res.body)
} catch(err) {
json = { reason: apnsErrors.unknownError }
json = { reason: Errors.unknownError }
}

json.statusCode = res.statusCode
json.notification = notification

this.emit(json.reason, json)
this.emit(apnsErrors.error, json)
this.emit(Errors.error, json)

return Promise.reject(json)
}
Expand Down Expand Up @@ -242,7 +214,7 @@ class APNS extends EventEmitter {
token = this._token = jwt.sign(claims, key, options)
} catch(err) {
token = this._token = null
this.emit(apnsErrors.invalidSigningKey)
this.emit(Errors.invalidSigningKey)
}

return token
Expand All @@ -258,4 +230,10 @@ class APNS extends EventEmitter {
}
}

module.exports = APNS
module.exports = {
APNS,
Errors,
Notification,
BasicNotification,
SilentNotification
}
Loading

0 comments on commit 2323031

Please sign in to comment.