Skip to content

Commit

Permalink
Merge pull request #10 from parse-server-modules/improveAPNSBatching
Browse files Browse the repository at this point in the history
Push notifications to APNS per batches
  • Loading branch information
flovilmart authored Jul 8, 2016
2 parents d789ef6 + 7c0235d commit a9089ec
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 19 deletions.
103 changes: 100 additions & 3 deletions spec/APNS.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ describe('APNS', () => {
{
deviceToken: '112233',
appIdentifier: 'bundleId'
},
{
deviceToken: '112234',
appIdentifier: 'bundleId'
},
{
deviceToken: '112235',
appIdentifier: 'bundleId'
},
{
deviceToken: '112236',
appIdentifier: 'bundleId'
}
];

Expand All @@ -299,9 +311,94 @@ describe('APNS', () => {
var notification = args[0];
expect(notification.alert).toEqual(data.data.alert);
expect(notification.expiry).toEqual(data['expiration_time']/1000);
var apnDevice = args[1]
expect(apnDevice.connIndex).toEqual(0);
expect(apnDevice.appIdentifier).toEqual('bundleId');
var apnDevices = args[1];
apnDevices.forEach((apnDevice) => {
expect(apnDevice.connIndex).toEqual(0);
expect(apnDevice.appIdentifier).toEqual('bundleId');
})
done();
});

it('can send APNS notification to multiple bundles', (done) => {
var args = [{
cert: 'prodCert.pem',
key: 'prodKey.pem',
production: true,
bundleId: 'bundleId'
},{
cert: 'devCert.pem',
key: 'devKey.pem',
production: false,
bundleId: 'bundleId.dev'
}];

var apns = new APNS(args);
var conn = {
pushNotification: jasmine.createSpy('send'),
bundleId: 'bundleId'
};
var conndev = {
pushNotification: jasmine.createSpy('send'),
bundleId: 'bundleId.dev'
};
apns.conns = [ conn, conndev ];
// Mock data
var expirationTime = 1454571491354
var data = {
'expiration_time': expirationTime,
'data': {
'alert': 'alert'
}
}
// Mock devices
var devices = [
{
deviceToken: '112233',
appIdentifier: 'bundleId'
},
{
deviceToken: '112234',
appIdentifier: 'bundleId'
},
{
deviceToken: '112235',
appIdentifier: 'bundleId'
},
{
deviceToken: '112235',
appIdentifier: 'bundleId.dev'
},
{
deviceToken: '112236',
appIdentifier: 'bundleId.dev'
}
];

var promise = apns.send(data, devices);

expect(conn.pushNotification).toHaveBeenCalled();
var args = conn.pushNotification.calls.first().args;
var notification = args[0];
expect(notification.alert).toEqual(data.data.alert);
expect(notification.expiry).toEqual(data['expiration_time']/1000);
var apnDevices = args[1];
expect(apnDevices.length).toBe(3);
apnDevices.forEach((apnDevice) => {
expect(apnDevice.connIndex).toEqual(0);
expect(apnDevice.appIdentifier).toEqual('bundleId');
})

expect(conndev.pushNotification).toHaveBeenCalled();
args = conndev.pushNotification.calls.first().args;
notification = args[0];
expect(notification.alert).toEqual(data.data.alert);
expect(notification.expiry).toEqual(data['expiration_time']/1000);
apnDevices = args[1];
expect(apnDevices.length).toBe(2);
apnDevices.forEach((apnDevice) => {
expect(apnDevice.connIndex).toEqual(1);
expect(apnDevice.appIdentifier).toEqual('bundleId.dev');
});
done();
});
});
45 changes: 29 additions & 16 deletions src/APNS.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,34 +104,47 @@ APNS.prototype.send = function(data, devices) {
let coreData = data.data;
let expirationTime = data['expiration_time'];
let notification = generateNotification(coreData, expirationTime);

let promises = devices.map((device) => {
let allPromises = [];
let devicesPerConnIndex = {};
// Start by clustering the devices per connections
devices.forEach((device) => {
let qualifiedConnIndexs = chooseConns(this.conns, device);
// We can not find a valid conn, just ignore this device
if (qualifiedConnIndexs.length == 0) {
log.error(LOG_PREFIX, 'no qualified connections for %s %s', device.appIdentifier, device.deviceToken);
return Promise.resolve({
let promise = Promise.resolve({
transmitted: false,
device: {
deviceToken: device.deviceToken,
deviceType: 'ios'
},
result: {error: 'No connection available'}
});
allPromises.push(promise);
} else {
let apnDevice = new apn.Device(device.deviceToken);
apnDevice.connIndex = qualifiedConnIndexs[0];
if (device.appIdentifier) {
apnDevice.appIdentifier = device.appIdentifier;
}
devicesPerConnIndex[apnDevice.connIndex] = devicesPerConnIndex[apnDevice.connIndex] || [];
devicesPerConnIndex[apnDevice.connIndex].push(apnDevice);
}
let conn = this.conns[qualifiedConnIndexs[0]];
let apnDevice = new apn.Device(device.deviceToken);
apnDevice.connIndex = qualifiedConnIndexs[0];
// Add additional appIdentifier info to apn device instance
if (device.appIdentifier) {
apnDevice.appIdentifier = device.appIdentifier;
}
return new Promise((resolve, reject) => {
apnDevice.callback = resolve;
conn.pushNotification(notification, apnDevice);
})

allPromises = Object.keys(devicesPerConnIndex).reduce((memo, connIndex) => {
let devices = devicesPerConnIndex[connIndex];
// Create a promise, attach the callback
let promises = devices.map((apnDevice) => {
return new Promise((resolve, reject) => {
apnDevice.callback = resolve;
});
});
});
return Parse.Promise.when(promises);
let conn = this.conns[connIndex];
conn.pushNotification(notification, devices);
return memo.concat(promises);
}, allPromises);

return Promise.all(allPromises);
}

function handleTransmissionError(conns, errCode, notification, apnDevice) {
Expand Down

0 comments on commit a9089ec

Please sign in to comment.