Comparing version 9.0.0 to 9.1.0
@@ -7,2 +7,8 @@ # Change Log | ||
## [9.1.0](https://github.com/AndrewBarba/apns2/releases/tag/9.1.0) | ||
1. Correctly handle socket error events | ||
2. Lazily connect on first request | ||
3. Keeps socket alive with ping request every 60s | ||
## [9.0.0](https://github.com/AndrewBarba/apns2/releases/tag/9.0.0) | ||
@@ -9,0 +15,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { EventEmitter } from "events" | ||
import { EventEmitter } from 'events' | ||
@@ -32,6 +32,6 @@ export class APNS extends EventEmitter { | ||
error: { | ||
reason: string; | ||
statusCode: number; | ||
notification: Notification; | ||
}; | ||
reason: string | ||
statusCode: number | ||
notification: Notification | ||
} | ||
} | ||
@@ -81,31 +81,34 @@ | ||
port?: number | ||
connections?: number | ||
requestTimeout?: number | ||
pingInterval?: number | ||
} | ||
declare interface NotificationOptions { | ||
alert?: string | { | ||
title?: string; | ||
subtitle?: string; | ||
body: string; | ||
'title-loc-key'?: string; | ||
'title-loc-args'?: string[]; | ||
'subtitle-loc-key'?: string; | ||
'subtitle-loc-args'?: string[]; | ||
'loc-key'?: string; | ||
'loc-args'?: string[]; | ||
'action-loc-key'?: string; | ||
'launch-image'?: string; | ||
}; | ||
aps?: any; | ||
badge?: number; | ||
category?: string; | ||
collapseId?: string; | ||
contentAvailable?: boolean; | ||
data?: { [key: string]: any; }; | ||
expiration?: number; | ||
priority?: number; | ||
pushType?: keyof PushType; | ||
sound?: string; | ||
threadId?: string; | ||
topic?: string; | ||
alert?: | ||
| string | ||
| { | ||
title?: string | ||
subtitle?: string | ||
body: string | ||
'title-loc-key'?: string | ||
'title-loc-args'?: string[] | ||
'subtitle-loc-key'?: string | ||
'subtitle-loc-args'?: string[] | ||
'loc-key'?: string | ||
'loc-args'?: string[] | ||
'action-loc-key'?: string | ||
'launch-image'?: string | ||
} | ||
aps?: any | ||
badge?: number | ||
category?: string | ||
collapseId?: string | ||
contentAvailable?: boolean | ||
data?: { [key: string]: any } | ||
expiration?: number | ||
priority?: number | ||
pushType?: keyof PushType | ||
sound?: string | ||
threadId?: string | ||
topic?: string | ||
} | ||
@@ -112,0 +115,0 @@ |
@@ -37,3 +37,3 @@ const { EventEmitter } = require('events') | ||
*/ | ||
const RESET_TOKEN_INTERVAL = 55 * 60 * 1000 | ||
const RESET_TOKEN_INTERVAL_MS = 55 * 60 * 1000 | ||
@@ -54,3 +54,12 @@ /** | ||
*/ | ||
constructor({ team, keyId, signingKey, defaultTopic = null, host = HOST, port = PORT }) { | ||
constructor({ | ||
team, | ||
keyId, | ||
signingKey, | ||
defaultTopic = null, | ||
host = HOST, | ||
port = PORT, | ||
requestTimeout = 5000, | ||
pingInterval = 60000 | ||
}) { | ||
if (!team) throw new Error(`team is required`) | ||
@@ -64,4 +73,7 @@ if (!keyId) throw new Error(`keyId is required`) | ||
this._defaultTopic = defaultTopic | ||
this._client = new Http2Client(host, port) | ||
this._interval = setInterval(() => this._resetSigningToken(), RESET_TOKEN_INTERVAL).unref() | ||
this._client = new Http2Client(host, { port, requestTimeout, pingInterval }) | ||
this._resetTokenInterval = setInterval( | ||
() => this._resetSigningToken(), | ||
RESET_TOKEN_INTERVAL_MS | ||
).unref() | ||
this.on(Errors.expiredProviderToken, () => this._resetSigningToken()) | ||
@@ -68,0 +80,0 @@ } |
const http2 = require('http2') | ||
const { | ||
HTTP2_HEADER_SCHEME, | ||
HTTP2_HEADER_METHOD, | ||
@@ -16,6 +17,8 @@ HTTP2_HEADER_PATH, | ||
*/ | ||
constructor(host, port = 443, { timeout = 5000 } = {}) { | ||
constructor(host, { port = 443, requestTimeout = 5000, pingInterval = 60000 } = {}) { | ||
if (!host) throw new Error('host is required') | ||
this._timeout = timeout | ||
this._client = http2.connect(`https://${host}:${port}`) | ||
this._requestTimeout = requestTimeout | ||
this._pingIntervalMs = pingInterval | ||
this._pingInterval = null | ||
this._url = `https://${host}:${port}` | ||
} | ||
@@ -31,16 +34,30 @@ | ||
/** | ||
* @param {Number} | ||
* Closes the underlying http2 client | ||
* | ||
* @method close | ||
*/ | ||
get timeout() { | ||
return this._timeout | ||
close(client = this._client) { | ||
return new Promise((resolve) => { | ||
if (client && !client.closed) { | ||
client.close(() => resolve()) | ||
} else { | ||
resolve() | ||
} | ||
}) | ||
} | ||
/** | ||
* @method close | ||
* Destroys the underlying http2 client | ||
* | ||
* @method destroy | ||
*/ | ||
close() { | ||
this.client.close() | ||
destroy(client = this._client) { | ||
if (client && !client.destroyed) { | ||
client.destroy(...arguments) | ||
} | ||
} | ||
/** | ||
* Sends an http2 request | ||
* | ||
* @method request | ||
@@ -59,9 +76,16 @@ * @param {Object} options | ||
return new Promise((resolve, reject) => { | ||
headers[HTTP2_HEADER_METHOD] = method | ||
headers[HTTP2_HEADER_PATH] = path | ||
Object.assign(headers, { | ||
[HTTP2_HEADER_SCHEME]: 'https', | ||
[HTTP2_HEADER_METHOD]: method, | ||
[HTTP2_HEADER_PATH]: path | ||
}) | ||
const req = this.client.request(headers) | ||
const req = this._getOrCreateClient().request(headers) | ||
// Store response properties | ||
let responseHeaders = {} | ||
let responseBody = '' | ||
// Cancel request after timeout | ||
req.setTimeout(this.timeout, () => { | ||
req.setTimeout(this._requestTimeout, () => { | ||
req.close(NGHTTP2_CANCEL) | ||
@@ -71,12 +95,18 @@ reject(new Error(`http2: timeout ${method} ${path}`)) | ||
// Response handling | ||
// Response header handling | ||
req.on('response', (headers) => { | ||
let data = '' | ||
req.on('data', (chunk) => (data += chunk)) | ||
req.on('end', () => { | ||
resolve({ | ||
statusCode: headers[HTTP2_HEADER_STATUS], | ||
headers: headers, | ||
body: data | ||
}) | ||
responseHeaders = headers | ||
}) | ||
// Response body handling | ||
req.on('data', (chunk) => { | ||
responseBody += chunk | ||
}) | ||
// End request handling | ||
req.on('end', () => { | ||
resolve({ | ||
statusCode: responseHeaders[HTTP2_HEADER_STATUS], | ||
headers: responseHeaders, | ||
body: responseBody | ||
}) | ||
@@ -96,4 +126,37 @@ }) | ||
} | ||
/** | ||
* Returns an existing client or creates a new one | ||
* | ||
* @private | ||
* @method _getOrCreateClient | ||
*/ | ||
_getOrCreateClient() { | ||
if (this._client) { | ||
return this._client | ||
} | ||
const client = http2.connect(this._url) | ||
client.on('close', () => this._closeAndDestroy(client)) | ||
client.on('error', () => this._closeAndDestroy(client)) | ||
client.on('socketError', () => this._closeAndDestroy(client)) | ||
client.on('goaway', () => this._closeAndDestroy(client)) | ||
this._client = client | ||
this._pingInterval = setInterval(() => client.ping(), this._pingIntervalMs).unref() | ||
return client | ||
} | ||
/** | ||
* Closes and destorys the existing client. A new client will be created on next request | ||
* | ||
* @private | ||
* @method _closeAndDestroy | ||
*/ | ||
async _closeAndDestroy(client) { | ||
this._client = null | ||
clearInterval(this._pingInterval) | ||
await this.close(client) | ||
this.destroy(client) | ||
} | ||
} | ||
module.exports = HTTP2Client |
{ | ||
"name": "apns2", | ||
"version": "9.0.0", | ||
"version": "9.1.0", | ||
"description": "Node client for connecting to Apple's Push Notification Service using the new HTTP/2 protocol with JSON web tokens.", | ||
@@ -5,0 +5,0 @@ "author": "Andrew Barba <barba@hey.com>", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32589
803