Skip to content

Adds support for badging on iOS #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions spec/PushController.spec.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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();
});

})

});
52 changes: 51 additions & 1 deletion src/Controllers/PushController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to reuse this code for Amazon SNS?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually don't need to use this piece, since this happens before PushAdapter, which is the extension point where SNS would be plugged in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better...thanks!

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") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine, what do you think if we would inplace mutate every installation instead before sending?
Which looks like you already sort of do with collection iteration aka results.reduce, but this would probably reduce complexity of reading this...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, would actually remove the requirement to send multiple times, since the complexity of sending with a badge and without is the same.

// 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);
});
}
Expand Down