Add APNS client
This commit is contained in:
95
APNS.js
Normal file
95
APNS.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
var Parse = require('parse/node').Parse;
|
||||||
|
// TODO: apn does not support the new HTTP/2 protocal. It is fine to use it in V1,
|
||||||
|
// but probably we will replace it in the future.
|
||||||
|
var apn = require('apn');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new connection to the APN service.
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} args Arguments to config APNS connection
|
||||||
|
* @param {String} args.cert The filename of the connection certificate to load from disk, default is cert.pem
|
||||||
|
* @param {String} args.key The filename of the connection key to load from disk, default is key.pem
|
||||||
|
* @param {String} args.passphrase The passphrase for the connection key, if required
|
||||||
|
* @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
|
||||||
|
*/
|
||||||
|
function APNS(args) {
|
||||||
|
this.sender = new apn.connection(args);
|
||||||
|
|
||||||
|
this.sender.on('connected', function() {
|
||||||
|
console.log('APNS Connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sender.on('transmissionError', function(errCode, notification, device) {
|
||||||
|
console.error('APNS Notification caused error: ' + errCode + ' for device ', device, notification);
|
||||||
|
// TODO: For error caseud by invalid deviceToken, we should mark those installations.
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sender.on("timeout", function () {
|
||||||
|
console.log("APNS Connection Timeout");
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sender.on("disconnected", function() {
|
||||||
|
console.log("APNS Disconnected");
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sender.on("socketError", console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send apns request.
|
||||||
|
* @param {Object} data The data we need to send, the format is the same with api request body
|
||||||
|
* @param {Array} deviceTokens A array of device tokens
|
||||||
|
* @returns {Object} A promise which is resolved immediately
|
||||||
|
*/
|
||||||
|
APNS.prototype.send = function(data, deviceTokens) {
|
||||||
|
var coreData = data.data;
|
||||||
|
var expirationTime = data['expiration_time'];
|
||||||
|
var notification = generateNotification(coreData, expirationTime);
|
||||||
|
this.sender.pushNotification(notification, deviceTokens);
|
||||||
|
// TODO: pushNotification will push the notification to apn's queue.
|
||||||
|
// We do not handle error in V1, we just relies apn to auto retry and send the
|
||||||
|
// notifications.
|
||||||
|
return Parse.Promise.as();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the apns notification from the data we get from api request.
|
||||||
|
* @param {Object} coreData The data field under api request body
|
||||||
|
* @returns {Object} A apns notification
|
||||||
|
*/
|
||||||
|
var generateNotification = function(coreData, expirationTime) {
|
||||||
|
var notification = new apn.notification();
|
||||||
|
var payload = {};
|
||||||
|
for (key in coreData) {
|
||||||
|
switch (key) {
|
||||||
|
case 'alert':
|
||||||
|
notification.setAlertText(coreData.alert);
|
||||||
|
break;
|
||||||
|
case 'badge':
|
||||||
|
notification.badge = coreData.badge;
|
||||||
|
break;
|
||||||
|
case 'sound':
|
||||||
|
notification.sound = coreData.sound;
|
||||||
|
break;
|
||||||
|
case 'content-available':
|
||||||
|
notification.setNewsstandAvailable(true);
|
||||||
|
var isAvailable = coreData['content-available'] === 1;
|
||||||
|
notification.setContentAvailable(isAvailable);
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
notification.category = coreData.category;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
payload[key] = coreData[key];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notification.payload = payload;
|
||||||
|
notification.expiry = expirationTime;
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
||||||
|
APNS.generateNotification = generateNotification;
|
||||||
|
}
|
||||||
|
module.exports = APNS;
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"apn": "^1.7.5",
|
||||||
"aws-sdk": "~2.2.33",
|
"aws-sdk": "~2.2.33",
|
||||||
"bcrypt-nodejs": "0.0.3",
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
|
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
|
||||||
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
|
"test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
|
||||||
"posttest": "mongodb-runner stop",
|
"posttest": "mongodb-runner stop",
|
||||||
"start": "./bin/parse-server"
|
"start": "./bin/parse-server"
|
||||||
},
|
},
|
||||||
|
|||||||
58
spec/APNS.spec.js
Normal file
58
spec/APNS.spec.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
var APNS = require('../APNS');
|
||||||
|
|
||||||
|
describe('APNS', () => {
|
||||||
|
it('can generate APNS notification', (done) => {
|
||||||
|
//Mock request data
|
||||||
|
var data = {
|
||||||
|
'alert': 'alert',
|
||||||
|
'badge': 100,
|
||||||
|
'sound': 'test',
|
||||||
|
'content-available': 1,
|
||||||
|
'category': 'INVITE_CATEGORY',
|
||||||
|
'key': 'value',
|
||||||
|
'keyAgain': 'valueAgain'
|
||||||
|
};
|
||||||
|
var expirationTime = 1454571491354
|
||||||
|
|
||||||
|
var notification = APNS.generateNotification(data, expirationTime);
|
||||||
|
|
||||||
|
expect(notification.alert).toEqual(data.alert);
|
||||||
|
expect(notification.badge).toEqual(data.badge);
|
||||||
|
expect(notification.sound).toEqual(data.sound);
|
||||||
|
expect(notification.contentAvailable).toEqual(1);
|
||||||
|
expect(notification.category).toEqual(data.category);
|
||||||
|
expect(notification.payload).toEqual({
|
||||||
|
'key': 'value',
|
||||||
|
'keyAgain': 'valueAgain'
|
||||||
|
});
|
||||||
|
expect(notification.expiry).toEqual(expirationTime);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can send APNS notification', (done) => {
|
||||||
|
var apns = new APNS();
|
||||||
|
var sender = {
|
||||||
|
pushNotification: jasmine.createSpy('send')
|
||||||
|
};
|
||||||
|
apns.sender = sender;
|
||||||
|
// Mock data
|
||||||
|
var expirationTime = 1454571491354
|
||||||
|
var data = {
|
||||||
|
'expiration_time': expirationTime,
|
||||||
|
'data': {
|
||||||
|
'alert': 'alert'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mock registrationTokens
|
||||||
|
var deviceTokens = ['token'];
|
||||||
|
|
||||||
|
var promise = apns.send(data, deviceTokens);
|
||||||
|
expect(sender.pushNotification).toHaveBeenCalled();
|
||||||
|
var args = sender.pushNotification.calls.first().args;
|
||||||
|
var notification = args[0];
|
||||||
|
expect(notification.alert).toEqual(data.data.alert);
|
||||||
|
expect(notification.expiry).toEqual(data['expiration_time']);
|
||||||
|
expect(args[1]).toEqual(deviceTokens);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user