Skip to content

Commit 05989a6

Browse files
committed
Add support for APNS
1 parent 1fc6648 commit 05989a6

File tree

6 files changed

+162
-35
lines changed

6 files changed

+162
-35
lines changed

spec/APNS.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var APNS = require('../src/APNS');
1+
var APNS = require('../src/APNS').APNS;
22

33
describe('APNS', () => {
44

spec/ParsePushAdapter.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var ParsePushAdapter = require('../src/Adapters/Push/ParsePushAdapter');
2-
var APNS = require('../src/APNS');
2+
var APNS = require('../src/APNS').APNS;
33
var GCM = require('../src/GCM').GCM;
44

55
describe('ParsePushAdapter', () => {

spec/SNSPushAdapter.spec.js

+95-13
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ describe('SNSPushAdapter', () => {
77
beforeEach(function() {
88
pushConfig = {
99
pushTypes: {
10-
ios: "APNS_ID",
11-
android: "GCM_ID"
10+
ios: {ARN : "APNS_ID", production: false, bundleId: 'com.parseplatform.myapp'},
11+
android: {ARN: "GCM_ID"}
1212
},
1313
accessKey: "accessKey",
1414
secretKey: "secretKey",
@@ -19,10 +19,9 @@ describe('SNSPushAdapter', () => {
1919

2020
it('can be initialized', (done) => {
2121
// Make mock config
22-
var arnMap = snsPushAdapter.arnMap;
22+
var snsPushConfig = snsPushAdapter.snsConfig;
2323

24-
expect(arnMap.ios).toEqual("APNS_ID");
25-
expect(arnMap.android).toEqual("GCM_ID");
24+
expect(snsPushConfig).toEqual(pushConfig.pushTypes);
2625

2726
done();
2827
});
@@ -126,13 +125,17 @@ describe('SNSPushAdapter', () => {
126125
});
127126

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

133-
var returnedData = SNSPushAdapter.generateiOSPayload(data);
132+
var returnedData = SNSPushAdapter.generateiOSPayload(data, true);
134133
var expectedData = {APNS: '{"aps":{"alert":"Check out these awesome deals!"}}'};
135-
expect(returnedData).toEqual(expectedData)
134+
135+
var returnedData = SNSPushAdapter.generateiOSPayload(data, false);
136+
var expectedData = {APNS_SANDBOX: '{"aps":{"alert":"Check out these awesome deals!"}}'};
137+
138+
expect(returnedData).toEqual(expectedData);
136139
done();
137140
});
138141

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

156-
var promise = snsPushAdapter.exchangeTokenPromise(makeDevice("androidToken"), "android");
159+
var promise = snsPushAdapter.exchangeTokenPromise(makeDevice("androidToken"), "GCM_ID");
157160

158161
promise.then(function() {
159162
expect(snsSender.createPlatformEndpoint).toHaveBeenCalled();
160-
args = snsSender.createPlatformEndpoint.calls.first().args;
163+
var args = snsSender.createPlatformEndpoint.calls.first().args;
161164
expect(args[0].PlatformApplicationArn).toEqual("GCM_ID");
162165
expect(args[0].Token).toEqual("androidToken");
163166
done();
@@ -186,7 +189,7 @@ describe('SNSPushAdapter', () => {
186189
var callback = jasmine.createSpy();
187190
promise.then(function () {
188191
expect(snsSender.publish).toHaveBeenCalled();
189-
args = snsSender.publish.calls.first().args;
192+
var args = snsSender.publish.calls.first().args;
190193
expect(args[0].MessageStructure).toEqual("json");
191194
expect(args[0].TargetArn).toEqual("123");
192195
expect(args[0].Message).toEqual('{"test":"hello"}');
@@ -202,8 +205,10 @@ describe('SNSPushAdapter', () => {
202205
callback("error", {});
203206
});
204207

205-
var promise = snsSender.getPlatformArn(makeDevice("android"), "android", function(err, data));
206-
done();
208+
snsPushAdapter.getPlatformArn(makeDevice("android"), "android", function(err, data) {
209+
expect(err).not.toBe(null);
210+
done();
211+
});
207212
});
208213

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

249+
it('can send to APNS with known identifier', (done) => {
250+
var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);
251+
252+
snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
253+
callback(null, {'EndpointArn': 'ARN'});
254+
});
255+
256+
snsSender.publish.and.callFake(function (object, callback) {
257+
callback(null, '123');
258+
});
259+
260+
snsPushAdapter.sns = snsSender;
261+
262+
var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "com.parseplatform.myapp")]);
263+
expect(promises.length).toEqual(1);
264+
265+
Promise.all(promises).then(function () {
266+
expect(snsSender.publish).toHaveBeenCalled();
267+
done();
268+
});
269+
270+
});
271+
272+
it('can send to APNS with unknown identifier', (done) => {
273+
var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);
274+
275+
snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
276+
callback(null, {'EndpointArn': 'ARN'});
277+
});
278+
279+
snsSender.publish.and.callFake(function (object, callback) {
280+
callback(null, '123');
281+
});
282+
283+
snsPushAdapter.sns = snsSender;
284+
285+
var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "com.parseplatform.unknown")]);
286+
expect(promises.length).toEqual(0);
287+
done();
288+
});
289+
290+
it('can send to APNS with multiple identifiers', (done) => {
291+
pushConfig = {
292+
pushTypes: {
293+
ios: [{ARN : "APNS_SANDBOX_ID", production: false, bundleId: 'beta.parseplatform.myapp'},
294+
{ARN : "APNS_PROD_ID", production: true, bundleId: 'com.parseplatform.myapp'}],
295+
android: {ARN: "GCM_ID"}
296+
},
297+
accessKey: "accessKey",
298+
secretKey: "secretKey",
299+
region: "region"
300+
};
301+
302+
snsPushAdapter = new SNSPushAdapter(pushConfig);
303+
304+
var snsSender = jasmine.createSpyObj('sns', ['publish', 'createPlatformEndpoint']);
305+
306+
snsSender.createPlatformEndpoint.and.callFake(function (object, callback) {
307+
callback(null, {'EndpointArn': 'APNS_PROD_ID'});
308+
});
309+
310+
snsSender.publish.and.callFake(function (object, callback) {
311+
callback(null, '123');
312+
});
313+
314+
snsPushAdapter.sns = snsSender;
315+
316+
var promises = snsPushAdapter.sendToAPNS({"test": "hello"}, [makeDevice("ios", "beta.parseplatform.myapp")]);
317+
expect(promises.length).toEqual(1);
318+
Promise.all(promises).then(function () {
319+
expect(snsSender.publish).toHaveBeenCalled();
320+
var args = snsSender.publish.calls.first().args[0];
321+
expect(args.Message).toEqual("{\"APNS_SANDBOX\":\"{}\"}");
322+
done();
323+
});
324+
});
325+
244326
function makeDevice(deviceToken, appIdentifier) {
245327
return {
246328
deviceToken: deviceToken,

src/APNS.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const apn = require('apn');
1616
* @param {String} args.bundleId The bundleId for cert
1717
* @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
1818
*/
19-
function APNS(args) {
19+
export function APNS(args) {
2020
// Since for ios, there maybe multiple cert/key pairs,
2121
// typePushConfig can be an array.
2222
let apnsArgsList = [];
@@ -187,7 +187,7 @@ function chooseConns(conns, device) {
187187
* @param {Object} coreData The data field under api request body
188188
* @returns {Object} A apns notification
189189
*/
190-
function generateNotification(coreData, expirationTime) {
190+
export function generateNotification(coreData, expirationTime) {
191191
let notification = new apn.notification();
192192
let payload = {};
193193
for (let key in coreData) {
@@ -224,4 +224,3 @@ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
224224
APNS.chooseConns = chooseConns;
225225
APNS.handleTransmissionError = handleTransmissionError;
226226
}
227-
module.exports = APNS;

src/Adapters/Push/ParsePushAdapter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
const Parse = require('parse/node').Parse;
77
const GCM = require('../../GCM').GCM;
8-
const APNS = require('../../APNS');
8+
const APNS = require('../../APNS').APNS;
99
import PushAdapter from './PushAdapter';
1010
import { classifyInstallations } from './PushAdapterUtils';
1111

src/Adapters/Push/SNSPushAdapter.js

+62-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import PushAdapter from './PushAdapter';
66

77
const Parse = require('parse/node').Parse;
88
const GCM = require('../../GCM');
9+
const APNS = require('../../APNS');
910

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

@@ -21,7 +22,7 @@ export class SNSPushAdapter extends PushAdapter {
2122
super(pushConfig);
2223
this.validPushTypes = ['ios', 'android'];
2324
this.availablePushTypes = [];
24-
this.arnMap = {};
25+
this.snsConfig = pushConfig.pushTypes;
2526
this.senderMap = {};
2627

2728
if (!pushConfig.accessKey || !pushConfig.secretKey) {
@@ -40,11 +41,9 @@ export class SNSPushAdapter extends PushAdapter {
4041
switch (pushType) {
4142
case 'ios':
4243
this.senderMap[pushType] = this.sendToAPNS.bind(this);
43-
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
4444
break;
4545
case 'android':
4646
this.senderMap[pushType] = this.sendToGCM.bind(this);
47-
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
4847
break;
4948
}
5049
}
@@ -69,10 +68,20 @@ export class SNSPushAdapter extends PushAdapter {
6968
}
7069

7170
//Generate proper json for APNS message
72-
static generateiOSPayload(data) {
73-
return {
74-
'APNS': JSON.stringify(data)
75-
};
71+
static generateiOSPayload(data, production) {
72+
var prefix = "";
73+
74+
if (production) {
75+
prefix = "APNS";
76+
} else {
77+
prefix = "APNS_SANDBOX"
78+
}
79+
80+
var notification = APNS.generateNotification(data.data, data.expirationTime);
81+
82+
var payload = {};
83+
payload[prefix] = notification.compile();
84+
return payload;
7685
}
7786

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

8897
sendToAPNS(data, devices) {
89-
var payload = SNSPushAdapter.generateiOSPayload(data);
9098

91-
return this.sendToSNS(payload, devices, 'ios');
99+
var iosPushConfig = this.snsConfig['ios'];
100+
101+
let iosConfigs = [];
102+
if (Array.isArray(iosPushConfig)) {
103+
iosConfigs = iosConfigs.concat(iosPushConfig);
104+
} else {
105+
iosConfigs.push(iosPushConfig)
106+
}
107+
108+
let promises = [];
109+
110+
for (let iosConfig of iosConfigs) {
111+
112+
let production = iosConfig.production || false;
113+
var payload = SNSPushAdapter.generateiOSPayload(data, production);
114+
115+
var deviceSends = [];
116+
for (let device of devices) {
117+
118+
// Follow the same logic as APNS service. If no appIdentifier, send it!
119+
if (!device.appIdentifier || device.appIdentifier === '') {
120+
deviceSends.push(device);
121+
}
122+
123+
else if (device.appIdentifier === iosConfig.bundleId) {
124+
deviceSends.push(device);
125+
}
126+
}
127+
if (deviceSends.length > 0) {
128+
promises.push(this.sendToSNS(payload, deviceSends, iosConfig.ARN));
129+
}
130+
}
131+
132+
return promises;
92133
}
93134

94135
sendToGCM(data, devices) {
95136
var payload = SNSPushAdapter.generateAndroidPayload(data);
96-
return this.sendToSNS(payload, devices, 'android');
137+
var pushConfig = this.snsConfig['android'];
138+
139+
return this.sendToSNS(payload, devices, pushConfig.ARN);
97140
}
98141

99-
sendToSNS(payload, devices, pushType) {
142+
sendToSNS(payload, devices, platformArn) {
100143
// Exchange the device token for the Amazon resource ID
144+
101145
let exchangePromises = devices.map((device) => {
102-
return this.exchangeTokenPromise(device, pushType);
146+
return this.exchangeTokenPromise(device, platformArn);
103147
});
104148

105149
// Publish off to SNS!
@@ -117,9 +161,9 @@ export class SNSPushAdapter extends PushAdapter {
117161
/**
118162
* Request a Amazon Resource Identifier if one is not set.
119163
*/
120-
getPlatformArn(device, pushType, callback) {
164+
getPlatformArn(device, arn, callback) {
121165
var params = {
122-
PlatformApplicationArn: this.arnMap[pushType],
166+
PlatformApplicationArn: arn,
123167
Token: device.deviceToken
124168
};
125169

@@ -129,9 +173,10 @@ export class SNSPushAdapter extends PushAdapter {
129173
/**
130174
* Exchange the device token for an ARN
131175
*/
132-
exchangeTokenPromise(device, pushType) {
176+
exchangeTokenPromise(device, platformARN) {
133177
return new Parse.Promise((resolve, reject) => {
134-
this.getPlatformArn(device, pushType, (err, data) => {
178+
179+
this.getPlatformArn(device, platformARN, (err, data) => {
135180
if (data.EndpointArn) {
136181
resolve(data.EndpointArn);
137182
} else {
@@ -174,6 +219,7 @@ export class SNSPushAdapter extends PushAdapter {
174219

175220
let sendPromises = Object.keys(deviceMap).forEach((pushType) => {
176221
var devices = deviceMap[pushType];
222+
177223
var sender = this.senderMap[pushType];
178224
return sender(data, devices);
179225
});

0 commit comments

Comments
 (0)