Merge pull request #5212 from HabitRPG/negue-WIP-Push-notifications

Negue wip push notifications
This commit is contained in:
Blade Barringer 2015-05-18 11:14:53 -05:00
commit bd76b762e6
14 changed files with 444 additions and 10 deletions

View file

@ -6,6 +6,8 @@
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Options > Social > Party.",
"inviteParty": "Invite Party",
"questInvitation": "Quest Invitation: ",
"questInvitationTitle": "Quest Invitation",
"questInvitationInfo": "Invitation for the Quest <%= quest %>",
"askLater": "Ask Later",
"buyQuest": "Buy Quest",
"accepted": "Accepted",

View file

@ -93,6 +93,7 @@
"wonChallenge": "You Won a Challenge",
"newPM": "Received Private Message",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
"invitedParty": "Invited To Party",
"invitedGuild": "Invited To Guild",

View file

@ -653,6 +653,19 @@ api.wrap = (user, main=true) ->
user.markModified? 'preferences.webhooks'
cb? null, user.preferences.webhooks
# ------
# Push Notifications
# ------
addPushDevice: (req, cb) ->
user.pushDevices = [] unless user.pushDevices
pd = user.pushDevices
item = {regId:req.body.regId, type:req.body.type};
i = _.findIndex pd, {regId: item.regId}
pd.push(item) unless i != -1
cb? null, user.pushDevices
# ------
# Inbox
# ------

View file

@ -51,5 +51,12 @@
"token": "token",
"username": "username",
"password": "password"
},
"PUSH_CONFIGS": {
"GCM_SERVER_API_KEY": "",
"APN_PEM_FILES": {
"KEY": "key.pem",
"CERT": "cert.pem"
}
}
}

View file

@ -31,11 +31,11 @@
"grunt-nodemon": "~0.3.0",
"grunt-spritesmith": "~3.5.0",
"icalendar": "git://github.com/lefnire/node-icalendar#master",
"image-size": "~0.3.2",
"in-app-purchase": "^0.2.0",
"jade": "~1.7.0",
"js2xmlparser": "~0.1.2",
"lodash": "~2.4.1",
"image-size": "~0.3.2",
"loggly": "~1.0.8",
"method-override": "~2.2.0",
"moment": "~2.8.3",
@ -51,6 +51,7 @@
"paypal-ipn": "2.1.0",
"paypal-rest-sdk": "^1.2.1",
"pretty-data": "git://github.com/vkiryukhin/pretty-data#master",
"push-notify": "^1.1.1",
"qs": "^2.3.2",
"request": "~2.44.0",
"s3-upload-stream": "^1.0.6",
@ -102,9 +103,11 @@
"mongoskin": "~0.6.1",
"phantomjssmith": "~0.5.4",
"protractor": "~2.0.0",
"rewire": "^2.3.3",
"rimraf": "^2.2.8",
"shelljs": "^0.4.0",
"sinon": "^1.12.2",
"sinon-chai": "^2.7.0",
"superagent": "~0.15.7",
"superagent-defaults": "~0.1.5",
"vinyl-source-stream": "^1.0.0",

View file

@ -12,7 +12,8 @@ global.shared = require("../../common")
global.User = require("../../website/src/models/user").model
global.chai = require("chai")
global.expect = require("chai").expect
chai.use(require("sinon-chai"))
global.expect = chai.expect
##############################
# Nconf config

View file

