Merge branch 'master' of https://github.com/argon/node-apn
This commit is contained in:
Коммит
1d5942d7e4
|
@ -1,2 +1,3 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
node_modules
|
||||
coverage/
|
|
@ -0,0 +1,21 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '0.12'
|
||||
- '0.10'
|
||||
- '0.8'
|
||||
- iojs
|
||||
before_install:
|
||||
- npm install -g npm@2.7.3
|
||||
after_success:
|
||||
- npm install -g istanbul
|
||||
- npm install coveralls
|
||||
- istanbul cover node_modules/.bin/_mocha --report lcovonly -- -R spec
|
||||
- ./node_modules/.bin/coveralls < ./coverage/lcov.info
|
||||
- rm -rf ./coverage
|
||||
deploy:
|
||||
provider: npm
|
||||
email: argon@mkbot.net
|
||||
api_key:
|
||||
secure: W4yhYfQUhyhOOG/nUwYXr2DgIv8Kp/pzyUMtWc2garnHLPMvaMY+w/0NzKC06/UmApiJdRalWDNNDqNJAl2G22Hs/eHVpAelHejzQw3BY26QW71tTCPQR3Cv7r6gDBUN2o9rxjlJt+vpbJR1wEoFRnwn3d6298zDw+fzjlM+Wvo=
|
||||
on:
|
||||
tags: true
|
24
ChangeLog.md
24
ChangeLog.md
|
@ -1,5 +1,29 @@
|
|||
## Changelog
|
||||
|
||||
1.7.3:
|
||||
|
||||
* Added support for increased payload length for VoIP applications (Closes #207)
|
||||
* Fixed a bug with trimming UTF-16 encoded payloads
|
||||
* Dropped support for node v0.6 as it doesn't support UTF-16 surrogate pairs. Plus it's old. It'll still work if needed though, if you use UTF-8.
|
||||
|
||||
1.7.2:
|
||||
|
||||
* Fixed: #238, only emit `error` when the problem is unrecoverable. Any use of `node-apn` should have an `error` listener attached to prevent uncaught exceptions.
|
||||
* Various coding style improvements.
|
||||
* CI.
|
||||
* Removed legacy protocol support. I don't see any reason to keep it around, let me know if this causes you any problems.
|
||||
|
||||
1.7.1:
|
||||
|
||||
* Fixed: #224, always passing a CA value even if no certificates were specified. In this case the TLS library will not use the built in root certificates and will always fail to trust the server certificate.
|
||||
* Changed: Socket timeout default has changed for disabled to 1 hour.
|
||||
* Documentation fixes
|
||||
|
||||
1.7.0:
|
||||
|
||||
* Added: Credential validator to catch common configuration errors.
|
||||
* Fixed: Documentation errors.
|
||||
|
||||
1.6.2:
|
||||
|
||||
* Updated maximum payload size to 2048 bytes. (See #181).
|
||||
|
|
38
README.md
38
README.md
|
@ -1,6 +1,36 @@
|
|||
#node-apn
|
||||
|
||||
A Node.js module for interfacing with the Apple Push Notification service.
|
||||
> A Node.js module for interfacing with the Apple Push Notification service.
|
||||
|
||||
[![NPM][npm-image] ][npm-url]
|
||||
|
||||
[![Build status][ci-image] ][ci-url]
|
||||
[![Code coverage][coverage-image]][coverage-url]
|
||||
[![Codacy][codacy-image]][codacy-url]
|
||||
|
||||
[![dependencies][dependencies-image]][dependencies-url]
|
||||
[![devdependencies][devdependencies-image]][devdependencies-url]
|
||||
|
||||
[![Issue Stats][issuestats-pr-image]][issuestats-url]
|
||||
[![Issue Stats][issuestats-image]][issuestats-url]
|
||||
|
||||
[npm-image]:https://nodei.co/npm/apn.png?downloads=true
|
||||
[npm-url]:https://npmjs.com/package/apn
|
||||
[ci-image]:https://travis-ci.org/argon/node-apn.png?branch=master
|
||||
[ci-url]:https://travis-ci.org/argon/node-apn
|
||||
[coverage-image]:https://coveralls.io/repos/argon/node-apn/badge.svg?branch=master
|
||||
[coverage-url]:https://coveralls.io/r/argon/node-apn
|
||||
[codacy-image]:https://www.codacy.com/project/badge/e7735fbe0db244f3b310657d0dabaa11
|
||||
[codacy-url]:https://www.codacy.com/public/argon/node-apn
|
||||
|
||||
[dependencies-image]:https://david-dm.org/argon/node-apn.png
|
||||
[dependencies-url]:https://david-dm.org/argon/node-apn
|
||||
[devdependencies-image]:https://david-dm.org/argon/node-apn/dev-status.png
|
||||
[devdependencies-url]:https://david-dm.org/argon/node-apn#info=devDependencies
|
||||
|
||||
[issuestats-image]:http://issuestats.com/github/argon/node-apn/badge/issue
|
||||
[issuestats-pr-image]:http://issuestats.com/github/argon/node-apn/badge/pr
|
||||
[issuestats-url]:http://issuestats.com/github/argon/node-apn
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -31,7 +61,7 @@ This is intended as a brief introduction, please refer to the documentation in `
|
|||
var apn = require('apn');
|
||||
|
||||
### Connecting
|
||||
Create a new connection to the APN gateway server, passing a dictionary of options to the constructor. If you name your certificate and key files appropriately (`cert.pem` and `key.pem`) then the defaults should be suitable to get you up and running. By default the module will connect to the sandbox environment unless the environment variable `NODE_ENV=production` is set. For more information consult the documentation (in doc/apn.markdown).
|
||||
Create a new connection to the APN gateway server, passing a dictionary of options to the constructor. If you name your certificate and key files appropriately (`cert.pem` and `key.pem`) then the defaults should be suitable to get you up and running.
|
||||
|
||||
```javascript
|
||||
var options = { };
|
||||
|
@ -39,6 +69,10 @@ Create a new connection to the APN gateway server, passing a dictionary of optio
|
|||
var apnConnection = new apn.Connection(options);
|
||||
```
|
||||
|
||||
By default, if the environment variable `NODE_ENV=production` is set, the module will connect to the production gateway. Otherwise it will connect to the sandbox. This along with many other settings can be overriden with the options object.
|
||||
|
||||
For more information about configuration options consult the [documentation](doc/connection.markdown).
|
||||
|
||||
Help with preparing the key and certificate files for connection can be found in the [wiki][certificateWiki]
|
||||
|
||||
### Sending a notification
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
## Handling Errors
|
||||
|
||||
When an error occurs while pushing a notification and the enhanced interface is enabled, Apple sends a message back to node-apn containing the "identifier" of the notification (a value managed internally within node-apn) which caused the error and an associated error code (See: [The Binary Interface and Notification Formats][errors]). Apple does not return the entire message so node-apn caches a number of notifications after they are sent, so in the event an error occurs node-apn can find the one with the correct identifier from the cache and trigger the `transmissionError` event with the appropriate `Notification` object and `Device` it should have been delivered to.
|
||||
When an error occurs while pushing a notification Apple sends a message back to node-apn. The message contains the "identifier" of the notification (a value managed internally within node-apn) which caused the error and an associated error code (See: [The Binary Interface and Notification Formats][errors]). Apple does not return the entire message so node-apn caches a number of notifications after they are sent. In the event an error occurs, node-apn will identify the notification with the correct identifier from the cache and emit a `transmissionError` event with the appropriate `Notification` object and `Device` it should have been delivered to.
|
||||
|
||||
Apple guarantees that if one notification causes an error none of the following notifications will be processed, so if node-apn can find the correct notification which caused the error in the cache, then it can automatically re-send all the ones afterward.
|
||||
|
||||
|
|
|
@ -4,44 +4,40 @@ Creates a new connection to the Apple Push Notification Service.
|
|||
|
||||
Options:
|
||||
|
||||
- `cert` {Buffer|String} TThe filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`)
|
||||
- `cert` {Buffer|String} The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`)
|
||||
|
||||
- `key` {Buffer|String} The filename of the connection key to load from disk, or a Buffer/String containing the key data. (Defaults to: `key.pem`)
|
||||
|
||||
- `ca` An array of trusted certificates. Each element should contain either a filename to load, or a Buffer/String (in PEM format) to be used directly. If this is omitted several well known "root" CAs will be used. - You may need to use this as some environments don't include the CA used by Apple (entrust_2048).
|
||||
|
||||
- `pfx` {Buffer|String} File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will be used instead of certificate and key above.
|
||||
- `pfx` {Buffer|String} File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will always be used instead of certificate and key above.
|
||||
|
||||
- `passphrase` {String} The passphrase for the connection key, if required
|
||||
|
||||
- `production` {Boolean} Specifies which environment to connect to: Production (if true) or Sandbox - The hostname will be set automatically. (Defaults to NODE_ENV == "production", i.e. false unless the NODE_ENV environment variable is set accordingly)
|
||||
|
||||
- `voip` {Boolean} Enable when you are using a VoIP certificate to enable paylods up to 4096 bytes.
|
||||
|
||||
- `port` {Number} Gateway port (Defaults to: `2195`)
|
||||
|
||||
- `rejectUnauthorized` {Boolean} Reject Unauthorized property to be passed through to tls.connect() (Defaults to `true`)
|
||||
|
||||
- `enhanced` {Boolean} Whether to use the enhanced notification format (recommended, defaults to: `true`)
|
||||
- `cacheLength` {Number} Number of notifications to cache for error purposes (See "Handling Errors" below, (Defaults to: 1000)
|
||||
|
||||
- `cacheLength` {Number} Number of notifications to cache for error purposes (See "Handling Errors" below, (Defaults to: 100)
|
||||
|
||||
- `autoAdjustCache` {Boolean} Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event) (Defaults to: `false`)
|
||||
- `autoAdjustCache` {Boolean} Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event) (Defaults to: `true`)
|
||||
|
||||
- `maxConnections` {Number} The maximum number of connections to create for sending messages. (Defaults to: `1`)
|
||||
|
||||
- `connectTimeout` {Number} The duration of time the module should wait, in milliseconds, when trying to establish a connection to Apple before failing. 0 = Disabled. {Defaults to: `10000`}
|
||||
|
||||
- `connectionTimeout` {Number} The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled. (Defaults to: `0`)
|
||||
- `connectionTimeout` {Number} The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled. (Defaults to: `3600000` - 1h)
|
||||
|
||||
- `connectionRetryLimit` {Number} The maximum number of connection failures that will be tolerated before `apn` will "terminate". [See below.](#connectionretrylimit) (Defaults to: 10)
|
||||
- `connectionRetryLimit` {Number} The maximum number of connection failures that will be tolerated before `apn` will "terminate". [See below.](#connection-retry-limit) (Defaults to: 10)
|
||||
|
||||
- `buffersNotifications` {Boolean} Whether to buffer notifications and resend them after failure. (Defaults to: `true`)
|
||||
|
||||
- `fastMode` {Boolean} Whether to aggresively empty the notification buffer while connected - if set to true node-apn may enter a tight loop under heavy load while delivering notifications. (Defaults to: `false`)
|
||||
|
||||
- `legacy` {Boolean} Whether to use the pre-iOS 7 protocol format. (Defaults to `false`)
|
||||
|
||||
- `largePayloads` {Boolean} Whether to raise the notification limit from 256 bytes to 2048 bytes - not yet available in Production, automatically enabled for Sandbox.
|
||||
|
||||
##### Connection retry limit
|
||||
TLS errors such as expired or invalid certificates will cause an error to be emitted, but in this case it is futile for `apn` to continue attempting to connect. There may also be other cases where connectivity issues mean that a process attempting to send notifications may simply become blocked with an ever-increasing queue of notifications. To attempt to combat this a (configurable) retry limit of 10 has been introduced. If ten consecutive connection failures occur then `apn` will emit an `error` event for the connection, then a `transmissionError` event will be emitted for *each* notification in the queue, with the error code `connectionRetryLimitExceeded` (514).
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ As of version 1.2.0 it is possible to use a set of methods provided by Notificat
|
|||
|
||||
For iOS 7 applications which support Silent Remote Notifications you can use the `note.contentAvailable` property. This is identical in functionality to `note.newsstandAvailable` without the confusion of the "Newstand" terminology.
|
||||
|
||||
A `Notification` enapsulates the data to be compiled down to JSON and pushed to a device. See the [payload documentation][pl] for more details. At present the total length of the payload accepted by Apple is 256 bytes.
|
||||
|
||||
*Note*: The maximum payload size will be increased to 2048 bytes when iOS 8 is released, these larger payloads are available for testing in the sandbox environment. `apn` is will automatically configure for larger payloads when connecting to the sandbox.
|
||||
A `Notification` enapsulates the data to be compiled down to JSON and pushed to a device. See the [payload documentation][pl] for more details. At present the total length of the payload accepted by Apple is 2048 bytes.
|
||||
|
||||
### notification.retryLimit
|
||||
|
||||
|
@ -19,7 +17,7 @@ The maximum number of retries which should be performed when sending a notificat
|
|||
|
||||
### notification.expiry
|
||||
|
||||
The UNIX timestamp representing when the notification should expire. This does not contribute to the 256 byte payload size limit. An expiry of 0 indicates that the notification expires immediately.
|
||||
The UNIX timestamp representing when the notification should expire. This does not contribute to the 2048 byte payload size limit. An expiry of 0 indicates that the notification expires immediately.
|
||||
|
||||
### notification.priority
|
||||
|
||||
|
@ -29,8 +27,6 @@ From [Apples' Documentation][notificationFormat], Provide one of the following v
|
|||
> The push notification must trigger an alert, sound, or badge on the device. It is an error use this priority for a push that contains only the content-available key.
|
||||
* 5 - The push message is sent at a time that conserves power on the device receiving it.
|
||||
|
||||
This value is not valid when the connection is in legacy mode.
|
||||
|
||||
### notification.encoding
|
||||
|
||||
The encoding to use when transmitting the notification to APNS, defaults to `utf8`. `utf16le` is also possible but as each character is represented by a minimum of 2 bytes, will at least halve the possible payload size. If in doubt leave as default.
|
||||
|
@ -111,7 +107,7 @@ Set the `url-args` property of the `aps` object.
|
|||
|
||||
### notification.trim()
|
||||
|
||||
Attempt to automatically trim the notification alert text body to meet the payload size limit of 256 bytes.
|
||||
Attempt to automatically trim the notification alert text body to meet the payload size limit of 2048 bytes.
|
||||
|
||||
[pl]:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1 "Local and Push Notification Programming Guide: Apple Push Notification Service"
|
||||
[notificationFormat]:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW9 "The Binary Interface and Notification Format"
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
var apn = require ('../index.js');
|
||||
"use strict";
|
||||
|
||||
// Setup a connection to the feedback service using a custom interval (10 seconds)
|
||||
var feedback = new apn.feedback({ address:'feedback.sandbox.push.apple.com', interval: 10 });
|
||||
|
||||
feedback.on('feedback', handleFeedback);
|
||||
feedback.on('feedbackError', console.error);
|
||||
var apn = require ("../index.js");
|
||||
|
||||
function handleFeedback(feedbackData) {
|
||||
var time, device;
|
||||
for(var i in feedbackData) {
|
||||
time = feedbackData[i].time;
|
||||
device = feedbackData[i].device;
|
||||
|
||||
console.log("Device: " + device.toString('hex') + " has been unreachable, since: " + time);
|
||||
}
|
||||
feedbackData.forEach(function(feedbackItem) {
|
||||
console.log("Device: " + feedbackItem.device.toString("hex") + " has been unreachable, since: " + feedbackItem.time);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup a connection to the feedback service using a custom interval (10 seconds)
|
||||
var feedback = new apn.feedback({ production: false, interval: 10 });
|
||||
|
||||
feedback.on("feedback", handleFeedback);
|
||||
feedback.on("feedbackError", console.error);
|
|
@ -1,40 +1,42 @@
|
|||
var apn = require ('../index.js');
|
||||
"use strict";
|
||||
|
||||
var apn = require ("../index.js");
|
||||
|
||||
var tokens = ["<insert token here>", "<insert token here>"];
|
||||
|
||||
if(tokens[0] == "<insert token here>") {
|
||||
if(tokens[0] === "<insert token here>") {
|
||||
console.log("Please set token to a valid device token for the push notification service");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
// Create a connection to the service using mostly default parameters.
|
||||
|
||||
var service = new apn.connection({ gateway:'gateway.sandbox.push.apple.com' });
|
||||
var service = new apn.connection({ production: false });
|
||||
|
||||
service.on('connected', function() {
|
||||
service.on("connected", function() {
|
||||
console.log("Connected");
|
||||
});
|
||||
|
||||
service.on('transmitted', function(notification, device) {
|
||||
console.log("Notification transmitted to:" + device.token.toString('hex'));
|
||||
service.on("transmitted", function(notification, device) {
|
||||
console.log("Notification transmitted to:" + device.token.toString("hex"));
|
||||
});
|
||||
|
||||
service.on('transmissionError', function(errCode, notification, device) {
|
||||
service.on("transmissionError", function(errCode, notification, device) {
|
||||
console.error("Notification caused error: " + errCode + " for device ", device, notification);
|
||||
if (errCode == 8) {
|
||||
if (errCode === 8) {
|
||||
console.log("A error code of 8 indicates that the device token is invalid. This could be for a number of reasons - are you using the correct environment? i.e. Production vs. Sandbox");
|
||||
}
|
||||
});
|
||||
|
||||
service.on('timeout', function () {
|
||||
service.on("timeout", function () {
|
||||
console.log("Connection Timeout");
|
||||
});
|
||||
|
||||
service.on('disconnected', function() {
|
||||
service.on("disconnected", function() {
|
||||
console.log("Disconnected from APNS");
|
||||
});
|
||||
|
||||
service.on('socketError', console.error);
|
||||
service.on("socketError", console.error);
|
||||
|
||||
|
||||
// If you plan on sending identical paylods to many devices you can do something like this.
|
||||
|
@ -53,13 +55,13 @@ pushNotificationToMany();
|
|||
// If you have a list of devices for which you want to send a customised notification you can create one and send it to and individual device.
|
||||
function pushSomeNotifications() {
|
||||
console.log("Sending a tailored notification to %d devices", tokens.length);
|
||||
for (var i in tokens) {
|
||||
tokens.forEach(function(token, i) {
|
||||
var note = new apn.notification();
|
||||
note.setAlertText("Hello, from node-apn! You are number: " + i);
|
||||
note.badge = i;
|
||||
|
||||
service.pushNotification(note, tokens[i]);
|
||||
}
|
||||
service.pushNotification(note, token);
|
||||
});
|
||||
}
|
||||
|
||||
pushSomeNotifications();
|
||||
|
|
4
index.js
4
index.js
|
@ -1,10 +1,10 @@
|
|||
exports.Connection = require('./lib/connection');
|
||||
exports.Connection = require("./lib/connection");
|
||||
exports.connection = exports.Connection;
|
||||
|
||||
exports.Device = require("./lib/device");
|
||||
exports.device = exports.Device;
|
||||
|
||||
exports.Errors = require('./lib/errors');
|
||||
exports.Errors = require("./lib/errors");
|
||||
exports.error = exports.Errors;
|
||||
|
||||
exports.Feedback = require("./lib/feedback");
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
var Errors = require('./errors');
|
||||
"use strict";
|
||||
|
||||
var q = require('q');
|
||||
var tls = require('tls');
|
||||
var sysu = require('util');
|
||||
var util = require('./util');
|
||||
var events = require('events');
|
||||
var Device = require('./device');
|
||||
var CredentialLoader = require('./credentials');
|
||||
var Errors = require("./errors");
|
||||
|
||||
var createSocket = require('./socket');
|
||||
var q = require("q");
|
||||
var sysu = require("util");
|
||||
var util = require("./util");
|
||||
var events = require("events");
|
||||
var Device = require("./device");
|
||||
var loadCredentials = require("./credentials/load");
|
||||
var parseCredentials = require("./credentials/parse");
|
||||
var validateCredentials = require("./credentials/validate");
|
||||
|
||||
var createSocket = require("./socket");
|
||||
var debug = function() {};
|
||||
var trace = function() {};
|
||||
if(process.env.DEBUG) {
|
||||
try {
|
||||
debug = require('debug')('apn');
|
||||
trace = require('debug')('apn:trace');
|
||||
debug = require("debug")("apn");
|
||||
trace = require("debug")("apn:trace");
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
|
||||
|
@ -34,57 +37,54 @@ if(process.env.DEBUG) {
|
|||
* @config {Boolean} [production=(NODE_ENV=='production')] Specifies which environment to connect to: Production (if true) or Sandbox. (Defaults to false, unless NODE_ENV == "production")
|
||||
* @config {Number} [port=2195] Gateway port
|
||||
* @config {Boolean} [rejectUnauthorized=true] Reject Unauthorized property to be passed through to tls.connect()
|
||||
* @config {Boolean} [enhanced=true] Whether to use the enhanced notification format (recommended)
|
||||
* @config {Function} [errorCallback] A callback which accepts 2 parameters (err, notification). Use `transmissionError` event instead.
|
||||
* @config {Number} [cacheLength=1000] Number of notifications to cache for error purposes (See doc/apn.markdown)
|
||||
* @config {Boolean} [autoAdjustCache=false] Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event)
|
||||
* @config {Number} [maxConnections=1] The maximum number of connections to create for sending messages.
|
||||
* @config {Number} [connectionTimeout=0] The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled.
|
||||
* @config {Number} [connectionTimeout=3600000] The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled.
|
||||
* @config {Boolean} [buffersNotifications=true] Whether to buffer notifications and resend them after failure.
|
||||
* @config {Boolean} [fastMode=false] Whether to aggresively empty the notification buffer while connected.
|
||||
* @config {Boolean} [legacy=false] Whether to use the old (pre-iOS 7) protocol format.
|
||||
*/
|
||||
function Connection (options) {
|
||||
if(false === (this instanceof Connection)) {
|
||||
return new Connection(options);
|
||||
}
|
||||
this.options = {
|
||||
cert: 'cert.pem',
|
||||
key: 'key.pem',
|
||||
cert: "cert.pem",
|
||||
key: "key.pem",
|
||||
ca: null,
|
||||
pfx: null,
|
||||
passphrase: null,
|
||||
production: (process.env.NODE_ENV === "production"),
|
||||
voip: false,
|
||||
address: null,
|
||||
port: 2195,
|
||||
rejectUnauthorized: true,
|
||||
enhanced: true,
|
||||
cacheLength: 1000,
|
||||
autoAdjustCache: true,
|
||||
maxConnections: 1,
|
||||
connectTimeout: 10000,
|
||||
connectionTimeout: 0,
|
||||
connectionTimeout: 3600000,
|
||||
connectionRetryLimit: 10,
|
||||
buffersNotifications: true,
|
||||
fastMode: false,
|
||||
legacy: false,
|
||||
disableNagle: false,
|
||||
disableEPIPEFix: false
|
||||
};
|
||||
|
||||
for (var key in options) {
|
||||
if (options[key] == null) {
|
||||
if (options[key] === null || options[key] === undefined) {
|
||||
debug("Option [" + key + "] set to null. This may cause unexpected behaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
util.extend(this.options, options);
|
||||
|
||||
if (this.options.gateway != null) {
|
||||
if (this.options.gateway) {
|
||||
this.options.address = this.options.gateway;
|
||||
}
|
||||
|
||||
if (this.options.address == null) {
|
||||
if (!this.options.address) {
|
||||
if (this.options.production) {
|
||||
this.options.address = "gateway.push.apple.com";
|
||||
}
|
||||
|
@ -92,6 +92,14 @@ function Connection (options) {
|
|||
this.options.address = "gateway.sandbox.push.apple.com";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.options.address === "gateway.push.apple.com") {
|
||||
this.options.production = true;
|
||||
}
|
||||
else {
|
||||
this.options.production = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.pfx || this.options.pfxData) {
|
||||
if (!options.cert) {
|
||||
|
@ -138,7 +146,22 @@ sysu.inherits(Connection, events.EventEmitter);
|
|||
Connection.prototype.initialize = function () {
|
||||
if (!this.initializationPromise) {
|
||||
debug("Initialising module");
|
||||
this.initializationPromise = CredentialLoader(this.options);
|
||||
|
||||
var production = this.options.production;
|
||||
this.initializationPromise = loadCredentials(this.options)
|
||||
.then(function(credentials) {
|
||||
var parsed;
|
||||
try {
|
||||
parsed = parseCredentials(credentials);
|
||||
}
|
||||
catch (e) {
|
||||
debug(e);
|
||||
return credentials;
|
||||
}
|
||||
parsed.production = production;
|
||||
validateCredentials(parsed);
|
||||
return credentials;
|
||||
});
|
||||
}
|
||||
|
||||
return this.initializationPromise;
|
||||
|
@ -155,7 +178,7 @@ Connection.prototype.connect = function () {
|
|||
|
||||
debug("Initialising connection");
|
||||
this.deferredConnection = q.defer();
|
||||
this.initialize().spread(function (pfxData, certData, keyData, caData) {
|
||||
this.initialize().then(function(credentials) {
|
||||
var socketOptions = {};
|
||||
|
||||
socketOptions.port = this.options.port;
|
||||
|
@ -165,21 +188,18 @@ Connection.prototype.connect = function () {
|
|||
socketOptions.disableNagle = this.options.disableNagle;
|
||||
socketOptions.connectionTimeout = this.options.connectionTimeout;
|
||||
|
||||
socketOptions.pfx = pfxData;
|
||||
socketOptions.cert = certData;
|
||||
socketOptions.key = keyData;
|
||||
socketOptions.ca = caData;
|
||||
socketOptions.pfx = credentials.pfx;
|
||||
socketOptions.cert = credentials.cert;
|
||||
socketOptions.key = credentials.key;
|
||||
socketOptions.ca = credentials.ca;
|
||||
socketOptions.passphrase = this.options.passphrase;
|
||||
socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;
|
||||
|
||||
this.socket = createSocket(this, socketOptions,
|
||||
function () {
|
||||
debug("Connection established");
|
||||
this.emit('connected', this.sockets.length + 1);
|
||||
this.emit("connected", this.sockets.length + 1);
|
||||
this.deferredConnection.resolve();
|
||||
|
||||
clearTimeout(this.connectionTimer);
|
||||
this.connectionTimer = null;
|
||||
}.bind(this));
|
||||
|
||||
this.socket.on("error", this.errorOccurred.bind(this, this.socket));
|
||||
|
@ -187,23 +207,27 @@ Connection.prototype.connect = function () {
|
|||
this.socket.on("data", this.handleTransmissionError.bind(this, this.socket));
|
||||
this.socket.on("drain", this.socketDrained.bind(this, this.socket, true));
|
||||
this.socket.once("close", this.socketClosed.bind(this, this.socket));
|
||||
|
||||
if (this.options.connectTimeout > 0) {
|
||||
this.connectionTimer = setTimeout(function () {
|
||||
this.deferredConnection.reject(new Error("Connect timed out"));
|
||||
this.socket.end();
|
||||
}.bind(this), this.options.connectTimeout);
|
||||
}
|
||||
|
||||
}.bind(this)).fail(function (error) {
|
||||
}.bind(this)).done(null, function (error) {
|
||||
debug("Module initialisation error:", error);
|
||||
|
||||
// This is a pretty fatal scenario, we don't have key/certificate to connect to APNS, there's not much we can do, so raise errors and clear the queue.
|
||||
this.rejectBuffer(Errors['moduleInitialisationFailed']);
|
||||
this.rejectBuffer(Errors.moduleInitialisationFailed);
|
||||
this.emit("error", error);
|
||||
this.deferredConnection.reject(error);
|
||||
this.terminated = true;
|
||||
}.bind(this));
|
||||
|
||||
if (this.options.connectTimeout > 0) {
|
||||
var connectionTimer = setTimeout(function () {
|
||||
this.deferredConnection.reject(new Error("Connect timed out"));
|
||||
this.socket.end();
|
||||
}.bind(this), this.options.connectTimeout);
|
||||
|
||||
return this.deferredConnection.promise.finally(function() {
|
||||
clearTimeout(connectionTimer);
|
||||
});
|
||||
}
|
||||
|
||||
return this.deferredConnection.promise;
|
||||
};
|
||||
|
||||
|
@ -238,12 +262,12 @@ Connection.prototype.createConnection = function() {
|
|||
trace("connection failed", delay);
|
||||
|
||||
this.raiseError(error);
|
||||
this.emit('error', error);
|
||||
this.emit("socketError", error);
|
||||
|
||||
if (this.options.connectionRetryLimit > 0
|
||||
&& this.failureCount > this.options.connectionRetryLimit
|
||||
&& this.sockets.length == 0) {
|
||||
this.rejectBuffer(Errors['connectionRetryLimitExceeded']);
|
||||
&& this.sockets.length === 0) {
|
||||
this.rejectBuffer(Errors.connectionRetryLimitExceeded);
|
||||
this.shutdown();
|
||||
this.terminated = true;
|
||||
return;
|
||||
|
@ -255,7 +279,9 @@ Connection.prototype.createConnection = function() {
|
|||
this.deferredConnection = null;
|
||||
this.socket = undefined;
|
||||
this.serviceBuffer();
|
||||
}.bind(this)).done();
|
||||
}.bind(this)).done(null, function(error) {
|
||||
this.emit("error", error);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -302,25 +328,25 @@ Connection.prototype.initialisingConnection = function() {
|
|||
}
|
||||
} while(repeat && socketsAvailable > 0 && this.notificationBuffer.length > 0);
|
||||
|
||||
if (this.notificationBuffer.length > 0 && socketsAvailable == 0) {
|
||||
if (this.notificationBuffer.length > 0 && socketsAvailable === 0) {
|
||||
this.createConnection();
|
||||
}
|
||||
|
||||
if (this.notificationBuffer.length === 0 && socketsAvailable == this.sockets.length){
|
||||
if (this.notificationBuffer.length === 0 && socketsAvailable === this.sockets.length){
|
||||
if (this.notificationsQueued) {
|
||||
this.emit('completed');
|
||||
this.emit("completed");
|
||||
this.notificationsQueued = false;
|
||||
}
|
||||
if (this.shutdownPending) {
|
||||
debug("closing connections");
|
||||
|
||||
for (var i = 0; i < this.sockets.length; i++) {
|
||||
var socket = this.sockets[i];
|
||||
for (var j = 0; j < this.sockets.length; j++) {
|
||||
socket = this.sockets[j];
|
||||
// We delay before closing connections to ensure we don't miss any error packets from the service.
|
||||
setTimeout(socket.end.bind(socket), 2500);
|
||||
this.retireSocket(socket);
|
||||
}
|
||||
if (this.sockets.length == 0) {
|
||||
if (this.sockets.length === 0) {
|
||||
this.shutdownPending = false;
|
||||
}
|
||||
}
|
||||
|
@ -335,16 +361,16 @@ Connection.prototype.initialisingConnection = function() {
|
|||
Connection.prototype.errorOccurred = function(socket, err) {
|
||||
debug("Socket error occurred", socket.apnSocketId, err);
|
||||
|
||||
if(socket.transmissionErrorOccurred && err.code == 'EPIPE') {
|
||||
if(socket.transmissionErrorOccurred && err.code === "EPIPE") {
|
||||
debug("EPIPE occurred after a transmission error which we can ignore");
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('socketError', err);
|
||||
if(this.socket == socket && this.deferredConnection && this.deferredConnection.promise.isPending()) {
|
||||
if(this.socket === socket && this.deferredConnection && this.deferredConnection.promise.isPending()) {
|
||||
this.deferredConnection.reject(err);
|
||||
}
|
||||
else {
|
||||
this.emit("socketError", err);
|
||||
this.raiseError(err, null);
|
||||
}
|
||||
|
||||
|
@ -372,9 +398,9 @@ Connection.prototype.socketAvailable = function(socket) {
|
|||
Connection.prototype.socketDrained = function(socket, serviceBuffer) {
|
||||
debug("Socket drained", socket.apnSocketId);
|
||||
socket.apnBusy = false;
|
||||
if((!this.options.legacy || this.options.enhanced) && socket.apnCachedNotifications.length > 0) {
|
||||
if(socket.apnCachedNotifications.length > 0) {
|
||||
var notification = socket.apnCachedNotifications[socket.apnCachedNotifications.length - 1];
|
||||
this.emit('transmitted', notification.notification, notification.recipient);
|
||||
this.emit("transmitted", notification.notification, notification.recipient);
|
||||
}
|
||||
if(serviceBuffer === true && !this.runningOnNextTick) {
|
||||
// There is a possibility that this could add multiple invocations to the
|
||||
|
@ -394,7 +420,7 @@ Connection.prototype.socketDrained = function(socket, serviceBuffer) {
|
|||
*/
|
||||
Connection.prototype.socketTimeout = function(socket) {
|
||||
debug("Socket timeout", socket.apnSocketId);
|
||||
this.emit('timeout');
|
||||
this.emit("timeout");
|
||||
this.destroyConnection(socket);
|
||||
|
||||
this.serviceBuffer();
|
||||
|
@ -428,7 +454,7 @@ Connection.prototype.socketClosed = function(socket) {
|
|||
}
|
||||
else {
|
||||
this.retireSocket(socket);
|
||||
this.emit('disconnected', this.sockets.length);
|
||||
this.emit("disconnected", this.sockets.length);
|
||||
}
|
||||
|
||||
this.serviceBuffer();
|
||||
|
@ -445,7 +471,7 @@ Connection.prototype.socketClosed = function(socket) {
|
|||
if (index > -1) {
|
||||
this.sockets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this method to modify the cache length after initialisation.
|
||||
|
@ -459,8 +485,8 @@ Connection.prototype.setCacheLength = function(newLength) {
|
|||
*/
|
||||
Connection.prototype.bufferNotification = function (notification) {
|
||||
if (notification.retryLimit === 0) {
|
||||
this.raiseError(Errors['retryLimitExceeded'], notification);
|
||||
this.emit('transmissionError', Errors['retryLimitExceeded'], notification.notification, notification.recipient);
|
||||
this.raiseError(Errors.retryLimitExceeded, notification);
|
||||
this.emit("transmissionError", Errors.retryLimitExceeded, notification.notification, notification.recipient);
|
||||
return;
|
||||
}
|
||||
notification.retryLimit -= 1;
|
||||
|
@ -475,9 +501,9 @@ Connection.prototype.rejectBuffer = function (errCode) {
|
|||
while(this.notificationBuffer.length > 0) {
|
||||
var notification = this.notificationBuffer.shift();
|
||||
this.raiseError(errCode, notification.notification, notification.recipient);
|
||||
this.emit('transmissionError', errCode, notification.notification, notification.recipient);
|
||||
this.emit("transmissionError", errCode, notification.notification, notification.recipient);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
@ -487,8 +513,8 @@ Connection.prototype.prepareNotification = function (notification, device) {
|
|||
// If a device token hasn't been given then we should raise an error.
|
||||
if (recipient === undefined) {
|
||||
util.setImmediate(function () {
|
||||
this.raiseError(Errors['missingDeviceToken'], notification);
|
||||
this.emit('transmissionError', Errors['missingDeviceToken'], notification);
|
||||
this.raiseError(Errors.missingDeviceToken, notification);
|
||||
this.emit("transmissionError", Errors.missingDeviceToken, notification);
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
@ -501,8 +527,8 @@ Connection.prototype.prepareNotification = function (notification, device) {
|
|||
catch (e) {
|
||||
// If an exception has been thrown it's down to an invalid token.
|
||||
util.setImmediate(function () {
|
||||
this.raiseError(Errors['invalidToken'], notification, device);
|
||||
this.emit('transmissionError', Errors['invalidToken'], notification, device);
|
||||
this.raiseError(Errors.invalidToken, notification, device);
|
||||
this.emit("transmissionError", Errors.invalidToken, notification, device);
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
@ -518,7 +544,7 @@ Connection.prototype.prepareNotification = function (notification, device) {
|
|||
Connection.prototype.cacheNotification = function (socket, notification) {
|
||||
socket.apnCachedNotifications.push(notification);
|
||||
if (socket.apnCachedNotifications.length > this.options.cacheLength) {
|
||||
debug("Clearing notification %d from the cache", socket.apnCachedNotifications[0]['_uid']);
|
||||
debug("Clearing notification %d from the cache", socket.apnCachedNotifications[0]._uid);
|
||||
socket.apnCachedNotifications.splice(0, socket.apnCachedNotifications.length - this.options.cacheLength);
|
||||
}
|
||||
};
|
||||
|
@ -527,11 +553,8 @@ Connection.prototype.cacheNotification = function (socket, notification) {
|
|||
* @private
|
||||
*/
|
||||
Connection.prototype.handleTransmissionError = function (socket, data) {
|
||||
if (data[0] == 8) {
|
||||
if (data[0] === 8) {
|
||||
socket.transmissionErrorOccurred = true;
|
||||
if (!this.options.enhanced && this.options.legacy) {
|
||||
return;
|
||||
}
|
||||
|
||||
var errorCode = data[1];
|
||||
var identifier = data.readUInt32BE(2);
|
||||
|
@ -543,7 +566,7 @@ Connection.prototype.handleTransmissionError = function (socket, data) {
|
|||
|
||||
while (socket.apnCachedNotifications.length) {
|
||||
notification = socket.apnCachedNotifications.shift();
|
||||
if (notification['_uid'] == identifier) {
|
||||
if (notification._uid === identifier) {
|
||||
foundNotification = true;
|
||||
break;
|
||||
}
|
||||
|
@ -554,21 +577,21 @@ Connection.prototype.handleTransmissionError = function (socket, data) {
|
|||
while (temporaryCache.length) {
|
||||
temporaryCache.shift();
|
||||
}
|
||||
this.emit('transmissionError', errorCode, notification.notification, notification.recipient);
|
||||
this.emit("transmissionError", errorCode, notification.notification, notification.recipient);
|
||||
this.raiseError(errorCode, notification.notification, notification.recipient);
|
||||
}
|
||||
else {
|
||||
socket.apnCachedNotifications = temporaryCache;
|
||||
|
||||
if(socket.apnCachedNotifications.length > 0) {
|
||||
var differentialSize = socket.apnCachedNotifications[0]['_uid'] - identifier;
|
||||
this.emit('cacheTooSmall', differentialSize);
|
||||
var differentialSize = socket.apnCachedNotifications[0]._uid - identifier;
|
||||
this.emit("cacheTooSmall", differentialSize);
|
||||
if(this.options.autoAdjustCache) {
|
||||
this.options.cacheLength += differentialSize * 2;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('transmissionError', errorCode, null);
|
||||
this.emit("transmissionError", errorCode, null);
|
||||
this.raiseError(errorCode, null);
|
||||
}
|
||||
|
||||
|
@ -596,9 +619,9 @@ Connection.prototype.raiseError = function(errorCode, notification, recipient) {
|
|||
debug("Error occurred with trace:", errorCode.stack);
|
||||
}
|
||||
|
||||
if (notification && typeof notification.errorCallback == 'function' ) {
|
||||
if (notification && typeof notification.errorCallback === "function" ) {
|
||||
notification.errorCallback(errorCode, recipient);
|
||||
} else if (typeof this.options.errorCallback == 'function') {
|
||||
} else if (typeof this.options.errorCallback === "function") {
|
||||
this.options.errorCallback(errorCode, notification, recipient);
|
||||
}
|
||||
};
|
||||
|
@ -608,13 +631,8 @@ Connection.prototype.raiseError = function(errorCode, notification, recipient) {
|
|||
* @return {Boolean} Write completed, returns true if socketDrained should be called by the caller of this method.
|
||||
*/
|
||||
Connection.prototype.transmitNotification = function(socket, notification) {
|
||||
if (!this.socketAvailable(socket)) {
|
||||
this.bufferNotification(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
var token = notification.recipient.token;
|
||||
var encoding = notification.notification.encoding || 'utf8';
|
||||
var encoding = notification.notification.encoding || "utf8";
|
||||
var message = notification.notification.compile();
|
||||
var messageLength = Buffer.byteLength(message, encoding);
|
||||
var position = 0;
|
||||
|
@ -624,95 +642,61 @@ Connection.prototype.transmitNotification = function(socket, notification) {
|
|||
if (socket.apnCurrentId > 0xffffffff) {
|
||||
socket.apnCurrentId = 0;
|
||||
}
|
||||
if (this.options.legacy) {
|
||||
if (this.options.enhanced) {
|
||||
data = new Buffer(1 + 4 + 4 + 2 + token.length + 2 + messageLength);
|
||||
// Command
|
||||
data[position] = 1;
|
||||
position++;
|
||||
|
||||
// Identifier
|
||||
data.writeUInt32BE(notification._uid, position);
|
||||
position += 4;
|
||||
// New Protocol uses framed notifications consisting of multiple items
|
||||
// 1: Device Token
|
||||
// 2: Payload
|
||||
// 3: Notification Identifier
|
||||
// 4: Expiration Date
|
||||
// 5: Priority
|
||||
// Each item has a 3 byte header: Type (1), Length (2) followed by data
|
||||
// The frame layout is hard coded for now as original dynamic system had a
|
||||
// significant performance penalty
|
||||
|
||||
// Expiry
|
||||
data.writeUInt32BE(notification.notification.expiry, position);
|
||||
position += 4;
|
||||
this.cacheNotification(socket, notification);
|
||||
}
|
||||
else {
|
||||
data = new Buffer(1 + 2 + token.length + 2 + messageLength);
|
||||
//Command
|
||||
data[position] = 0;
|
||||
position++;
|
||||
}
|
||||
// Token Length
|
||||
data.writeUInt16BE(token.length, position);
|
||||
position += 2;
|
||||
// Device Token
|
||||
position += token.copy(data, position, 0);
|
||||
// Payload Length
|
||||
data.writeUInt16BE(messageLength, position);
|
||||
position += 2;
|
||||
//Payload
|
||||
position += data.write(message, position, encoding);
|
||||
var frameLength = 3 + token.length + 3 + messageLength + 3 + 4;
|
||||
if(notification.notification.expiry > 0) {
|
||||
frameLength += 3 + 4;
|
||||
}
|
||||
if(notification.notification.priority !== 10) {
|
||||
frameLength += 3 + 1;
|
||||
}
|
||||
else {
|
||||
// New Protocol uses framed notifications consisting of multiple items
|
||||
// 1: Device Token
|
||||
// 2: Payload
|
||||
// 3: Notification Identifier
|
||||
// 4: Expiration Date
|
||||
// 5: Priority
|
||||
// Each item has a 3 byte header: Type (1), Length (2) followed by data
|
||||
// The frame layout is hard coded for now as original dynamic system had a
|
||||
// significant performance penalty
|
||||
|
||||
var frameLength = 3 + token.length + 3 + messageLength + 3 + 4;
|
||||
if(notification.notification.expiry > 0) {
|
||||
frameLength += 3 + 4;
|
||||
}
|
||||
if(notification.notification.priority != 10) {
|
||||
frameLength += 3 + 1;
|
||||
}
|
||||
// Frame has a 5 byte header: Type (1), Length (4) followed by items.
|
||||
data = new Buffer(5 + frameLength);
|
||||
data[position] = 2; position += 1;
|
||||
|
||||
// Frame has a 5 byte header: Type (1), Length (4) followed by items.
|
||||
data = new Buffer(5 + frameLength);
|
||||
data[position] = 2; position += 1;
|
||||
// Frame Length
|
||||
data.writeUInt32BE(frameLength, position); position += 4;
|
||||
|
||||
// Frame Length
|
||||
data.writeUInt32BE(frameLength, position); position += 4;
|
||||
// Token Item
|
||||
data[position] = 1; position += 1;
|
||||
data.writeUInt16BE(token.length, position); position += 2;
|
||||
position += token.copy(data, position, 0);
|
||||
|
||||
// Token Item
|
||||
data[position] = 1; position += 1;
|
||||
data.writeUInt16BE(token.length, position); position += 2;
|
||||
position += token.copy(data, position, 0);
|
||||
// Payload Item
|
||||
data[position] = 2; position += 1;
|
||||
data.writeUInt16BE(messageLength, position); position += 2;
|
||||
position += data.write(message, position, encoding);
|
||||
|
||||
// Payload Item
|
||||
data[position] = 2; position += 1;
|
||||
data.writeUInt16BE(messageLength, position); position += 2;
|
||||
position += data.write(message, position, encoding);
|
||||
// Identifier Item
|
||||
data[position] = 3; position += 1;
|
||||
data.writeUInt16BE(4, position); position += 2;
|
||||
data.writeUInt32BE(notification._uid, position); position += 4;
|
||||
|
||||
// Identifier Item
|
||||
data[position] = 3; position += 1;
|
||||
if(notification.notification.expiry > 0) {
|
||||
// Expiry Item
|
||||
data[position] = 4; position += 1;
|
||||
data.writeUInt16BE(4, position); position += 2;
|
||||
data.writeUInt32BE(notification._uid, position); position += 4;
|
||||
|
||||
if(notification.notification.expiry > 0) {
|
||||
// Expiry Item
|
||||
data[position] = 4; position += 1;
|
||||
data.writeUInt16BE(4, position); position += 2;
|
||||
data.writeUInt32BE(notification.notification.expiry, position); position += 4;
|
||||
}
|
||||
if(notification.notification.priority != 10) {
|
||||
// Priority Item
|
||||
data[position] = 5; position += 1;
|
||||
data.writeUInt16BE(1, position); position += 2;
|
||||
data[position] = notification.notification.priority; position += 1;
|
||||
}
|
||||
|
||||
this.cacheNotification(socket, notification);
|
||||
data.writeUInt32BE(notification.notification.expiry, position); position += 4;
|
||||
}
|
||||
if(notification.notification.priority !== 10) {
|
||||
// Priority Item
|
||||
data[position] = 5; position += 1;
|
||||
data.writeUInt16BE(1, position); position += 2;
|
||||
data[position] = notification.notification.priority; position += 1;
|
||||
}
|
||||
|
||||
this.cacheNotification(socket, notification);
|
||||
|
||||
socket.apnBusy = true;
|
||||
return socket.write(data);
|
||||
|
@ -720,11 +704,12 @@ Connection.prototype.transmitNotification = function(socket, notification) {
|
|||
|
||||
Connection.prototype.validNotification = function (notification, recipient) {
|
||||
var messageLength = notification.length();
|
||||
|
||||
if (messageLength > 2048) {
|
||||
var maxLength = (this.options.voip ? 4096 : 2048);
|
||||
|
||||
if (messageLength > maxLength) {
|
||||
util.setImmediate(function () {
|
||||
this.raiseError(Errors['invalidPayloadSize'], notification, recipient);
|
||||
this.emit('transmissionError', Errors['invalidPayloadSize'], notification, recipient);
|
||||
this.raiseError(Errors.invalidPayloadSize, notification, recipient);
|
||||
this.emit("transmissionError", Errors.invalidPayloadSize, notification, recipient);
|
||||
}.bind(this));
|
||||
return false;
|
||||
}
|
||||
|
@ -739,7 +724,7 @@ Connection.prototype.validNotification = function (notification, recipient) {
|
|||
*/
|
||||
Connection.prototype.pushNotification = function (notification, recipient) {
|
||||
if (this.terminated) {
|
||||
this.emit('transmissionError', Errors['connectionTerminated'], notification, recipient);
|
||||
this.emit("transmissionError", Errors.connectionTerminated, notification, recipient);
|
||||
return false;
|
||||
}
|
||||
if (!this.validNotification(notification, recipient)) {
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
var fs = require('fs');
|
||||
var q = require('q');
|
||||
var sysu = require('util');
|
||||
|
||||
function Credentials(credentials) {
|
||||
var readFile = q.nfbind(fs.readFile);
|
||||
|
||||
// Prepare PKCS#12 data if available
|
||||
var pfxPromise = null;
|
||||
if(credentials.pfx != null || credentials.pfxData != null) {
|
||||
if(credentials.pfxData) {
|
||||
pfxPromise = credentials.pfxData;
|
||||
}
|
||||
else if(Buffer.isBuffer(credentials.pfx)) {
|
||||
pfxPromise = credentials.pfx;
|
||||
}
|
||||
else {
|
||||
pfxPromise = readFile(credentials.pfx);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare Certificate data if available.
|
||||
var certPromise = null;
|
||||
if (credentials.certData) {
|
||||
certPromise = credentials.certData;
|
||||
}
|
||||
else if(Buffer.isBuffer(credentials.cert) || checkPEMType(credentials.cert, "CERTIFICATE")) {
|
||||
certPromise = credentials.cert;
|
||||
}
|
||||
else if(credentials.cert){
|
||||
// Nothing has matched so attempt to load from disk
|
||||
certPromise = readFile(credentials.cert);
|
||||
}
|
||||
|
||||
// Prepare Key data if available
|
||||
var keyPromise = null;
|
||||
if (credentials.keyData) {
|
||||
keyPromise = credentials.keyData;
|
||||
}
|
||||
else if(Buffer.isBuffer(credentials.key) || checkPEMType(credentials.key, "PRIVATE KEY")) {
|
||||
keyPromise = credentials.key;
|
||||
}
|
||||
else if(credentials.key) {
|
||||
keyPromise = readFile(credentials.key);
|
||||
}
|
||||
|
||||
// Prepare Certificate Authority data if available.
|
||||
var caPromises = [];
|
||||
if (credentials.ca != null && !sysu.isArray(credentials.ca)) {
|
||||
credentials.ca = [ credentials.ca ];
|
||||
}
|
||||
for(var i in credentials.ca) {
|
||||
var ca = credentials.ca[i];
|
||||
if(Buffer.isBuffer(ca) || checkPEMType(ca, "CERTIFICATE")) {
|
||||
caPromises.push(ca);
|
||||
}
|
||||
else if (ca){
|
||||
caPromises.push(readFile(ca));
|
||||
}
|
||||
}
|
||||
if (caPromises.length == 0) {
|
||||
caPromises = undefined;
|
||||
}
|
||||
else {
|
||||
caPromises = q.all(caPromises);
|
||||
}
|
||||
|
||||
return q.all([pfxPromise, certPromise, keyPromise, caPromises]);
|
||||
}
|
||||
|
||||
function checkPEMType(input, type) {
|
||||
if (input == null) {
|
||||
return false;
|
||||
}
|
||||
var matches = input.match(/\-\-\-\-\-BEGIN ([A-Z\s*]+)\-\-\-\-\-/);
|
||||
|
||||
if (matches != null) {
|
||||
return matches[1].indexOf(type) >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = Credentials;
|
|
@ -0,0 +1,35 @@
|
|||
"use strict";
|
||||
|
||||
var APNKey = require("./APNKey");
|
||||
var oids = require("./oids");
|
||||
|
||||
function APNCertificate(cert) {
|
||||
if(!cert.publicKey || !cert.validity || !cert.subject) {
|
||||
throw new Error("certificate object is invalid");
|
||||
}
|
||||
|
||||
this._cert = cert;
|
||||
}
|
||||
|
||||
APNCertificate.prototype.key = function() {
|
||||
return new APNKey(this._cert.publicKey);
|
||||
};
|
||||
|
||||
APNCertificate.prototype.validity = function() {
|
||||
return this._cert.validity;
|
||||
};
|
||||
|
||||
APNCertificate.prototype.environment = function() {
|
||||
var environment = { sandbox: false, production: false };
|
||||
|
||||
if (this._cert.getExtension({ "id": oids.applePushServiceClientDevelopment })) {
|
||||
environment.sandbox = true;
|
||||
}
|
||||
|
||||
if (this._cert.getExtension({ "id": oids.applePushServiceClientProduction })) {
|
||||
environment.production = true;
|
||||
}
|
||||
return environment;
|
||||
};
|
||||
|
||||
module.exports = APNCertificate;
|
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
var forge = require("node-forge");
|
||||
|
||||
function APNKey(key) {
|
||||
if(!key || !key.n || !key.e) {
|
||||
throw new Error("key is not a valid public key");
|
||||
}
|
||||
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
APNKey.prototype.fingerprint = function() {
|
||||
return forge.pki.getPublicKeyFingerprint(this._key, {encoding: "hex"});
|
||||
};
|
||||
|
||||
module.exports = APNKey;
|
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
var q = require("q");
|
||||
var sysu = require("util");
|
||||
|
||||
var resolve = require("./resolve");
|
||||
|
||||
function loadCredentials(credentials) {
|
||||
|
||||
// Prepare PKCS#12 data if available
|
||||
var pfxPromise = resolve(credentials.pfx || credentials.pfxData);
|
||||
|
||||
// Prepare Certificate data if available.
|
||||
var certPromise = resolve(credentials.cert || credentials.certData);
|
||||
|
||||
// Prepare Key data if available
|
||||
var keyPromise = resolve(credentials.key || credentials.keyData);
|
||||
|
||||
// Prepare Certificate Authority data if available.
|
||||
var caPromises = [];
|
||||
if (credentials.ca !== null) {
|
||||
if(!sysu.isArray(credentials.ca)) {
|
||||
credentials.ca = [ credentials.ca ];
|
||||
}
|
||||
credentials.ca.forEach(function(ca) {
|
||||
caPromises.push(resolve(ca));
|
||||
});
|
||||
}
|
||||
if (caPromises.length === 0) {
|
||||
caPromises = undefined;
|
||||
}
|
||||
else {
|
||||
caPromises = q.all(caPromises);
|
||||
}
|
||||
|
||||
return q.all([pfxPromise, certPromise, keyPromise, caPromises])
|
||||
.spread(function(pfx, cert, key, ca) {
|
||||
return { pfx: pfx, cert: cert, key: key, ca: ca, passphrase: credentials.passphrase };
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = loadCredentials;
|
|
@ -0,0 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
var oids = {
|
||||
"applePushServiceClientDevelopment" : "1.2.840.113635.100.6.3.1",
|
||||
"applePushServiceClientProduction" : "1.2.840.113635.100.6.3.2",
|
||||
};
|
||||
|
||||
module.exports = oids;
|
|
@ -0,0 +1,20 @@
|
|||
var parsePkcs12 = require("./parsePkcs12");
|
||||
var parsePemKey = require("./parsePemKey");
|
||||
var parsePemCert = require("./parsePemCertificate");
|
||||
|
||||
function parse(credentials) {
|
||||
var parsed = {};
|
||||
|
||||
parsed.key = parsePemKey(credentials.key, credentials.passphrase);
|
||||
parsed.certificates = parsePemCert(credentials.cert);
|
||||
|
||||
var pkcs12Parsed = parsePkcs12(credentials.pfx, credentials.passphrase);
|
||||
if (pkcs12Parsed) {
|
||||
parsed.key = pkcs12Parsed.key;
|
||||
parsed.certificates = pkcs12Parsed.certificates;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
module.exports = parse;
|
|
@ -0,0 +1,35 @@
|
|||
"use strict";
|
||||
|
||||
var forge = require("node-forge");
|
||||
|
||||
var APNCertificate = require("./APNCertificate");
|
||||
|
||||
function apnCertificateFromPem(certData) {
|
||||
if (!certData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var pemMessages;
|
||||
try {
|
||||
pemMessages = forge.pem.decode(certData);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message.match("Invalid PEM formatted message.")) {
|
||||
throw new Error("unable to parse certificate, not a valid PEM file");
|
||||
}
|
||||
}
|
||||
var certificates = [];
|
||||
|
||||
pemMessages.forEach(function(message) {
|
||||
if (!message.type.match(new RegExp("CERTIFICATE$"))) {
|
||||
return;
|
||||
}
|
||||
var certAsn1 = forge.asn1.fromDer(message.body);
|
||||
var forgeCertificate = forge.pki.certificateFromAsn1(certAsn1);
|
||||
|
||||
certificates.push(new APNCertificate(forgeCertificate));
|
||||
});
|
||||
return certificates;
|
||||
}
|
||||
|
||||
module.exports = apnCertificateFromPem;
|
|
@ -0,0 +1,60 @@
|
|||
"use strict";
|
||||
|
||||
var forge = require("node-forge");
|
||||
|
||||
var APNKey = require("./APNKey");
|
||||
|
||||
function findAndDecryptKey(pemMessages, passphrase) {
|
||||
var apnKey = null;
|
||||
pemMessages.forEach(function(message) {
|
||||
if (!message.type.match(/KEY/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = forge.pki.decryptRsaPrivateKey(forge.pem.encode(message), passphrase);
|
||||
|
||||
if(!key) {
|
||||
if ((message.procType && message.procType.type === "ENCRYPTED") || message.type.match(/ENCRYPTED/)) {
|
||||
throw new Error("unable to parse key, incorrect passphrase");
|
||||
}
|
||||
}
|
||||
else if(apnKey) {
|
||||
throw new Error("multiple keys found in PEM file");
|
||||
}
|
||||
else {
|
||||
apnKey = new APNKey(key);
|
||||
}
|
||||
});
|
||||
return apnKey;
|
||||
}
|
||||
|
||||
function apnKeyFromPem(keyPem, passphrase) {
|
||||
if (!keyPem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var pemMessages = forge.pem.decode(keyPem);
|
||||
var apnKey = findAndDecryptKey(pemMessages, passphrase);
|
||||
if (apnKey) {
|
||||
return apnKey;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message.match(/Unsupported OID/)) {
|
||||
throw new Error("unable to parse key, unsupported format: " + e.oid);
|
||||
}
|
||||
else if(e.message.match(/Invalid PEM formatted message/)) {
|
||||
throw new Error("unable to parse key, not a valid PEM file");
|
||||
}
|
||||
else if (e.message.match(/multiple keys/)) {
|
||||
throw e;
|
||||
}
|
||||
else if (e.message.match(/unable to parse key/)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw new Error("unable to parse key, no private key found");
|
||||
}
|
||||
|
||||
module.exports = apnKeyFromPem;
|
|
@ -0,0 +1,59 @@
|
|||
"use strict";
|
||||
|
||||
var forge = require("node-forge");
|
||||
|
||||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
var APNCertificate = require("../../lib/credentials/APNCertificate");
|
||||
|
||||
function decryptPkcs12FromAsn1(asn1, passphrase) {
|
||||
try {
|
||||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, passphrase);
|
||||
}
|
||||
catch (e) {
|
||||
// OpenSSL-exported files need an empty string, if no password was specified
|
||||
// during export.
|
||||
if (passphrase) {
|
||||
throw e;
|
||||
}
|
||||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, "");
|
||||
}
|
||||
}
|
||||
|
||||
function apnCredentialsFromPkcs12(p12Data, passphrase) {
|
||||
if (!p12Data) {
|
||||
return;
|
||||
}
|
||||
|
||||
var asn1 = forge.asn1.fromDer(p12Data.toString("binary"), false);
|
||||
var pkcs12;
|
||||
try {
|
||||
pkcs12 = decryptPkcs12FromAsn1(asn1, passphrase);
|
||||
}
|
||||
catch(e) {
|
||||
if (e.message.match("Invalid password")) {
|
||||
throw new Error("unable to parse credentials, incorrect passphrase");
|
||||
}
|
||||
else {
|
||||
throw new Error("unable to parse credentials, not a PFX/P12 file");
|
||||
}
|
||||
}
|
||||
|
||||
var credentials = { "key": null, "certificates": []};
|
||||
pkcs12.safeContents.forEach(function(safeContents) {
|
||||
safeContents.safeBags.forEach(function(safeBag) {
|
||||
if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
|
||||
if(credentials.key) {
|
||||
throw new Error("multiple keys found in PFX/P12 file");
|
||||
}
|
||||
credentials.key = new APNKey(safeBag.key);
|
||||
}
|
||||
else if(safeBag.type === forge.pki.oids.certBag) {
|
||||
credentials.certificates.push(new APNCertificate(safeBag.cert));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
module.exports = apnCredentialsFromPkcs12;
|
|
@ -0,0 +1,21 @@
|
|||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var q = require("q");
|
||||
|
||||
function resolveCredential(value) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if(/-----BEGIN ([A-Z\s*]+)-----/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
else if(Buffer.isBuffer(value)) {
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
return q.nfbind(fs.readFile)(value);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = resolveCredential;
|
|
@ -0,0 +1,24 @@
|
|||
"use strict";
|
||||
|
||||
function validateCredentials(credentials) {
|
||||
var certificate = credentials.certificates[0];
|
||||
|
||||
if (credentials.key.fingerprint() !== certificate.key().fingerprint()) {
|
||||
throw new Error("certificate and key do not match");
|
||||
}
|
||||
|
||||
var validity = certificate.validity();
|
||||
if (validity.notAfter.getTime() < Date.now()) {
|
||||
throw new Error("certificate has expired: " + validity.notAfter.toJSON());
|
||||
}
|
||||
|
||||
if (credentials.production !== undefined) {
|
||||
var environment = certificate.environment();
|
||||
if ( (credentials.production && !environment.production) ||
|
||||
(!credentials.production && !environment.sandbox)) {
|
||||
throw new Error("certificate does not support configured environment, production: " + credentials.production);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = validateCredentials;
|
|
@ -1,3 +1,4 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Creates a Device.
|
||||
* @constructor
|
||||
|
@ -8,7 +9,7 @@ function Device(deviceToken) {
|
|||
return new Device(deviceToken);
|
||||
}
|
||||
|
||||
if(typeof deviceToken == "string") {
|
||||
if(typeof deviceToken === "string") {
|
||||
this.token = new Buffer(deviceToken.replace(/[^0-9a-f]/gi, ""), "hex");
|
||||
}
|
||||
else if(Buffer.isBuffer(deviceToken)) {
|
||||
|
@ -16,8 +17,8 @@ function Device(deviceToken) {
|
|||
deviceToken.copy(this.token);
|
||||
}
|
||||
|
||||
if (!this.token || this.token.length == 0) {
|
||||
throw new Error('Invalid Token Specified, must be a Buffer or valid hex String');
|
||||
if (!this.token || this.token.length === 0) {
|
||||
throw new Error("Invalid Token Specified, must be a Buffer or valid hex String");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Error codes used by Apple
|
||||
* @see <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4">The Binary Interface and Notification Formats</a>
|
||||
*/
|
||||
|
||||
var Errors = {
|
||||
'noErrorsEncountered': 0,
|
||||
'processingError': 1,
|
||||
'missingDeviceToken': 2,
|
||||
'missingTopic': 3,
|
||||
'missingPayload': 4,
|
||||
'invalidTokenSize': 5,
|
||||
'invalidTopicSize': 6,
|
||||
'invalidPayloadSize': 7,
|
||||
'invalidToken': 8,
|
||||
'apnsShutdown': 10,
|
||||
'none': 255,
|
||||
'retryLimitExceeded': 512,
|
||||
'moduleInitialisationFailed': 513,
|
||||
'connectionRetryLimitExceeded': 514, // When a connection is unable to be established. Usually because of a network / SSL error this will be emitted
|
||||
'connectionTerminated': 515
|
||||
"noErrorsEncountered": 0,
|
||||
"processingError": 1,
|
||||
"missingDeviceToken": 2,
|
||||
"missingTopic": 3,
|
||||
"missingPayload": 4,
|
||||
"invalidTokenSize": 5,
|
||||
"invalidTopicSize": 6,
|
||||
"invalidPayloadSize": 7,
|
||||
"invalidToken": 8,
|
||||
"apnsShutdown": 10,
|
||||
"none": 255,
|
||||
"retryLimitExceeded": 512,
|
||||
"moduleInitialisationFailed": 513,
|
||||
"connectionRetryLimitExceeded": 514, // When a connection is unable to be established. Usually because of a network / SSL error this will be emitted
|
||||
"connectionTerminated": 515
|
||||
};
|
||||
|
||||
module.exports = Errors;
|
|
@ -1,18 +1,20 @@
|
|||
var CredentialLoader = require('./credentials');
|
||||
var Device = require('./device');
|
||||
var Errors = require('./errors');
|
||||
"use strict";
|
||||
|
||||
var createSocket = require('./socket');
|
||||
var loadCredentials = require("./credentials/load");
|
||||
var parseCredentials = require("./credentials/parse");
|
||||
var validateCredentials = require("./credentials/validate");
|
||||
var Device = require("./device");
|
||||
|
||||
var q = require('q');
|
||||
var tls = require('tls');
|
||||
var sysu = require('util');
|
||||
var util = require('./util');
|
||||
var events = require('events');
|
||||
var createSocket = require("./socket");
|
||||
|
||||
var q = require("q");
|
||||
var sysu = require("util");
|
||||
var util = require("./util");
|
||||
var events = require("events");
|
||||
var debug = function() {};
|
||||
if(process.env.DEBUG) {
|
||||
try {
|
||||
debug = require('debug')('apnfb');
|
||||
debug = require("debug")("apnfb");
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
|
||||
|
@ -41,8 +43,8 @@ function Feedback(options) {
|
|||
return new Feedback(options);
|
||||
}
|
||||
this.options = {
|
||||
cert: 'cert.pem', /* Certificate file */
|
||||
key: 'key.pem', /* Key file */
|
||||
cert: "cert.pem", /* Certificate file */
|
||||
key: "key.pem", /* Key file */
|
||||
ca: null, /* Certificate Authority */
|
||||
pfx: null, /* PFX File */
|
||||
passphrase: null, /* Passphrase for key */
|
||||
|
@ -57,14 +59,14 @@ function Feedback(options) {
|
|||
};
|
||||
|
||||
for (var key in options) {
|
||||
if (options[key] == null) {
|
||||
if (options[key] === null) {
|
||||
debug("Option [" + key + "] set to null. This may cause unexpected behaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
util.extend(this.options, options);
|
||||
|
||||
if (this.options.address == null) {
|
||||
if (!this.options.address) {
|
||||
if (this.options.production) {
|
||||
this.options.address = "feedback.push.apple.com";
|
||||
}
|
||||
|
@ -72,6 +74,14 @@ function Feedback(options) {
|
|||
this.options.address = "feedback.sandbox.push.apple.com";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.options.address === "feedback.push.apple.com") {
|
||||
this.options.production = true;
|
||||
}
|
||||
else {
|
||||
this.options.production = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.pfx || this.options.pfxData) {
|
||||
if (!options.cert) {
|
||||
|
@ -90,16 +100,16 @@ function Feedback(options) {
|
|||
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
if (typeof this.options.errorCallback == 'function') {
|
||||
this.on('error', this.options.errorCallback);
|
||||
if (typeof this.options.errorCallback === "function") {
|
||||
this.on("error", this.options.errorCallback);
|
||||
}
|
||||
|
||||
if (typeof this.options.feedback == 'function') {
|
||||
this.on('feedback', this.options.feedback);
|
||||
if (typeof this.options.feedback === "function") {
|
||||
this.on("feedback", this.options.feedback);
|
||||
}
|
||||
|
||||
process.nextTick(function() {
|
||||
if(this.listeners('feedback').length === 0) {
|
||||
if(this.listeners("feedback").length === 0) {
|
||||
debug("WARNING: A `feedback` listener has not been specified. Data may be lost.");
|
||||
}
|
||||
}.bind(this));
|
||||
|
@ -116,7 +126,22 @@ sysu.inherits(Feedback, events.EventEmitter);
|
|||
Feedback.prototype.initialize = function () {
|
||||
if (!this.initializationPromise) {
|
||||
debug("Initialising module");
|
||||
this.initializationPromise = CredentialLoader(this.options);
|
||||
|
||||
var production = this.options.production;
|
||||
this.initializationPromise = loadCredentials(this.options)
|
||||
.then(function(credentials) {
|
||||
var parsed;
|
||||
try {
|
||||
parsed = parseCredentials(credentials);
|
||||
}
|
||||
catch (e) {
|
||||
debug(e);
|
||||
return credentials;
|
||||
}
|
||||
parsed.production = production;
|
||||
validateCredentials(parsed);
|
||||
return credentials;
|
||||
});
|
||||
}
|
||||
|
||||
return this.initializationPromise;
|
||||
|
@ -133,15 +158,15 @@ Feedback.prototype.connect = function () {
|
|||
|
||||
debug("Initialising connection");
|
||||
this.deferredConnection = q.defer();
|
||||
this.initialize().spread(function(pfxData, certData, keyData, caData) {
|
||||
this.initialize().then(function(credentials) {
|
||||
var socketOptions = {};
|
||||
|
||||
socketOptions.port = this.options.port;
|
||||
socketOptions.host = this.options.address;
|
||||
socketOptions.pfx = pfxData;
|
||||
socketOptions.cert = certData;
|
||||
socketOptions.key = keyData;
|
||||
socketOptions.ca = caData;
|
||||
socketOptions.pfx = credentials.pfx;
|
||||
socketOptions.cert = credentials.cert;
|
||||
socketOptions.key = credentials.key;
|
||||
socketOptions.ca = credentials.ca;
|
||||
socketOptions.passphrase = this.options.passphrase;
|
||||
socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;
|
||||
|
||||
|
@ -153,12 +178,16 @@ Feedback.prototype.connect = function () {
|
|||
|
||||
this.readBuffer = new Buffer(0);
|
||||
this.feedbackData = [];
|
||||
this.socket.on('data', this.receive.bind(this));
|
||||
this.socket.on("data", this.receive.bind(this));
|
||||
this.socket.on("error", this.destroyConnection.bind(this));
|
||||
this.socket.once('close', this.resetConnection.bind(this));
|
||||
}.bind(this)).fail(function (error) {
|
||||
this.socket.once("close", this.resetConnection.bind(this));
|
||||
}.bind(this), function (error) {
|
||||
debug("Module initialisation error:", error);
|
||||
this.emit('error', error);
|
||||
this.cancel();
|
||||
|
||||
throw error;
|
||||
}.bind(this)).done(null, function(error) {
|
||||
this.emit("error", error);
|
||||
this.deferredConnection.reject(error);
|
||||
this.deferredConnection = null;
|
||||
}.bind(this));
|
||||
|
@ -192,11 +221,11 @@ Feedback.prototype.receive = function (data) {
|
|||
var device = new Device(token);
|
||||
if (!this.options.batchFeedback) {
|
||||
debug("Emitting feedback event");
|
||||
this.emit('feedback', time, device);
|
||||
this.emit("feedback", time, device);
|
||||
} else {
|
||||
this.feedbackData.push({ time: time, device: device });
|
||||
if (this.options.batchSize > 0 && this.options.batchSize <= this.feedbackData.length) {
|
||||
this.emit('feedback', this.feedbackData);
|
||||
this.emit("feedback", this.feedbackData);
|
||||
this.feedbackData = [];
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +239,7 @@ Feedback.prototype.receive = function (data) {
|
|||
Feedback.prototype.destroyConnection = function (err) {
|
||||
debug("Destroying connection");
|
||||
if(err) {
|
||||
this.emit('feedbackError', err);
|
||||
this.emit("feedbackError", err);
|
||||
}
|
||||
if (this.socket) {
|
||||
this.socket.destroySoon();
|
||||
|
@ -225,7 +254,7 @@ Feedback.prototype.resetConnection = function () {
|
|||
|
||||
if (this.options.batchFeedback) {
|
||||
debug("Emitting " + this.feedbackData.length + " feedback tokens");
|
||||
this.emit('feedback', this.feedbackData);
|
||||
this.emit("feedback", this.feedbackData);
|
||||
this.feedbackData = [];
|
||||
}
|
||||
|
||||
|
@ -257,7 +286,7 @@ Feedback.prototype.start = function () {
|
|||
Feedback.prototype.request = function () {
|
||||
debug("Performing feedback request");
|
||||
this.connect().fail(function (error) {
|
||||
this.emit('feedbackError', error);
|
||||
this.emit("feedbackError", error);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Create a notification
|
||||
* @constructor
|
||||
*/
|
||||
function Notification (payload) {
|
||||
this.encoding = 'utf8';
|
||||
this.encoding = "utf8";
|
||||
|
||||
this.payload = payload || {};
|
||||
this.expiry = 0;
|
||||
|
@ -14,12 +15,6 @@ function Notification (payload) {
|
|||
/** @deprecated since v1.3.0 used connection#pushNotification instead which accepts device token separately **/
|
||||
this.device = undefined;
|
||||
|
||||
this.alert = undefined;
|
||||
this.badge = undefined;
|
||||
this.sound = undefined;
|
||||
/** @since v1.2.0 */
|
||||
this.newsstandAvailable = undefined;
|
||||
|
||||
this.contentAvailable = undefined;
|
||||
|
||||
this.mdm = undefined;
|
||||
|
@ -34,6 +29,59 @@ function Notification (payload) {
|
|||
this.category = undefined;
|
||||
}
|
||||
|
||||
Notification.prototype = {
|
||||
get alert() {
|
||||
return this._alert;
|
||||
},
|
||||
set alert(value) {
|
||||
var type = typeof value;
|
||||
if (type == "string" || type == "object" || value === undefined) {
|
||||
this._alert = value;
|
||||
}
|
||||
},
|
||||
|
||||
get badge() {
|
||||
return this._badge;
|
||||
},
|
||||
set badge(value) {
|
||||
if (typeof value === "number" || value === undefined) {
|
||||
this._badge = value;
|
||||
}
|
||||
},
|
||||
|
||||
get sound() {
|
||||
return this._sound;
|
||||
},
|
||||
set sound(value) {
|
||||
if (typeof value == "string" || value === undefined) {
|
||||
this._sound = value;
|
||||
}
|
||||
},
|
||||
|
||||
get contentAvailable() {
|
||||
return this._contentAvailable;
|
||||
},
|
||||
set contentAvailable(value) {
|
||||
if (typeof value == "boolean" || value === undefined) {
|
||||
this._contentAvailable = value;
|
||||
}
|
||||
},
|
||||
|
||||
get newsstandAvailable() {
|
||||
return this.contentAvailable;
|
||||
},
|
||||
set newsstandAvailable(value) {
|
||||
this.contentAvailable = value;
|
||||
},
|
||||
|
||||
get mdm() {
|
||||
return this._mdm;
|
||||
},
|
||||
set mdm(value) {
|
||||
this._mdm = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a notification to send to multiple devices
|
||||
* @param {Device} [device] Device the notification will be sent to
|
||||
|
@ -82,7 +130,7 @@ Notification.prototype.setExpiry = function (expiry) {
|
|||
Notification.prototype.setPriority = function (priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the "badge" value on the alert object
|
||||
|
@ -104,6 +152,13 @@ Notification.prototype.setSound = function (sound) {
|
|||
return this;
|
||||
};
|
||||
|
||||
Notification.prototype.getAlertText = function () {
|
||||
if(typeof this.alert === "object") {
|
||||
return this.alert.body;
|
||||
}
|
||||
return this.alert;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alert text for the notification
|
||||
* @param {String} alertText The text of the alert message.
|
||||
|
@ -111,27 +166,51 @@ Notification.prototype.setSound = function (sound) {
|
|||
* @since v1.2.0
|
||||
*/
|
||||
Notification.prototype.setAlertText = function (text) {
|
||||
if(typeof this.alert != "object") {
|
||||
if(typeof this.alert !== "object") {
|
||||
this.alert = text;
|
||||
}
|
||||
else {
|
||||
this.prepareAlert();
|
||||
this.alert['body'] = text;
|
||||
this.alert.body = text;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alert title for the notification - used with Safari Push Notifications
|
||||
* Set the alert title for the notification - used with Safari Push Notifications and iOS Push Notifications displayed on Apple Watch
|
||||
* @param {String} alertTitle The title for the alert.
|
||||
* @see The <a href="https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/PushNotifications/PushNotifications.html#//apple_ref/doc/uid/TP40013225-CH3-SW12">Pushing Notifications</a> in the Notification Programming Guide for Websites
|
||||
* @since v1.5.0
|
||||
*/
|
||||
Notification.prototype.setAlertTitle = function(alertTitle) {
|
||||
this.prepareAlert();
|
||||
this.alert['title'] = alertTitle;
|
||||
this.alert.title = alertTitle;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alert title-loc-key for the notification - used with iOS Push Notifications displayed on Apple Watch. Please note: The corresponding localization key must be in your host app's (i.e. iPhone app) Localizable.strings file and not inside your WatchKit extension or WatchKit app.
|
||||
* @param {String} titleLocKey The localization key for the alert title.
|
||||
* @see The <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">Payload Documentation</a>
|
||||
* @since XXX
|
||||
*/
|
||||
Notification.prototype.setTitleLocKey = function(titleLocKey) {
|
||||
this.prepareAlert();
|
||||
this.alert["title-loc-key"] = titleLocKey;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alert title-loc-args for the notification - used with iOS Push Notifications displayed on Apple Watch
|
||||
* @param {String[]} [titleLocArgs] Variable string values to appear in place of the format specifiers in title-loc-key.
|
||||
* @see The <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">Payload Documentation</a>
|
||||
* @since XXX
|
||||
*/
|
||||
Notification.prototype.setTitleLocArgs = function(titleLocArgs) {
|
||||
this.prepareAlert();
|
||||
this.alert["title-loc-args"] = titleLocArgs;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alert action label for the notification - used with Safari Push Notifications
|
||||
|
@ -141,9 +220,9 @@ Notification.prototype.setAlertTitle = function(alertTitle) {
|
|||
*/
|
||||
Notification.prototype.setAlertAction = function(alertAction) {
|
||||
this.prepareAlert();
|
||||
this.alert['action'] = alertAction;
|
||||
this.alert.action = alertAction;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the action-loc-key property on the alert object
|
||||
|
@ -153,7 +232,7 @@ Notification.prototype.setAlertAction = function(alertAction) {
|
|||
*/
|
||||
Notification.prototype.setActionLocKey = function (key) {
|
||||
this.prepareAlert();
|
||||
this.alert['action-loc-key'] = key;
|
||||
this.alert["action-loc-key"] = key;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -169,7 +248,7 @@ Notification.prototype.setLocKey = function (key) {
|
|||
delete this.alert["loc-key"];
|
||||
return;
|
||||
}
|
||||
this.alert['loc-key'] = key;
|
||||
this.alert["loc-key"] = key;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -185,7 +264,7 @@ Notification.prototype.setLocArgs = function (args) {
|
|||
delete this.alert["loc-args"];
|
||||
return;
|
||||
}
|
||||
this.alert['loc-args'] = args;
|
||||
this.alert["loc-args"] = args;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -271,9 +350,9 @@ Notification.prototype.setCategory = function (category) {
|
|||
*/
|
||||
Notification.prototype.prepareAlert = function () {
|
||||
var existingValue = this.alert;
|
||||
if(typeof existingValue != "object") {
|
||||
if(typeof existingValue !== "object") {
|
||||
this.alert = {};
|
||||
if(typeof existingValue == "string") {
|
||||
if(typeof existingValue === "string") {
|
||||
this.alert.body = existingValue;
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +363,8 @@ Notification.prototype.prepareAlert = function () {
|
|||
* @since v1.2.0
|
||||
*/
|
||||
Notification.prototype.length = function () {
|
||||
return Buffer.byteLength(this.compile(), this.encoding || 'utf8');
|
||||
this.compiled = false;
|
||||
return Buffer.byteLength(this.compile(), this.encoding || "utf8");
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -293,21 +373,16 @@ Notification.prototype.length = function () {
|
|||
* @since v1.2.0
|
||||
*/
|
||||
Notification.prototype.trim = function(length) {
|
||||
var tooLong = this.length() - (length || 2048);
|
||||
var payloadLength = this.length();
|
||||
var tooLong = payloadLength - (length || 2048);
|
||||
if(tooLong <= 0) {
|
||||
return 0;
|
||||
}
|
||||
this.compiled = false;
|
||||
var length;
|
||||
var encoding = this.encoding || 'utf8';
|
||||
var escaped; // Working variable for trimming string
|
||||
if(typeof this.alert == "string") {
|
||||
escaped = this.alert;
|
||||
}
|
||||
else if(typeof this.alert == "object" && typeof this.alert.body == "string") {
|
||||
escaped = this.alert.body;
|
||||
}
|
||||
else {
|
||||
var encoding = this.encoding || "utf8";
|
||||
var escaped = this.getAlertText();
|
||||
|
||||
if(!escaped) {
|
||||
return -tooLong;
|
||||
}
|
||||
|
||||
|
@ -316,17 +391,15 @@ Notification.prototype.trim = function(length) {
|
|||
if (length < tooLong) {
|
||||
return length - tooLong;
|
||||
}
|
||||
escaped = this.truncateStringToLength(escaped, length - tooLong);
|
||||
escaped = escaped.replace(/(\\|\\x\d{0,1}|\\u\d{0,3})$/, '');
|
||||
escaped = JSON.parse('"' + escaped + '"');
|
||||
escaped = this.truncateStringToLength(escaped, length - tooLong, encoding);
|
||||
escaped = escaped.replace(/(\\u[0-9a-fA-F]{0,3})$/, "");
|
||||
escaped = escaped.replace(/\\+$/, function(a){ return a.length % 2 === 0 ? a : a.slice(0, -1); });
|
||||
|
||||
if (typeof this.alert == "string") {
|
||||
this.alert = escaped;
|
||||
}
|
||||
else {
|
||||
this.alert.body = escaped;
|
||||
}
|
||||
return tooLong;
|
||||
var trimmed = Buffer.byteLength(escaped, encoding);
|
||||
escaped = JSON.parse("\"" + escaped + "\"");
|
||||
|
||||
this.setAlertText(escaped);
|
||||
return length - trimmed;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -335,30 +408,38 @@ Notification.prototype.trim = function(length) {
|
|||
* @since v1.3.0
|
||||
*/
|
||||
Notification.prototype.compile = function () {
|
||||
if(this.compiled) {
|
||||
return this.compiledPayload;
|
||||
if(!this.compiled) {
|
||||
this.compiled = JSON.stringify(this);
|
||||
}
|
||||
this.compiledPayload = JSON.stringify(this);
|
||||
this.compiled = true;
|
||||
return this.compiledPayload;
|
||||
return this.compiled;
|
||||
};
|
||||
|
||||
function hasValidUnicodeTail(string, encoding) {
|
||||
var code = string.charCodeAt(string.length - 1);
|
||||
if (code !== 0xFFFD && encoding === "utf8") {
|
||||
return true;
|
||||
}
|
||||
else if ((code < 0xD800 || code > 0xDBFF) && (encoding === "utf16le" || encoding === "ucs2")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} [string] Unicode string to be truncated
|
||||
* @param {Number} [length] The maximum number of bytes permitted in the Unicode string
|
||||
* @returns {String} Truncated String
|
||||
* @private
|
||||
*/
|
||||
Notification.prototype.truncateStringToLength = function (string, length) {
|
||||
Notification.prototype.truncateStringToLength = function (string, length, encoding) {
|
||||
// Convert to a buffer and back to a string with the correct encoding to truncate the unicode series correctly.
|
||||
var result = new Buffer(string, this.encoding || 'utf8').toString(this.encoding || 'utf8', 0, length);
|
||||
var result = new Buffer(string, encoding).toString(encoding, 0, length);
|
||||
|
||||
if (this.truncateAtWordEnd === true) {
|
||||
var lastSpaceIndexInResult = result.lastIndexOf(' ');
|
||||
var lastSpaceIndexInResult = result.lastIndexOf(" ");
|
||||
|
||||
// Only truncate the string further if the remainder isn't a whole word
|
||||
// which we can tell by checking the *next* character in the original string
|
||||
if(lastSpaceIndexInResult != -1 && string.charAt(result.length + 1) != ' '){
|
||||
result=result.substr(0,lastSpaceIndexInResult);
|
||||
if(lastSpaceIndexInResult !== -1 && string.charAt(result.length) !== " "){
|
||||
result = result.substr(0, lastSpaceIndexInResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,23 +447,9 @@ Notification.prototype.truncateStringToLength = function (string, length) {
|
|||
// invalid characters (represented as U+FFFD "REPLACEMENT CHARACTER") for UTF-8
|
||||
// or orphaned lead surrogates for UTF-16 (UCS-2) - where only the tail surrogate
|
||||
// has been removed.
|
||||
var done = false;
|
||||
var encoding = this.encoding || 'utf8';
|
||||
|
||||
if (encoding != 'utf8' && encoding != 'utf16le' && encoding != 'ucs2') {
|
||||
return result;
|
||||
}
|
||||
while (! done) {
|
||||
var lastIndex = result.length - 1;
|
||||
var code = result.charCodeAt(lastIndex);
|
||||
if (code != 0xFFFD && encoding == 'utf8') {
|
||||
done = true;
|
||||
}
|
||||
else if ((code < 0xD800 || code > 0xDBFF) && (encoding == 'utf16le' || encoding == 'ucs2')) {
|
||||
done = true;
|
||||
}
|
||||
else {
|
||||
result = result.substr(0, lastIndex);
|
||||
if (encoding === "utf8" || encoding === "utf16le" || encoding === "ucs2") {
|
||||
while( result.length > 0 && !hasValidUnicodeTail(result, encoding) ) {
|
||||
result = result.substr(0, result.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,46 +459,40 @@ Notification.prototype.truncateStringToLength = function (string, length) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
Notification.prototype.toJSON = function () {
|
||||
if (this.payload === undefined) {
|
||||
this.payload = {};
|
||||
Notification.prototype.apsPayload = function() {
|
||||
var aps = this.payload.aps || {};
|
||||
|
||||
if (typeof this.badge === "number") {
|
||||
aps.badge = this.badge;
|
||||
}
|
||||
if (typeof this.mdm == 'string') {
|
||||
if (typeof this.sound === "string") {
|
||||
aps.sound = this.sound;
|
||||
}
|
||||
if (this.alert !== undefined) {
|
||||
aps.alert = this.alert;
|
||||
}
|
||||
if (this.contentAvailable) {
|
||||
aps["content-available"] = 1;
|
||||
}
|
||||
if (Array.isArray(this.urlArgs)) {
|
||||
aps["url-args"] = this.urlArgs;
|
||||
}
|
||||
if (typeof this.category === "string") {
|
||||
aps.category = this.category;
|
||||
}
|
||||
return aps;
|
||||
};
|
||||
|
||||
Notification.prototype.toJSON = function () {
|
||||
if (typeof this.mdm === "string") {
|
||||
this.payload.mdm = this.mdm;
|
||||
return this.payload;
|
||||
}
|
||||
var apsSet = true;
|
||||
if (this.payload.aps === undefined) {
|
||||
this.payload.aps = {};
|
||||
apsSet = false;
|
||||
}
|
||||
if (typeof this.badge == 'number') {
|
||||
this.payload.aps.badge = this.badge;
|
||||
apsSet = true;
|
||||
}
|
||||
if (typeof this.sound == 'string') {
|
||||
this.payload.aps.sound = this.sound;
|
||||
apsSet = true;
|
||||
}
|
||||
if (typeof this.alert == 'string' || typeof this.alert == 'object') {
|
||||
this.payload.aps.alert = this.alert;
|
||||
apsSet = true;
|
||||
}
|
||||
if (this.newsstandAvailable || this.contentAvailable) {
|
||||
this.payload.aps['content-available'] = 1;
|
||||
apsSet = true;
|
||||
}
|
||||
if (Array.isArray(this.urlArgs)) {
|
||||
this.payload.aps['url-args'] = this.urlArgs;
|
||||
apsSet = true;
|
||||
}
|
||||
if (typeof this.category == 'string') {
|
||||
this.payload.aps.category = this.category;
|
||||
apsSet = true;
|
||||
}
|
||||
|
||||
var aps = this.apsPayload();
|
||||
|
||||
if (!apsSet) {
|
||||
delete this.payload.aps;
|
||||
if (Object.keys(aps).length > 0) {
|
||||
this.payload.aps = aps;
|
||||
}
|
||||
|
||||
return this.payload;
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
var tls = require('tls');
|
||||
var net = require('net');
|
||||
"use strict";
|
||||
var tls = require("tls");
|
||||
var net = require("net");
|
||||
|
||||
var debug = function() {};
|
||||
if(process.env.DEBUG) {
|
||||
try {
|
||||
debug = require('debug')('apn:socket');
|
||||
debug = require("debug")("apn:socket");
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function destroyEPIPEFix(e) {
|
||||
function DestroyEPIPEFix(e) {
|
||||
// When a write error occurs we miss the opportunity to
|
||||
// read error data from APNS. Delay the call to destroy
|
||||
// to allow more data to be read.
|
||||
var self = this;
|
||||
var socket = this;
|
||||
var args = arguments;
|
||||
var call = function () {
|
||||
self._apnDestroy.apply(self, args);
|
||||
}
|
||||
socket._apnDestroy.apply(socket, args);
|
||||
};
|
||||
|
||||
if (e && e.syscall == "write") {
|
||||
if (e && e.syscall === "write") {
|
||||
setTimeout(call, 1000);
|
||||
}
|
||||
else {
|
||||
|
@ -33,14 +34,14 @@ function apnSocketLegacy(connection, socketOptions, connected) {
|
|||
|
||||
if (!socketOptions.disableEPIPEFix) {
|
||||
socketOptions.socket._apnDestroy = socketOptions.socket._destroy;
|
||||
socketOptions.socket._destroy = destroyEPIPEFix;
|
||||
socketOptions.socket._destroy = DestroyEPIPEFix;
|
||||
socketOptions.socket.on("error", function () {});
|
||||
}
|
||||
|
||||
var socket = tls.connect( socketOptions['port'], socketOptions['host'],
|
||||
var socket = tls.connect( socketOptions.port, socketOptions.host,
|
||||
socketOptions, connected);
|
||||
|
||||
debug("connecting to: ", socketOptions['host'] + ":" + socketOptions['port']);
|
||||
debug("connecting to: ", socketOptions.host + ":" + socketOptions.port);
|
||||
|
||||
socketOptions.socket.setNoDelay(socketOptions.disableNagle);
|
||||
socketOptions.socket.setKeepAlive(true);
|
||||
|
@ -50,7 +51,7 @@ function apnSocketLegacy(connection, socketOptions, connected) {
|
|||
|
||||
// The actual connection is delayed until after all the event listeners have
|
||||
// been attached.
|
||||
socketOptions.socket.connect(socketOptions['port'], socketOptions['host']);
|
||||
socketOptions.socket.connect(socketOptions.port, socketOptions.host);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
@ -61,7 +62,7 @@ function apnSocket(connection, socketOptions, connected) {
|
|||
|
||||
if (!socketOptions.disableEPIPEFix) {
|
||||
socket._apnDestroy = socket._destroy;
|
||||
socket._destroy = destroyEPIPEFix;
|
||||
socket._destroy = DestroyEPIPEFix;
|
||||
}
|
||||
|
||||
socket.setNoDelay(socketOptions.disableNagle);
|
||||
|
@ -70,7 +71,7 @@ function apnSocket(connection, socketOptions, connected) {
|
|||
socket.setTimeout(socketOptions.connectionTimeout);
|
||||
}
|
||||
|
||||
debug("connecting to: ", socketOptions['host'] + ":" + socketOptions['port']);
|
||||
debug("connecting to: ", socketOptions.host + ":" + socketOptions.port);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
var extend = function(target) {
|
||||
Array.prototype.slice.call(arguments, 1).forEach(function(source) {
|
||||
for (var key in source) {
|
||||
|
@ -9,7 +11,7 @@ var extend = function(target) {
|
|||
};
|
||||
|
||||
var apnSetImmediate = function (method) {
|
||||
if('function' === typeof setImmediate) {
|
||||
if("function" === typeof setImmediate) {
|
||||
setImmediate(method);
|
||||
}
|
||||
else {
|
||||
|
|
19
package.json
19
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "apn",
|
||||
"description": "An interface to the Apple Push Notification service for Node.js",
|
||||
"version": "1.6.2",
|
||||
"version": "1.7.3",
|
||||
"author": "Andrew Naylor <argon@mkbot.net>",
|
||||
"contributors": [
|
||||
{
|
||||
|
@ -27,18 +27,25 @@
|
|||
"url": "https://github.com/argon/node-apn.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"q": "1.x"
|
||||
"node-forge": "^0.6.20",
|
||||
"q": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "2.x",
|
||||
"chai-as-promised": "*",
|
||||
"mocha": "*",
|
||||
"chai": "*",
|
||||
"chai-as-promised": "*"
|
||||
"rewire": "^2.3.0",
|
||||
"sinon": "^1.12.2",
|
||||
"sinon-chai": "^2.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -w"
|
||||
"test": "node_modules/.bin/mocha"
|
||||
},
|
||||
"jshintConfig": {
|
||||
"node": true
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6.14"
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"expr": true,
|
||||
"strict": false,
|
||||
"mocha": true,
|
||||
"node": true,
|
||||
"globals": {
|
||||
"expect": true
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
var apn = require("../");
|
||||
var fs = require("fs");
|
||||
var rewire = require("rewire");
|
||||
var Connection = rewire("../lib/connection");
|
||||
|
||||
var events = require("events");
|
||||
var sinon = require("sinon");
|
||||
var Q = require("q");
|
||||
|
||||
describe("Connection", function() {
|
||||
describe('constructor', function () {
|
||||
describe("constructor", function () {
|
||||
var originalEnv;
|
||||
|
||||
before(function() {
|
||||
|
@ -11,40 +15,441 @@ describe("Connection", function() {
|
|||
|
||||
after(function() {
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
})
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
process.env.NODE_ENV = "";
|
||||
})
|
||||
});
|
||||
|
||||
// Issue #50
|
||||
it("should use gateway.sandbox.push.apple.com as the default connection address", function () {
|
||||
apn.Connection().options.address.should.equal("gateway.sandbox.push.apple.com");
|
||||
expect(Connection().options.address).to.equal("gateway.sandbox.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use gateway.push.apple.com when NODE_ENV=production", function () {
|
||||
process.env.NODE_ENV = "production";
|
||||
apn.Connection().options.address.should.equal("gateway.push.apple.com");
|
||||
expect(Connection().options.address).to.equal("gateway.push.apple.com");
|
||||
});
|
||||
|
||||
it("should give precedence to production flag over NODE_ENV=production", function () {
|
||||
process.env.NODE_ENV = "production";
|
||||
apn.Connection({ production: false }).options.address.should.equal("gateway.sandbox.push.apple.com");
|
||||
expect(Connection({ production: false }).options.address).to.equal("gateway.sandbox.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use gateway.push.apple.com when production:true", function () {
|
||||
apn.Connection({production:true}).options.address.should.equal("gateway.push.apple.com");
|
||||
expect(Connection({production:true}).options.address).to.equal("gateway.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use a custom address when passed", function () {
|
||||
apn.Connection({address: "testaddress"}).options.address.should.equal("testaddress");
|
||||
expect(Connection({address: "testaddress"}).options.address).to.equal("testaddress");
|
||||
});
|
||||
|
||||
describe("address is passed", function() {
|
||||
it("sets production to true when using production address", function() {
|
||||
expect(Connection({address: "gateway.push.apple.com"}).options.production).to.be.true;
|
||||
});
|
||||
|
||||
it("sets production to false when using sandbox address", function() {
|
||||
process.env.NODE_ENV = "production";
|
||||
expect(Connection({address: "gateway.sandbox.push.apple.com"}).options.production).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#initialize', function () {
|
||||
it("should be fulfilled", function () {
|
||||
return apn.Connection({ pfx: "test/support/initializeTest.pfx" })
|
||||
.initialize().should.be.fulfilled;
|
||||
describe("#initialize", function () {
|
||||
var loadStub, parseStub, validateStub, removeStubs;
|
||||
beforeEach(function() {
|
||||
loadStub = sinon.stub();
|
||||
loadStub.displayName = "loadCredentials";
|
||||
|
||||
parseStub = sinon.stub();
|
||||
parseStub.displayName = "parseCredentials";
|
||||
|
||||
validateStub = sinon.stub();
|
||||
validateStub.displayName = "validateCredentials";
|
||||
|
||||
removeStubs = Connection.__set__({
|
||||
"loadCredentials": loadStub,
|
||||
"parseCredentials": parseStub,
|
||||
"validateCredentials": validateStub,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
removeStubs();
|
||||
});
|
||||
|
||||
it("only loads credentials once", function() {
|
||||
loadStub.returns(Q({}));
|
||||
|
||||
var connection = Connection();
|
||||
connection.initialize();
|
||||
connection.initialize();
|
||||
expect(loadStub).to.be.calledOnce;
|
||||
});
|
||||
|
||||
describe("with valid credentials", function() {
|
||||
var initialization;
|
||||
var testOptions = {
|
||||
pfx: "myCredentials.pfx", cert: "myCert.pem", key: "myKey.pem", ca: "myCa.pem",
|
||||
passphrase: "apntest", production: true
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadStub.withArgs(sinon.match(function(v) {
|
||||
return v.pfx === "myCredentials.pfx" && v.cert === "myCert.pem" && v.key === "myKey.pem" &&
|
||||
v.ca === "myCa.pem" && v.passphrase === "apntest";
|
||||
})).returns(Q({ pfx: "myPfxData", cert: "myCertData", key: "myKeyData", ca: ["myCaData"], passphrase: "apntest" }));
|
||||
|
||||
parseStub.returnsArg(0);
|
||||
|
||||
initialization = Connection(testOptions).initialize();
|
||||
});
|
||||
|
||||
it("should be fulfilled", function () {
|
||||
return expect(initialization).to.be.fulfilled;
|
||||
});
|
||||
|
||||
describe("the validation stage", function() {
|
||||
it("is called once", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
it("is passed the production flag", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("production", true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("passed credentials", function() {
|
||||
it("contains the PFX data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("pfx", "myPfxData");
|
||||
});
|
||||
});
|
||||
|
||||
it("contains the key data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("key", "myKeyData");
|
||||
});
|
||||
});
|
||||
|
||||
it("contains the certificate data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("cert", "myCertData");
|
||||
});
|
||||
});
|
||||
|
||||
it("includes passphrase", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("passphrase", "apntest");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolution value", function() {
|
||||
it("contains the PFX data", function() {
|
||||
return expect(initialization).to.eventually.have.property("pfx", "myPfxData");
|
||||
});
|
||||
|
||||
it("contains the key data", function() {
|
||||
return expect(initialization).to.eventually.have.property("key", "myKeyData");
|
||||
});
|
||||
|
||||
it("contains the certificate data", function() {
|
||||
return expect(initialization).to.eventually.have.property("cert", "myCertData");
|
||||
});
|
||||
|
||||
it("contains the CA data", function() {
|
||||
return expect(initialization).to.eventually.have.deep.property("ca[0]", "myCaData");
|
||||
});
|
||||
|
||||
it("includes passphrase", function() {
|
||||
return expect(initialization).to.eventually.have.property("passphrase", "apntest");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential file cannot be parsed", function() {
|
||||
beforeEach(function() {
|
||||
loadStub.returns(Q({ cert: "myCertData", key: "myKeyData" }));
|
||||
parseStub.throws(new Error("unable to parse key"));
|
||||
});
|
||||
|
||||
it("should resolve with the credentials", function() {
|
||||
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
return expect(initialization).to.become({ cert: "myCertData", key: "myKeyData" });
|
||||
});
|
||||
|
||||
it("should log an error", function() {
|
||||
var debug = sinon.spy();
|
||||
var reset = Connection.__set__("debug", debug);
|
||||
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
|
||||
return initialization.finally(function() {
|
||||
reset();
|
||||
expect(debug).to.be.calledWith(sinon.match(function(err) {
|
||||
return err.message ? err.message.match(/unable to parse key/) : false;
|
||||
}, "\"unable to parse key\""));
|
||||
});
|
||||
});
|
||||
|
||||
it("should not attempt to validate", function() {
|
||||
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential validation fails", function() {
|
||||
it("should be rejected", function() {
|
||||
loadStub.returns(Q({ cert: "myCertData", key: "myMismatchedKeyData" }));
|
||||
parseStub.returnsArg(0);
|
||||
validateStub.throws(new Error("certificate and key do not match"));
|
||||
|
||||
var initialization = Connection({ cert: "myCert.pem", key: "myMistmatchedKey.pem" }).initialize();
|
||||
return expect(initialization).to.eventually.be.rejectedWith(/certificate and key do not match/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential file cannot be loaded", function() {
|
||||
it("should be rejected", function() {
|
||||
loadStub.returns(Q.reject(new Error("ENOENT, no such file or directory")));
|
||||
|
||||
var initialization = Connection({ cert: "noSuchFile.pem", key: "myKey.pem" }).initialize();
|
||||
return expect(initialization).to.eventually.be.rejectedWith("ENOENT, no such file or directory");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect", function() {
|
||||
var socketDouble, socketStub, removeSocketStub;
|
||||
|
||||
before(function() {
|
||||
var initializeStub = sinon.stub(Connection.prototype, "initialize");
|
||||
initializeStub.returns(Q({
|
||||
pfx: "pfxData",
|
||||
key: "keyData",
|
||||
cert: "certData",
|
||||
ca: ["caData1", "caData2"],
|
||||
passphrase: "apntest" }));
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
socketDouble = new events.EventEmitter();
|
||||
socketDouble.end = sinon.spy();
|
||||
|
||||
socketStub = sinon.stub();
|
||||
socketStub.callsArg(2);
|
||||
socketStub.returns(socketDouble);
|
||||
|
||||
removeSocketStub = Connection.__set__("createSocket", socketStub);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
socketDouble.removeAllListeners();
|
||||
removeSocketStub();
|
||||
});
|
||||
|
||||
it("initializes the module", function(done) {
|
||||
var connection = Connection({ pfx: "myCredentials.pfx" });
|
||||
return connection.connect().finally(function() {
|
||||
expect(connection.initialize).to.have.been.calledOnce;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with valid credentials", function() {
|
||||
it("resolves", function() {
|
||||
var connection = Connection({
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
});
|
||||
return expect(connection.connect()).to.be.fulfilled;
|
||||
});
|
||||
|
||||
describe("the call to create socket", function() {
|
||||
var connect;
|
||||
|
||||
it("passes PFX data", function() {
|
||||
connect = Connection({
|
||||
pfx: "myCredentials.pfx",
|
||||
passphrase: "apntest"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.pfx).to.equal("pfxData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the passphrase", function() {
|
||||
connect = Connection({
|
||||
passphrase: "apntest",
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.passphrase).to.equal("apntest");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the cert", function() {
|
||||
connect = Connection({
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.cert).to.equal("certData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the key", function() {
|
||||
connect = Connection({
|
||||
cert: "test/credentials/support/cert.pem",
|
||||
key: "test/credentials/support/key.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.key).to.equal("keyData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the ca certificates", function() {
|
||||
connect = Connection({
|
||||
cert: "test/credentials/support/cert.pem",
|
||||
key: "test/credentials/support/key.pem",
|
||||
ca: [ "test/credentials/support/issuerCert.pem" ]
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.ca[0]).to.equal("caData1");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("intialization failure", function() {
|
||||
it("is rejected", function() {
|
||||
var connection = Connection({ pfx: "a-non-existant-file-which-really-shouldnt-exist.pfx" });
|
||||
connection.on("error", function() {});
|
||||
connection.initialize = sinon.stub();
|
||||
|
||||
connection.initialize.returns(Q.reject(new Error("initialize failed")));
|
||||
|
||||
return expect(connection.connect()).to.be.rejectedWith("initialize failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("timeout option", function() {
|
||||
var clock, timeoutRestore;
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers();
|
||||
timeoutRestore = Connection.__set__({
|
||||
"setTimeout": clock.setTimeout,
|
||||
"clearTimeout": clock.clearTimeout
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
timeoutRestore();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it("ends the socket when connection takes too long", function() {
|
||||
var connection = Connection({connectTimeout: 3000}).connect();
|
||||
socketStub.onCall(0).returns(socketDouble);
|
||||
|
||||
process.nextTick(function(){
|
||||
clock.tick(5000);
|
||||
});
|
||||
|
||||
return connection.then(function() {
|
||||
throw "connection did not time out";
|
||||
}, function() {
|
||||
expect(socketDouble.end).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it("does not end the socket when the connnection succeeds", function() {
|
||||
var connection = Connection({connectTimeout: 3000}).connect();
|
||||
|
||||
return connection.then(function() {
|
||||
clock.tick(5000);
|
||||
expect(socketDouble.end).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it("does not end the socket when the connection fails", function() {
|
||||
var connection = Connection({connectTimeout: 3000}).connect();
|
||||
socketStub.onCall(0).returns(socketDouble);
|
||||
|
||||
process.nextTick(function() {
|
||||
socketDouble.emit("close");
|
||||
});
|
||||
|
||||
return connection.then(function() {
|
||||
throw "connection should have failed";
|
||||
}, function() {
|
||||
clock.tick(5000);
|
||||
expect(socketDouble.end).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it("disabled", function() {
|
||||
var connection = Connection({connectTimeout: 0}).connect();
|
||||
socketStub.onCall(0).returns(socketDouble);
|
||||
|
||||
process.nextTick(function() {
|
||||
clock.tick(100000);
|
||||
socketDouble.emit("close");
|
||||
});
|
||||
|
||||
return connection.then(function() {
|
||||
throw "connection should have failed";
|
||||
}, function() {
|
||||
expect(socketDouble.end).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validNotification", function() {
|
||||
describe("notification is shorter than max allowed", function() {
|
||||
it("returns true", function() {
|
||||
var connection = Connection();
|
||||
var notification = { length: function() { return 128; }};
|
||||
expect(connection.validNotification(notification)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("notification is the maximum length", function() {
|
||||
it("returns true", function() {
|
||||
var connection = Connection();
|
||||
var notification = { length: function() { return 2048; }};
|
||||
expect(connection.validNotification(notification)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("notification too long", function() {
|
||||
it("returns false", function() {
|
||||
var connection = Connection();
|
||||
var notification = { length: function() { return 2176; }};
|
||||
expect(connection.validNotification(notification)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("VoIP flag set", function() {
|
||||
it("allows longer payload", function() {
|
||||
var connection = Connection({"voip": true});
|
||||
var notification = { length: function() { return 4096; }};
|
||||
expect(connection.validNotification(notification)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,108 +0,0 @@
|
|||
var Credentials = require("../lib/credentials");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("Credentials", function() {
|
||||
var pfx, cert, key, ca;
|
||||
before(function () {
|
||||
pfx = fs.readFileSync("test/support/initializeTest.pfx");
|
||||
cert = fs.readFileSync("test/support/initializeTest.crt");
|
||||
key = fs.readFileSync("test/support/initializeTest.key");
|
||||
});
|
||||
|
||||
it("should eventually load a pfx file from disk", function () {
|
||||
return Credentials({ pfx: "test/support/initializeTest.pfx" })
|
||||
.get(0).post("toString")
|
||||
.should.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide pfx data from memory", function () {
|
||||
return Credentials({ pfx: pfx }).get(0).post("toString")
|
||||
.should.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide pfx data explicitly passed in pfxData parameter", function () {
|
||||
return Credentials({ pfxData: pfx }).get(0).post("toString")
|
||||
.should.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a certificate from disk", function () {
|
||||
return Credentials({ cert: "test/support/initializeTest.crt", key: null})
|
||||
.get(1).post("toString")
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a certificate from a Buffer", function () {
|
||||
return Credentials({ cert: cert, key: null})
|
||||
.get(1).post("toString")
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a certificate from a String", function () {
|
||||
return Credentials({ cert: cert.toString(), key: null})
|
||||
.get(1)
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide certificate data explicitly passed in the certData parameter", function () {
|
||||
return Credentials({ certData: cert, key: null})
|
||||
.get(1).post("toString")
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a key from disk", function () {
|
||||
return Credentials({ cert: null, key: "test/support/initializeTest.key"})
|
||||
.get(2).post("toString")
|
||||
.should.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a key from a Buffer", function () {
|
||||
return Credentials({ cert: null, key: key})
|
||||
.get(2).post("toString")
|
||||
.should.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a key from a String", function () {
|
||||
return Credentials({ cert: null, key: key.toString()})
|
||||
.get(2)
|
||||
.should.eventually.equal(key.toString());
|
||||
})
|
||||
|
||||
it("should eventually provide key data explicitly passed in the keyData parameter", function () {
|
||||
return Credentials({ cert: null, keyData: key})
|
||||
.get(2).post("toString")
|
||||
.should.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a single CA certificate from disk", function () {
|
||||
return Credentials({ cert: null, key: null, ca: "test/support/initializeTest.crt" })
|
||||
.get(3).get(0).post("toString")
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a single CA certificate from a Buffer", function () {
|
||||
return Credentials({ cert: null, key: null, ca: cert })
|
||||
.get(3).get(0).post("toString")
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a single CA certificate from a String", function () {
|
||||
return Credentials({ cert: null, key: null, ca: cert.toString() })
|
||||
.get(3).get(0)
|
||||
.should.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually load an array of CA certificates", function (done) {
|
||||
Credentials({ cert: null, key: null, ca: ["test/support/initializeTest.crt", cert, cert.toString()] })
|
||||
.get(3).spread(function(cert1, cert2, cert3) {
|
||||
var certString = cert.toString();
|
||||
if (cert1.toString() == certString &&
|
||||
cert2.toString() == certString &&
|
||||
cert3.toString() == certString) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
done(new Error("provided certificates did not match"));
|
||||
}
|
||||
}, done);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
var APNCertificate = require("../../lib/credentials/APNCertificate");
|
||||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
var forge = require("node-forge");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("APNCertificate", function() {
|
||||
var certPem;
|
||||
before(function() {
|
||||
certPem = fs.readFileSync("test/credentials/support/cert.pem");
|
||||
});
|
||||
|
||||
var cert;
|
||||
beforeEach(function() {
|
||||
cert = forge.pki.certificateFromPem(certPem.toString());
|
||||
});
|
||||
|
||||
describe("accepts a Certificate object", function() {
|
||||
it("does not throw", function() {
|
||||
expect(function() {
|
||||
new APNCertificate(cert);
|
||||
}).to.not.throw(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws", function() {
|
||||
it("missing public key", function() {
|
||||
delete cert.publicKey;
|
||||
|
||||
expect(function() {
|
||||
new APNCertificate(cert);
|
||||
}).to.throw("certificate object is invalid");
|
||||
});
|
||||
|
||||
it("missing validity", function() {
|
||||
delete cert.validity;
|
||||
|
||||
expect(function() {
|
||||
new APNCertificate(cert);
|
||||
}).to.throw("certificate object is invalid");
|
||||
});
|
||||
|
||||
it("missing subject", function() {
|
||||
delete cert.subject;
|
||||
|
||||
expect(function() {
|
||||
new APNCertificate(cert);
|
||||
}).to.throw("certificate object is invalid");
|
||||
});
|
||||
});
|
||||
|
||||
describe("key", function() {
|
||||
it("returns an APNKey", function() {
|
||||
expect(new APNCertificate(cert).key()).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("returns the the certificates public key", function() {
|
||||
expect(new APNCertificate(cert).key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validity", function() {
|
||||
it("returns an object containing notBefore", function() {
|
||||
expect(new APNCertificate(cert).validity())
|
||||
.to.have.property("notBefore")
|
||||
.and
|
||||
.to.eql(new Date("2015-01-01T00:00:00"));
|
||||
});
|
||||
|
||||
it("returns an object containing notAfter", function() {
|
||||
expect(new APNCertificate(cert).validity())
|
||||
.to.have.property("notAfter")
|
||||
.and
|
||||
.to.eql(new Date("2025-01-01T00:00:00"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("environment", function() {
|
||||
describe("development certificate", function() {
|
||||
it("sandbox flag", function() {
|
||||
expect(new APNCertificate(cert).environment().sandbox).to.be.true;
|
||||
});
|
||||
|
||||
it("production flag", function() {
|
||||
expect(new APNCertificate(cert).environment().production).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("production certificate", function() {
|
||||
var productionCertPem, productionCert;
|
||||
before(function() {
|
||||
productionCertPem = fs.readFileSync("test/credentials/support/certProduction.pem");
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
productionCert = forge.pki.certificateFromPem(productionCertPem.toString());
|
||||
});
|
||||
|
||||
it("sandbox flag", function() {
|
||||
expect(new APNCertificate(productionCert).environment().sandbox).to.be.false;
|
||||
});
|
||||
|
||||
it("production flag", function() {
|
||||
expect(new APNCertificate(productionCert).environment().production).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
var forge = require("node-forge");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("APNKey", function() {
|
||||
it("initialises with a node-forge public key", function() {
|
||||
expect(new APNKey({ n: 12345, e: 65536})).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
describe("throws", function() {
|
||||
it("missing modulus", function() {
|
||||
expect(function() {
|
||||
new APNKey({ e: 65536 });
|
||||
}).to.throw("key is not a valid public key");
|
||||
});
|
||||
|
||||
it("missing exponent", function() {
|
||||
expect(function() {
|
||||
new APNKey({ n: 12345 });
|
||||
}).to.throw("key is not a valid public key");
|
||||
});
|
||||
|
||||
it("undefined", function() {
|
||||
expect(function() {
|
||||
new APNKey();
|
||||
}).to.throw("key is not a valid public key");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fingerprint", function() {
|
||||
it("returns the fingerprint of the public key", function() {
|
||||
var keyPem = fs.readFileSync("test/credentials/support/key.pem");
|
||||
var apnKey = new APNKey(forge.pki.decryptRsaPrivateKey(keyPem));
|
||||
expect(apnKey.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
var loadCredentials = require("../../lib/credentials/load");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("loadCredentials", function() {
|
||||
var pfx, cert, key;
|
||||
before(function () {
|
||||
pfx = fs.readFileSync("test/support/initializeTest.pfx");
|
||||
cert = fs.readFileSync("test/support/initializeTest.crt");
|
||||
key = fs.readFileSync("test/support/initializeTest.key");
|
||||
});
|
||||
|
||||
it("should eventually load a pfx file from disk", function () {
|
||||
return expect(loadCredentials({ pfx: "test/support/initializeTest.pfx" })
|
||||
.get("pfx").post("toString"))
|
||||
.to.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide pfx data from memory", function () {
|
||||
return expect(loadCredentials({ pfx: pfx }).get("pfx").post("toString"))
|
||||
.to.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide pfx data explicitly passed in pfxData parameter", function () {
|
||||
return expect(loadCredentials({ pfxData: pfx }).get("pfx").post("toString"))
|
||||
.to.eventually.equal(pfx.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a certificate from disk", function () {
|
||||
return expect(loadCredentials({ cert: "test/support/initializeTest.crt", key: null})
|
||||
.get("cert").post("toString"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a certificate from a Buffer", function () {
|
||||
return expect(loadCredentials({ cert: cert, key: null})
|
||||
.get("cert").post("toString"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a certificate from a String", function () {
|
||||
return expect(loadCredentials({ cert: cert.toString(), key: null})
|
||||
.get("cert"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide certificate data explicitly passed in the certData parameter", function () {
|
||||
return expect(loadCredentials({ certData: cert, key: null})
|
||||
.get("cert").post("toString"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a key from disk", function () {
|
||||
return expect(loadCredentials({ cert: null, key: "test/support/initializeTest.key"})
|
||||
.get("key").post("toString"))
|
||||
.to.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a key from a Buffer", function () {
|
||||
return expect(loadCredentials({ cert: null, key: key})
|
||||
.get("key").post("toString"))
|
||||
.to.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a key from a String", function () {
|
||||
return expect(loadCredentials({ cert: null, key: key.toString()})
|
||||
.get("key"))
|
||||
.to.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide key data explicitly passed in the keyData parameter", function () {
|
||||
return expect(loadCredentials({ cert: null, keyData: key})
|
||||
.get("key").post("toString"))
|
||||
.to.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("should eventually load a single CA certificate from disk", function () {
|
||||
return expect(loadCredentials({ cert: null, key: null, ca: "test/support/initializeTest.crt" })
|
||||
.get("ca").get(0).post("toString"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a single CA certificate from a Buffer", function () {
|
||||
return expect(loadCredentials({ cert: null, key: null, ca: cert })
|
||||
.get("ca").get(0).post("toString"))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually provide a single CA certificate from a String", function () {
|
||||
return expect(loadCredentials({ cert: null, key: null, ca: cert.toString() })
|
||||
.get("ca").get(0))
|
||||
.to.eventually.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("should eventually load an array of CA certificates", function (done) {
|
||||
loadCredentials({ cert: null, key: null, ca: ["test/support/initializeTest.crt", cert, cert.toString()] })
|
||||
.get("ca").spread(function(cert1, cert2, cert3) {
|
||||
var certString = cert.toString();
|
||||
if (cert1.toString() === certString &&
|
||||
cert2.toString() === certString &&
|
||||
cert3.toString() === certString) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
done(new Error("provided certificates did not match"));
|
||||
}
|
||||
}, done);
|
||||
});
|
||||
|
||||
it("returns undefined if no CA values are specified", function() {
|
||||
return expect(loadCredentials({ cert: null, key: null, ca: null}).get("ca")).to.eventually.be.undefined;
|
||||
});
|
||||
|
||||
it("should inclue the passphrase in the resolved value", function() {
|
||||
return expect(loadCredentials({ passphrase: "apntest" }).get("passphrase"))
|
||||
.to.eventually.equal("apntest");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,131 @@
|
|||
var sinon = require("sinon");
|
||||
var rewire = require("rewire");
|
||||
var parseCredentials = rewire("../../lib/credentials/parse");
|
||||
|
||||
var APNCertificate = require("../../lib/credentials/APNCertificate");
|
||||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
|
||||
describe("parseCredentials", function() {
|
||||
var reset;
|
||||
var pkcs12Spy, pemKeySpy, pemCertSpy;
|
||||
|
||||
var pfxKey = new APNKey({n: 1, e: 1 });
|
||||
var pfxCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} });
|
||||
|
||||
var pemKey = new APNKey({n: 2, e: 1 });
|
||||
var pemCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} });
|
||||
|
||||
beforeEach(function() {
|
||||
pkcs12Spy = sinon.stub();
|
||||
|
||||
pemKeySpy = sinon.stub();
|
||||
pemKeySpy.withArgs("pemkey").returns(pemKey);
|
||||
|
||||
pemCertSpy = sinon.stub();
|
||||
pemCertSpy.withArgs("pemcert").returns(pemCert);
|
||||
|
||||
reset = parseCredentials.__set__({
|
||||
"parsePkcs12": pkcs12Spy,
|
||||
"parsePemKey": pemKeySpy,
|
||||
"parsePemCert": pemCertSpy,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
reset();
|
||||
});
|
||||
|
||||
describe("with PFX file", function() {
|
||||
it("returns the parsed key", function() {
|
||||
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
|
||||
|
||||
var parsed = parseCredentials({ pfx: "pfxData" });
|
||||
expect(parsed.key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("returns the parsed certificates", function() {
|
||||
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
|
||||
|
||||
var parsed = parseCredentials({ pfx: "pfxData" });
|
||||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate);
|
||||
});
|
||||
|
||||
describe("having passphrase", function() {
|
||||
beforeEach(function() {
|
||||
pkcs12Spy.withArgs("encryptedPfxData", "apntest").returns({ key: pfxKey, certificates: [pfxCert] });
|
||||
pkcs12Spy.withArgs("encryptedPfxData", sinon.match.any).throws(new Error("unable to read credentials, incorrect passphrase"));
|
||||
});
|
||||
|
||||
it("returns the parsed key", function() {
|
||||
var parsed = parseCredentials({ pfx: "encryptedPfxData", passphrase: "apntest" });
|
||||
expect(parsed.key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("throws when passphrase is incorrect", function() {
|
||||
expect(function() {
|
||||
parseCredentials({ pfx: "encryptedPfxData", passphrase: "incorrectpassphrase" });
|
||||
}).to.throw(/incorrect passphrase/);
|
||||
});
|
||||
|
||||
it("throws when passphrase is not supplied", function() {
|
||||
expect(function() {
|
||||
parseCredentials({ pfx: "encryptedPfxData" });
|
||||
}).to.throw(/incorrect passphrase/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with PEM key", function() {
|
||||
it("returns the parsed key", function() {
|
||||
pemKeySpy.withArgs("pemKeyData").returns(pemKey);
|
||||
|
||||
var parsed = parseCredentials({ key: "pemKeyData" });
|
||||
expect(parsed.key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
describe("having passphrase", function() {
|
||||
beforeEach(function() {
|
||||
pemKeySpy.withArgs("encryptedPemKeyData", "apntest").returns(pemKey);
|
||||
pemKeySpy.withArgs("encryptedPemKeyData", sinon.match.any).throws(new Error("unable to load key, incorrect passphrase"));
|
||||
});
|
||||
|
||||
it("returns the parsed key", function() {
|
||||
var parsed = parseCredentials({ key: "encryptedPemKeyData", passphrase: "apntest" });
|
||||
expect(parsed.key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("throws when passphrase is incorrect", function() {
|
||||
expect(function() {
|
||||
parseCredentials({ key: "encryptedPemKeyData", passphrase: "incorrectpassphrase" });
|
||||
}).to.throw(/incorrect passphrase/);
|
||||
});
|
||||
|
||||
it("throws when passphrase is not supplied", function() {
|
||||
expect(function() {
|
||||
parseCredentials({ key: "encryptedPemKeyData" });
|
||||
}).to.throw(/incorrect passphrase/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with PEM certificate", function() {
|
||||
it("returns the parsed certificate", function() {
|
||||
pemCertSpy.withArgs("pemCertData").returns([pemCert]);
|
||||
|
||||
var parsed = parseCredentials({ cert: "pemCertData" });
|
||||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate);
|
||||
});
|
||||
});
|
||||
|
||||
describe("both PEM and PFX data is supplied", function() {
|
||||
it("it prefers PFX to PEM", function() {
|
||||
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
|
||||
pemKeySpy.withArgs("pemKeyData").returns(pemKey);
|
||||
pemCertSpy.withArgs("pemCertData").returns([pemCert]);
|
||||
|
||||
var parsed = parseCredentials({ pfx: "pfxData", key: "pemKeyData", cert: "pemCertData"});
|
||||
expect(parsed.key).to.equal(pfxKey);
|
||||
expect(parsed.certificates[0]).to.equal(pfxCert);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
var parsePemCertificate = require("../../lib/credentials/parsePemCertificate");
|
||||
var APNCertificate = require("../../lib/credentials/APNCertificate");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("parsePemCertificate", function() {
|
||||
describe("with PEM certificate", function() {
|
||||
var cert, certProperties;
|
||||
before(function() {
|
||||
cert = fs.readFileSync("test/credentials/support/cert.pem");
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
certProperties = parsePemCertificate(cert);
|
||||
});
|
||||
|
||||
describe("return value", function() {
|
||||
it("is an array", function() {
|
||||
expect(certProperties).to.be.an("array");
|
||||
});
|
||||
|
||||
it("contains one element", function() {
|
||||
expect(certProperties).to.have.length(1);
|
||||
});
|
||||
|
||||
describe("certificate [0]", function() {
|
||||
it("is an APNCertificate", function() {
|
||||
expect(certProperties[0]).to.be.an.instanceof(APNCertificate);
|
||||
});
|
||||
|
||||
it("has the correct fingerprint", function() {
|
||||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with PEM containing multiple certificates", function() {
|
||||
var cert, certProperties;
|
||||
before(function() {
|
||||
cert = fs.readFileSync("test/credentials/support/certIssuerKey.pem");
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
certProperties = parsePemCertificate(cert);
|
||||
});
|
||||
|
||||
it("returns the correct number of certificates", function() {
|
||||
expect(certProperties).to.have.length(2);
|
||||
});
|
||||
|
||||
describe("certificate [0]", function() {
|
||||
it("has the correct fingerprint", function() {
|
||||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
|
||||
describe("certificate [1]", function() {
|
||||
it("has the correct fingerprint", function() {
|
||||
expect(certProperties[1].key().fingerprint()).to.equal("ccff221d67cb3335649f9b4fbb311948af76f4b2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with a PKCS#12 file", function() {
|
||||
it("throws", function() {
|
||||
var pfx = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
|
||||
expect(function() {
|
||||
parsePemCertificate(pfx);
|
||||
}).to.throw("unable to parse certificate, not a valid PEM file");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with a key", function() {
|
||||
it("returns an empty array", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/key.pem");
|
||||
expect(parsePemCertificate(key)).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe("returns null", function() {
|
||||
it("for null", function() {
|
||||
expect(parsePemCertificate(null)).to.be.null;
|
||||
});
|
||||
|
||||
it("for undefined", function() {
|
||||
expect(parsePemCertificate(undefined)).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
var parsePemKey = require("../../lib/credentials/parsePemKey");
|
||||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("parsePemKey", function() {
|
||||
describe("returns APNKey", function() {
|
||||
describe("RSA key", function() {
|
||||
var key;
|
||||
beforeEach(function() {
|
||||
var keyData = fs.readFileSync("test/credentials/support/key.pem");
|
||||
key = parsePemKey(keyData);
|
||||
});
|
||||
|
||||
it("correct type", function() {
|
||||
expect(key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("with correct fingerprint", function() {
|
||||
expect(key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
|
||||
it("openssl-encrypted RSA key, correct password", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/keyEncrypted.pem");
|
||||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("PKCS#8 encrypted key, correct password", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem");
|
||||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("PEM containing certificates and key", function() {
|
||||
var certAndKey = fs.readFileSync("test/credentials/support/certKey.pem");
|
||||
expect(parsePemKey(certAndKey)).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws with", function() {
|
||||
it("PKCS#8 key (unsupported format)", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/keyPKCS8.pem");
|
||||
expect(function() {
|
||||
parsePemKey(key);
|
||||
}).to.throw("unable to parse key, unsupported format");
|
||||
});
|
||||
|
||||
it("RSA encrypted key, incorrect passphrase", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/keyEncrypted.pem");
|
||||
expect(function() {
|
||||
parsePemKey(key, "not-the-passphrase");
|
||||
}).to.throw("unable to parse key, incorrect passphrase");
|
||||
});
|
||||
|
||||
it("PKCS#8 encrypted key, incorrect passphrase", function() {
|
||||
var key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem");
|
||||
expect(function() {
|
||||
parsePemKey(key, "not-the-passphrase");
|
||||
}).to.throw("unable to parse key, incorrect passphrase");
|
||||
});
|
||||
|
||||
it("PEM certificate", function() {
|
||||
var cert = fs.readFileSync("test/credentials/support/cert.pem");
|
||||
expect(function() {
|
||||
parsePemKey(cert);
|
||||
}).to.throw("unable to parse key, no private key found");
|
||||
});
|
||||
|
||||
it("PKCS#12 file", function() {
|
||||
var pkcs12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
|
||||
expect(function() {
|
||||
parsePemKey(pkcs12);
|
||||
}).to.throw("unable to parse key, not a valid PEM file");
|
||||
});
|
||||
});
|
||||
|
||||
describe("multiple keys", function() {
|
||||
it("throws", function() {
|
||||
var keys = fs.readFileSync("test/credentials/support/multipleKeys.pem");
|
||||
expect(function() {
|
||||
parsePemKey(keys);
|
||||
}).to.throw("multiple keys found in PEM file");
|
||||
});
|
||||
});
|
||||
|
||||
describe("returns null", function() {
|
||||
it("for null", function() {
|
||||
expect(parsePemKey()).to.be.null;
|
||||
});
|
||||
|
||||
it("for undefined", function() {
|
||||
expect(parsePemKey()).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,120 @@
|
|||
var parsePkcs12 = require("../../lib/credentials/parsePkcs12");
|
||||
|
||||
var APNKey = require("../../lib/credentials/APNKey");
|
||||
var APNCertificate = require("../../lib/credentials/APNCertificate");
|
||||
|
||||
var fs = require("fs");
|
||||
|
||||
describe("parsePkcs12", function() {
|
||||
describe("with PKCS#12 data", function() {
|
||||
var p12, properties;
|
||||
describe("return value", function() {
|
||||
var credentials;
|
||||
before(function() {
|
||||
p12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
|
||||
credentials = parsePkcs12(p12);
|
||||
});
|
||||
|
||||
it("is an object", function() {
|
||||
expect(credentials).to.be.an("object");
|
||||
});
|
||||
|
||||
it("contains a private key", function() {
|
||||
expect(credentials).to.include.keys("key");
|
||||
});
|
||||
|
||||
describe("private key", function() {
|
||||
it("is an instance of APNKey", function() {
|
||||
expect(credentials.key).to.be.an.instanceof(APNKey);
|
||||
});
|
||||
|
||||
it("has the correct fingerprint", function() {
|
||||
expect(credentials.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
|
||||
it("contains a certificate chain", function() {
|
||||
expect(credentials).to.include.keys("certificates");
|
||||
});
|
||||
|
||||
describe("certificate chain", function() {
|
||||
it("is an array", function() {
|
||||
expect(credentials.certificates).to.be.an("array");
|
||||
});
|
||||
|
||||
it("contains the correct number of certificates", function() {
|
||||
expect(credentials.certificates.length).to.equal(2);
|
||||
});
|
||||
|
||||
it("contains APNCertificate objects", function() {
|
||||
var certificates = credentials.certificates;
|
||||
certificates.forEach(function(certificate) {
|
||||
expect(certificate).to.be.an.instanceof(APNCertificate);
|
||||
});
|
||||
});
|
||||
|
||||
it("contains certificates with the correct fingerprints", function() {
|
||||
var fingerprints = ["2d594c9861227dd22ba5ae37cc9354e9117a804d", "ccff221d67cb3335649f9b4fbb311948af76f4b2"];
|
||||
var certificates = credentials.certificates;
|
||||
certificates.forEach(function(certificate, index) {
|
||||
expect(certificate.key().fingerprint()).to.equal(fingerprints[index]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// OpenSSL exports keys having no passphrase as a C string with a \0 byte appended
|
||||
describe("having empty passphrase (OpenSSL-CLI-generated file)", function() {
|
||||
describe("return value", function() {
|
||||
it("has the correct key", function() {
|
||||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyOpenSSL.p12");
|
||||
properties = parsePkcs12(p12);
|
||||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with correct passphrase", function() {
|
||||
describe("return value", function() {
|
||||
it("has the correct key", function() {
|
||||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12");
|
||||
properties = parsePkcs12(p12, "apntest");
|
||||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("with incorrect passphrase", function() {
|
||||
it("throws", function() {
|
||||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12");
|
||||
expect(function() {
|
||||
parsePkcs12(p12, "notthepassphrase");
|
||||
}).to.throw("unable to parse credentials, incorrect passphrase");
|
||||
});
|
||||
});
|
||||
|
||||
// Unclear whether multiple keys in one PKCS#12 file can be distinguished
|
||||
// at present if there's more than one just throw a warning. Should also
|
||||
// do the same thing in apnKeyFromPem
|
||||
describe("multiple keys", function() {
|
||||
it("throws", function() {
|
||||
p12 = fs.readFileSync("test/credentials/support/multipleKeys.p12");
|
||||
expect(function() {
|
||||
parsePkcs12(p12);
|
||||
}).to.throw("multiple keys found in PFX/P12 file");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("PEM file", function() {
|
||||
it("throws", function() {
|
||||
var pem = fs.readFileSync("test/credentials/support/certKey.pem");
|
||||
expect(function() {
|
||||
parsePkcs12(pem);
|
||||
}).to.throw("unable to parse credentials, not a PFX/P12 file");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined for undefined", function() {
|
||||
expect(parsePkcs12()).to.be.undefined;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
var resolve = require("../../lib/credentials/resolve");
|
||||
var fs = require("fs");
|
||||
|
||||
describe("resolve", function() {
|
||||
var pfx, cert, key;
|
||||
before(function () {
|
||||
pfx = fs.readFileSync("test/support/initializeTest.pfx");
|
||||
cert = fs.readFileSync("test/support/initializeTest.crt");
|
||||
key = fs.readFileSync("test/support/initializeTest.key");
|
||||
});
|
||||
|
||||
it("returns PEM string as supplied", function() {
|
||||
expect(resolve(cert.toString()))
|
||||
.to.be.a("string")
|
||||
.and.to.equal(cert.toString());
|
||||
});
|
||||
|
||||
it("returns Buffer as supplied", function() {
|
||||
expect(resolve(pfx))
|
||||
.to.satisfy(Buffer.isBuffer)
|
||||
.and.to.equal(pfx);
|
||||
});
|
||||
|
||||
describe("with file path", function() {
|
||||
it("eventually returns a Buffer for valid path", function() {
|
||||
return expect(resolve("test/support/initializeTest.key"))
|
||||
.to.eventually.satisfy(Buffer.isBuffer);
|
||||
});
|
||||
|
||||
it("eventually returns contents for value path", function () {
|
||||
return expect(resolve("test/support/initializeTest.key")
|
||||
.post("toString")).to.eventually.equal(key.toString());
|
||||
});
|
||||
|
||||
it("is eventually rejected for invalid path", function() {
|
||||
return expect(resolve("test/support/fail/initializeTest.key"))
|
||||
.to.eventually.be.rejected;
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null/undefined as supplied", function() {
|
||||
expect(resolve(null)).to.be.null;
|
||||
expect(resolve()).to.be.undefined;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
|
||||
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
|
||||
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
|
||||
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
|
||||
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
|
||||
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
|
||||
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
|
||||
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
|
||||
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
|
||||
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
|
||||
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
|
||||
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
|
||||
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
|
||||
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
|
||||
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
|
||||
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
|
||||
-----END CERTIFICATE-----
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,44 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
|
||||
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
|
||||
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
|
||||
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
|
||||
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
|
||||
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
|
||||
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
|
||||
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
|
||||
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
|
||||
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
|
||||
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
|
||||
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
|
||||
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
|
||||
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
|
||||
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
|
||||
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDiTCCAnGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE0MTIzMTAwMDAwMFoXDTMwMDEwMTAwMDAwMFow
|
||||
TjELMAkGA1UEBhMCR0IxETAPBgNVBAsTCG5vZGUtYXBuMSwwKgYDVQQDEyNub2Rl
|
||||
LWFwbiBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBALWYd1+Uli9q1P9H/g812ZFsOtrYZIrkYxim1VcMGBDt
|
||||
knULBH5ZhRNV0Cymh6r4RmmNwDQ4wvQoVVZ6rRt2SL1VtAPRq0rtXMeOiouVkkEc
|
||||
Ly9ssvHY6SzNM4no4B1i+Eg028Nhm0aGyx1NhLHWBHz69C0PxCQrDq+N5efF8B4N
|
||||
DR89R3PPpGdXMlgD8aMlUMOOksXJMZUwiFmbclMhb5mdnvqoX3uyi0AzFEkgtjI5
|
||||
GsxRIh8ZKXAXRMk4Ub5L7sYYVHiNMc2pHB99vngxveDNjZBbZD/qXYZyWEGcRTmU
|
||||
Ht1UPWKxm0wgFzq4zRAlGM9sXKy3GaSyeJdfh2KU6xMCAwEAAaNyMHAwCwYDVR0P
|
||||
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMz/Ih1nyzM1ZJ+bT7sx
|
||||
GUivdvSyMB8GA1UdIwQYMBaAFMz/Ih1nyzM1ZJ+bT7sxGUivdvSyMBAGCiqGSIb3
|
||||
Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQCfi/mKQ4MBPtUNMYAumiG8VpVO
|
||||
I1g25Tp5BjnT23lVjn733mD7grpxVZ+wVvRv0+cPsw5sIwF+WvW+J2BZQB9v5+Ph
|
||||
k3UXZkG9rkx/f4yuzsrW0sX70HtVHt7uQeFAWE6mZpRu6aTJqqNtpsi0YhhNSLMG
|
||||
suoLwe8X39W3GIDiMy5WisLaR7MgvcRW5Ud7JtWgm5HHOShHN5CtawmJP94hPW4W
|
||||
VsS8/vHzSKqQe9AFtb1Pk0TR0A5gTwVOzi9cw3qqvtSP2Jhhab79D2/JoiII16Vi
|
||||
CVQTczcY5NpIQNhrO/w5SxjbigU9ngO4zprl2KYURxSDNbBSsXmyCU2Y71HA
|
||||
-----END CERTIFICATE-----
|
||||
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -0,0 +1,49 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
|
||||
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
|
||||
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
|
||||
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
|
||||
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
|
||||
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
|
||||
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
|
||||
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
|
||||
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
|
||||
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
|
||||
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
|
||||
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
|
||||
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
|
||||
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
|
||||
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
|
||||
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
|
||||
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
|
||||
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
|
||||
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
|
||||
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
|
||||
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
|
||||
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
|
||||
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
|
||||
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
|
||||
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
|
||||
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
|
||||
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
|
||||
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
|
||||
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
|
||||
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
|
||||
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
|
||||
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
|
||||
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
|
||||
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
|
||||
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
|
||||
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
|
||||
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
|
||||
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
|
||||
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
|
||||
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,50 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
|
||||
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
|
||||
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
|
||||
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
|
||||
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
|
||||
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
|
||||
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
|
||||
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
|
||||
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
|
||||
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
|
||||
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
|
||||
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
|
||||
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
|
||||
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
|
||||
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
|
||||
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
|
||||
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
|
||||
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
|
||||
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
|
||||
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
|
||||
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
|
||||
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
|
||||
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
|
||||
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
|
||||
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
|
||||
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
|
||||
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
|
||||
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
|
||||
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
|
||||
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
|
||||
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
|
||||
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
|
||||
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
|
||||
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
|
||||
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
|
||||
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
|
||||
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
|
||||
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
|
||||
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
|
||||
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
|
||||
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
|
||||
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
|
||||
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
|
||||
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
|
||||
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
|
||||
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
|
||||
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
|
||||
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
|
||||
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
|
||||
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
|
||||
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
|
||||
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
|
||||
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
|
||||
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
|
||||
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
|
||||
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
|
||||
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
|
||||
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
|
||||
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
|
||||
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
|
||||
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
|
||||
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
|
||||
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
|
||||
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
|
||||
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
|
||||
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
|
||||
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
|
||||
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
|
||||
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
|
||||
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
|
||||
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
|
||||
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
|
||||
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
|
||||
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
|
||||
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
|
||||
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
|
||||
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
|
||||
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
|
||||
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-CBC,F52B6FE328BBB66A
|
||||
|
||||
GAWRc5S4nLqm30eTG0XoZ0qjtiIyN8CQomNEw1nmzf+FEq0qBsnkCd3BBQ6nHhQU
|
||||
3sV4nUxcf90bT1P/qs3dJSs6wssG5XYn9IsXJ5a4KmLrSw9SmeSD08hu+uLF0cNz
|
||||
UBvnvLoaCX1JiSR5SnLs3lofdHMvz8xD3MHTRlCi/x5vbeocgZaIdLdbscJrYI+O
|
||||
cxsM5US/ryOAu4qnTYSIR1NOirKMXQV0AfSBdNKSL4uQ95kKwRPi01cuFf6Gvyvz
|
||||
W2PRSz6ddgR32raZ6zmMZmmcvzFSn6772MK3K5OjlC750MLazRvvwPlo0omc1zvu
|
||||
HqRGaDhaTgGufR5iWHbbbWE/WDBcTSdG6TjCGYoQ3t1Ke56NP4DF8hQL2eSwa5ph
|
||||
8KlDJasDO9b8FaZyD7dyoqPXMVyceyXRCIUjwj92Chj+uMn4IdGMalk+jPVF4CHS
|
||||
kocRr9l+Xk7dTHh+wK6rWbcn/RB6leQAg5i0I2mVd8ihBx8/KHv76/8M32NcNxr4
|
||||
KKMXSSkslA9rPsYOKjioWJUUXwUV58CqscQejpQ3NNz9Gw8OTrDOhnxqDPMCNjvr
|
||||
KDQU+MbZL+hjXYqzMzqCNNHi7CXU+IVrPh3amEOaqZ7SZ/IT7PofRjBOwEyAIza5
|
||||
r4h2W8QAjiDztgOZxGFVjmkcbPhToutvMi99O1rwryziFh7VaOigSGYgAc7yLnCo
|
||||
h83IO+hT5z1Ao4WFi2k4TQp8xozflOWQ75J6vSdMuh7CxLn1Hr/hWTTanfpiuG+3
|
||||
UvpqxJaXLhSbN/ZlbeqN46wnWmuUQpEgNebGAIAlOyi7ueEO3NuqeoHRWdhXWDsQ
|
||||
KEF+YnElPRJEi4wbX4vRUYU/aLbRhvbUdqtOIUa2P97ME4lzkv64L7ob+qPCMtc8
|
||||
NVazYcYPHi6CW/ZNz3dpL5Wb6zwW/swK9BWmsS+QOpz5CURpvfns14Xn/2x4Vav9
|
||||
K3kYC/miUs5qKGJnA4r/miJV5gYTkmYa7arv7VJxfM603JT1D9mlGrHswhgqZ/U3
|
||||
iGv2rWXM10YyZSow8WMhkDT55dOCWxbSY8uYEQtpsSdp3Ms/bEnobyUgbA0Lcsfq
|
||||
qTxigQ3qaw8GT6xcfr4EOdqDIxvBF2Yvc+RZtDQe+8KZPKa0HnipTXKq1lUw0PdL
|
||||
4O5L6EtrWw2PzMqR1KZidE4dKcJSwsctWhMpI/NXdyI0i+bMp2phdC5JkSXoHfe6
|
||||
GqqmBscRmVZMDtoxBxEtfVRuLo/2AYGP5ZtHOuTXqNtWJ3QvXJl7TlUqr+05YBgL
|
||||
Jy91+m5fc91+5bHdkMSBoPB/ysia956tIzoXUCFrTZJmtbtMcZ2Ku5T81RGcjdaW
|
||||
TqpdLUESpTHRMq/g4IkY//hOdceEY/JSLmXVhebcGzhnBaHbTlcHd0ZJ8+XFwl38
|
||||
lHzzuXM/JNwhtcLG1+1AVRDl1UWxdJH6Z8dmnPyRaphWpYcxgC+dQrBwZCyG/Qgj
|
||||
kyVXRGzDUHU89QCUhpbgfJ8eRbXJjdqIfUM4oAMqPatT9SIMl5F41PJSdqwTSKnZ
|
||||
9nStMIURjFe8GCEe3j/0xkNdIkd94RftXp3GAT4HPup3TC8ni3L+Yw==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,CDC4C2E547EB0BDC
|
||||
|
||||
KhB+U13K9TrIVhpFRvUfElnLoXlmMCoV9YA7Dg07HlicXkvLKELjlcNRTuvhIFQv
|
||||
yiY+p1iCmPvlCxx2HDI5c+152GqWRoIUkigC/jGg2oA3nLLHZiDSlJOJTjHzPLpn
|
||||
xKqRAerHMcYRiEmu0xut75Rwhs3IjhrtL/Po426B4k217FRBGZnlKJOeDA5lhGY/
|
||||
YiSmdXWhQdqhNWxG6B/t2oKAaAuLrlK6UhW4eqsyKleJElzAGoEr0KkKE+9f5S80
|
||||
x+rnZ1h0R0rRjjnxQk9D8686NUn8O8/yKSYkZGc7iw5kyhbhpzGAv5l113e1YpV1
|
||||
dO20fDYMu4aiHbn2iZMj1uFRae0DA53+KWo4VYqm2OxT0kAKsX5PBKL7gZtt2g1m
|
||||
4qSfpk+XCWaDVGm566Vmmpu+BNkuNiQd6F5vLkODErYLqLTEFSAP99GKitOFiL/O
|
||||
hS7fniU8+ln1kN4t1UBduxK9K9wuDdO33g5D2eLLIwofg/Y8Dc7WCS/zdenZ6+mx
|
||||
t+T97ICUtnvayaEMCk44u6/E9wgXOnP0aO0sy6gPgt8Gph/xknYY9e3nWcJnguYU
|
||||
p9enjVj9lTku/vU3B+hcjJp2ZFcxENHhFRgaklxvZj+HoyERqwQPoJe3cCvi47P1
|
||||
LJthRTWLfBYLALjEOpxfheqYgqD7U8VNOjyRDdv4NZ7jwk3zmIEYic25N/+Q8MKp
|
||||
pgTNTT+i23uiWU5u71gh+y+lzYeVqt0KooDgp3I/0v7vQAhUeXw4J6Jei+9lWR8i
|
||||
tCXn2BQfbu2ix7OnAlhiSL3wSMhsVaulYWH99zQAopEcj0x0w4twbWu6t7v2FJEd
|
||||
5DpHlBB1g0gT0htXhPeGxttmvFFQg+qykhaLqZ800sUvCjSvGWzMoHx48oHBh77l
|
||||
tLqdO/WVtR/qXSuUBAY8gE8t5kZ6LwLkxbbRuMbS1V5iellfK+/I5qXfw40TyCi1
|
||||
k7UQmTrH/e2MZnURHzKGHdl84lK8PKP+/ATOp0B9qJ0yB97bxU3j5OxzxFIvW4Aq
|
||||
uozB47Kf7V/gbN9nr/1EluLXboDR/Mh34r+4atm2SRgOyFnfomr5s4OX0BRyj8kQ
|
||||
74iu2uZukNWqLt4YJx3XP4Mzt6nk/xpc2ZLenvYS/i1Wt25MOf2wxWdN/FC4FCDc
|
||||
itCd/xgmITeP/t7aVa3lcmG/fCK+R5Qj0BR5RJYtpiXAgVEKI4tn90UfQVBSe2cU
|
||||
PNtVjlImB0tb6i6kY7M0EG1iZWxI3Yk244Jtmzm41BelBa1P7zeOf6FMdQ9NWsox
|
||||
P9pvyrjVv6XpI/ORMzDn1LtEEjXWcrWW7sGFi/ukEsBc1MbwvuWkQ5kvglu6+Dg7
|
||||
redYOt58b/YhRXVZoadbh4lWqXhFcz0sYgh9lj68RakVxixPLMlockqtOkZeR6lb
|
||||
6irjAhHCz2ikZ1F2Rm9usqrT81VIuHaYNkNFILJqx1Udd4nAg17fN2BOW+1rN1lU
|
||||
E0sHHR0ggq/9FtIu5tTA02hM4mG2Knnha87hqYn4jTr4SgmQnqJqPkS6txwL0MLT
|
||||
5u/4MhzvHJfcTzZ1bQSK0cNf1UvRG1jWv15uYsINABbsTXLjrpqdCnhY6rZ1HF7h
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIE6TAbBgkqhkiG9w0BBQMwDgQIDUmwcibfl/MCAggABIIEyMHQ9n0p97qnHdWK
|
||||
fFkZYs8HyHn4JRfPgEw8BY8w2/iuCboewkbigHdMj7NM4Epz9i3fbU9YgranCDSS
|
||||
cY0yFOTn6R+Dq0B2yRu475p+68aLJq0y0eTCM42Kov7HOQ1BXnxrCdaKcFUB9GXH
|
||||
eS878zNhWC3DaZTkvOAJTgNdwj5wKtI6E5IqNzGHTxnc9P6m1az7uVXTO97py51p
|
||||
EpHe5LSOXPRfR1sWuWMbcYVbfS4AHIpnFFb3v2DDm8gBYWcghOXulunQwfGaVoxC
|
||||
G5AAbdIbLo3ODwwRLGabiDbXDPG0V2bN8HQ8vZI5a5MUXaCpyeBEHjupsF8l4Ytt
|
||||
XB+CEs9kXwPihogRItXiHpZFI6J/GxrETHxLKnEBsLPnGQPVbm4HMKOdwWmfGVIX
|
||||
uM/PUD9DYZTsqwkd5m4ckR3DuBEcosBLoK3FeHVRG6nqnNWNe8R2AGyTWdNVaKVl
|
||||
0XvFiQTwTe2mx2Dne02KyK/QX8yL3LKp5Dx8YqpgOZRGTQoC+S82EaljcVGFimoU
|
||||
hMsPdBE6Z7iFgCIN6TV/NSLeTRSTPi7TInE46ZaGCEL89MeKS4a6GsvatRsx3eVb
|
||||
YAXk7f7iT6e4V2r2O1kfypL+dkd4FfMGY4hjsNbJKYatYrWYAQVT5d6FhwjHerD7
|
||||
kWuEh4HrBPHU5UNdBnO8wW4Ba+0/AWis7OvdIYh230PqklsXil2Ligv0fqd2q/Qk
|
||||
ftUQa2e5F2m5RIQSjc2mtsAnSe1VfMSxz3iHr5yaSZ8deVSJZHhR1lCNWnasM+KC
|
||||
GTNHxP90mNuXTf4z5b9yZi5EIa/xtpWCWSAt88w65JJGwQLWirwWiRiuQ90vtA+u
|
||||
x8vZa28rTjF4xtar1ArsrucdvYEQVEtpCnQIL2tWV4JAG3hnoGv2qgWdaGeqEkv5
|
||||
e3PAiVI46HEdbw6b5z1YvvP4bE01cLgHpPg25CNSWgOtitdZ2hq9KCcEW38vYz8y
|
||||
LMZ92lI/DJwgcux41u7TUhqvs1tHn5i7W5h0BYvzy17daby4ra4HSPlpiq5APLkO
|
||||
iMhvXaFsZ6oXmy2MhzXDovdriE+fTyf0XzNMIlswTy+14ePkIdBdFI2efuL8wrQN
|
||||
a5/wnwpNyvIZIAzNuWvhazVGylL8C10RMaQoFtUtun2Fak4BP2lH5HnmoyhfS5Gu
|
||||
1kbyukIEk3hlOqg3An+wdyl8WGGAGct9IDmwzjxGIo/fWA1usniiFlyC39B41Ixu
|
||||
oGCqtkiXXbmaY3FQLgTwOGYd7okyxIDQIpqBTYrYoNH3hO75w2JRLIBd7AAmX/RK
|
||||
s0d/jb3mkcYO1PArFnwmEwIhkSakClPyEKhx0HJ63sIPlua2rcsdDfsUvt7FOL4p
|
||||
7a/sk8vJ5swjhHnoJjxQorZ3pKLoW1/HoNdKLGDtQqWN/uOtD6IPi5uNeiJCV6Nt
|
||||
H+ZemhN6lUcUd0cOFD81JjByoghzxdALZoeAltQnEe7Web/OZVKG4dAhC/LBD2T2
|
||||
FZdJ8iX5qD3qp9UfyqLWo8ej89JwOWT8btYOIMoWLonkBNB3lJ7S/OcWkecNYPb2
|
||||
WYKg0o5FiwPK23a75Zsw1rEiq9WGdqFOg3SaE9q4RnWxUAwFD8KoIUHQasaE1q8u
|
||||
ToxJDqEzq6lksxXsIA==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECGtNrvTcx2VVAgIIAASCBMhVNYUKhBeY+sYe
|
||||
eM0Sqv936AXXLCnksnhvi3f2CoRPAS5AOkY0aINuq7xy4kR4Apv7tgk4kJcyj96E
|
||||
w1c26fA4K3ozxCYGRoE55Q2WPW8K176hy8p66czpTXmfBPEqC847vwQvsSPyw48C
|
||||
toWZajxIK380QUXC7CIEVCP15YdvZBaNAnGVM0jZ8rvw5TmVJKMwju4azmwDkpr9
|
||||
uv+3YIuGDquvnsaHA+u7U82b4m/KlZVL8HJd8uMs7ggZZXy3H6ukVsN3DwNs0Ji1
|
||||
FdrF6FAsHLNJ5Ram0bOE9Y06wcJMveTC6uObz572bg4xWu7C1hfMGd7JIJ8+eEir
|
||||
bowBc2wkb2JHMN3ShZBtGYpaRmFn5caMQkzqtxSi22TThMxPYa6jvOBRdMhCbA5F
|
||||
FAixF1CTFSUiI+2jplb/ygcEZvoe9mdpnazdI+x/g5Z3rzdaQOyQbip1GrHQJnAz
|
||||
gPuN9YItI5xWJaP25LcgM9cLPXiDeLUa+PquZFYLUiWhbvYLguysUK0RQALWzavU
|
||||
MiMjuJOw1YcBqDTV7QziKvN+vrds7ApHiyuuUtha0bXdYs9FVzJSrC+UdQwVOHUe
|
||||
FU7UejzFEYnguOsBBUGU30+j2F8FIkqxktAzgJEA/kmazuemTH5bCI45G9rWtoRl
|
||||
RNW8qi3Ja+jtO3THIi27QyExcHh3p3OPv5hjfbpFKIy/k3iL2KN9X2W2+fhHbLV/
|
||||
5ZxDEUtSwY/BqjlVQHtPY7IlPPBEv+8aGsPEGgaUGY8ZfJvK1upGw04rsnrRsXbk
|
||||
sQJ51hLVVUXo+U6OnkeHxixXkqQ1ZUPQsbFC3S5HhMJDntlAHF5f4UG2YKh7l9Ob
|
||||
txzgoUPLC82c7bcbx/11Nl8nTmr1Y38NSCnpphhz8NKAPaPSwVCeFH5UrUhdBzf5
|
||||
lvnJ7BUF5pF7DPWI9jMJCyOlAc6J/6pwje3cpl8L28623cJBXrD8S56kV91b8PkE
|
||||
DwX5in2nHlPOHYbJk9pI+9DKHxHYHA3mgIc+eZTMciwAkB65QKN9pjC+Kyb53Ibt
|
||||
UadOO8OMwjGz3ZoQJ6LJicv3u/lakYwQ8mHgxfkLZKLwOZBZjgYHnHJDpds9Y2mt
|
||||
LTsBAbX8GJtPkVrftp2KuS00n6wuvefTmyqQ8O7XIgYSgy22w+mSLAKHcFlUT/FK
|
||||
l5P0UsbTCkK0o8xzJlevFblUdGKgxYe915vsLPiBzMu6BiXmKclWWuslGKLwFmNj
|
||||
dt90ZQ+e6b5xJ4YpTlvr9rRYR/n1SZqZLMHpZCd8kpn8/5AUiQllLIawuZZlnEyz
|
||||
zjdsdpvw1mAxt+YSf7DbKsqp0JnYK83nCnBwhpKdhdtfC19eYMbUp8Ju8hsgRRxq
|
||||
f5RCQHwgeHx9/+8kheusHLOswAS7+DOUwziD1iE9CjBWx6LA56jr1fn2CLgxTrOw
|
||||
CZ4z/BYcTyX6a3vne+P0i/8Z63pOPV5U5VPFkWMc8c3iUQPBvpZGfcjGfuztnk3k
|
||||
Y7GVg+dBXJWaFM6mWKTBTLTZ7o9bBXb2McI8glZPExKyTCqW3FP7YJJpqmpUHnA/
|
||||
Wgor9T5XY/G1vZo4uchn4tmOU3AeFHROjqnGXxFOrJGbrb1TNmoZMuH5q1tsL3ns
|
||||
0YBcPeLgKJY9Rru/T+o=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
|
||||
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
|
||||
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
|
||||
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
|
||||
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
|
||||
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
|
||||
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
|
||||
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
|
||||
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
|
||||
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
|
||||
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
|
||||
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
|
||||
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
|
||||
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
|
||||
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
|
||||
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
|
||||
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
|
||||
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
|
||||
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
|
||||
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
|
||||
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
|
||||
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
|
||||
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
|
||||
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
|
||||
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,99 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
|
||||
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
|
||||
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
|
||||
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
|
||||
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
|
||||
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
|
||||
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
|
||||
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
|
||||
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
|
||||
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
|
||||
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
|
||||
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
|
||||
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
|
||||
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
|
||||
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
|
||||
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
|
||||
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
|
||||
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
|
||||
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
|
||||
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
|
||||
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
|
||||
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
|
||||
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
|
||||
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
|
||||
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
|
||||
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
|
||||
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
|
||||
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
|
||||
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
|
||||
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
|
||||
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
|
||||
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
|
||||
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
|
||||
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
|
||||
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
|
||||
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
|
||||
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
|
||||
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
|
||||
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
|
||||
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
|
||||
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
|
||||
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
|
||||
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
|
||||
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
|
||||
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
|
||||
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
|
||||
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
|
||||
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
|
||||
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
|
||||
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
|
||||
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
|
||||
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
|
||||
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
|
||||
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
|
||||
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
|
||||
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
|
||||
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
|
||||
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
|
||||
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
|
||||
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
|
||||
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
|
||||
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
|
||||
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
|
||||
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
|
||||
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
|
||||
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
|
||||
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
|
||||
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
|
||||
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
|
||||
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
|
||||
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
|
||||
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
|
||||
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
|
||||
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
|
||||
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
|
||||
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
|
||||
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
|
||||
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
|
||||
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
|
||||
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
|
||||
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
var sinon = require("sinon");
|
||||
var validateCredentials = require("../../lib/credentials/validate");
|
||||
var fakeCredentials;
|
||||
|
||||
describe("validateCredentials", function() {
|
||||
var credentials;
|
||||
beforeEach(function() {
|
||||
credentials = fakeCredentials();
|
||||
});
|
||||
|
||||
describe("with valid credentials", function() {
|
||||
it("returns", function() {
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with mismatched key and certificate", function() {
|
||||
it("throws", function() {
|
||||
sinon.stub(credentials.certificates[0]._key, "fingerprint").returns("fingerprint2");
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.throw(/certificate and key do not match/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with expired certificate", function() {
|
||||
it("throws", function() {
|
||||
sinon.stub(credentials.certificates[0], "validity")
|
||||
.returns({
|
||||
notBefore: new Date(Date.now() - 100000),
|
||||
notAfter: new Date(Date.now() - 10000)
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.throw(/certificate has expired/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with incorrect environment", function() {
|
||||
it("throws with sandbox cert in production", function() {
|
||||
sinon.stub(credentials.certificates[0], "environment")
|
||||
.returns({
|
||||
production: false,
|
||||
sandbox: true
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.throw("certificate does not support configured environment, production: true");
|
||||
});
|
||||
|
||||
it("throws with production cert in sandbox", function() {
|
||||
sinon.stub(credentials.certificates[0], "environment")
|
||||
.returns({
|
||||
production: true,
|
||||
sandbox: false
|
||||
});
|
||||
credentials.production = false;
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.throw("certificate does not support configured environment, production: false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with missing production flag", function() {
|
||||
it("does not throw", function() {
|
||||
sinon.stub(credentials.certificates[0], "environment")
|
||||
.returns({
|
||||
production: true,
|
||||
sandbox: false
|
||||
});
|
||||
credentials.production = undefined;
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with certificate supporting both environments", function() {
|
||||
it("does not throw", function() {
|
||||
sinon.stub(credentials.certificates[0], "environment")
|
||||
.returns({
|
||||
production: true,
|
||||
sandbox: true
|
||||
});
|
||||
credentials.production = false;
|
||||
|
||||
expect(function() {
|
||||
validateCredentials(credentials);
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fakeCredentials = function() {
|
||||
return {
|
||||
key: {
|
||||
_fingerprint: "fingerprint1",
|
||||
fingerprint: function() { return this._fingerprint; },
|
||||
},
|
||||
certificates: [{
|
||||
_key: {
|
||||
_fingerprint: "fingerprint1",
|
||||
fingerprint: function() { return this._fingerprint; },
|
||||
},
|
||||
_validity: {
|
||||
notBefore: new Date(Date.now() - 100000),
|
||||
notAfter: new Date(Date.now() + 100000)
|
||||
},
|
||||
key: function() { return this._key; },
|
||||
validity: function() {
|
||||
return this._validity;
|
||||
},
|
||||
environment: function() {
|
||||
return { production: true, sandbox: false };
|
||||
}
|
||||
}],
|
||||
production: true
|
||||
};
|
||||
};
|
|
@ -1,28 +1,28 @@
|
|||
var apn = require("../");
|
||||
|
||||
describe("Device", function() {
|
||||
describe('constructor', function () {
|
||||
describe("constructor", function () {
|
||||
|
||||
// Issue #149
|
||||
it("should error when given a device string which contains no hex characters and results in 0 length token", function () {
|
||||
(function () {
|
||||
expect(function () {
|
||||
apn.Device("som string without hx lttrs");
|
||||
}).should.throw();
|
||||
}).to.throw();
|
||||
});
|
||||
|
||||
it("should error when given a device string which contains an odd number of hex characters", function () {
|
||||
(function () {
|
||||
expect(function () {
|
||||
apn.Device("01234");
|
||||
}).should.throw();
|
||||
}).to.throw();
|
||||
});
|
||||
|
||||
it("should return a Device object containing the correct token when given a hex string", function () {
|
||||
apn.Device("<0123 4567 89AB CDEF>").toString().should.equal("0123456789abcdef");
|
||||
expect(apn.Device("<0123 4567 89AB CDEF>").toString()).to.equal("0123456789abcdef");
|
||||
});
|
||||
|
||||
it("should return a Device object containing the correct token when given a Buffer", function () {
|
||||
var buf = new Buffer([1, 35, 69, 103, 137, 171, 205, 239]);
|
||||
apn.Device(buf).toString().should.equal("0123456789abcdef");
|
||||
expect(apn.Device(buf).toString()).to.equal("0123456789abcdef");
|
||||
});
|
||||
});
|
||||
});
|
332
test/feedback.js
332
test/feedback.js
|
@ -1,19 +1,29 @@
|
|||
var apn = require("../");
|
||||
var fs = require("fs");
|
||||
var rewire = require("rewire");
|
||||
var Feedback = rewire("../lib/feedback");
|
||||
|
||||
var sinon = require("sinon");
|
||||
var Q = require("q");
|
||||
|
||||
describe("Feedback", function() {
|
||||
describe('constructor', function () {
|
||||
var requestMethod, originalEnv;
|
||||
var startMethod;
|
||||
before(function() {
|
||||
// Constructor has side effects :-(
|
||||
startMethod = Feedback.prototype.start;
|
||||
Feedback.prototype.start = function() { };
|
||||
});
|
||||
|
||||
after(function() {
|
||||
Feedback.prototype.start = startMethod;
|
||||
});
|
||||
|
||||
describe("constructor", function () {
|
||||
var originalEnv;
|
||||
|
||||
before(function() {
|
||||
requestMethod = apn.Feedback.prototype.request;
|
||||
apn.Feedback.prototype.request = function() { };
|
||||
|
||||
originalEnv = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
apn.Feedback.prototype.request = requestMethod;
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
|
||||
|
@ -23,33 +33,321 @@ describe("Feedback", function() {
|
|||
|
||||
// Issue #50
|
||||
it("should use feedback.sandbox.push.apple.com as the default Feedback address", function () {
|
||||
apn.Feedback().options.address.should.equal("feedback.sandbox.push.apple.com");
|
||||
expect(Feedback().options.address).to.equal("feedback.sandbox.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use feedback.push.apple.com when NODE_ENV=production", function () {
|
||||
process.env.NODE_ENV = "production";
|
||||
apn.Feedback().options.address.should.equal("feedback.push.apple.com");
|
||||
expect(Feedback().options.address).to.equal("feedback.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use feedback.push.apple.com when production:true", function () {
|
||||
apn.Feedback({production:true}).options.address.should.equal("feedback.push.apple.com");
|
||||
expect(Feedback({production:true}).options.address).to.equal("feedback.push.apple.com");
|
||||
});
|
||||
|
||||
it("should give precedence to production flag over NODE_ENV=production", function () {
|
||||
process.env.NODE_ENV = "production";
|
||||
apn.Feedback({ production: false }).options.address.should.equal("feedback.sandbox.push.apple.com");
|
||||
expect(Feedback({ production: false }).options.address).to.equal("feedback.sandbox.push.apple.com");
|
||||
});
|
||||
|
||||
it("should use a custom address when passed", function () {
|
||||
apn.Feedback({address: "testaddress"}).options.address.should.equal("testaddress");
|
||||
expect(Feedback({address: "testaddress"}).options.address).to.equal("testaddress");
|
||||
});
|
||||
|
||||
describe("address is passed", function() {
|
||||
it("sets production to true when using production address", function() {
|
||||
expect(Feedback({address: "feedback.push.apple.com"}).options.production).to.be.true;
|
||||
});
|
||||
|
||||
it("sets production to false when using sandbox address", function() {
|
||||
process.env.NODE_ENV = "production";
|
||||
expect(Feedback({address: "feedback.sandbox.push.apple.com"}).options.production).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#initialize', function () {
|
||||
describe("#initialize", function () {
|
||||
var loadStub, parseStub, validateStub, removeStubs;
|
||||
beforeEach(function() {
|
||||
loadStub = sinon.stub();
|
||||
loadStub.displayName = "loadCredentials";
|
||||
|
||||
it("should be fulfilled", function () {
|
||||
return apn.Feedback({ pfx: "test/support/initializeTest.pfx" })
|
||||
.initialize().should.be.fulfilled;
|
||||
parseStub = sinon.stub();
|
||||
parseStub.displayName = "parseCredentials";
|
||||
|
||||
validateStub = sinon.stub();
|
||||
validateStub.displayName = "validateCredentials";
|
||||
|
||||
removeStubs = Feedback.__set__({
|
||||
"loadCredentials": loadStub,
|
||||
"parseCredentials": parseStub,
|
||||
"validateCredentials": validateStub,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
removeStubs();
|
||||
});
|
||||
|
||||
it("only loads credentials once", function() {
|
||||
loadStub.returns(Q({}));
|
||||
|
||||
var feedback = Feedback();
|
||||
feedback.initialize();
|
||||
feedback.initialize();
|
||||
expect(loadStub).to.be.calledOnce;
|
||||
});
|
||||
|
||||
describe("with valid credentials", function() {
|
||||
var initialization;
|
||||
var testOptions = {
|
||||
pfx: "myCredentials.pfx", cert: "myCert.pem", key: "myKey.pem", ca: "myCa.pem",
|
||||
passphrase: "apntest", production: true
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadStub.withArgs(sinon.match(function(v) {
|
||||
return v.pfx === "myCredentials.pfx" && v.cert === "myCert.pem" && v.key === "myKey.pem" &&
|
||||
v.ca === "myCa.pem" && v.passphrase === "apntest";
|
||||
})).returns(Q({ pfx: "myPfxData", cert: "myCertData", key: "myKeyData", ca: ["myCaData"], passphrase: "apntest" }));
|
||||
|
||||
parseStub.returnsArg(0);
|
||||
|
||||
initialization = Feedback(testOptions).initialize();
|
||||
});
|
||||
|
||||
it("should be fulfilled", function () {
|
||||
return expect(initialization).to.be.fulfilled;
|
||||
});
|
||||
|
||||
describe("the validation stage", function() {
|
||||
it("is called once", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
it("is passed the production flag", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("production", true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("passed credentials", function() {
|
||||
it("contains the PFX data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("pfx", "myPfxData");
|
||||
});
|
||||
});
|
||||
|
||||
it("contains the key data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("key", "myKeyData");
|
||||
});
|
||||
});
|
||||
|
||||
it("contains the certificate data", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("cert", "myCertData");
|
||||
});
|
||||
});
|
||||
|
||||
it("includes passphrase", function() {
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub.getCall(0).args[0]).to.have.property("passphrase", "apntest");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolution value", function() {
|
||||
it("contains the PFX data", function() {
|
||||
return expect(initialization).to.eventually.have.property("pfx", "myPfxData");
|
||||
});
|
||||
|
||||
it("contains the key data", function() {
|
||||
return expect(initialization).to.eventually.have.property("key", "myKeyData");
|
||||
});
|
||||
|
||||
it("contains the certificate data", function() {
|
||||
return expect(initialization).to.eventually.have.property("cert", "myCertData");
|
||||
});
|
||||
|
||||
it("contains the CA data", function() {
|
||||
return expect(initialization).to.eventually.have.deep.property("ca[0]", "myCaData");
|
||||
});
|
||||
|
||||
it("includes passphrase", function() {
|
||||
return expect(initialization).to.eventually.have.property("passphrase", "apntest");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential file cannot be parsed", function() {
|
||||
beforeEach(function() {
|
||||
loadStub.returns(Q({ cert: "myCertData", key: "myKeyData" }));
|
||||
parseStub.throws(new Error("unable to parse key"));
|
||||
});
|
||||
|
||||
it("should resolve with the credentials", function() {
|
||||
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
return expect(initialization).to.become({ cert: "myCertData", key: "myKeyData" });
|
||||
});
|
||||
|
||||
it("should log an error", function() {
|
||||
var debug = sinon.spy();
|
||||
var reset = Feedback.__set__("debug", debug);
|
||||
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
|
||||
return initialization.finally(function() {
|
||||
reset();
|
||||
expect(debug).to.be.calledWith(sinon.match(function(err) {
|
||||
return err.message ? err.message.match(/unable to parse key/) : false;
|
||||
}, "\"unable to parse key\""));
|
||||
});
|
||||
});
|
||||
|
||||
it("should not attempt to validate", function() {
|
||||
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
|
||||
return initialization.finally(function() {
|
||||
expect(validateStub).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential validation fails", function() {
|
||||
it("should be rejected", function() {
|
||||
loadStub.returns(Q({ cert: "myCertData", key: "myMismatchedKeyData" }));
|
||||
parseStub.returnsArg(0);
|
||||
validateStub.throws(new Error("certificate and key do not match"));
|
||||
|
||||
var initialization = Feedback({ cert: "myCert.pem", key: "myMistmatchedKey.pem" }).initialize();
|
||||
return expect(initialization).to.eventually.be.rejectedWith(/certificate and key do not match/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential file cannot be loaded", function() {
|
||||
it("should be rejected", function() {
|
||||
loadStub.returns(Q.reject(new Error("ENOENT, no such file or directory")));
|
||||
|
||||
var initialization = Feedback({ cert: "noSuchFile.pem", key: "myKey.pem" }).initialize();
|
||||
return expect(initialization).to.eventually.be.rejectedWith("ENOENT, no such file or directory");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect", function() {
|
||||
var socketStub, removeSocketStub;
|
||||
|
||||
before(function() {
|
||||
var initializeStub = sinon.stub(Feedback.prototype, "initialize");
|
||||
initializeStub.returns(Q({
|
||||
pfx: "pfxData",
|
||||
key: "keyData",
|
||||
cert: "certData",
|
||||
ca: ["caData1", "caData2"],
|
||||
passphrase: "apntest" }));
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
socketStub = sinon.stub();
|
||||
socketStub.callsArg(2);
|
||||
socketStub.returns({ on: function() {}, once: function() {}, end: function() {} });
|
||||
|
||||
removeSocketStub = Feedback.__set__("createSocket", socketStub);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
removeSocketStub();
|
||||
});
|
||||
|
||||
it("initializes the module", function(done) {
|
||||
var feedback = Feedback({ pfx: "myCredentials.pfx" });
|
||||
return feedback.connect().finally(function() {
|
||||
expect(feedback.initialize).to.have.been.calledOnce;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with valid credentials", function() {
|
||||
it("resolves", function() {
|
||||
var feedback = Feedback({
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
});
|
||||
return expect(feedback.connect()).to.be.fulfilled;
|
||||
});
|
||||
|
||||
describe("the call to create socket", function() {
|
||||
var connect;
|
||||
|
||||
it("passes PFX data", function() {
|
||||
connect = Feedback({
|
||||
pfx: "myCredentials.pfx",
|
||||
passphrase: "apntest"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.pfx).to.equal("pfxData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the passphrase", function() {
|
||||
connect = Feedback({
|
||||
passphrase: "apntest",
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.passphrase).to.equal("apntest");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the cert", function() {
|
||||
connect = Feedback({
|
||||
cert: "myCert.pem",
|
||||
key: "myKey.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.cert).to.equal("certData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the key", function() {
|
||||
connect = Feedback({
|
||||
cert: "test/credentials/support/cert.pem",
|
||||
key: "test/credentials/support/key.pem"
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.key).to.equal("keyData");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes the ca certificates", function() {
|
||||
connect = Feedback({
|
||||
cert: "test/credentials/support/cert.pem",
|
||||
key: "test/credentials/support/key.pem",
|
||||
ca: [ "test/credentials/support/issuerCert.pem" ]
|
||||
}).connect();
|
||||
return connect.then(function() {
|
||||
var socketOptions = socketStub.args[0][1];
|
||||
expect(socketOptions.ca[0]).to.equal("caData1");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("intialization failure", function() {
|
||||
it("is rejected", function() {
|
||||
var feedback = Feedback({ pfx: "a-non-existant-file-which-really-shouldnt-exist.pfx" });
|
||||
feedback.on("error", function() {});
|
||||
feedback.initialize.returns(Q.reject(new Error("initialize failed")));
|
||||
|
||||
return expect(feedback.connect()).to.be.rejectedWith("initialize failed");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
--require test/support
|
||||
--reporter spec
|
||||
--recursive
|
||||
--ui bdd
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -2,6 +2,10 @@
|
|||
|
||||
var chai = require("chai");
|
||||
var chaiAsPromised = require("chai-as-promised");
|
||||
var sinonChai = require("sinon-chai");
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
chai.config.includeStack = true;
|
||||
chai.use(chaiAsPromised);
|
||||
chai.use(sinonChai);
|
||||
|
||||
global.expect = chai.expect;
|
Загрузка…
Ссылка в новой задаче