From c2c4f514643cab7d9faec5c56b2352833b167d67 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 31 Mar 2016 00:42:47 -0400 Subject: [PATCH 1/2] Push notifications to APNS per batches --- spec/APNS.spec.js | 103 ++++++++++++++++++++++++++++++++++++++++++++-- src/APNS.js | 70 ++++++++++++++++++++++++------- 2 files changed, 155 insertions(+), 18 deletions(-) diff --git a/spec/APNS.spec.js b/spec/APNS.spec.js index 90b485c2..8f2dc5a3 100644 --- a/spec/APNS.spec.js +++ b/spec/APNS.spec.js @@ -290,6 +290,18 @@ describe('APNS', () => { { deviceToken: '112233', appIdentifier: 'bundleId' + }, + { + deviceToken: '112234', + appIdentifier: 'bundleId' + }, + { + deviceToken: '112235', + appIdentifier: 'bundleId' + }, + { + deviceToken: '112236', + appIdentifier: 'bundleId' } ]; @@ -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(); }); }); diff --git a/src/APNS.js b/src/APNS.js index fa801a52..042e63b4 100644 --- a/src/APNS.js +++ b/src/APNS.js @@ -104,13 +104,14 @@ APNS.prototype.send = function(data, devices) { let coreData = data.data; let expirationTime = data['expiration_time']; let notification = generateNotification(coreData, expirationTime); + let allPromises = []; - let promises = devices.map((device) => { + // Start by clustering the devices per connections + let devicesPerConnIndex = devices.reduce((memo, 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, @@ -118,20 +119,59 @@ APNS.prototype.send = function(data, devices) { }, 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; + } + memo[apnDevice.connIndex] = memo[apnDevice.connIndex] || []; + memo[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); + return memo; + }, {}) + + 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) + + // let promises = devices.map((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({ + // transmitted: false, + // device: { + // deviceToken: device.deviceToken, + // deviceType: 'ios' + // }, + // result: {error: 'No connection available'} + // }); + // } + // 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); + // }); + // }); + return Parse.Promise.when(allPromises); } function handleTransmissionError(conns, errCode, notification, apnDevice) { From 7c0235d79b15efde863aba90f7aa2a3c72bc3aff Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 31 Mar 2016 08:15:30 -0400 Subject: [PATCH 2/2] nits --- src/APNS.js | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/src/APNS.js b/src/APNS.js index 042e63b4..a5b996fc 100644 --- a/src/APNS.js +++ b/src/APNS.js @@ -105,9 +105,9 @@ APNS.prototype.send = function(data, devices) { let expirationTime = data['expiration_time']; let notification = generateNotification(coreData, expirationTime); let allPromises = []; - + let devicesPerConnIndex = {}; // Start by clustering the devices per connections - let devicesPerConnIndex = devices.reduce((memo, device) => { + devices.forEach((device) => { let qualifiedConnIndexs = chooseConns(this.conns, device); if (qualifiedConnIndexs.length == 0) { log.error(LOG_PREFIX, 'no qualified connections for %s %s', device.appIdentifier, device.deviceToken); @@ -126,11 +126,10 @@ APNS.prototype.send = function(data, devices) { if (device.appIdentifier) { apnDevice.appIdentifier = device.appIdentifier; } - memo[apnDevice.connIndex] = memo[apnDevice.connIndex] || []; - memo[apnDevice.connIndex].push(apnDevice); + devicesPerConnIndex[apnDevice.connIndex] = devicesPerConnIndex[apnDevice.connIndex] || []; + devicesPerConnIndex[apnDevice.connIndex].push(apnDevice); } - return memo; - }, {}) + }) allPromises = Object.keys(devicesPerConnIndex).reduce((memo, connIndex) => { let devices = devicesPerConnIndex[connIndex]; @@ -143,35 +142,9 @@ APNS.prototype.send = function(data, devices) { let conn = this.conns[connIndex]; conn.pushNotification(notification, devices); return memo.concat(promises); - }, allPromises) + }, allPromises); - // let promises = devices.map((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({ - // transmitted: false, - // device: { - // deviceToken: device.deviceToken, - // deviceType: 'ios' - // }, - // result: {error: 'No connection available'} - // }); - // } - // 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); - // }); - // }); - return Parse.Promise.when(allPromises); + return Promise.all(allPromises); } function handleTransmissionError(conns, errCode, notification, apnDevice) {