From ee3b37d4a25acc2d6b0d918df0c37ae454953dd1 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 1 Mar 2016 15:10:06 -0500 Subject: [PATCH] Adds support for badging on iOS --- spec/PushController.spec.js | 118 ++++++++++++++++++++++++++++++ src/Controllers/PushController.js | 52 ++++++++++++- 2 files changed, 169 insertions(+), 1 deletion(-) diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 6c86b011a1..1821d1a3cf 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -1,5 +1,7 @@ var PushController = require('../src/Controllers/PushController').PushController; +var Config = require('../src/Config'); + describe('PushController', () => { it('can check valid master key of request', (done) => { // Make mock request @@ -127,5 +129,121 @@ describe('PushController', () => { }).toThrow(); done(); }); + + it('properly increment badges', (done) => { + + var payload = { + alert: "Hello World!", + badge: "Increment", + } + var installations = []; + while(installations.length != 10) { + var installation = new Parse.Object("_Installation"); + installation.set("installationId", "installation_"+installations.length); + installation.set("deviceToken","device_token_"+installations.length) + installation.set("badge", installations.length); + installation.set("originalBadge", installations.length); + installation.set("deviceType", "ios"); + installations.push(installation); + } + + while(installations.length != 15) { + var installation = new Parse.Object("_Installation"); + installation.set("installationId", "installation_"+installations.length); + installation.set("deviceToken","device_token_"+installations.length) + installation.set("deviceType", "android"); + installations.push(installation); + } + + var pushAdapter = { + send: function(body, installations) { + var badge = body.badge; + installations.forEach((installation) => { + if (installation.deviceType == "ios") { + expect(installation.badge).toEqual(badge); + expect(installation.originalBadge+1).toEqual(installation.badge); + } else { + expect(installation.badge).toBeUndefined(); + } + }) + return Promise.resolve({ + body: body, + installations: installations + }) + }, + getValidPushTypes: function() { + return ["ios", "android"]; + } + } + + var config = new Config(Parse.applicationId); + var auth = { + isMaster: true + } + + var pushController = new PushController(pushAdapter, Parse.applicationId); + Parse.Object.saveAll(installations).then((installations) => { + return pushController.sendPush(payload, {}, config, auth); + }).then((result) => { + done(); + }, (err) => { + console.error(err); + fail("should not fail"); + done(); + }); + + }); + + it('properly set badges to 1', (done) => { + + var payload = { + alert: "Hello World!", + badge: 1, + } + var installations = []; + while(installations.length != 10) { + var installation = new Parse.Object("_Installation"); + installation.set("installationId", "installation_"+installations.length); + installation.set("deviceToken","device_token_"+installations.length) + installation.set("badge", installations.length); + installation.set("originalBadge", installations.length); + installation.set("deviceType", "ios"); + installations.push(installation); + } + + var pushAdapter = { + send: function(body, installations) { + var badge = body.badge; + installations.forEach((installation) => { + expect(installation.badge).toEqual(badge); + expect(1).toEqual(installation.badge); + }) + return Promise.resolve({ + body: body, + installations: installations + }) + }, + getValidPushTypes: function() { + return ["ios"]; + } + } + + var config = new Config(Parse.applicationId); + var auth = { + isMaster: true + } + + var pushController = new PushController(pushAdapter, Parse.applicationId); + Parse.Object.saveAll(installations).then((installations) => { + return pushController.sendPush(payload, {}, config, auth); + }).then((result) => { + done(); + }, (err) => { + console.error(err); + fail("should not fail"); + done(); + }); + + }) }); diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 2e2134a6c4..55cb609517 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -3,9 +3,11 @@ import PromiseRouter from '../PromiseRouter'; import rest from '../rest'; import AdaptableController from './AdaptableController'; import { PushAdapter } from '../Adapters/Push/PushAdapter'; +import deepcopy from 'deepcopy'; import features from '../features'; const FEATURE_NAME = 'push'; +const UNSUPPORTED_BADGE_KEY = "unsupported"; export class PushController extends AdaptableController { @@ -58,7 +60,55 @@ export class PushController extends AdaptableController { body['expiration_time'] = PushController.getExpirationTime(body); // TODO: If the req can pass the checking, we return immediately instead of waiting // pushes to be sent. We probably change this behaviour in the future. - rest.find(config, auth, '_Installation', where).then(function(response) { + let badgeUpdate = Promise.resolve(); + + if (body.badge) { + var op = {}; + if (body.badge == "Increment") { + op = {'$inc': {'badge': 1}} + } else if (Number(body.badge)) { + op = {'$set': {'badge': body.badge } } + } else { + throw "Invalid value for badge, expected number or 'Increment'"; + } + let updateWhere = deepcopy(where); + + // Only on iOS! + updateWhere.deviceType = 'ios'; + + // TODO: @nlutsenko replace with better thing + badgeUpdate = config.database.rawCollection("_Installation").then((coll) => { + return coll.update(updateWhere, op, { multi: true }); + }); + } + + return badgeUpdate.then(() => { + return rest.find(config, auth, '_Installation', where) + }).then((response) => { + if (body.badge && body.badge == "Increment") { + // Collect the badges to reduce the # of calls + let badgeInstallationsMap = response.results.reduce((map, installation) => { + let badge = installation.badge; + if (installation.deviceType != "ios") { + badge = UNSUPPORTED_BADGE_KEY; + } + map[badge] = map[badge] || []; + map[badge].push(installation); + return map; + }, {}); + + // Map the on the badges count and return the send result + let promises = Object.keys(badgeInstallationsMap).map((badge) => { + let payload = deepcopy(body); + if (badge == UNSUPPORTED_BADGE_KEY) { + delete payload.badge; + } else { + payload.badge = parseInt(badge); + } + return pushAdapter.send(payload, badgeInstallationsMap[badge]); + }); + return Promise.all(promises); + } return pushAdapter.send(body, response.results); }); }