Skip to content

Commit a9089ec

Browse files
authored
Merge pull request #10 from parse-server-modules/improveAPNSBatching
Push notifications to APNS per batches
2 parents d789ef6 + 7c0235d commit a9089ec

File tree

2 files changed

+129
-19
lines changed

2 files changed

+129
-19
lines changed

spec/APNS.spec.js

+100-3
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,18 @@ describe('APNS', () => {
290290
{
291291
deviceToken: '112233',
292292
appIdentifier: 'bundleId'
293+
},
294+
{
295+
deviceToken: '112234',
296+
appIdentifier: 'bundleId'
297+
},
298+
{
299+
deviceToken: '112235',
300+
appIdentifier: 'bundleId'
301+
},
302+
{
303+
deviceToken: '112236',
304+
appIdentifier: 'bundleId'
293305
}
294306
];
295307

@@ -299,9 +311,94 @@ describe('APNS', () => {
299311
var notification = args[0];
300312
expect(notification.alert).toEqual(data.data.alert);
301313
expect(notification.expiry).toEqual(data['expiration_time']/1000);
302-
var apnDevice = args[1]
303-
expect(apnDevice.connIndex).toEqual(0);
304-
expect(apnDevice.appIdentifier).toEqual('bundleId');
314+
var apnDevices = args[1];
315+
apnDevices.forEach((apnDevice) => {
316+
expect(apnDevice.connIndex).toEqual(0);
317+
expect(apnDevice.appIdentifier).toEqual('bundleId');
318+
})
319+
done();
320+
});
321+
322+
it('can send APNS notification to multiple bundles', (done) => {
323+
var args = [{
324+
cert: 'prodCert.pem',
325+
key: 'prodKey.pem',
326+
production: true,
327+
bundleId: 'bundleId'
328+
},{
329+
cert: 'devCert.pem',
330+
key: 'devKey.pem',
331+
production: false,
332+
bundleId: 'bundleId.dev'
333+
}];
334+
335+
var apns = new APNS(args);
336+
var conn = {
337+
pushNotification: jasmine.createSpy('send'),
338+
bundleId: 'bundleId'
339+
};
340+
var conndev = {
341+
pushNotification: jasmine.createSpy('send'),
342+
bundleId: 'bundleId.dev'
343+
};
344+
apns.conns = [ conn, conndev ];
345+
// Mock data
346+
var expirationTime = 1454571491354
347+
var data = {
348+
'expiration_time': expirationTime,
349+
'data': {
350+
'alert': 'alert'
351+
}
352+
}
353+
// Mock devices
354+
var devices = [
355+
{
356+
deviceToken: '112233',
357+
appIdentifier: 'bundleId'
358+
},
359+
{
360+
deviceToken: '112234',
361+
appIdentifier: 'bundleId'
362+
},
363+
{
364+
deviceToken: '112235',
365+
appIdentifier: 'bundleId'
366+
},
367+
{
368+
deviceToken: '112235',
369+
appIdentifier: 'bundleId.dev'
370+
},
371+
{
372+
deviceToken: '112236',
373+
appIdentifier: 'bundleId.dev'
374+
}
375+
];
376+
377+
var promise = apns.send(data, devices);
378+
379+
expect(conn.pushNotification).toHaveBeenCalled();
380+
var args = conn.pushNotification.calls.first().args;
381+
var notification = args[0];
382+
expect(notification.alert).toEqual(data.data.alert);
383+
expect(notification.expiry).toEqual(data['expiration_time']/1000);
384+
var apnDevices = args[1];
385+
expect(apnDevices.length).toBe(3);
386+
apnDevices.forEach((apnDevice) => {
387+
expect(apnDevice.connIndex).toEqual(0);
388+
expect(apnDevice.appIdentifier).toEqual('bundleId');
389+
})
390+
391+
expect(conndev.pushNotification).toHaveBeenCalled();
392+
args = conndev.pushNotification.calls.first().args;
393+
notification = args[0];
394+
expect(notification.alert).toEqual(data.data.alert);
395+
expect(notification.expiry).toEqual(data['expiration_time']/1000);
396+
apnDevices = args[1];
397+
expect(apnDevices.length).toBe(2);
398+
apnDevices.forEach((apnDevice) => {
399+
expect(apnDevice.connIndex).toEqual(1);
400+
expect(apnDevice.appIdentifier).toEqual('bundleId.dev');
401+
});
305402
done();
306403
});
307404
});

src/APNS.js

+29-16
Original file line numberDiff line numberDiff line change
@@ -104,34 +104,47 @@ APNS.prototype.send = function(data, devices) {
104104
let coreData = data.data;
105105
let expirationTime = data['expiration_time'];
106106
let notification = generateNotification(coreData, expirationTime);
107-
108-
let promises = devices.map((device) => {
107+
let allPromises = [];
108+
let devicesPerConnIndex = {};
109+
// Start by clustering the devices per connections
110+
devices.forEach((device) => {
109111
let qualifiedConnIndexs = chooseConns(this.conns, device);
110-
// We can not find a valid conn, just ignore this device
111112
if (qualifiedConnIndexs.length == 0) {
112113
log.error(LOG_PREFIX, 'no qualified connections for %s %s', device.appIdentifier, device.deviceToken);
113-
return Promise.resolve({
114+
let promise = Promise.resolve({
114115
transmitted: false,
115116
device: {
116117
deviceToken: device.deviceToken,
117118
deviceType: 'ios'
118119
},
119120
result: {error: 'No connection available'}
120121
});
122+
allPromises.push(promise);
123+
} else {
124+
let apnDevice = new apn.Device(device.deviceToken);
125+
apnDevice.connIndex = qualifiedConnIndexs[0];
126+
if (device.appIdentifier) {
127+
apnDevice.appIdentifier = device.appIdentifier;
128+
}
129+
devicesPerConnIndex[apnDevice.connIndex] = devicesPerConnIndex[apnDevice.connIndex] || [];
130+
devicesPerConnIndex[apnDevice.connIndex].push(apnDevice);
121131
}
122-
let conn = this.conns[qualifiedConnIndexs[0]];
123-
let apnDevice = new apn.Device(device.deviceToken);
124-
apnDevice.connIndex = qualifiedConnIndexs[0];
125-
// Add additional appIdentifier info to apn device instance
126-
if (device.appIdentifier) {
127-
apnDevice.appIdentifier = device.appIdentifier;
128-
}
129-
return new Promise((resolve, reject) => {
130-
apnDevice.callback = resolve;
131-
conn.pushNotification(notification, apnDevice);
132+
})
133+
134+
allPromises = Object.keys(devicesPerConnIndex).reduce((memo, connIndex) => {
135+
let devices = devicesPerConnIndex[connIndex];
136+
// Create a promise, attach the callback
137+
let promises = devices.map((apnDevice) => {
138+
return new Promise((resolve, reject) => {
139+
apnDevice.callback = resolve;
140+
});
132141
});
133-
});
134-
return Parse.Promise.when(promises);
142+
let conn = this.conns[connIndex];
143+
conn.pushNotification(notification, devices);
144+
return memo.concat(promises);
145+
}, allPromises);
146+
147+
return Promise.all(allPromises);
135148
}
136149

137150
function handleTransmissionError(conns, errCode, notification, apnDevice) {

0 commit comments

Comments
 (0)