Skip to content

Commit

Permalink
Add support for APNS
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerhu committed Mar 17, 2016
1 parent a445108 commit c25001a
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 35 deletions.
2 changes: 1 addition & 1 deletion spec/APNS.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var APNS = require('../src/APNS');
var APNS = require('../src/APNS').APNS;

describe('APNS', () => {

Expand Down
2 changes: 1 addition & 1 deletion spec/ParsePushAdapter.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var ParsePushAdapter = require('../src/Adapters/Push/ParsePushAdapter');
var APNS = require('../src/APNS');
var APNS = require('../src/APNS').APNS;
var GCM = require('../src/GCM').GCM;

describe('ParsePushAdapter', () => {
Expand Down
108 changes: 95 additions & 13 deletions spec/SNSPushAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ describe('SNSPushAdapter', () => {
beforeEach(function() {
pushConfig = {
pushTypes: {
ios: "APNS_ID",
android: "GCM_ID"
ios: {ARN : "APNS_ID", production: false, bundleId: 'com.parseplatform.myapp'},
android: {ARN: "GCM_ID"}
},
accessKey: "accessKey",
secretKey: "secretKey",
Expand All @@ -19,10 +19,9 @@ describe('SNSPushAdapter', () => {

it('can be initialized', (done) => {
// Make mock config
var arnMap = snsPushAdapter.arnMap;
var snsPushConfig = snsPushAdapter.snsConfig;

expect(arnMap.ios).toEqual("APNS_ID");
expect(arnMap.android).toEqual("GCM_ID");
expect(snsPushConfig).toEqual(pushConfig.pushTypes);

done();
});
Expand Down Expand Up @@ -126,13 +125,17 @@ describe('SNSPushAdapter', () => {
});

it('can generate the right iOS payload', (done) => {
var data = {"aps": {"alert": "Check out these awesome deals!"}};
var data = {data : {"alert": "Check out these awesome deals!"}};
var pushId = '123';
var timeStamp = 1456728000;

var returnedData = SNSPushAdapter.generateiOSPayload(data);
var returnedData = SNSPushAdapter.generateiOSPayload(data, true);
var expectedData = {APNS: '{"aps":{"alert":"Check out these awesome deals!"}}'};
expect(returnedData).toEqual(expectedData)

var returnedData = SNSPushAdapter.generateiOSPayload(data, false);
var expectedData = {APNS_SANDBOX: '{"aps":{"alert":"Check out these awesome deals!"}}'};

expect(returnedData).toEqual(expectedData);
done();
});

Expand All @@ -153,11 +156,11 @@ describe('SNSPushAdapter', () => {
callback(null, {'EndpointArn' : 'ARN'});
});

var promise = snsPushAdapter.exchangeTokenPromise(makeDevice("androidToken"), "android");
var promise = snsPushAdapter.exchangeTokenPromise(makeDevice("androidToken"), "GCM_ID");

promise.then(function() {
expect(snsSender.createPlatformEndpoint).toHaveBeenCalled();
args = snsSender.createPlatformEndpoint.calls.first().args;
var args = snsSender.createPlatformEndpoint.calls.first().args;
expect(args[0].PlatformApplicationArn).toEqual("GCM_ID");
expect(args[0].Token).toEqual("androidToken");
done();
Expand Down Expand Up @@ -186,7 +189,7 @@ describe('SNSPushAdapter', () => {
var callback = jasmine.createSpy();
promise.then(function () {
expect(snsSender.publish).toHaveBeenCalled();
args = snsSender.publish.calls.first().args;
var args = snsSender.publish.calls.first().args;
expect(args[0].MessageStructure).toEqual("json");
expect(args[0].TargetArn).toEqual("123");
expect(args[0].Message).toEqual('{"test":"hello"}');
Expand All @@ -202,8 +205,10 @@ describe('SNSPushAdapter', () => {
callback("error", {});
});

var promise = snsSender.getPlatformArn(makeDevice("android"), "android", function(err, data));
done();
snsPushAdapter.getPlatformArn(makeDevice("android"), "android", function(err, data) {
expect(err).not.toBe(null);
done();
});
});

it('can send SNS Payload to Android and iOS', (done) => {
Expand Down Expand Up @@ -241,6 +246,83 @@ describe('SNSPushAdapter', () => {
});
});

it('can send to APNS with known identifier', (done) => {
var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);

snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
callback(null, {'EndpointArn': 'ARN'});
});

snsSender.publish.and.callFake(function (object, callback) {
callback(null, '123');
});

snsPushAdapter.sns = snsSender;

var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "com.parseplatform.myapp")]);
expect(promises.length).toEqual(1);

Promise.all(promises).then(function () {
expect(snsSender.publish).toHaveBeenCalled();
done();
});

});

it('can send to APNS with unknown identifier', (done) => {
var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);

snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
callback(null, {'EndpointArn': 'ARN'});
});

snsSender.publish.and.callFake(function (object, callback) {
callback(null, '123');
});

snsPushAdapter.sns = snsSender;

var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "com.parseplatform.unknown")]);
expect(promises.length).toEqual(0);
done();
});

it('can send to APNS with multiple identifiers', (done) => {
pushConfig = {
pushTypes: {
ios: [{ARN : "APNS_SANDBOX_ID", production: false, bundleId: 'beta.parseplatform.myapp'},
{ARN : "APNS_PROD_ID", production: true, bundleId: 'com.parseplatform.myapp'}],
android: {ARN: "GCM_ID"}
},
accessKey: "accessKey",
secretKey: "secretKey",
region: "region"
};

snsPushAdapter = new SNSPushAdapter(pushConfig);

var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);

snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
callback(null, {'EndpointArn': 'APNS_PROD_ID'});
});

snsSender.publish.and.callFake(function (object, callback) {
callback(null, '123');
});

snsPushAdapter.sns = snsSender;

var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "beta.parseplatform.myapp")]);
expect(promises.length).toEqual(1);
Promise.all(promises).then(function () {
expect(snsSender.publish).toHaveBeenCalled();
var args = snsSender.publish.calls.first().args[0];
expect(args.Message).toEqual("{\"APNS_SANDBOX\":\"{}\"}");
done();
});
});

