Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

apn

Package Overview
Dependencies
Maintainers
2
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apn - npm Package Compare versions

Comparing version 2.1.5 to 3.0.0-alpha1

9

ChangeLog.md
## 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

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc