diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 9255c5c9..68e52794 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -105,9 +105,9 @@ describe('PushController', () => { }).toThrow(); done(); }); - + it('properly increment badges', (done) => { - + var payload = {data:{ alert: "Hello World!", badge: "Increment", @@ -122,7 +122,7 @@ describe('PushController', () => { installation.set("deviceType", "ios"); installations.push(installation); } - + while(installations.length != 15) { var installation = new Parse.Object("_Installation"); installation.set("installationId", "installation_"+installations.length); @@ -130,7 +130,7 @@ describe('PushController', () => { installation.set("deviceType", "android"); installations.push(installation); } - + var pushAdapter = { send: function(body, installations) { var badge = body.data.badge; @@ -151,14 +151,14 @@ describe('PushController', () => { 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) => { + Parse.Object.saveAll(installations).then((installations) => { return pushController.sendPush(payload, {}, config, auth); }).then((result) => { done(); @@ -167,11 +167,11 @@ describe('PushController', () => { fail("should not fail"); done(); }); - + }); - + it('properly set badges to 1', (done) => { - + var payload = {data: { alert: "Hello World!", badge: 1, @@ -186,7 +186,7 @@ describe('PushController', () => { installation.set("deviceType", "ios"); installations.push(installation); } - + var pushAdapter = { send: function(body, installations) { var badge = body.data.badge; @@ -203,14 +203,14 @@ describe('PushController', () => { 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) => { + Parse.Object.saveAll(installations).then((installations) => { return pushController.sendPush(payload, {}, config, auth); }).then((result) => { done(); @@ -219,15 +219,56 @@ describe('PushController', () => { fail("should not fail"); done(); }); - + }); - + + it('properly creates _PushStatus', (done) => { + + var payload = {data: { + alert: "Hello World!", + badge: 1, + }} + + var pushAdapter = { + send: function(body, installations) { + var badge = body.data.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); + pushController.sendPush(payload, {}, config, auth).then((result) => { + let query = new Parse.Query('_PushStatus'); + return query.find({useMasterKey: true}); + }).then((results) => { + expect(results.length).toBe(1); + let result = results[0]; + expect(result.get('source')).toEqual('rest'); + expect(result.get('query')).toEqual(JSON.stringify({})); + expect(result.get('payload')).toEqual(payload.data); + expect(result.get('status')).toEqual("running"); + done(); + }); + + }); + it('should support full RESTQuery for increment', (done) => { var payload = {data: { alert: "Hello World!", badge: 'Increment', }} - + var pushAdapter = { send: function(body, installations) { return Promise.resolve(); @@ -236,12 +277,12 @@ describe('PushController', () => { return ["ios"]; } } - + var config = new Config(Parse.applicationId); var auth = { isMaster: true } - + let where = { 'deviceToken': { '$inQuery': { diff --git a/src/Adapters/Push/PushAdapter.js b/src/Adapters/Push/PushAdapter.js index 846124b5..30cbed8f 100644 --- a/src/Adapters/Push/PushAdapter.js +++ b/src/Adapters/Push/PushAdapter.js @@ -4,13 +4,13 @@ // // Adapter classes must implement the following functions: // * getValidPushTypes() -// * send(devices, installations) +// * send(devices, installations, pushStatus) // // Default is ParsePushAdapter, which uses GCM for // android push and APNS for ios push. export class PushAdapter { - send(devices, installations) { } + send(devices, installations, pushStatus) { } /** * Get an array of valid push types. diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 15896382..d8b3b792 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -4,8 +4,10 @@ import rest from '../rest'; import AdaptableController from './AdaptableController'; import { PushAdapter } from '../Adapters/Push/PushAdapter'; import deepcopy from 'deepcopy'; +import { md5Hash } from '../cryptoUtils'; import features from '../features'; import RestQuery from '../RestQuery'; +import RestWrite from '../RestWrite'; const FEATURE_NAME = 'push'; const UNSUPPORTED_BADGE_KEY = "unsupported"; @@ -65,7 +67,7 @@ export class PushController extends AdaptableController { } let updateWhere = deepcopy(where); - badgeUpdate = () => { + badgeUpdate = () => { let badgeQuery = new RestQuery(config, auth, '_Installation', updateWhere); return badgeQuery.buildRestWhere().then(() => { let restWhere = deepcopy(badgeQuery.restWhere); @@ -81,8 +83,13 @@ export class PushController extends AdaptableController { }) } } - - return badgeUpdate().then(() => { + let pushStatus; + return Promise.resolve().then(() => { + return this.saveInitialPushStatus(body, where, config); + }).then((res) => { + pushStatus = res.response; + return badgeUpdate(); + }).then(() => { return rest.find(config, auth, '_Installation', where); }).then((response) => { if (body.data && body.data.badge && body.data.badge == "Increment") { @@ -105,14 +112,38 @@ export class PushController extends AdaptableController { } else { payload.data.badge = parseInt(badge); } - return pushAdapter.send(payload, badgeInstallationsMap[badge]); + return pushAdapter.send(payload, badgeInstallationsMap[badge], pushStatus); }); return Promise.all(promises); } - return pushAdapter.send(body, response.results); + return pushAdapter.send(body, response.results, pushStatus); + }).then(() => { + return this.updatePushStatus({status: "running"}, pushStatus, config); }); } + saveInitialPushStatus(body, where, config, options = {source: 'rest'}) { + let pushStatus = { + pushTime: (new Date()).toISOString(), + query: JSON.stringify(where), + payload: body.data, + source: options.source, + title: options.title, + expiry: body.expiration_time, + status: "pending", + numSent: 0, + pushHash: md5Hash(JSON.stringify(body.data)), + ACL: new Parse.ACL() // lockdown! + } + let restWrite = new RestWrite(config, {isMaster: true},'_PushStatus',null, pushStatus); + return restWrite.execute(); + } + + updatePushStatus(update, pushStatus, config) { + let restWrite = new RestWrite(config, {isMaster: true}, '_PushStatus', {objectId: pushStatus.objectId, "status": "pending"}, update); + return restWrite.execute(); + } + /** * Get expiration time from the request body. * @param {Object} request A request object diff --git a/src/Schema.js b/src/Schema.js index 79c49496..2b3e2e39 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -71,7 +71,7 @@ var defaultColumns = { }, _PushStatus: { "pushTime": {type:'String'}, - "source": {type:'String'}, // rest or web + "source": {type:'String'}, // rest or webui "query": {type:'String'}, // the stringified JSON query "payload": {type:'Object'}, // the JSON payload, "title": {type:'String'}, diff --git a/src/cryptoUtils.js b/src/cryptoUtils.js index 47bc2e45..4b529293 100644 --- a/src/cryptoUtils.js +++ b/src/cryptoUtils.js @@ -1,6 +1,6 @@ /* @flow */ -import { randomBytes } from 'crypto'; +import { randomBytes, createHash } from 'crypto'; // Returns a new random hex string of the given even size. export function randomHexString(size: number): string { @@ -44,3 +44,7 @@ export function newObjectId(): string { export function newToken(): string { return randomHexString(32); } + +export function md5Hash(string: string): string { + return createHash('md5').update(string).digest('hex'); +}