diff --git a/spec/helper.js b/spec/helper.js index 7ed21c254f..56282d2ce7 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -219,7 +219,7 @@ afterEach(function(done) { } else { // Other system classes will break Parse.com, so make sure that we don't save anything to _SCHEMA that will // break it. - return ['_User', '_Installation', '_Role', '_Session', '_Product'].indexOf(className) >= 0; + return ['_User', '_Installation', '_Role', '_Session', '_Product', '_Push'].indexOf(className) >= 0; } }}); }); diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 84e2a79d4b..f406456a08 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -93,8 +93,14 @@ export class PushController extends AdaptableController { if (!response.results) { return Promise.reject({error: 'PushController: no results in query'}) } - pushStatus.setRunning(response.results); - return this.sendToAdapter(body, response.results, pushStatus, config); + let installations = response.results; + pushStatus.setRunning(installations); + return this.sendToAdapter(body, response.results, pushStatus, config).then((results) => { + return { + installations, + results + }; + }); }).then((results) => { return pushStatus.complete(results); }).catch((err) => { diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 316d3da362..09ae2ad706 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -73,6 +73,12 @@ const defaultColumns = Object.freeze({ "title": {type:'String'}, "subtitle": {type:'String'}, }, + _Push: { + "result": {type:'Object'}, + "pushStatus": {type:'String'}, + "installation": {type:'Pointer', targetClass:'_Installation'}, + "deviceToken": {type:'String'}, + }, _PushStatus: { "pushTime": {type:'String'}, "source": {type:'String'}, // rest or webui @@ -113,9 +119,9 @@ const requiredColumns = Object.freeze({ _Role: ["name", "ACL"] }); -const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus', '_JobStatus']); +const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_Push', '_PushStatus', '_JobStatus']); -const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig']); +const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Push', '_Hooks', '_GlobalConfig']); // 10 alpha numberic chars + uppercase const userIdRegex = /^[a-zA-Z0-9]{10}$/; diff --git a/src/StatusHandler.js b/src/StatusHandler.js index 8d7718dfc1..1e635f6c97 100644 --- a/src/StatusHandler.js +++ b/src/StatusHandler.js @@ -1,8 +1,10 @@ import { md5Hash, newObjectId } from './cryptoUtils'; import { logger } from './logger'; +import _ from 'lodash'; const PUSH_STATUS_COLLECTION = '_PushStatus'; const JOB_STATUS_COLLECTION = '_JobStatus'; +const PUSH_COLLECTION = '_Push'; export function flatten(array) { return array.reduce((memo, element) => { @@ -34,9 +36,84 @@ function statusHandler(className, database) { return lastPromise; } + function createPush(object) { + return database.create(PUSH_COLLECTION, object).then(() => { + return Promise.resolve(object); + }); + } + + function updatePush(query, updateFields) { + return database.update(PUSH_COLLECTION, query, updateFields); + } + + function insertPushes(pushStatusObjectId, installations) { + // Insert a Push object for each installation we're pushing to + let now = new Date(); + let promises = _.map(installations, installation => { + let pushObjectId = newObjectId(); + let push = { + objectId: pushObjectId, + createdAt: now, + updatedAt: now, + deviceToken: installation.deviceToken, + installation: { + __type: 'Pointer', + className: "_Installation", + objectId: installation.objectId, + }, + pushStatus: pushStatusObjectId + }; + return createPush(push); + }); + return Promise.all(promises); + } + + function updatePushes(pushStatusObjectId, installations, results) { + let now = new Date(); + + let resultsByDeviceToken = _.keyBy(results, r => r.device.deviceToken); + + // Update the push record for each installation + let promises = _.map(installations, installation => { + let deviceToken = installation.deviceToken; + let result = null; + + // Handle different failure scenarios + if (!deviceToken) { + result = { transmitted: false, error: 'No deviceToken found on installation' } + } else if (deviceToken in resultsByDeviceToken) { + result = resultsByDeviceToken[deviceToken]; + } else { + result = { transmitted: false, error: 'No result from adapter' } + } + + // Find the record to update + let query = { + pushStatus: pushStatusObjectId, + installation: { + __type: 'Pointer', + className: "_Installation", + objectId: installation.objectId, + } + }; + let updateFields = { + result: result, + updatedAt: now + }; + + return updatePush(query, updateFields); + }); + + return Promise.all(promises); + } + return Object.freeze({ create, - update + update, + createPush, + updatePush, + insertPushes, + updatePushes }) } @@ -138,11 +215,17 @@ export function pushStatusHandler(config) { let setRunning = function(installations) { logger.verbose('sending push to %d installations', installations.length); - return handler.update({status:"pending", objectId: objectId}, - {status: "running", updatedAt: new Date() }); + return handler.update({status:"pending", objectId: objectId}, + {status: "running", updatedAt: new Date() }).then((result) => { + return handler.insertPushes(objectId, installations).then(() => { + return result; + }); + }); } - let complete = function(results) { + let complete = function(data) { + let results = data.results; + let installations = data.installations; let update = { status: 'succeeded', updatedAt: new Date(), @@ -173,7 +256,11 @@ export function pushStatusHandler(config) { }, update); } logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed); - return handler.update({status:"running", objectId }, update); + return handler.update({status:"running", objectId }, update).then((result) => { + return handler.updatePushes(objectId, installations, results).then(() => { + return result; + }); + }); } let fail = function(err) {