diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 1e9cd266..23ce7a60 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -1254,4 +1254,84 @@ describe('PushController', () => { .then(done, done.fail); }); }); + + describe('With expiration defined', () => { + const auth = {isMaster: true}; + const pushController = new PushController(); + + let config = Config.get(Parse.applicationId); + + const pushes = []; + const pushAdapter = { + send(body, installations) { + pushes.push(body); + return successfulTransmissions(body, installations); + }, + getValidPushTypes() { + return ["ios"]; + } + }; + + beforeEach((done) => { + reconfigureServer({ + push: {adapter: pushAdapter}, + }) + .then(() => { + config = Config.get(Parse.applicationId); + }) + .then(done, done.fail); + }); + + it('should throw if both expiration_time and expiration_interval are set', () => { + expect(() => pushController.sendPush({ + expiration_time: '2017-09-25T13:21:20.841Z', + expiration_interval: 1000, + }, {}, config, auth)).toThrow() + }); + + it('should throw on invalid expiration_interval', () => { + expect(() => pushController.sendPush({ + expiration_interval: -1 + }, {}, config, auth)).toThrow(); + expect(() => pushController.sendPush({ + expiration_interval: '', + }, {}, config, auth)).toThrow(); + expect(() => pushController.sendPush({ + expiration_time: {}, + }, {}, config, auth)).toThrow(); + }); + + describe('For immediate pushes',() => { + it('should transform the expiration_interval into an absolute time', (done) => { + const now = new Date('2017-09-25T13:30:10.452Z'); + + reconfigureServer({ + push: {adapter: pushAdapter}, + }) + .then(() => + new Promise((resolve) => { + pushController.sendPush({ + data: { + alert: 'immediate push', + }, + expiration_interval: 20 * 60, // twenty minutes + }, {}, Config.get(Parse.applicationId), auth, resolve, now) + })) + .then((pushStatusId) => { + const p = new Parse.Object('_PushStatus'); + p.id = pushStatusId; + return p.fetch({useMasterKey: true}); + }) + .then((pushStatus) => { + expect(pushStatus.get('expiry')).toBeDefined('expiry must be set'); + expect(pushStatus.get('expiry')) + .toEqual(new Date('2017-09-25T13:50:10.452Z').valueOf()); + + expect(pushStatus.get('expiration_interval')).toBeDefined('expiration_interval must be defined'); + expect(pushStatus.get('expiration_interval')).toBe(20 * 60); + }) + .then(done, done.fail); + }); + }); + }); }); diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 73eb7c48..416b0ea4 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -7,13 +7,27 @@ import { applyDeviceTokenExists } from '../Push/utils'; export class PushController { - sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}) { + sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}, now = new Date()) { if (!config.hasPushSupport) { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration'); } + // Replace the expiration_time and push_time with a valid Unix epoch milliseconds time body.expiration_time = PushController.getExpirationTime(body); + body.expiration_interval = PushController.getExpirationInterval(body); + if (body.expiration_time && body.expiration_interval) { + throw new Parse.Error( + Parse.Error.PUSH_MISCONFIGURED, + 'Both expiration_time and expiration_interval cannot be set'); + } + + // Immediate push + if (body.expiration_interval && !body.hasOwnProperty('push_time')) { + const ttlMs = body.expiration_interval * 1000; + body.expiration_time = (new Date(now.valueOf() + ttlMs)).valueOf(); + } + const pushTime = PushController.getPushTime(body); if (pushTime && pushTime.date !== 'undefined') { body['push_time'] = PushController.formatPushTime(pushTime); @@ -108,6 +122,20 @@ export class PushController { return expirationTime.valueOf(); } + static getExpirationInterval(body = {}) { + const hasExpirationInterval = body.hasOwnProperty('expiration_interval'); + if (!hasExpirationInterval) { + return; + } + + var expirationIntervalParam = body['expiration_interval']; + if (typeof expirationIntervalParam !== 'number' || expirationIntervalParam <= 0) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + `expiration_interval must be a number greater than 0`); + } + return expirationIntervalParam; + } + /** * Get push time from the request body. * @param {Object} request A request object diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 6c60575b..d1c15fe4 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -72,22 +72,23 @@ const defaultColumns = Object.freeze({ "subtitle": {type:'String'}, }, _PushStatus: { - "pushTime": {type:'String'}, - "source": {type:'String'}, // rest or webui - "query": {type:'String'}, // the stringified JSON query - "payload": {type:'String'}, // the stringified JSON payload, - "title": {type:'String'}, - "expiry": {type:'Number'}, - "status": {type:'String'}, - "numSent": {type:'Number'}, - "numFailed": {type:'Number'}, - "pushHash": {type:'String'}, - "errorMessage": {type:'Object'}, - "sentPerType": {type:'Object'}, - "failedPerType": {type:'Object'}, - "sentPerUTCOffset": {type:'Object'}, - "failedPerUTCOffset": {type:'Object'}, - "count": {type:'Number'} + "pushTime": {type:'String'}, + "source": {type:'String'}, // rest or webui + "query": {type:'String'}, // the stringified JSON query + "payload": {type:'String'}, // the stringified JSON payload, + "title": {type:'String'}, + "expiry": {type:'Number'}, + "expiration_interval": {type:'Number'}, + "status": {type:'String'}, + "numSent": {type:'Number'}, + "numFailed": {type:'Number'}, + "pushHash": {type:'String'}, + "errorMessage": {type:'Object'}, + "sentPerType": {type:'Object'}, + "failedPerType": {type:'Object'}, + "sentPerUTCOffset": {type:'Object'}, + "failedPerUTCOffset": {type:'Object'}, + "count": {type:'Number'} }, _JobStatus: { "jobName": {type: 'String'}, diff --git a/src/StatusHandler.js b/src/StatusHandler.js index 02df9deb..f77c0bbc 100644 --- a/src/StatusHandler.js +++ b/src/StatusHandler.js @@ -174,6 +174,7 @@ export function pushStatusHandler(config, existingObjectId) { source: options.source, title: options.title, expiry: body.expiration_time, + expiration_interval: body.expiration_interval, status: status, numSent: 0, pushHash,