function makeDevice(deviceToken, appIdentifier) {
return {
deviceToken: deviceToken,
Expand Down
5 changes: 2 additions & 3 deletions src/APNS.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const apn = require('apn');
* @param {String} args.bundleId The bundleId for cert
* @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
*/
function APNS(args) {
export function APNS(args) {
// Since for ios, there maybe multiple cert/key pairs,
// typePushConfig can be an array.
let apnsArgsList = [];
Expand Down Expand Up @@ -187,7 +187,7 @@ function chooseConns(conns, device) {
* @param {Object} coreData The data field under api request body
* @returns {Object} A apns notification
*/
function generateNotification(coreData, expirationTime) {
export function generateNotification(coreData, expirationTime) {
let notification = new apn.notification();
let payload = {};
for (let key in coreData) {
Expand Down Expand Up @@ -224,4 +224,3 @@ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
APNS.chooseConns = chooseConns;
APNS.handleTransmissionError = handleTransmissionError;
}
module.exports = APNS;
2 changes: 1 addition & 1 deletion src/Adapters/Push/ParsePushAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

const Parse = require('parse/node').Parse;
const GCM = require('../../GCM').GCM;
const APNS = require('../../APNS');
const APNS = require('../../APNS').APNS;
import PushAdapter from './PushAdapter';
import { classifyInstallations } from './PushAdapterUtils';

Expand Down
78 changes: 62 additions & 16 deletions src/Adapters/Push/SNSPushAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import PushAdapter from './PushAdapter';

const Parse = require('parse/node').Parse;
const GCM = require('../../GCM');
const APNS = require('../../APNS');

const AWS = require('aws-sdk');

Expand All @@ -21,7 +22,7 @@ export class SNSPushAdapter extends PushAdapter {
super(pushConfig);
this.validPushTypes = ['ios', 'android'];
this.availablePushTypes = [];
this.arnMap = {};
this.snsConfig = pushConfig.pushTypes;
this.senderMap = {};

if (!pushConfig.accessKey || !pushConfig.secretKey) {
Expand All @@ -40,11 +41,9 @@ export class SNSPushAdapter extends PushAdapter {
switch (pushType) {
case 'ios':
this.senderMap[pushType] = this.sendToAPNS.bind(this);
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
break;
case 'android':
this.senderMap[pushType] = this.sendToGCM.bind(this);
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
break;
}
}
Expand All @@ -69,10 +68,20 @@ export class SNSPushAdapter extends PushAdapter {
}

//Generate proper json for APNS message
static generateiOSPayload(data) {
return {
'APNS': JSON.stringify(data)
};
static generateiOSPayload(data, production) {
var prefix = "";

if (production) {
prefix = "APNS";
} else {
prefix = "APNS_SANDBOX"
}

var notification = APNS.generateNotification(data.data, data.expirationTime);

var payload = {};
payload[prefix] = notification.compile();
return payload;
}

// Generate proper json for GCM message
Expand All @@ -86,20 +95,55 @@ export class SNSPushAdapter extends PushAdapter {
}

sendToAPNS(data, devices) {
var payload = SNSPushAdapter.generateiOSPayload(data);

return this.sendToSNS(payload, devices, 'ios');
var iosPushConfig = this.snsConfig['ios'];

let iosConfigs = [];
if (Array.isArray(iosPushConfig)) {
iosConfigs = iosConfigs.concat(iosPushConfig);
} else {
iosConfigs.push(iosPushConfig)
}

let promises = [];

for (let iosConfig of iosConfigs) {

let production = iosConfig.production || false;
var payload = SNSPushAdapter.generateiOSPayload(data, production);

var deviceSends = [];
for (let device of devices) {

// Follow the same logic as APNS service. If no appIdentifier, send it!
if (!device.appIdentifier || device.appIdentifier === '') {
deviceSends.push(device);
}

else if (device.appIdentifier === iosConfig.bundleId) {
deviceSends.push(device);
}
}
if (deviceSends.length > 0) {
promises.push(this.sendToSNS(payload, deviceSends, iosConfig.ARN));
}
}

return promises;
}

sendToGCM(data, devices) {
var payload = SNSPushAdapter.generateAndroidPayload(data);
return this.sendToSNS(payload, devices, 'android');
var pushConfig = this.snsConfig['android'];

return this.sendToSNS(payload, devices, pushConfig.ARN);
}

sendToSNS(payload, devices, pushType) {
sendToSNS(payload, devices, platformArn) {
// Exchange the device token for the Amazon resource ID

let exchangePromises = devices.map((device) => {
return this.exchangeTokenPromise(device, pushType);
return this.exchangeTokenPromise(device, platformArn);
});

// Publish off to SNS!
Expand All @@ -117,9 +161,9 @@ export class SNSPushAdapter extends PushAdapter {
/**
* Request a Amazon Resource Identifier if one is not set.
*/
getPlatformArn(device, pushType, callback) {
getPlatformArn(device, arn, callback) {
var params = {
PlatformApplicationArn: this.arnMap[pushType],
PlatformApplicationArn: arn,
Token: device.deviceToken
};

Expand All @@ -129,9 +173,10 @@ export class SNSPushAdapter extends PushAdapter {
/**
* Exchange the device token for an ARN
*/
exchangeTokenPromise(device, pushType) {
exchangeTokenPromise(device, platformARN) {
return new Parse.Promise((resolve, reject) => {
this.getPlatformArn(device, pushType, (err, data) => {

this.getPlatformArn(device, platformARN, (err, data) => {
if (data.EndpointArn) {
resolve(data.EndpointArn);
} else {
Expand Down Expand Up @@ -174,6 +219,7 @@ export class SNSPushAdapter extends PushAdapter {

let sendPromises = Object.keys(deviceMap).forEach((pushType) => {
var devices = deviceMap[pushType];

var sender = this.senderMap[pushType];
return sender(data, devices);
});
Expand Down

0 comments on commit c25001a

Please sign in to comment.