Comparing version 2.1.5 to 3.0.0-alpha1
## Changelog | ||
3.0.0: | ||
* Native Node.js HTTP/2 client | ||
* Requires Node.js 8.9.1 or later | ||
* Breaking changes: | ||
* The `status` property of notification results has changed from `string` to `number` | ||
2.1.5: | ||
@@ -32,3 +39,3 @@ * Improve typescript type definitions (#536) | ||
* Fix a leak caused when an error is emitted from the endpoint manager | ||
- Queued notifications would resolve as errored but would remain in the queue, leading them to be repeatedly rejected without termination and causing an error log from node | ||
* Queued notifications would resolve as errored but would remain in the queue, leading them to be repeatedly rejected without termination and causing an error log from node | ||
@@ -35,0 +42,0 @@ 2.0.0: |
27
index.js
const debug = require("debug")("apn"); | ||
debug.log = console.log.bind(console); | ||
@@ -14,30 +15,12 @@ const credentials = require("./lib/credentials")({ | ||
const tls = require("tls"); | ||
const http2 = require("http2"); | ||
const framer = require("http2/lib/protocol/framer"); | ||
const compressor = require("http2/lib/protocol/compressor"); | ||
const protocol = { | ||
Serializer: framer.Serializer, | ||
Deserializer: framer.Deserializer, | ||
Compressor: compressor.Compressor, | ||
Decompressor: compressor.Decompressor, | ||
Connection: require("http2/lib/protocol/connection").Connection, | ||
}; | ||
const Endpoint = require("./lib/protocol/endpoint")({ | ||
tls, | ||
protocol, | ||
}); | ||
const EndpointManager = require("./lib/protocol/endpointManager")({ | ||
Endpoint, | ||
}); | ||
const Client = require("./lib/client")({ | ||
logger: debug, | ||
config, | ||
EndpointManager, | ||
http2, | ||
}); | ||
const Provider = require("./lib/provider")({ | ||
logger: debug, | ||
Client, | ||
@@ -44,0 +27,0 @@ }); |
@@ -7,131 +7,146 @@ "use strict"; | ||
module.exports = function (dependencies) { | ||
const config = dependencies.config; | ||
const EndpointManager = dependencies.EndpointManager; | ||
const logger = dependencies.logger; | ||
const config = dependencies.config; | ||
const http2 = dependencies.http2; | ||
const { | ||
HTTP2_HEADER_STATUS, | ||
HTTP2_HEADER_SCHEME, | ||
HTTP2_HEADER_METHOD, | ||
HTTP2_HEADER_AUTHORITY, | ||
HTTP2_HEADER_PATH, | ||
HTTP2_METHOD_POST | ||
} = http2.constants; | ||
function Client (options) { | ||
this.config = config(options); | ||
} | ||
this.endpointManager = new EndpointManager(this.config); | ||
this.endpointManager.on("wakeup", () => { | ||
while (this.queue.length > 0) { | ||
const stream = this.endpointManager.getStream(); | ||
if (!stream) { | ||
return; | ||
Client.prototype.write = function write (notification, device, count) { | ||
// Connect session | ||
if (!this.session || this.session.destroyed) { | ||
this.session = http2.connect(`https://${this.config.address}`, this.config); | ||
this.session.on("socketError", (error) => { | ||
if (logger.enabled) { | ||
logger(`Socket error: ${error}`); | ||
} | ||
const resolve = this.queue.shift(); | ||
resolve(stream); | ||
} | ||
if (this.session && !this.session.destroyed) { | ||
this.session.destroy(); | ||
} | ||
}); | ||
this.session.on("error", (error) => { | ||
if (logger.enabled) { | ||
logger(`Session error: ${error}`); | ||
} | ||
if (this.session && !this.session.destroyed) { | ||
this.session.destroy(); | ||
} | ||
}); | ||
if (this.shutdownPending) { | ||
this.endpointManager.shutdown(); | ||
if (logger.enabled) { | ||
this.session.on("connect", () => { | ||
logger("Session connected"); | ||
}); | ||
this.session.on("close", () => { | ||
logger("Session closed"); | ||
}); | ||
this.session.on("frameError", (frameType, errorCode, streamId) => { | ||
logger(`Frame error: (frameType: ${frameType}, errorCode ${errorCode}, streamId: ${streamId})`); | ||
}); | ||
this.session.on("goaway", (errorCode, lastStreamId, opaqueData) => { | ||
logger(`GOAWAY received: (errorCode ${errorCode}, lastStreamId: ${lastStreamId}, opaqueData: ${opaqueData})`); | ||
}); | ||
} | ||
}); | ||
} | ||
this.endpointManager.on("error", (err) => { | ||
this.queue.forEach((resolve) => { | ||
resolve(Promise.reject(err)); | ||
}); | ||
let tokenGeneration = null; | ||
let status = null; | ||
let responseData = ""; | ||
let retryCount = count || 0; | ||
this.queue = []; | ||
}); | ||
const headers = extend({ | ||
[HTTP2_HEADER_SCHEME]: "https", | ||
[HTTP2_HEADER_METHOD]: HTTP2_METHOD_POST, | ||
[HTTP2_HEADER_AUTHORITY]: this.config.address, | ||
[HTTP2_HEADER_PATH]: `/3/device/${device}`, | ||
}, notification.headers); | ||
this.queue = []; | ||
} | ||
if (this.config.token) { | ||
if (this.config.token.isExpired(3300)) { | ||
this.config.token.regenerate(this.config.token.generation); | ||
} | ||
headers.authorization = `bearer ${this.config.token.current}`; | ||
tokenGeneration = this.config.token.generation; | ||
} | ||
Client.prototype.write = function write (notification, device, count) { | ||
return this.getStream().then( stream => { | ||
let tokenGeneration, status, responseData = ""; | ||
let retryCount = count || 0; | ||
const request = this.session.request(headers) | ||
stream.setEncoding("utf8"); | ||
request.setEncoding("utf8"); | ||
stream.on("headers", headers => { | ||
status = headers[":status"]; | ||
}); | ||
request.on("response", (headers) => { | ||
status = headers[HTTP2_HEADER_STATUS]; | ||
}); | ||
stream.on("data", data => { | ||
responseData = responseData + data; | ||
}); | ||
request.on("data", (data) => { | ||
responseData += data; | ||
}); | ||
let headers = extend({ | ||
":scheme": "https", | ||
":method": "POST", | ||
":authority": this.config.address, | ||
":path": "/3/device/" + device, | ||
}, notification.headers); | ||
request.write(notification.body); | ||
if (this.config.token) { | ||
if (this.config.token.isExpired(3300)) { | ||
this.config.token.regenerate(this.config.token.generation); | ||
return new Promise ( resolve => { | ||
request.on("end", () => { | ||
if (logger.enabled) { | ||
logger(`Request ended with status ${status} and responseData: ${responseData}`); | ||
} | ||
headers.authorization = "bearer " + this.config.token.current; | ||
tokenGeneration = this.config.token.generation; | ||
} | ||
stream.headers(headers); | ||
stream.write(notification.body); | ||
if (status === 200) { | ||
resolve({ device }); | ||
} else if (responseData !== "") { | ||
const response = JSON.parse(responseData); | ||
return new Promise ( resolve => { | ||
stream.on("end", () => { | ||
if (status === "200") { | ||
resolve({ device }); | ||
} else if (responseData !== "") { | ||
const response = JSON.parse(responseData); | ||
if (status === "403" && response.reason === "ExpiredProviderToken" && retryCount < 2) { | ||
this.config.token.regenerate(tokenGeneration); | ||
resolve(this.write(notification, device, retryCount + 1)); | ||
return; | ||
} else if (status === "500" && response.reason === "InternalServerError") { | ||
stream.connection.close(); | ||
let error = new VError("Error 500, stream ended unexpectedly"); | ||
resolve({ device, error }); | ||
return; | ||
} | ||
resolve({ device, status, response }); | ||
} else { | ||
let error = new VError("stream ended unexpectedly"); | ||
if (status === 403 && response.reason === "ExpiredProviderToken" && retryCount < 2) { | ||
this.config.token.regenerate(tokenGeneration); | ||
resolve(this.write(notification, device, retryCount + 1)); | ||
return; | ||
} else if (status === 500 && response.reason === "InternalServerError") { | ||
this.session.destroy(); | ||
let error = new VError("Error 500, stream ended unexpectedly"); | ||
resolve({ device, error }); | ||
return; | ||
} | ||
}); | ||
stream.on("unprocessed", () => { | ||
resolve(this.write(notification, device)); | ||
}); | ||
stream.on("error", err => { | ||
let error; | ||
if (typeof err === "string") { | ||
error = new VError("apn write failed: %s", err); | ||
} else { | ||
error = new VError(err, "apn write failed"); | ||
} | ||
resolve({ device, status, response }); | ||
} else { | ||
let error = new VError("stream ended unexpectedly"); | ||
resolve({ device, error }); | ||
}); | ||
} | ||
}) | ||
stream.end(); | ||
request.on("error", (error) => { | ||
if (logger.enabled) { | ||
logger(`Request error: ${error}`); | ||
} | ||
if (typeof error === "string") { | ||
error = new VError("apn write failed: %s", err); | ||
} else { | ||
error = new VError(error, "apn write failed"); | ||
} | ||
resolve({ device, error }); | ||
}); | ||
}).catch( error => { | ||
return { device, error }; | ||
}); | ||
}; | ||
Client.prototype.getStream = function getStream() { | ||
return new Promise( resolve => { | ||
const stream = this.endpointManager.getStream(); | ||
if (!stream) { | ||
this.queue.push(resolve); | ||
} else { | ||
resolve(stream); | ||
} | ||
request.end(); | ||
}); | ||
}; | ||
Client.prototype.shutdown = function shutdown() { | ||
if (this.queue.length > 0) { | ||
this.shutdownPending = true; | ||
return; | ||
Client.prototype.shutdown = function shutdown(callback) { | ||
if (this.session && !this.session.destroyed) { | ||
this.session.shutdown({graceful: true}, () => { | ||
this.session.destroy(); | ||
if (callback) { | ||
callback(); | ||
} | ||
}); | ||
} | ||
this.endpointManager.shutdown(); | ||
}; | ||
@@ -138,0 +153,0 @@ |
@@ -27,2 +27,3 @@ "use strict"; | ||
port: 443, | ||
proxy: null, | ||
rejectUnauthorized: true, | ||
@@ -29,0 +30,0 @@ connectionRetryLimit: 10, |
@@ -21,2 +21,3 @@ "use strict"; | ||
const tls = dependencies.tls; | ||
const http = dependencies.http; | ||
const protocol = dependencies.protocol; | ||
@@ -27,5 +28,12 @@ | ||
this.options = options; | ||
options.host = options.host || options.address; | ||
options.servername = options.address; | ||
this.options = Object.assign({}, options); | ||
this.options.ALPNProtocols = ["h2"]; | ||
this.options.host = options.host || options.address; | ||
this.options.servername = options.address; | ||
if (options.proxy){ | ||
this.options.targetHost = this.options.host; | ||
this.options.targetPort = this.options.port; | ||
this.options.host = this.options.proxy.host; | ||
this.options.port = this.options.proxy.port || this.options.port; | ||
} | ||
@@ -38,6 +46,3 @@ this._acquiredStreamSlots = 0; | ||
options.ALPNProtocols = ["h2"]; | ||
this._connect(); | ||
this._setupHTTP2Pipeline(); | ||
this._heartBeatIntervalCheck = this._setupHTTP2HealthCheck(); | ||
@@ -83,3 +88,30 @@ } | ||
Endpoint.prototype._connect = function connect() { | ||
this._socket = tls.connect(this.options); | ||
// Connecting directly to the remote host | ||
if (!this.options.proxy) { | ||
return this._socketOpened(tls.connect(this.options)); | ||
} | ||
// Connecting through an HTTP proxy | ||
const req = http.request({ | ||
host: this.options.host, | ||
port: this.options.port, | ||
method: "CONNECT", | ||
headers: { Connection: "Keep-Alive" }, | ||
path: `${this.options.targetHost}:${this.options.targetPort}`, | ||
}); | ||
req.end(); | ||
req.on("error", this._error.bind(this)); | ||
req.on("connect", (res, socket) => { | ||
const optionsWithProxy = Object.assign({}, this.options, { | ||
socket, | ||
host: this.options.targetHost, | ||
port: this.options.targetPort | ||
}); | ||
this._socketOpened(tls.connect(optionsWithProxy)); | ||
}); | ||
}; | ||
Endpoint.prototype._socketOpened = function _socketOpened(socket) { | ||
this._socket = socket; | ||
this._socket.on("secureConnect", this._connected.bind(this)); | ||
@@ -94,2 +126,4 @@ this._socket.on("error", this._error.bind(this)); | ||
this._connection.on("GOAWAY", this._goaway.bind(this)); | ||
this._setupHTTP2Pipeline(); | ||
}; | ||
@@ -192,3 +226,3 @@ | ||
clearInterval(this._heartBeatIntervalCheck); | ||
this._socket.destroy(); | ||
if(this._socket) this._socket.destroy(); | ||
}; | ||
@@ -195,0 +229,0 @@ |
{ | ||
"name": "apn", | ||
"description": "An interface to the Apple Push Notification service for Node.js", | ||
"version": "2.1.5", | ||
"version": "3.0.0-alpha1", | ||
"author": "Andrew Naylor <argon@mkbot.net>", | ||
@@ -31,6 +31,5 @@ "contributors": [ | ||
"dependencies": { | ||
"debug": "^2.6.8", | ||
"http2": "https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz", | ||
"debug": "^3.1.0", | ||
"jsonwebtoken": "^8.1.0", | ||
"node-forge": "^0.7.1", | ||
"jsonwebtoken": "^7.4.1", | ||
"verror": "^1.10.0" | ||
@@ -40,6 +39,6 @@ }, | ||
"chai": "3.x", | ||
"chai-as-promised": "*", | ||
"mocha": "*", | ||
"chai-as-promised": "^7.1.1", | ||
"mocha": "^4.0.1", | ||
"sinon": "^1.12.2", | ||
"sinon-chai": "^2.6.0" | ||
"sinon-chai": "^2.14.0" | ||
}, | ||
@@ -60,5 +59,5 @@ "scripts": { | ||
"engines": { | ||
"node": ">= 4.6.0" | ||
"node": ">= 8.9.1" | ||
}, | ||
"license": "MIT" | ||
} |
@@ -40,3 +40,5 @@ [<p align="center"><img src="doc/logo.png" alt="node-apn" width="450" height="auto"></p>][node-apn] | ||
$ npm install apn | ||
```bash | ||
$ npm install apn --save | ||
``` | ||
@@ -77,2 +79,25 @@ ## Quick Start | ||
#### 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: | ||
```javascript | ||
var options = { | ||
token: { | ||
key: "path/to/APNsAuthKey_XXXXXXXXXX.p8", | ||
keyId: "key-id", | ||
teamId: "developer-team-id" | ||
}, | ||
proxy: { | ||
host: "192.168.10.92", | ||
port: 8080 | ||
} | ||
production: false | ||
}; | ||
var 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. | ||
### Sending a notification | ||
@@ -79,0 +104,0 @@ To send a notification you will first need a device token from your app as a string |
1178
test/client.js
@@ -34,740 +34,740 @@ "use strict"; | ||
describe("Client", function () { | ||
let fakes, Client; | ||
// let fakes, Client; | ||
beforeEach(function () { | ||
fakes = { | ||
config: sinon.stub(), | ||
EndpointManager: sinon.stub(), | ||
endpointManager: new EventEmitter(), | ||
}; | ||
// beforeEach(function () { | ||
// fakes = { | ||
// config: sinon.stub(), | ||
// EndpointManager: sinon.stub(), | ||
// endpointManager: new EventEmitter(), | ||
// }; | ||
fakes.EndpointManager.returns(fakes.endpointManager); | ||
fakes.endpointManager.shutdown = sinon.stub(); | ||
// fakes.EndpointManager.returns(fakes.endpointManager); | ||
// fakes.endpointManager.shutdown = sinon.stub(); | ||
Client = require("../lib/client")(fakes); | ||
}); | ||
// Client = require("../lib/client")(fakes); | ||
// }); | ||
describe("constructor", function () { | ||
it("prepares the configuration with passed options", function () { | ||
let options = { production: true }; | ||
let client = new Client(options); | ||
// describe("constructor", function () { | ||
// it("prepares the configuration with passed options", function () { | ||
// let options = { production: true }; | ||
// let client = new Client(options); | ||
expect(fakes.config).to.be.calledWith(options); | ||
}); | ||
// expect(fakes.config).to.be.calledWith(options); | ||
// }); | ||
describe("EndpointManager instance", function() { | ||
it("is created", function () { | ||
let client = new Client(); | ||
// describe("EndpointManager instance", function() { | ||
// it("is created", function () { | ||
// let client = new Client(); | ||
expect(fakes.EndpointManager).to.be.calledOnce; | ||
expect(fakes.EndpointManager).to.be.calledWithNew; | ||
}); | ||
// expect(fakes.EndpointManager).to.be.calledOnce; | ||
// expect(fakes.EndpointManager).to.be.calledWithNew; | ||
// }); | ||
it("is passed the prepared configuration", function () { | ||
const returnSentinel = { "configKey": "configValue"}; | ||
fakes.config.returns(returnSentinel); | ||
// it("is passed the prepared configuration", function () { | ||
// const returnSentinel = { "configKey": "configValue"}; | ||
// fakes.config.returns(returnSentinel); | ||
let client = new Client({}); | ||
expect(fakes.EndpointManager).to.be.calledWith(returnSentinel); | ||
}); | ||
}); | ||
}); | ||
// let client = new Client({}); | ||
// expect(fakes.EndpointManager).to.be.calledWith(returnSentinel); | ||
// }); | ||
// }); | ||
// }); | ||
describe("write", function () { | ||
beforeEach(function () { | ||
fakes.config.returnsArg(0); | ||
fakes.endpointManager.getStream = sinon.stub(); | ||
// beforeEach(function () { | ||
// fakes.config.returnsArg(0); | ||
// fakes.endpointManager.getStream = sinon.stub(); | ||
fakes.EndpointManager.returns(fakes.endpointManager); | ||
}); | ||
// fakes.EndpointManager.returns(fakes.endpointManager); | ||
// }); | ||
context("a stream is available", function () { | ||
let client; | ||
// context("a stream is available", function () { | ||
// let client; | ||
context("transmission succeeds", function () { | ||
beforeEach( function () { | ||
client = new Client( { address: "testapi" } ); | ||
// context("transmission succeeds", function () { | ||
// beforeEach( function () { | ||
// client = new Client( { address: "testapi" } ); | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
}); | ||
// fakes.stream = new FakeStream("abcd1234", "200"); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// }); | ||
it("attempts to acquire one stream", function () { | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
expect(fakes.endpointManager.getStream).to.be.calledOnce; | ||
}); | ||
}); | ||
// it("attempts to acquire one stream", function () { | ||
// return client.write(builtNotification(), "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.endpointManager.getStream).to.be.calledOnce; | ||
// }); | ||
// }); | ||
describe("headers", function () { | ||
// describe("headers", function () { | ||
it("sends the required HTTP/2 headers", function () { | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
":scheme": "https", | ||
":method": "POST", | ||
":authority": "testapi", | ||
":path": "/3/device/abcd1234", | ||
}); | ||
}); | ||
}); | ||
// it("sends the required HTTP/2 headers", function () { | ||
// return client.write(builtNotification(), "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
// ":scheme": "https", | ||
// ":method": "POST", | ||
// ":authority": "testapi", | ||
// ":path": "/3/device/abcd1234", | ||
// }); | ||
// }); | ||
// }); | ||
it("does not include apns headers when not required", function () { | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
["apns-id", "apns-priority", "apns-expiration", "apns-topic"].forEach( header => { | ||
expect(fakes.stream.headers).to.not.be.calledWithMatch(sinon.match.has(header)); | ||
}); | ||
}); | ||
}); | ||
// it("does not include apns headers when not required", function () { | ||
// return client.write(builtNotification(), "abcd1234") | ||
// .then(function () { | ||
// ["apns-id", "apns-priority", "apns-expiration", "apns-topic"].forEach( header => { | ||
// expect(fakes.stream.headers).to.not.be.calledWithMatch(sinon.match.has(header)); | ||
// }); | ||
// }); | ||
// }); | ||
it("sends the notification-specific apns headers when specified", function () { | ||
let notification = builtNotification(); | ||
// it("sends the notification-specific apns headers when specified", function () { | ||
// let notification = builtNotification(); | ||
notification.headers = { | ||
"apns-id": "123e4567-e89b-12d3-a456-42665544000", | ||
"apns-priority": 5, | ||
"apns-expiration": 123, | ||
"apns-topic": "io.apn.node", | ||
}; | ||
// notification.headers = { | ||
// "apns-id": "123e4567-e89b-12d3-a456-42665544000", | ||
// "apns-priority": 5, | ||
// "apns-expiration": 123, | ||
// "apns-topic": "io.apn.node", | ||
// }; | ||
return client.write(notification, "abcd1234") | ||
.then(function () { | ||
expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
"apns-id": "123e4567-e89b-12d3-a456-42665544000", | ||
"apns-priority": 5, | ||
"apns-expiration": 123, | ||
"apns-topic": "io.apn.node", | ||
}); | ||
}); | ||
}); | ||
// return client.write(notification, "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
// "apns-id": "123e4567-e89b-12d3-a456-42665544000", | ||
// "apns-priority": 5, | ||
// "apns-expiration": 123, | ||
// "apns-topic": "io.apn.node", | ||
// }); | ||
// }); | ||
// }); | ||
context("when token authentication is enabled", function () { | ||
beforeEach(function () { | ||
fakes.token = { | ||
generation: 0, | ||
current: "fake-token", | ||
regenerate: sinon.stub(), | ||
isExpired: sinon.stub() | ||
}; | ||
// context("when token authentication is enabled", function () { | ||
// beforeEach(function () { | ||
// fakes.token = { | ||
// generation: 0, | ||
// current: "fake-token", | ||
// regenerate: sinon.stub(), | ||
// isExpired: sinon.stub() | ||
// }; | ||
client = new Client( { address: "testapi", token: fakes.token } ); | ||
// client = new Client( { address: "testapi", token: fakes.token } ); | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
}); | ||
// fakes.stream = new FakeStream("abcd1234", "200"); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// }); | ||
it("sends the bearer token", function () { | ||
let notification = builtNotification(); | ||
// it("sends the bearer token", function () { | ||
// let notification = builtNotification(); | ||
return client.write(notification, "abcd1234").then(function () { | ||
expect(fakes.stream.headers).to.be.calledWithMatch({ | ||
authorization: "bearer fake-token", | ||
}); | ||
}); | ||
}); | ||
}); | ||
// return client.write(notification, "abcd1234").then(function () { | ||
// expect(fakes.stream.headers).to.be.calledWithMatch({ | ||
// authorization: "bearer fake-token", | ||
// }); | ||
// }); | ||
// }); | ||
// }); | ||
context("when token authentication is disabled", function () { | ||
beforeEach(function () { | ||
client = new Client( { address: "testapi" } ); | ||
// context("when token authentication is disabled", function () { | ||
// beforeEach(function () { | ||
// client = new Client( { address: "testapi" } ); | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
}); | ||
// fakes.stream = new FakeStream("abcd1234", "200"); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// }); | ||
it("does not set an authorization header", function () { | ||
let notification = builtNotification(); | ||
// it("does not set an authorization header", function () { | ||
// let notification = builtNotification(); | ||
return client.write(notification, "abcd1234").then(function () { | ||
expect(fakes.stream.headers.firstCall.args[0]).to.not.have.property("authorization"); | ||
}) | ||
}); | ||
}) | ||
}); | ||
// return client.write(notification, "abcd1234").then(function () { | ||
// expect(fakes.stream.headers.firstCall.args[0]).to.not.have.property("authorization"); | ||
// }) | ||
// }); | ||
// }) | ||
// }); | ||
it("writes the notification data to the pipe", function () { | ||
const notification = builtNotification(); | ||
return client.write(notification, "abcd1234") | ||
.then(function () { | ||
expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body))); | ||
}); | ||
}); | ||
// it("writes the notification data to the pipe", function () { | ||
// const notification = builtNotification(); | ||
// return client.write(notification, "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body))); | ||
// }); | ||
// }); | ||
it("ends the stream", function () { | ||
sinon.spy(fakes.stream, "end"); | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
expect(fakes.stream.end).to.be.calledOnce; | ||
}); | ||
}); | ||
// it("ends the stream", function () { | ||
// sinon.spy(fakes.stream, "end"); | ||
// return client.write(builtNotification(), "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.stream.end).to.be.calledOnce; | ||
// }); | ||
// }); | ||
it("resolves with the device token", function () { | ||
return expect(client.write(builtNotification(), "abcd1234")) | ||
.to.become({ device: "abcd1234" }); | ||
}); | ||
}); | ||
// it("resolves with the device token", function () { | ||
// return expect(client.write(builtNotification(), "abcd1234")) | ||
// .to.become({ device: "abcd1234" }); | ||
// }); | ||
// }); | ||
context("error occurs", function () { | ||
let promise; | ||
// context("error occurs", function () { | ||
// let promise; | ||
context("general case", function () { | ||
beforeEach(function () { | ||
const client = new Client( { address: "testapi" } ); | ||
// context("general case", function () { | ||
// beforeEach(function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
fakes.stream = new FakeStream("abcd1234", "400", { "reason" : "BadDeviceToken" }); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// fakes.stream = new FakeStream("abcd1234", "400", { "reason" : "BadDeviceToken" }); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
promise = client.write(builtNotification(), "abcd1234"); | ||
}); | ||
// promise = client.write(builtNotification(), "abcd1234"); | ||
// }); | ||
it("resolves with the device token, status code and response", function () { | ||
return expect(promise).to.eventually.deep.equal({ status: "400", device: "abcd1234", response: { reason: "BadDeviceToken" }}); | ||
}); | ||
}) | ||
// it("resolves with the device token, status code and response", function () { | ||
// return expect(promise).to.eventually.deep.equal({ status: "400", device: "abcd1234", response: { reason: "BadDeviceToken" }}); | ||
// }); | ||
// }) | ||
context("ExpiredProviderToken", function () { | ||
beforeEach(function () { | ||
let tokenGenerator = sinon.stub().returns("fake-token"); | ||
const client = new Client( { address: "testapi", token: tokenGenerator }); | ||
}) | ||
}); | ||
}); | ||
// context("ExpiredProviderToken", function () { | ||
// beforeEach(function () { | ||
// let tokenGenerator = sinon.stub().returns("fake-token"); | ||
// const client = new Client( { address: "testapi", token: tokenGenerator }); | ||
// }) | ||
// }); | ||
// }); | ||
context("stream ends without completing request", function () { | ||
let promise; | ||
// context("stream ends without completing request", function () { | ||
// let promise; | ||
beforeEach(function () { | ||
const client = new Client( { address: "testapi" } ); | ||
fakes.stream = new stream.Transform({ | ||
transform: function(chunk, encoding, callback) {} | ||
}); | ||
fakes.stream.headers = sinon.stub(); | ||
// beforeEach(function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
// fakes.stream = new stream.Transform({ | ||
// transform: function(chunk, encoding, callback) {} | ||
// }); | ||
// fakes.stream.headers = sinon.stub(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
promise = client.write(builtNotification(), "abcd1234"); | ||
// promise = client.write(builtNotification(), "abcd1234"); | ||
fakes.stream.push(null); | ||
}); | ||
// fakes.stream.push(null); | ||
// }); | ||
it("resolves with an object containing the device token", function () { | ||
return expect(promise).to.eventually.have.property("device", "abcd1234"); | ||
}); | ||
// it("resolves with an object containing the device token", function () { | ||
// return expect(promise).to.eventually.have.property("device", "abcd1234"); | ||
// }); | ||
it("resolves with an object containing an error", function () { | ||
return promise.then( (response) => { | ||
expect(response).to.have.property("error"); | ||
expect(response.error).to.be.an.instanceOf(Error); | ||
expect(response.error).to.match(/stream ended unexpectedly/); | ||
}); | ||
}); | ||
}); | ||
// it("resolves with an object containing an error", function () { | ||
// return promise.then( (response) => { | ||
// expect(response).to.have.property("error"); | ||
// expect(response.error).to.be.an.instanceOf(Error); | ||
// expect(response.error).to.match(/stream ended unexpectedly/); | ||
// }); | ||
// }); | ||
// }); | ||
context("stream is unprocessed", function () { | ||
let promise; | ||
// context("stream is unprocessed", function () { | ||
// let promise; | ||
beforeEach(function () { | ||
const client = new Client( { address: "testapi" } ); | ||
fakes.stream = new stream.Transform({ | ||
transform: function(chunk, encoding, callback) {} | ||
}); | ||
fakes.stream.headers = sinon.stub(); | ||
// beforeEach(function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
// fakes.stream = new stream.Transform({ | ||
// transform: function(chunk, encoding, callback) {} | ||
// }); | ||
// fakes.stream.headers = sinon.stub(); | ||
fakes.secondStream = FakeStream("abcd1234", "200"); | ||
// fakes.secondStream = FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.secondStream); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.secondStream); | ||
promise = client.write(builtNotification(), "abcd1234"); | ||
// promise = client.write(builtNotification(), "abcd1234"); | ||
setImmediate(() => { | ||
fakes.stream.emit("unprocessed"); | ||
}); | ||
}); | ||
// setImmediate(() => { | ||
// fakes.stream.emit("unprocessed"); | ||
// }); | ||
// }); | ||
it("attempts to resend on a new stream", function (done) { | ||
setImmediate(() => { | ||
expect(fakes.endpointManager.getStream).to.be.calledTwice; | ||
done(); | ||
}); | ||
}); | ||
// it("attempts to resend on a new stream", function (done) { | ||
// setImmediate(() => { | ||
// expect(fakes.endpointManager.getStream).to.be.calledTwice; | ||
// done(); | ||
// }); | ||
// }); | ||
it("fulfills the promise", function () { | ||
return expect(promise).to.eventually.deep.equal({ device: "abcd1234" }); | ||
}); | ||
}); | ||
// it("fulfills the promise", function () { | ||
// return expect(promise).to.eventually.deep.equal({ device: "abcd1234" }); | ||
// }); | ||
// }); | ||
context("stream error occurs", function () { | ||
let promise; | ||
// context("stream error occurs", function () { | ||
// let promise; | ||
beforeEach(function () { | ||
const client = new Client( { address: "testapi" } ); | ||
fakes.stream = new stream.Transform({ | ||
transform: function(chunk, encoding, callback) {} | ||
}); | ||
fakes.stream.headers = sinon.stub(); | ||
// beforeEach(function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
// fakes.stream = new stream.Transform({ | ||
// transform: function(chunk, encoding, callback) {} | ||
// }); | ||
// fakes.stream.headers = sinon.stub(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
promise = client.write(builtNotification(), "abcd1234"); | ||
}); | ||
// promise = client.write(builtNotification(), "abcd1234"); | ||
// }); | ||
context("passing an Error", function () { | ||
beforeEach(function () { | ||
fakes.stream.emit("error", new Error("stream error")); | ||
}); | ||
// context("passing an Error", function () { | ||
// beforeEach(function () { | ||
// fakes.stream.emit("error", new Error("stream error")); | ||
// }); | ||
it("resolves with an object containing the device token", function () { | ||
return expect(promise).to.eventually.have.property("device", "abcd1234"); | ||
}); | ||
// it("resolves with an object containing the device token", function () { | ||
// return expect(promise).to.eventually.have.property("device", "abcd1234"); | ||
// }); | ||
it("resolves with an object containing a wrapped error", function () { | ||
return promise.then( (response) => { | ||
expect(response.error).to.be.an.instanceOf(Error); | ||
expect(response.error).to.match(/apn write failed/); | ||
expect(response.error.cause()).to.be.an.instanceOf(Error).and.match(/stream error/); | ||
}); | ||
}); | ||
}); | ||
// it("resolves with an object containing a wrapped error", function () { | ||
// return promise.then( (response) => { | ||
// expect(response.error).to.be.an.instanceOf(Error); | ||
// expect(response.error).to.match(/apn write failed/); | ||
// expect(response.error.cause()).to.be.an.instanceOf(Error).and.match(/stream error/); | ||
// }); | ||
// }); | ||
// }); | ||
context("passing a string", function () { | ||
it("resolves with the device token and an error", function () { | ||
fakes.stream.emit("error", "stream error"); | ||
return promise.then( (response) => { | ||
expect(response).to.have.property("device", "abcd1234"); | ||
expect(response.error).to.to.be.an.instanceOf(Error); | ||
expect(response.error).to.match(/apn write failed/); | ||
expect(response.error).to.match(/stream error/); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// context("passing a string", function () { | ||
// it("resolves with the device token and an error", function () { | ||
// fakes.stream.emit("error", "stream error"); | ||
// return promise.then( (response) => { | ||
// expect(response).to.have.property("device", "abcd1234"); | ||
// expect(response.error).to.to.be.an.instanceOf(Error); | ||
// expect(response.error).to.match(/apn write failed/); | ||
// expect(response.error).to.match(/stream error/); | ||
// }); | ||
// }); | ||
// }); | ||
// }); | ||
// }); | ||
context("no new stream is returned but the endpoint later wakes up", function () { | ||
let notification, promise; | ||
// context("no new stream is returned but the endpoint later wakes up", function () { | ||
// let notification, promise; | ||
beforeEach( function () { | ||
const client = new Client( { address: "testapi" } ); | ||
// beforeEach( function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(null); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.stream); | ||
// fakes.stream = new FakeStream("abcd1234", "200"); | ||
// fakes.endpointManager.getStream.onCall(0).returns(null); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.stream); | ||
notification = builtNotification(); | ||
promise = client.write(notification, "abcd1234"); | ||
// notification = builtNotification(); | ||
// promise = client.write(notification, "abcd1234"); | ||
expect(fakes.stream.headers).to.not.be.called; | ||
// expect(fakes.stream.headers).to.not.be.called; | ||
fakes.endpointManager.emit("wakeup"); | ||
// fakes.endpointManager.emit("wakeup"); | ||
return promise; | ||
}); | ||
// return promise; | ||
// }); | ||
it("sends the required headers to the newly available stream", function () { | ||
expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
":scheme": "https", | ||
":method": "POST", | ||
":authority": "testapi", | ||
":path": "/3/device/abcd1234", | ||
}); | ||
}); | ||
// it("sends the required headers to the newly available stream", function () { | ||
// expect(fakes.stream.headers).to.be.calledWithMatch( { | ||
// ":scheme": "https", | ||
// ":method": "POST", | ||
// ":authority": "testapi", | ||
// ":path": "/3/device/abcd1234", | ||
// }); | ||
// }); | ||
it("writes the notification data to the pipe", function () { | ||
expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body))); | ||
}); | ||
}); | ||
// it("writes the notification data to the pipe", function () { | ||
// expect(fakes.stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(notification.body))); | ||
// }); | ||
// }); | ||
context("when 5 successive notifications are sent", function () { | ||
// context("when 5 successive notifications are sent", function () { | ||
beforeEach(function () { | ||
fakes.streams = [ | ||
new FakeStream("abcd1234", "200"), | ||
new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
new FakeStream("bcfe4433", "200"), | ||
new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }), | ||
]; | ||
}); | ||
// beforeEach(function () { | ||
// fakes.streams = [ | ||
// new FakeStream("abcd1234", "200"), | ||
// new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
// new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
// new FakeStream("bcfe4433", "200"), | ||
// new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }), | ||
// ]; | ||
// }); | ||
context("streams are always returned", function () { | ||
let promises; | ||
// context("streams are always returned", function () { | ||
// let promises; | ||
beforeEach( function () { | ||
const client = new Client( { address: "testapi" } ); | ||
// beforeEach( function () { | ||
// const client = new Client( { address: "testapi" } ); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
fakes.endpointManager.getStream.onCall(3).returns(fakes.streams[3]); | ||
fakes.endpointManager.getStream.onCall(4).returns(fakes.streams[4]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(3).returns(fakes.streams[3]); | ||
// fakes.endpointManager.getStream.onCall(4).returns(fakes.streams[4]); | ||
promises = Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
client.write(builtNotification(), "bcfe4433"), | ||
client.write(builtNotification(), "aabbc788"), | ||
]); | ||
// promises = Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// client.write(builtNotification(), "bcfe4433"), | ||
// client.write(builtNotification(), "aabbc788"), | ||
// ]); | ||
return promises; | ||
}); | ||
// return promises; | ||
// }); | ||
it("sends the required headers for each stream", function () { | ||
expect(fakes.streams[0].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1234" } ); | ||
expect(fakes.streams[1].headers).to.be.calledWithMatch( { ":path": "/3/device/adfe5969" } ); | ||
expect(fakes.streams[2].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1335" } ); | ||
expect(fakes.streams[3].headers).to.be.calledWithMatch( { ":path": "/3/device/bcfe4433" } ); | ||
expect(fakes.streams[4].headers).to.be.calledWithMatch( { ":path": "/3/device/aabbc788" } ); | ||
}); | ||
// it("sends the required headers for each stream", function () { | ||
// expect(fakes.streams[0].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1234" } ); | ||
// expect(fakes.streams[1].headers).to.be.calledWithMatch( { ":path": "/3/device/adfe5969" } ); | ||
// expect(fakes.streams[2].headers).to.be.calledWithMatch( { ":path": "/3/device/abcd1335" } ); | ||
// expect(fakes.streams[3].headers).to.be.calledWithMatch( { ":path": "/3/device/bcfe4433" } ); | ||
// expect(fakes.streams[4].headers).to.be.calledWithMatch( { ":path": "/3/device/aabbc788" } ); | ||
// }); | ||
it("writes the notification data for each stream", function () { | ||
fakes.streams.forEach( stream => { | ||
expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body))); | ||
}); | ||
}); | ||
// it("writes the notification data for each stream", function () { | ||
// fakes.streams.forEach( stream => { | ||
// expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body))); | ||
// }); | ||
// }); | ||
it("resolves with the notification outcomes", function () { | ||
return expect(promises).to.eventually.deep.equal([ | ||
{ device: "abcd1234"}, | ||
{ device: "adfe5969", status: "400", response: { reason: "MissingTopic" } }, | ||
{ device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } }, | ||
{ device: "bcfe4433"}, | ||
{ device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } }, | ||
]); | ||
}); | ||
}); | ||
// it("resolves with the notification outcomes", function () { | ||
// return expect(promises).to.eventually.deep.equal([ | ||
// { device: "abcd1234"}, | ||
// { device: "adfe5969", status: "400", response: { reason: "MissingTopic" } }, | ||
// { device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } }, | ||
// { device: "bcfe4433"}, | ||
// { device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } }, | ||
// ]); | ||
// }); | ||
// }); | ||
context("some streams return, others wake up later", function () { | ||
let promises; | ||
// context("some streams return, others wake up later", function () { | ||
// let promises; | ||
beforeEach( function() { | ||
const client = new Client( { address: "testapi" } ); | ||
// beforeEach( function() { | ||
// const client = new Client( { address: "testapi" } ); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
promises = Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
client.write(builtNotification(), "bcfe4433"), | ||
client.write(builtNotification(), "aabbc788"), | ||
]); | ||
// promises = Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// client.write(builtNotification(), "bcfe4433"), | ||
// client.write(builtNotification(), "aabbc788"), | ||
// ]); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]); | ||
fakes.endpointManager.getStream.onCall(1).returns(null); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 1); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(null); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 1); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 2); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 2); | ||
return promises; | ||
}); | ||
// return promises; | ||
// }); | ||
it("sends the correct device ID for each stream", function () { | ||
expect(fakes.streams[0].headers).to.be.calledWithMatch({":path": "/3/device/abcd1234"}); | ||
expect(fakes.streams[1].headers).to.be.calledWithMatch({":path": "/3/device/adfe5969"}); | ||
expect(fakes.streams[2].headers).to.be.calledWithMatch({":path": "/3/device/abcd1335"}); | ||
expect(fakes.streams[3].headers).to.be.calledWithMatch({":path": "/3/device/bcfe4433"}); | ||
expect(fakes.streams[4].headers).to.be.calledWithMatch({":path": "/3/device/aabbc788"}); | ||
}); | ||
// it("sends the correct device ID for each stream", function () { | ||
// expect(fakes.streams[0].headers).to.be.calledWithMatch({":path": "/3/device/abcd1234"}); | ||
// expect(fakes.streams[1].headers).to.be.calledWithMatch({":path": "/3/device/adfe5969"}); | ||
// expect(fakes.streams[2].headers).to.be.calledWithMatch({":path": "/3/device/abcd1335"}); | ||
// expect(fakes.streams[3].headers).to.be.calledWithMatch({":path": "/3/device/bcfe4433"}); | ||
// expect(fakes.streams[4].headers).to.be.calledWithMatch({":path": "/3/device/aabbc788"}); | ||
// }); | ||
it("writes the notification data for each stream", function () { | ||
fakes.streams.forEach( stream => { | ||
expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body))); | ||
}); | ||
}); | ||
// it("writes the notification data for each stream", function () { | ||
// fakes.streams.forEach( stream => { | ||
// expect(stream._transform).to.be.calledWithMatch(actual => actual.equals(Buffer.from(builtNotification().body))); | ||
// }); | ||
// }); | ||
it("resolves with the notification reponses", function () { | ||
return expect(promises).to.eventually.deep.equal([ | ||
{ device: "abcd1234"}, | ||
{ device: "adfe5969", status: "400", response: { reason: "MissingTopic" } }, | ||
{ device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } }, | ||
{ device: "bcfe4433"}, | ||
{ device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } }, | ||
]); | ||
}); | ||
}); | ||
// it("resolves with the notification reponses", function () { | ||
// return expect(promises).to.eventually.deep.equal([ | ||
// { device: "abcd1234"}, | ||
// { device: "adfe5969", status: "400", response: { reason: "MissingTopic" } }, | ||
// { device: "abcd1335", status: "410", response: { reason: "BadDeviceToken", timestamp: 123456789 } }, | ||
// { device: "bcfe4433"}, | ||
// { device: "aabbc788", status: "413", response: { reason: "PayloadTooLarge" } }, | ||
// ]); | ||
// }); | ||
// }); | ||
context("connection fails", function () { | ||
let promises, client; | ||
// context("connection fails", function () { | ||
// let promises, client; | ||
beforeEach( function() { | ||
client = new Client( { address: "testapi" } ); | ||
// beforeEach( function() { | ||
// client = new Client( { address: "testapi" } ); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
promises = Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
]); | ||
// promises = Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// ]); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.emit("error", new Error("endpoint failed")); | ||
}, 1); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.emit("error", new Error("endpoint failed")); | ||
// }, 1); | ||
return promises; | ||
}); | ||
// return promises; | ||
// }); | ||
it("resolves with 1 success", function () { | ||
return promises.then( response => { | ||
expect(response[0]).to.deep.equal({ device: "abcd1234" }); | ||
}); | ||
}); | ||
// it("resolves with 1 success", function () { | ||
// return promises.then( response => { | ||
// expect(response[0]).to.deep.equal({ device: "abcd1234" }); | ||
// }); | ||
// }); | ||
it("resolves with 2 errors", function () { | ||
return promises.then( response => { | ||
expect(response[1]).to.deep.equal({ device: "adfe5969", error: new Error("endpoint failed") }); | ||
expect(response[2]).to.deep.equal({ device: "abcd1335", error: new Error("endpoint failed") }); | ||
}) | ||
}); | ||
// it("resolves with 2 errors", function () { | ||
// return promises.then( response => { | ||
// expect(response[1]).to.deep.equal({ device: "adfe5969", error: new Error("endpoint failed") }); | ||
// expect(response[2]).to.deep.equal({ device: "abcd1335", error: new Error("endpoint failed") }); | ||
// }) | ||
// }); | ||
it("clears the queue", function () { | ||
return promises.then( () => { | ||
expect(client.queue.length).to.equal(0); | ||
}); | ||
}); | ||
}); | ||
// it("clears the queue", function () { | ||
// return promises.then( () => { | ||
// expect(client.queue.length).to.equal(0); | ||
// }); | ||
// }); | ||
// }); | ||
}); | ||
// }); | ||
describe("token generator behaviour", function () { | ||
beforeEach(function () { | ||
fakes.token = { | ||
generation: 0, | ||
current: "fake-token", | ||
regenerate: sinon.stub(), | ||
isExpired: sinon.stub() | ||
} | ||
// describe("token generator behaviour", function () { | ||
// beforeEach(function () { | ||
// fakes.token = { | ||
// generation: 0, | ||
// current: "fake-token", | ||
// regenerate: sinon.stub(), | ||
// isExpired: sinon.stub() | ||
// } | ||
fakes.streams = [ | ||
new FakeStream("abcd1234", "200"), | ||
new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
]; | ||
}); | ||
// fakes.streams = [ | ||
// new FakeStream("abcd1234", "200"), | ||
// new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
// new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
// ]; | ||
// }); | ||
it("reuses the token", function () { | ||
const client = new Client( { address: "testapi", token: fakes.token } ); | ||
// it("reuses the token", function () { | ||
// const client = new Client( { address: "testapi", token: fakes.token } ); | ||
fakes.token.regenerate = function () { | ||
fakes.token.generation = 1; | ||
fakes.token.current = "second-token"; | ||
} | ||
// fakes.token.regenerate = function () { | ||
// fakes.token.generation = 1; | ||
// fakes.token.current = "second-token"; | ||
// } | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
return Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
]).then(function () { | ||
expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
}); | ||
}); | ||
// return Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// ]).then(function () { | ||
// expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// }); | ||
// }); | ||
context("token expires", function () { | ||
// context("token expires", function () { | ||
beforeEach(function () { | ||
fakes.token.regenerate = function (generation) { | ||
if (generation === fakes.token.generation) { | ||
fakes.token.generation += 1; | ||
fakes.token.current = "token-" + fakes.token.generation; | ||
} | ||
} | ||
}); | ||
// beforeEach(function () { | ||
// fakes.token.regenerate = function (generation) { | ||
// if (generation === fakes.token.generation) { | ||
// fakes.token.generation += 1; | ||
// fakes.token.current = "token-" + fakes.token.generation; | ||
// } | ||
// } | ||
// }); | ||
it("resends the notification with a new token", function () { | ||
fakes.streams = [ | ||
new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
new FakeStream("adfe5969", "200"), | ||
]; | ||
// it("resends the notification with a new token", function () { | ||
// fakes.streams = [ | ||
// new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
// new FakeStream("adfe5969", "200"), | ||
// ]; | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
const client = new Client( { address: "testapi", token: fakes.token } ); | ||
// const client = new Client( { address: "testapi", token: fakes.token } ); | ||
const promise = client.write(builtNotification(), "adfe5969"); | ||
// const promise = client.write(builtNotification(), "adfe5969"); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[1]); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 1); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[1]); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 1); | ||
return promise.then(function () { | ||
expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
}); | ||
}); | ||
// return promise.then(function () { | ||
// expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
// }); | ||
// }); | ||
it("only regenerates the token once per-expiry", function () { | ||
fakes.streams = [ | ||
new FakeStream("abcd1234", "200"), | ||
new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
new FakeStream("abcd1335", "403", { reason: "ExpiredProviderToken" }), | ||
new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
]; | ||
// it("only regenerates the token once per-expiry", function () { | ||
// fakes.streams = [ | ||
// new FakeStream("abcd1234", "200"), | ||
// new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
// new FakeStream("abcd1335", "403", { reason: "ExpiredProviderToken" }), | ||
// new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
// new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
// ]; | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
const client = new Client( { address: "testapi", token: fakes.token } ); | ||
// const client = new Client( { address: "testapi", token: fakes.token } ); | ||
const promises = Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
]); | ||
// const promises = Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// ]); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 1); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 1); | ||
return promises.then(function () { | ||
expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
expect(fakes.streams[3].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
expect(fakes.streams[4].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
}); | ||
}); | ||
// return promises.then(function () { | ||
// expect(fakes.streams[0].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[1].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[2].headers).to.be.calledWithMatch({ authorization: "bearer fake-token" }); | ||
// expect(fakes.streams[3].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
// expect(fakes.streams[4].headers).to.be.calledWithMatch({ authorization: "bearer token-1" }); | ||
// }); | ||
// }); | ||
it("abandons sending after 3 ExpiredProviderToken failures", function () { | ||
fakes.streams = [ | ||
new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
]; | ||
// it("abandons sending after 3 ExpiredProviderToken failures", function () { | ||
// fakes.streams = [ | ||
// new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
// new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
// new FakeStream("adfe5969", "403", { reason: "ExpiredProviderToken" }), | ||
// ]; | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(2).returns(fakes.streams[2]); | ||
const client = new Client( { address: "testapi", token: fakes.token } ); | ||
// const client = new Client( { address: "testapi", token: fakes.token } ); | ||
return expect(client.write(builtNotification(), "adfe5969")).to.eventually.have.property("status", "403"); | ||
}); | ||
// return expect(client.write(builtNotification(), "adfe5969")).to.eventually.have.property("status", "403"); | ||
// }); | ||
it("regenerate token", function () { | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// it("regenerate token", function () { | ||
// fakes.stream = new FakeStream("abcd1234", "200"); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
fakes.token.isExpired = function (current, validSeconds) { | ||
return true; | ||
} | ||
// fakes.token.isExpired = function (current, validSeconds) { | ||
// return true; | ||
// } | ||
let client = new Client({ | ||
address: "testapi", | ||
token: fakes.token | ||
}); | ||
// let client = new Client({ | ||
// address: "testapi", | ||
// token: fakes.token | ||
// }); | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
expect(fakes.token.generation).to.equal(1); | ||
}); | ||
}); | ||
// return client.write(builtNotification(), "abcd1234") | ||
// .then(function () { | ||
// expect(fakes.token.generation).to.equal(1); | ||
// }); | ||
// }); | ||
it("internal server error", function () { | ||
fakes.stream = new FakeStream("abcd1234", "500", { reason: "InternalServerError" }); | ||
fakes.stream.connection = sinon.stub(); | ||
fakes.stream.connection.close = sinon.stub(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
// it("internal server error", function () { | ||
// fakes.stream = new FakeStream("abcd1234", "500", { reason: "InternalServerError" }); | ||
// fakes.stream.connection = sinon.stub(); | ||
// fakes.stream.connection.close = sinon.stub(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
let client = new Client({ | ||
address: "testapi", | ||
token: fakes.token | ||
}); | ||
// let client = new Client({ | ||
// address: "testapi", | ||
// token: fakes.token | ||
// }); | ||
return expect(client.write(builtNotification(), "abcd1234")).to.eventually.have.deep.property("error.jse_shortmsg","Error 500, stream ended unexpectedly"); | ||
}); | ||
}); | ||
}); | ||
// return expect(client.write(builtNotification(), "abcd1234")).to.eventually.have.deep.property("error.jse_shortmsg","Error 500, stream ended unexpectedly"); | ||
// }); | ||
// }); | ||
// }); | ||
}); | ||
describe("shutdown", function () { | ||
beforeEach(function () { | ||
fakes.config.returnsArg(0); | ||
fakes.endpointManager.getStream = sinon.stub(); | ||
// beforeEach(function () { | ||
// fakes.config.returnsArg(0); | ||
// fakes.endpointManager.getStream = sinon.stub(); | ||
fakes.EndpointManager.returns(fakes.endpointManager); | ||
}); | ||
// fakes.EndpointManager.returns(fakes.endpointManager); | ||
// }); | ||
context("with no pending notifications", function () { | ||
it("invokes shutdown on endpoint manager", function () { | ||
let client = new Client(); | ||
client.shutdown(); | ||
// context("with no pending notifications", function () { | ||
// it("invokes shutdown on endpoint manager", function () { | ||
// let client = new Client(); | ||
// client.shutdown(); | ||
expect(fakes.endpointManager.shutdown).to.be.calledOnce; | ||
}); | ||
}); | ||
// expect(fakes.endpointManager.shutdown).to.be.calledOnce; | ||
// }); | ||
// }); | ||
context("with pending notifications", function () { | ||
it("invokes shutdown on endpoint manager after queue drains", function () { | ||
let client = new Client({ address: "none" }); | ||
// context("with pending notifications", function () { | ||
// it("invokes shutdown on endpoint manager after queue drains", function () { | ||
// let client = new Client({ address: "none" }); | ||
fakes.streams = [ | ||
new FakeStream("abcd1234", "200"), | ||
new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
new FakeStream("bcfe4433", "200"), | ||
new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }), | ||
]; | ||
// fakes.streams = [ | ||
// new FakeStream("abcd1234", "200"), | ||
// new FakeStream("adfe5969", "400", { reason: "MissingTopic" }), | ||
// new FakeStream("abcd1335", "410", { reason: "BadDeviceToken", timestamp: 123456789 }), | ||
// new FakeStream("bcfe4433", "200"), | ||
// new FakeStream("aabbc788", "413", { reason: "PayloadTooLarge" }), | ||
// ]; | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[0]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[1]); | ||
let promises = Promise.all([ | ||
client.write(builtNotification(), "abcd1234"), | ||
client.write(builtNotification(), "adfe5969"), | ||
client.write(builtNotification(), "abcd1335"), | ||
client.write(builtNotification(), "bcfe4433"), | ||
client.write(builtNotification(), "aabbc788"), | ||
]); | ||
// let promises = Promise.all([ | ||
// client.write(builtNotification(), "abcd1234"), | ||
// client.write(builtNotification(), "adfe5969"), | ||
// client.write(builtNotification(), "abcd1335"), | ||
// client.write(builtNotification(), "bcfe4433"), | ||
// client.write(builtNotification(), "aabbc788"), | ||
// ]); | ||
client.shutdown(); | ||
// client.shutdown(); | ||
expect(fakes.endpointManager.shutdown).to.not.be.called; | ||
// expect(fakes.endpointManager.shutdown).to.not.be.called; | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]); | ||
fakes.endpointManager.getStream.onCall(1).returns(null); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 1); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[2]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(null); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 1); | ||
setTimeout(function () { | ||
fakes.endpointManager.getStream.reset(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
fakes.endpointManager.emit("wakeup"); | ||
}, 2); | ||
// setTimeout(function () { | ||
// fakes.endpointManager.getStream.reset(); | ||
// fakes.endpointManager.getStream.onCall(0).returns(fakes.streams[3]); | ||
// fakes.endpointManager.getStream.onCall(1).returns(fakes.streams[4]); | ||
// fakes.endpointManager.emit("wakeup"); | ||
// }, 2); | ||
return promises.then( () => { | ||
expect(fakes.endpointManager.shutdown).to.have.been.called; | ||
}); | ||
}); | ||
}); | ||
// return promises.then( () => { | ||
// expect(fakes.endpointManager.shutdown).to.have.been.called; | ||
// }); | ||
// }); | ||
// }); | ||
}); | ||
}); |
@@ -30,2 +30,3 @@ "use strict"; | ||
port: 443, | ||
proxy: null, | ||
rejectUnauthorized: true, | ||
@@ -32,0 +33,0 @@ connectionRetryLimit: 10, |
@@ -5,2 +5,3 @@ "use strict"; | ||
const stream = require("stream"); | ||
const EventEmitter = require("events"); | ||
@@ -19,3 +20,3 @@ const bunyanLogger = sinon.match({ | ||
beforeEach(function () { | ||
beforeEach(function () { | ||
fakes = { | ||
@@ -25,2 +26,5 @@ tls: { | ||
}, | ||
http: { | ||
request: sinon.stub() | ||
}, | ||
protocol: { | ||
@@ -36,8 +40,9 @@ Connection: sinon.stub(), | ||
streams = { | ||
socket: new stream.PassThrough(), | ||
connection: new stream.PassThrough(), | ||
serializer: new stream.PassThrough(), | ||
deserializer: new stream.PassThrough(), | ||
compressor: new stream.PassThrough(), | ||
decompressor: new stream.PassThrough(), | ||
socket: new stream.PassThrough(), | ||
tunneledSocket: new stream.PassThrough(), | ||
connection: new stream.PassThrough(), | ||
serializer: new stream.PassThrough(), | ||
deserializer: new stream.PassThrough(), | ||
compressor: new stream.PassThrough(), | ||
decompressor: new stream.PassThrough(), | ||
}; | ||
@@ -49,2 +54,3 @@ | ||
sinon.stub(streams.socket, "pipe"); | ||
sinon.stub(streams.tunneledSocket, "pipe"); | ||
sinon.stub(streams.connection, "pipe"); | ||
@@ -113,6 +119,6 @@ | ||
context("host is not omitted", function () { | ||
it("falls back on 'address'", function () { | ||
new Endpoint({ | ||
address: "localtest", port: 443 | ||
}); | ||
it("falls back on 'address'", function () { | ||
new Endpoint({ | ||
address: "localtest", port: 443 | ||
}); | ||
@@ -172,2 +178,134 @@ expect(fakes.tls.connect).to.be.calledWith(sinon.match({ | ||
context("using an HTTP proxy", function () { | ||
let endpointOptions; | ||
let fakeHttpRequest; | ||
beforeEach(function(){ | ||
endpointOptions = { | ||
address: "localtest", | ||
port: 443, | ||
proxy: {host: "proxyaddress", port: 8080} | ||
}; | ||
fakeHttpRequest = new EventEmitter(); | ||
Object.assign(fakeHttpRequest, { | ||
end: sinon.stub() | ||
}); | ||
fakes.http.request | ||
.withArgs(sinon.match({ | ||
host: "proxyaddress", | ||
port: 8080, | ||
method: "CONNECT", | ||
headers: { Connection: "Keep-Alive" }, | ||
path: "localtest:443", | ||
})) | ||
.returns(fakeHttpRequest); | ||
}); | ||
it("sends an HTTP CONNECT request to the proxy", function () { | ||
const endpoint = new Endpoint(endpointOptions); | ||
expect(fakeHttpRequest.end).to.have.been.called.once; | ||
}); | ||
it("bubbles error events from the HTTP request", function () { | ||
const endpoint = new Endpoint(endpointOptions); | ||
const errorSpy = sinon.spy(); | ||
endpoint.on("error", errorSpy); | ||
fakeHttpRequest.emit("error", "this should be bubbled"); | ||
expect(errorSpy).to.have.been.calledWith("this should be bubbled"); | ||
}); | ||
it("opens tls socket using the tunnel socket from the HTTP request", function() { | ||
const endpoint = new Endpoint(endpointOptions); | ||
const httpSocket = {the: "HTTP socket"}; | ||
fakeHttpRequest.emit("connect", null, httpSocket); | ||
expect(fakes.tls.connect).to.have.been.called.once; | ||
expect(fakes.tls.connect).to.have.been.calledWith(sinon.match({ | ||
socket: httpSocket, | ||
host: "localtest", | ||
port: 443 | ||
})); | ||
}); | ||
it("uses all the additional options when openning the tls socket using the tunnel socket from the HTTP request", function() { | ||
endpointOptions = Object.assign(endpointOptions, { | ||
address: "localtestaddress", host: "localtest", port: 443, | ||
pfx: "pfxData", cert: "certData", | ||
key: "keyData", passphrase: "p4ssphr4s3", | ||
rejectUnauthorized: true, | ||
ALPNProtocols: ["h2"] | ||
}); | ||
const endpoint = new Endpoint(endpointOptions); | ||
const httpSocket = {the: "HTTP socket"}; | ||
fakeHttpRequest.emit("connect", null, httpSocket); | ||
expect(fakes.tls.connect).to.have.been.calledWith(sinon.match({ | ||
socket: httpSocket, | ||
host: "localtest", | ||
port: 443, | ||
servername: "localtestaddress", | ||
pfx: "pfxData", cert: "certData", | ||
key: "keyData", passphrase: "p4ssphr4s3", | ||
rejectUnauthorized: true, | ||
ALPNProtocols: ["h2"] | ||
})); | ||
}); | ||
context("tunnel established", function () { | ||
let endpoint; | ||
let httpSocket; | ||
beforeEach(function(){ | ||
endpoint = new Endpoint(endpointOptions); | ||
httpSocket = {the: "HTTP socket"}; | ||
fakes.tls.connect.withArgs(sinon.match({ | ||
socket: httpSocket, | ||
host: "localtest", | ||
port: 443 | ||
})).returns(streams.tunneledSocket); | ||
sinon.spy(streams.tunneledSocket, "write"); | ||
fakeHttpRequest.emit("connect", null, httpSocket); | ||
}); | ||
it("writes the HTTP/2 prelude", function () { | ||
const HTTP2_PRELUDE = Buffer.from("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); | ||
expect(streams.tunneledSocket.write.firstCall).to.be.calledWith(HTTP2_PRELUDE); | ||
}); | ||
it("emits 'connect' event once secure connection with end host is established", function () { | ||
const connect = sinon.spy(); | ||
endpoint.on("connect", connect); | ||
streams.tunneledSocket.emit("secureConnect"); | ||
expect(connect).to.be.calledOnce; | ||
}); | ||
it("bubbles error events", function () { | ||
const errorSpy = sinon.spy(); | ||
endpoint.on("error", errorSpy); | ||
streams.tunneledSocket.emit("error", "this should be bubbled"); | ||
expect(errorSpy).to.have.been.calledWith("this should be bubbled"); | ||
}); | ||
it("bubbles end events", function () { | ||
const endSpy = sinon.spy(); | ||
endpoint.on("end", endSpy); | ||
streams.tunneledSocket.emit("end"); | ||
expect(endSpy).to.have.been.calledOnce; | ||
}); | ||
}); | ||
}); | ||
describe("HTTP/2 layer", function () { | ||
@@ -174,0 +312,0 @@ let endpoint; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 4 instances in 1 package
349480
4
5168
201
0
2
83
2
+ Addeddebug@3.2.7(transitive)
+ Addedjsonwebtoken@8.5.1(transitive)
+ Addedlodash.includes@4.3.0(transitive)
+ Addedlodash.isboolean@3.0.3(transitive)
+ Addedlodash.isinteger@4.0.4(transitive)
+ Addedlodash.isnumber@3.0.3(transitive)
+ Addedlodash.isplainobject@4.0.6(transitive)
+ Addedlodash.isstring@4.0.1(transitive)
+ Addedms@2.1.3(transitive)
+ Addedsemver@5.7.2(transitive)
- Removedhttp2@https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz
- Removeddebug@2.6.9(transitive)
- Removedhoek@2.16.3(transitive)
- Removedisemail@1.2.0(transitive)
- Removedjoi@6.10.1(transitive)
- Removedjsonwebtoken@7.4.3(transitive)
- Removedmoment@2.30.1(transitive)
- Removedms@2.0.0(transitive)
- Removedtopo@1.1.0(transitive)
- Removedxtend@4.0.2(transitive)
Updateddebug@^3.1.0
Updatedjsonwebtoken@^8.1.0