diff --git a/src/Config.js b/src/Config.js index a1a51abc08..8958a079c5 100644 --- a/src/Config.js +++ b/src/Config.js @@ -61,6 +61,7 @@ export class Config { this.pushControllerQueue = cacheInfo.pushControllerQueue; this.pushWorker = cacheInfo.pushWorker; this.hasPushSupport = cacheInfo.hasPushSupport; + this.hasPushScheduledSupport = cacheInfo.hasPushScheduledSupport; this.loggerController = cacheInfo.loggerController; this.userController = cacheInfo.userController; this.authDataManager = cacheInfo.authDataManager; diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index a390d31ec5..20c363ff55 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -12,8 +12,9 @@ export class PushController { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration'); } - // Replace the expiration_time with a valid Unix epoch milliseconds time + // Replace the expiration_time and push_time with a valid Unix epoch milliseconds time body['expiration_time'] = PushController.getExpirationTime(body); + body['push_time'] = PushController.getPushTime(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. let badgeUpdate = () => { @@ -49,6 +50,9 @@ export class PushController { onPushStatusSaved(pushStatus.objectId); return badgeUpdate(); }).then(() => { + if (body.push_time && config.hasPushScheduledSupport) { + return Promise.resolve(); + } return config.pushControllerQueue.enqueue(body, where, config, auth, pushStatus); }).catch((err) => { return pushStatus.fail(err).then(() => { @@ -63,7 +67,7 @@ export class PushController { * @returns {Number|undefined} The expiration time if it exists in the request */ static getExpirationTime(body = {}) { - var hasExpirationTime = !!body['expiration_time']; + var hasExpirationTime = body.hasOwnProperty('expiration_time'); if (!hasExpirationTime) { return; } @@ -84,6 +88,34 @@ export class PushController { } return expirationTime.valueOf(); } + + /** + * Get push time from the request body. + * @param {Object} request A request object + * @returns {Number|undefined} The push time if it exists in the request + */ + static getPushTime(body = {}) { + var hasPushTime = body.hasOwnProperty('push_time'); + if (!hasPushTime) { + return; + } + var pushTimeParam = body['push_time']; + var pushTime; + if (typeof pushTimeParam === 'number') { + pushTime = new Date(pushTimeParam * 1000); + } else if (typeof pushTimeParam === 'string') { + pushTime = new Date(pushTimeParam); + } else { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + body['push_time'] + ' is not valid time.'); + } + // Check pushTime is valid or not, if it is not valid, pushTime is NaN + if (!isFinite(pushTime)) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + body['push_time'] + ' is not valid time.'); + } + return pushTime.valueOf(); + } } export default PushController; diff --git a/src/ParseServer.js b/src/ParseServer.js index 11cb38f56e..79c388bfee 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -95,6 +95,7 @@ class ParseServer { analyticsAdapter, filesAdapter, push, + scheduledPush = false, loggerAdapter, jsonLogs = defaults.jsonLogs, logsFolder = defaults.logsFolder, @@ -182,6 +183,7 @@ class ParseServer { const pushController = new PushController(); const hasPushSupport = pushAdapter && push; + const hasPushScheduledSupport = pushAdapter && push && scheduledPush; const { disablePushWorker @@ -259,7 +261,8 @@ class ParseServer { userSensitiveFields, pushWorker, pushControllerQueue, - hasPushSupport + hasPushSupport, + hasPushScheduledSupport }); Config.validate(AppCache.get(appId)); diff --git a/src/Routers/FeaturesRouter.js b/src/Routers/FeaturesRouter.js index 74e45eb141..a82488ab4c 100644 --- a/src/Routers/FeaturesRouter.js +++ b/src/Routers/FeaturesRouter.js @@ -30,7 +30,7 @@ export class FeaturesRouter extends PromiseRouter { }, push: { immediatePush: req.config.hasPushSupport, - scheduledPush: false, + scheduledPush: req.config.hasPushScheduledSupport, storedPushData: req.config.hasPushSupport, pushAudiences: false, }, diff --git a/src/StatusHandler.js b/src/StatusHandler.js index c92933d992..1d46bcbe89 100644 --- a/src/StatusHandler.js +++ b/src/StatusHandler.js @@ -110,6 +110,8 @@ export function pushStatusHandler(config, objectId = newObjectId()) { const handler = statusHandler(PUSH_STATUS_COLLECTION, database); const setInitial = function(body = {}, where, options = {source: 'rest'}) { const now = new Date(); + const pushTime = body.push_time || new Date(); + const status = body.push_time ? "scheduled" : "pending"; const data = body.data || {}; const payloadString = JSON.stringify(data); let pushHash; @@ -123,13 +125,13 @@ export function pushStatusHandler(config, objectId = newObjectId()) { const object = { objectId, createdAt: now, - pushTime: now.toISOString(), + pushTime: pushTime.toISOString(), query: JSON.stringify(where), payload: payloadString, source: options.source, title: options.title, expiry: body.expiration_time, - status: "pending", + status: status, numSent: 0, pushHash, // lockdown! diff --git a/src/cli/definitions/parse-server.js b/src/cli/definitions/parse-server.js index 16e857a860..81c2cb428d 100644 --- a/src/cli/definitions/parse-server.js +++ b/src/cli/definitions/parse-server.js @@ -81,6 +81,11 @@ export default { help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push", action: objectParser }, + "scheduledPush": { + env: "PARSE_SERVER_SCHEDULED_PUSH", + help: "Configuration for push scheduling. Defaults to false.", + action: booleanParser + }, "oauth": { env: "PARSE_SERVER_OAUTH_PROVIDERS", help: "[DEPRECATED (use auth option)] Configuration for your oAuth providers, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth",