Skip to content

Commit cf5e031

Browse files
committed
Adding SNS
Fixups Get basic tests for SNS going. Fix to nest createPlatformEndpoint before sending payload Don't need my own classifyInstallations Use refactored GCM code Fix this JSON too. Refactor this code.
1 parent 43632aa commit cf5e031

File tree

2 files changed

+357
-0
lines changed

2 files changed

+357
-0
lines changed

spec/SNSPushAdapter.spec.js

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
2+
var SNSPushAdapter = require('../src/Adapters/Push/SNSPushAdapter');
3+
describe('SNSPushAdapter', () => {
4+
5+
var pushConfig = {
6+
pushTypes: {
7+
ios: "APNS_ID",
8+
android: "GCM_ID"
9+
},
10+
accessKey: "accessKey",
11+
secretKey: "secretKey",
12+
region: "region"
13+
};
14+
15+
it('can be initialized', (done) => {
16+
// Make mock config
17+
18+
var snsPushAdapter = new SNSPushAdapter(pushConfig);
19+
var arnMap = snsPushAdapter.arnMap;
20+
21+
expect(arnMap.ios).toEqual("APNS_ID");
22+
expect(arnMap.android).toEqual("GCM_ID");
23+
24+
done();
25+
});
26+
27+
it('can get valid push types', (done) => {
28+
var snsPushAdapter = new SNSPushAdapter(pushConfig);
29+
expect(snsPushAdapter.getValidPushTypes()).toEqual(['ios', 'android']);
30+
done();
31+
});
32+
33+
it('can classify installation', (done) => {
34+
// Mock installations
35+
var validPushTypes = ['ios', 'android'];
36+
var installations = [
37+
{
38+
deviceType: 'android',
39+
deviceToken: 'androidToken'
40+
},
41+
{
42+
deviceType: 'ios',
43+
deviceToken: 'iosToken'
44+
},
45+
{
46+
deviceType: 'win',
47+
deviceToken: 'winToken'
48+
},
49+
{
50+
deviceType: 'android',
51+
deviceToken: undefined
52+
}
53+
];
54+
var snsPushAdapter = new SNSPushAdapter(pushConfig);
55+
var deviceMap = SNSPushAdapter.classifyInstallations(installations, validPushTypes);
56+
expect(deviceMap['android']).toEqual([makeDevice('androidToken')]);
57+
expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]);
58+
expect(deviceMap['win']).toBe(undefined);
59+
done();
60+
});
61+
62+
it('can send push notifications', (done) => {
63+
var snsPushAdapter = new SNSPushAdapter(pushConfig);
64+
65+
// Mock SNS sender
66+
var snsSender = jasmine.createSpyObj('sns', ['createPlatformEndpoint', 'publish']);
67+
snsPushAdapter.sns = snsSender;
68+
69+
// Mock android ios senders
70+
var androidSender = jasmine.createSpy('send')
71+
var iosSender = jasmine.createSpy('send')
72+
73+
var senderMap = {
74+
ios: iosSender,
75+
android: androidSender
76+
};
77+
snsPushAdapter.senderMap = senderMap;
78+
79+
// Mock installations
80+
var installations = [
81+
{
82+
deviceType: 'android',
83+
deviceToken: 'androidToken'
84+
},
85+
{
86+
deviceType: 'ios',
87+
deviceToken: 'iosToken'
88+
},
89+
{
90+
deviceType: 'win',
91+
deviceToken: 'winToken'
92+
},
93+
{
94+
deviceType: 'android',
95+
deviceToken: undefined
96+
}
97+
];
98+
var data = {};
99+
100+
snsPushAdapter.send(data, installations);
101+
// Check SNS sender
102+
expect(androidSender).toHaveBeenCalled();
103+
var args = androidSender.calls.first().args;
104+
expect(args[0]).toEqual(data);
105+
expect(args[1]).toEqual([
106+
makeDevice('androidToken')
107+
]);
108+
// Check ios sender
109+
expect(iosSender).toHaveBeenCalled();
110+
args = iosSender.calls.first().args;
111+
expect(args[0]).toEqual(data);
112+
expect(args[1]).toEqual([
113+
makeDevice('iosToken')
114+
]);
115+
done();
116+
});
117+
118+
it("can send iOS notifications", (done) => {
119+
var snsSignalPushAdapter = new SNSPushAdapter(pushConfig);
120+
var snsSender = jasmine.createSpyObj('sns', ['createPlatformEndpoint', 'publish']);
121+
snsSignalPushAdapter.sns = snsSender;
122+
123+
var data = {'data': {
124+
'badge': 1,
125+
'alert': "Example content",
126+
'sound': "Example sound",
127+
'content-available': 1
128+
}};
129+
130+
snsSignalPushAdapter.sendToAPNS(data,[{'deviceToken':'iosToken1', 'arn': 'abc'},{'deviceToken':'iosToken2', 'arn': 'abd'}]);
131+
132+
expect(snsSender.publish).toHaveBeenCalled();
133+
var args = snsSender.publish.calls.first().args;
134+
expect(args[0].Message).toEqual(JSON.stringify({'APNS' : { aps : data }}));
135+
done();
136+
});
137+
138+
it("can send Android notifications", (done) => {
139+
var snsSignalPushAdapter = new SNSPushAdapter(pushConfig);
140+
var snsSender = jasmine.createSpyObj('sns', ['createPlatformEndpoint', 'publish']);
141+
snsSignalPushAdapter.sns = snsSender;
142+
143+
var data = {'data' : { 'message': 'test' } };
144+
snsSignalPushAdapter.sendToGCM(data, [{'deviceToken':'androidToken1', 'arn' : '123'},{'deviceToken':'androidToken2', 'arn': '456'}]);
145+
146+
expect(snsSender.publish).toHaveBeenCalled();
147+
var args = snsSender.publish.calls.first().args;
148+
expect(args[0].Message).toEqual(JSON.stringify({
149+
'GCM': { 'data': {'message': 'test'}}}));
150+
done();
151+
});
152+
153+
function makeDevice(deviceToken, appIdentifier, arn) {
154+
return {
155+
deviceToken: deviceToken,
156+
appIdentifier: appIdentifier,
157+
arn: arn
158+
};
159+
}
160+
161+
});

src/Adapters/Push/SNSPushAdapter.js

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"use strict";
2+
// SNSAdapter
3+
//
4+
// Uses SNS for push notification
5+
import PushAdapter from './PushAdapter';
6+
7+
const Parse = require('parse/node').Parse;
8+
const GCM = require('../../GCM');
9+
const cryptoUtils = require('../../cryptoUtils');
10+
11+
const AWS = require('aws-sdk');
12+
const path = require('path');
13+
const rest = require('../../rest');
14+
15+
var DEFAULT_REGION = "us-east-1";
16+
import { classifyInstallations } from './PushAdapterUtils';
17+
18+
export class SNSPushAdapter extends PushAdapter {
19+
20+
// Publish to an SNS endpoint
21+
// Providing AWS access and secret keys is mandatory
22+
// Region will use sane defaults if omitted
23+
constructor(pushConfig = {}) {
24+
super(pushConfig);
25+
this.validPushTypes = ['ios', 'android'];
26+
this.availablePushTypes = [];
27+
this.arnMap = {};
28+
this.senderMap = {};
29+
30+
if (!pushConfig.accessKey || !pushConfig.secretKey) {
31+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
32+
'Need to provide AWS keys');
33+
}
34+
35+
if (pushConfig.pushTypes) {
36+
let pushTypes = Object.keys(pushConfig.pushTypes);
37+
for (let pushType of pushTypes) {
38+
if (this.validPushTypes.indexOf(pushType) < 0) {
39+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
40+
'Push to ' + pushTypes + ' is not supported');
41+
}
42+
this.availablePushTypes.push(pushType);
43+
switch (pushType) {
44+
case 'ios':
45+
this.senderMap[pushType] = this.sendToAPNS.bind(this);
46+
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
47+
break;
48+
case 'android':
49+
this.senderMap[pushType] = this.sendToGCM.bind(this);
50+
this.arnMap[pushType] = pushConfig.pushTypes[pushType];
51+
break;
52+
}
53+
}
54+
}
55+
56+
AWS.config.update({
57+
accessKeyId: pushConfig.accessKey,
58+
secretAccessKey: pushConfig.secretKey,
59+
region: pushConfig.region || DEFAULT_REGION
60+
});
61+
62+
// Instantiate after config is setup.
63+
this.sns = new AWS.SNS();
64+
}
65+
66+
getValidPushTypes() {
67+
return this.availablePushTypes;
68+
}
69+
70+
static classifyInstallations(installations, validTypes) {
71+
return classifyInstallations(installations, validTypes)
72+
}
73+
74+
//Generate proper json for APNS message
75+
generateiOSPayload(data) {
76+
return {
77+
'APNS': JSON.stringify(data)
78+
};
79+
}
80+
81+
// Generate proper json for GCM message
82+
generateAndroidPayload(data) {
83+
var payload = GCM.generateGCMPayload(data);
84+
85+
// SNS is verify sensitive to the body being JSON stringified but not GCM key.
86+
return {
87+
'GCM': JSON.stringify(payload)
88+
};
89+
}
90+
91+
sendToAPNS(data, devices) {
92+
var payload = this.generateiOSPayload(data);
93+
94+
return this.sendToSNS(payload, devices, 'ios');
95+
}
96+
97+
sendToGCM(data, devices) {
98+
var payload = this.generateAndroidPayload(data);
99+
return this.sendToSNS(payload, devices, 'android');
100+
}
101+
102+
sendToSNS(payload, devices, pushType) {
103+
var promises = [];
104+
var exchangePromises = [];
105+
var self = this;
106+
107+
// Exchange the device token for the Amazon resource ID
108+
for (let device of devices) {
109+
exchangePromises.push(this.exchangeTokenPromise(device, pushType));
110+
}
111+
112+
// Publish off to SNS!
113+
Parse.Promise.when(exchangePromises).then(function(arns) {
114+
for (let arn of arns) {
115+
promises.push(self.sendSNSPayload(arn, payload));
116+
}
117+
});
118+
119+
return promises;
120+
}
121+
122+
123+
/**
124+
* Request a Amazon Resource Identifier if one is not set.
125+
*/
126+
getPlatformArn(device, pushType, callback) {
127+
var params = {
128+
PlatformApplicationArn: this.arnMap[pushType],
129+
Token: device.deviceToken
130+
};
131+
132+
this.sns.createPlatformEndpoint(params, callback);
133+
}
134+
135+
/**
136+
* Exchange the device token for an ARN
137+
*/
138+
exchangeTokenPromise(device, pushType) {
139+
return new Parse.Promise((resolve, reject) => {
140+
this.getPlatformArn(device, pushType, function (err, data) {
141+
if (data.EndpointArn) {
142+
console.log("Generating ARN " + data.EndpointArn);
143+
resolve(data.EndpointArn);
144+
} else {
145+
console.log(err);
146+
reject(err);
147+
}
148+
});
149+
});
150+
}
151+
152+
// Bulk publishing is not yet supported on Amazon SNS.
153+
sendSNSPayload(arn, payload) {
154+
155+
var object = {
156+
Message: JSON.stringify(payload),
157+
MessageStructure: 'json',
158+
TargetArn: arn
159+
};
160+
161+
return new Parse.Promise((resolve, reject) => {
162+
this.sns.publish(object, function (err, data) {
163+
if (err != null) {
164+
console.log("Error sending push " + err);
165+
return reject(err);
166+
}
167+
console.log("Sent push to " + object.TargetArn);
168+
resolve(object);
169+
});
170+
});
171+
}
172+
173+
// For a given config object, endpoint and payload, publish via SNS
174+
// Returns a promise containing the SNS object publish response
175+
send(data, installations) {
176+
let sendPromises = [];
177+
let deviceMap = classifyInstallations(installations, this.availablePushTypes);
178+
179+
for (let pushType in deviceMap) {
180+
181+
let sender = this.senderMap[pushType];
182+
183+
if (!sender) {
184+
console.log('Can not find sender for push type %s, %j', pushType, data);
185+
continue;
186+
}
187+
188+
let devices = deviceMap[pushType];
189+
sendPromises.push(sender(data, devices));
190+
}
191+
return Parse.Promise.when(sendPromises);
192+
}
193+
}
194+
195+
export default SNSPushAdapter;
196+
module.exports = SNSPushAdapter;

0 commit comments

Comments
 (0)