@ -0,0 +1,306 @@
'use strict'
app = require("../../website/src/server")
rewire = require('rewire')
sinon = require('sinon')
describe "Push-Notifications", ->
before (done) ->
registerNewUser(done, true)
describe "POST /user/pushDevice", ->
it "Registers a DeviceID", (done) ->
request.post(baseURL + "/user/pushDevice").send(
{ regId: "123123", type: "android"}
).end (res) ->
expectCode res, 200
User.findOne
_id: global.user._id
, (err, _user) ->
expect(_user.pushDevices.length).to.equal 1
expect(_user.pushDevices[0].regId).to.equal "123123"
done()
describe "Events that send push notifications", ->
pushSpy = { sendNotify: sinon.spy() }
afterEach (done) ->
pushSpy.sendNotify.reset()
done()
context "Challenges", ->
challenges = rewire("../../website/src/controllers/challenges")
challenges.__set__('pushNotify', pushSpy)
challengeMock = {
findById: (arg, cb) ->
cb(null, {leader: user._id, name: 'challenge-name'})
}
userMock = {
findById: (arg, cb) ->
cb(null, user)
}
challenges.__set__('Challenge', challengeMock)
challenges.__set__('User', userMock)
challenges.__set__('closeChal', -> true)
beforeEach (done) ->
registerNewUser ->
user.preferences.emailNotifications.wonChallenge = false
user.save = (cb) -> cb(null, user)
done()
, true
it "sends a push notification when you win a challenge", (done) ->
req = {
params: { cid: 'challenge-id' }
query: {uid: 'user-id'}
}
res = {
locals: { user: user }
}
challenges.selectWinner req, res
setTimeout -> # Allow selectWinner to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
user,
'You Won a Challenge',
'challenge-name'
)
done()
, 100
context "Groups", ->
recipient = null
groups = rewire("../../website/src/controllers/groups")
groups.__set__('questStart', -> true)
groups.__set__('pushNotify', pushSpy)
before (done) ->
registerNewUser (err,_user)->
recipient = _user
recipient.invitations.guilds = []
recipient.save = (cb) -> cb(null, recipient)
recipient.preferences.emailNotifications.invitedGuild = false
recipient.preferences.emailNotifications.invitedParty = false
recipient.preferences.emailNotifications.invitedQuest = false
userMock = {
findById: (arg, cb) ->
cb(null, recipient)
find: (arg, arg2, cb) ->
cb(null, [recipient])
}
groups.__set__('User', userMock)
done()
, false
it "sends a push notification when invited to a guild", (done) ->
group = { _id: 'guild-id', name: 'guild-name', type: 'guild', members: [user._id], invites: [] }
group.save = (cb) -> cb(null, group)
req = {
body: { uuids: [recipient._id] }
}
res = {
locals: { group: group, user: user }
json: -> return true
}
groups.invite req, res
setTimeout -> # Allow invite to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Invited To Guild',
group.name
)
done()
, 100
it "sends a push notification when invited to a party", (done) ->
group = { _id: 'party-id', name: 'party-name', type: 'party', members: [user._id], invites: [] }
group.save = (cb) -> cb(null, group)
req = {
body: { uuids: [recipient._id] }
}
res = {
locals: { group: group, user: user }
json: -> return true
}
groups.invite req, res
setTimeout -> # Allow invite to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Invited To Party',
group.name
)
done()
, 100
it "sends a push notification when invited to a quest", (done) ->
group = { _id: 'party-id', name: 'party-name', type: 'party', members: [user._id, recipient._id], invites: [], quest: {}}
user.items.quests.hedgehog = 5
group.save = (cb) -> cb(null, group)
req = {
body: { uuids: [recipient._id] }
query: { key: 'hedgehog' }
}
res = {
locals: { group: group, user: user }
json: -> return true
}
groups.questAccept req, res
setTimeout -> # Allow questAccept to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Quest Invitation',
'Invitation for the Quest The Hedgebeast'
)
done()
, 100
describe "Gifts", ->
recipient = null
before (done) ->
registerNewUser (err, _user) ->
recipient = _user
recipient.preferences.emailNotifications.giftedGems = false
user.balance = 4
user.save = -> return true
recipient.save = -> return true
done()
, false
context "sending gems from balance", ->
members = rewire("../../website/src/controllers/members")
members.sendMessage = -> true
members.__set__('pushNotify', pushSpy)
members.__set__ 'fetchMember', (id) ->
return (cb) -> cb(null, recipient)
it "sends a push notification", (done) ->
req = {
params: { uuid: "uuid" },
body: {
type: 'gems',
gems: { amount: 1 }
}
}
res = { locals: { user: user } }
members.sendGift req, res
setTimeout -> # Allow sendGift to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Gifted Gems',
'1 Gems - by ' + user.profile.name
)
done()
, 100
describe "Purchases", ->
payments = rewire("../../website/src/controllers/payments")
payments.__set__('pushNotify', pushSpy)
membersMock = { sendMessage: -> true }
payments.__set__('members', membersMock)
context "buying gems as a purchased gift", ->
it "sends a push notification", (done) ->
data = {
user: user,
gift: {
member: recipient,
gems: { amount: 1 }
}
}
payments.buyGems data
setTimeout -> # Allow buyGems to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Gifted Gems',
'1 Gems - by ' + user.profile.name
)
done()
, 100
it "does not send a push notification if buying gems for self", (done) ->
data = {
user: user,
gift: {
member: user
gems: { amount: 1 }
}
}
payments.buyGems data
setTimeout -> # Allow buyGems to finish
expect(pushSpy.sendNotify).to.not.have.been.called
done()
, 100
context "sending a subscription as a purchased gift", ->
it "sends a push notification", (done) ->
data = {
user: user,
gift: {
member: recipient
subscription: { key: 'basic_6mo' }
}
}
payments.createSubscription data
setTimeout -> # Allow createSubscription to finish
expect(pushSpy.sendNotify).to.have.been.calledOnce
expect(pushSpy.sendNotify).to.have.been.calledWith(
recipient,
'Gifted Subscription',
'6 months - by ' + user.profile.name
)
done()
, 100
it "does not send a push notification if buying subscription for self", (done) ->
data = {
user: user,
gift: {
member: user
subscription: { key: 'basic_6mo' }
}
}
payments.createSubscription data
setTimeout -> # Allow buyGems to finish
expect(pushSpy.sendNotify).to.not.have.been.called
done()
, 100

View file

@ -11,7 +11,7 @@ var logging = require('./../logging');
var csv = require('express-csv');
var utils = require('../utils');
var api = module.exports;
var pushNotify = require('./pushNotifications');
/*
------------------------------------------------------------------------
@ -347,6 +347,9 @@ api.selectWinner = function(req, res, next) {
{name: 'CHALLENGE_NAME', content: chal.name}
]);
}
pushNotify.sendNotify(saved, shared.i18n.t('wonChallenge'), chal.name);
closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb);
}
], function(err){

View file

@ -15,6 +15,7 @@ var Challenge = require('./../models/challenge').model;
var EmailUnsubscription = require('./../models/emailUnsubscription').model;
var isProd = nconf.get('NODE_ENV') === 'production';
var api = module.exports;
var pushNotify = require('./pushNotifications');
/*
------------------------------------------------------------------------
@ -578,9 +579,13 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
function sendInvite (){
if(group.type === 'guild'){
invite.invitations.guilds.push({id: group._id, name: group.name, inviter:res.locals.user._id});
pushNotify.sendNotify(invite, shared.i18n.t('invitedGuild'), group.name);
}else{
//req.body.type in 'guild', 'party'
invite.invitations.party = {id: group._id, name: group.name, inviter:res.locals.user._id};
pushNotify.sendNotify(invite, shared.i18n.t('invitedParty'), group.name);
}
group.invites.push(invite._id);
@ -908,6 +913,10 @@ api.questAccept = function(req, res, next) {
group.quest.leader = user._id;
} else {
group.quest.members[m] = undefined;
User.findById(m, function(err,groupMember){
pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() }));
});
}
});

View file

@ -7,6 +7,7 @@ var _ = require('lodash');
var shared = require('../../../common');
var utils = require('../utils');
var nconf = require('nconf');
var pushNotify = require('./pushNotifications');
var fetchMember = function(uuid, restrict){
return function(cb){
@ -96,12 +97,18 @@ api.sendGift = function(req, res, next){
member.balance += amt;
user.balance -= amt;
api.sendMessage(user, member, req.body);
var byUsername = utils.getUserInfo(user, ['name']).name;
if(member.preferences.emailNotifications.giftedGems !== false){
utils.txnEmail(member, 'gifted-gems', [
{name: 'GIFTER', content: utils.getUserInfo(user, ['name']).name},
{name: 'GIFTER', content: byUsername},
{name: 'X_GEMS_GIFTED', content: req.body.gems.amount}
]);
}
pushNotify.sendNotify(member, shared.i18n.t('giftedGems'), shared.i18n.t('giftedGemsInfo', { amount: req.body.gems.amount, name: byUsername }));
return async.parallel([
function (cb2) { member.save(cb2) },
function (cb2) { user.save(cb2) }

View file

@ -12,6 +12,7 @@ var async = require('async');
var iap = require('./iap');
var mongoose= require('mongoose');
var cc = require('coupon-code');
var pushNotify = require('./../pushNotifications');
function revealMysteryItems(user) {
_.each(shared.content.gear.flat, function(item) {
@ -75,12 +76,19 @@ exports.createSubscription = function(data, cb) {
data.user.purchased.txnCount++;
if (data.gift){
members.sendMessage(data.user, data.gift.member, data.gift);
var byUserName = utils.getUserInfo(data.user, ['name']).name;
if(data.gift.member.preferences.emailNotifications.giftedSubscription !== false){
utils.txnEmail(data.gift.member, 'gifted-subscription', [
{name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name},
{name: 'GIFTER', content: byUserName},
{name: 'X_MONTHS_SUBSCRIPTION', content: months}
]);
}
if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), months + " months - by "+ byUserName);
}
}
async.parallel([
function(cb2){data.user.save(cb2)},
@ -119,13 +127,20 @@ exports.buyGems = function(data, cb) {
utils.ga.transaction(data.user._id, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send();
}
if (data.gift){
var byUsername = utils.getUserInfo(data.user, ['name']).name;
var gemAmount = data.gift.gems.amount || 20;
members.sendMessage(data.user, data.gift.member, data.gift);
if(data.gift.member.preferences.emailNotifications.giftedGems !== false){
utils.txnEmail(data.gift.member, 'gifted-gems', [
{name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name},
{name: 'X_GEMS_GIFTED', content: data.gift.gems.amount || 20}
{name: 'GIFTER', content: byUsername},
{name: 'X_GEMS_GIFTED', content: gemAmount}
]);
}
if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), gemAmount + ' Gems - by '+byUsername);
}
}
async.parallel([
function(cb2){data.user.save(cb2)},

View file

@ -0,0 +1,52 @@
var api = module.exports;
var _ = require('lodash');
var nconf = require('nconf');
var pushNotify = require('push-notify');
var gcmApiKey = nconf.get("PUSH_CONFIGS:GCM_SERVER_API_KEY");
var gcm = gcmApiKey ? pushNotify.gcm({
apiKey: gcmApiKey,
retries: 3
}) : undefined;
if(gcm){
gcm.on('transmitted', function (result, message, registrationId) {
console.info("transmitted", result, message, registrationId);
});
gcm.on('transmissionError', function (error, message, registrationId) {
console.info("transmissionError", error, message, registrationId);
});
gcm.on('updated', function (result, registrationId) {
console.info("updated", result, registrationId);
});
}
api.sendNotify = function(user, title, msg, timeToLive){
timeToLive = timeToLive || 15;
_.forEach(user.pushDevices, function(pushDevice){
switch(pushDevice.type){
case "android":
if(gcm){
gcm.send({
registrationId: pushDevice.regId,
//collapseKey: 'COLLAPSE_KEY',
delayWhileIdle: true,
timeToLive: timeToLive,
data: {
title: title,
message: msg
}
});
}
break;
case "ios":
break;
}
});
};

View file

@ -393,7 +393,12 @@ var UserSchema = new Schema({
todos: {type:[TaskSchemas.TodoSchema]},
rewards: {type:[TaskSchemas.RewardSchema]},
extra: Schema.Types.Mixed
extra: Schema.Types.Mixed,
pushDevices: {type: [{
regId: {type: String},
type: {type: String}
}],'default': []}
}, {
strict: true,

View file

@ -396,6 +396,16 @@ module.exports = (swagger, v2) ->
]
action: user.deleteWebhook
# Push Notifications
"/user/pushDevice":
spec:
method: 'POST'
description: 'Add a new push devices registration ID'
parameters: [
body '','New push registration { regId: "123123", type: "android"}','object'
]
action: user.addPushDevice
# ---------------------------------
# Groups
# ---------------------------------