Skip to content

Commit 907b160

Browse files
authored
Adds support for PushScheduling (#3722)
* Add support for push scheduling Add a configuration flag on the server to handle the availability of push scheduling. * Update push controller to skip sending only if scheduling is configured Only skip push sending if scheduling is configured * Update bad conventions * Add CLI definitions for push scheduling * Adds tests for pushTime * Adds test for scheduling * nits * Test for not scheduled
1 parent 302a0dd commit 907b160

File tree

7 files changed

+186
-8
lines changed

7 files changed

+186
-8
lines changed

spec/PushController.spec.js

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,5 +531,130 @@ describe('PushController', () => {
531531
it('should flatten', () => {
532532
var res = StatusHandler.flatten([1, [2], [[3, 4], 5], [[[6]]]])
533533
expect(res).toEqual([1,2,3,4,5,6]);
534-
})
534+
});
535+
536+
it('properly transforms push time', () => {
537+
expect(PushController.getPushTime()).toBe(undefined);
538+
expect(PushController.getPushTime({
539+
'push_time': 1000
540+
})).toEqual(new Date(1000 * 1000));
541+
expect(PushController.getPushTime({
542+
'push_time': '2017-01-01'
543+
})).toEqual(new Date('2017-01-01'));
544+
expect(() => {PushController.getPushTime({
545+
'push_time': 'gibberish-time'
546+
})}).toThrow();
547+
expect(() => {PushController.getPushTime({
548+
'push_time': Number.NaN
549+
})}).toThrow();
550+
});
551+
552+
it('should not schedule push when not configured', (done) => {
553+
var config = new Config(Parse.applicationId);
554+
var auth = {
555+
isMaster: true
556+
}
557+
var pushAdapter = {
558+
send: function(body, installations) {
559+
return successfulTransmissions(body, installations);
560+
},
561+
getValidPushTypes: function() {
562+
return ["ios"];
563+
}
564+
}
565+
566+
var pushController = new PushController();
567+
const payload = {
568+
data: {
569+
alert: 'hello',
570+
},
571+
push_time: new Date().getTime()
572+
}
573+
574+
var installations = [];
575+
while(installations.length != 10) {
576+
const installation = new Parse.Object("_Installation");
577+
installation.set("installationId", "installation_" + installations.length);
578+
installation.set("deviceToken","device_token_" + installations.length)
579+
installation.set("badge", installations.length);
580+
installation.set("originalBadge", installations.length);
581+
installation.set("deviceType", "ios");
582+
installations.push(installation);
583+
}
584+
585+
reconfigureServer({
586+
push: { adapter: pushAdapter }
587+
}).then(() => {
588+
return Parse.Object.saveAll(installations).then(() => {
589+
return pushController.sendPush(payload, {}, config, auth);
590+
});
591+
}).then(() => {
592+
const query = new Parse.Query('_PushStatus');
593+
return query.find({useMasterKey: true}).then((results) => {
594+
expect(results.length).toBe(1);
595+
const pushStatus = results[0];
596+
expect(pushStatus.get('status')).not.toBe('scheduled');
597+
done();
598+
});
599+
}).catch((err) => {
600+
console.error(err);
601+
fail('should not fail');
602+
done();
603+
});
604+
});
605+
606+
it('should not schedule push when configured', (done) => {
607+
var auth = {
608+
isMaster: true
609+
}
610+
var pushAdapter = {
611+
send: function(body, installations) {
612+
return successfulTransmissions(body, installations);
613+
},
614+
getValidPushTypes: function() {
615+
return ["ios"];
616+
}
617+
}
618+
619+
var pushController = new PushController();
620+
const payload = {
621+
data: {
622+
alert: 'hello',
623+
},
624+
push_time: new Date().getTime() / 1000
625+
}
626+
627+
var installations = [];
628+
while(installations.length != 10) {
629+
const installation = new Parse.Object("_Installation");
630+
installation.set("installationId", "installation_" + installations.length);
631+
installation.set("deviceToken","device_token_" + installations.length)
632+
installation.set("badge", installations.length);
633+
installation.set("originalBadge", installations.length);
634+
installation.set("deviceType", "ios");
635+
installations.push(installation);
636+
}
637+
638+
reconfigureServer({
639+
push: { adapter: pushAdapter },
640+
scheduledPush: true
641+
}).then(() => {
642+
var config = new Config(Parse.applicationId);
643+
return Parse.Object.saveAll(installations).then(() => {
644+
return pushController.sendPush(payload, {}, config, auth);
645+
});
646+
}).then(() => {
647+
const query = new Parse.Query('_PushStatus');
648+
return query.find({useMasterKey: true}).then((results) => {
649+
expect(results.length).toBe(1);
650+
const pushStatus = results[0];
651+
expect(pushStatus.get('status')).toBe('scheduled');
652+
done();
653+
});
654+
}).catch((err) => {
655+
console.error(err);
656+
fail('should not fail');
657+
done();
658+
});
659+
});
535660
});

src/Config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export class Config {
6161
this.pushControllerQueue = cacheInfo.pushControllerQueue;
6262
this.pushWorker = cacheInfo.pushWorker;
6363
this.hasPushSupport = cacheInfo.hasPushSupport;
64+
this.hasPushScheduledSupport = cacheInfo.hasPushScheduledSupport;
6465
this.loggerController = cacheInfo.loggerController;
6566
this.userController = cacheInfo.userController;
6667
this.authDataManager = cacheInfo.authDataManager;

src/Controllers/PushController.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export class PushController {
1212
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
1313
'Missing push configuration');
1414
}
15-
// Replace the expiration_time with a valid Unix epoch milliseconds time
16-
body['expiration_time'] = PushController.getExpirationTime(body);
15+
// Replace the expiration_time and push_time with a valid Unix epoch milliseconds time
16+
body.expiration_time = PushController.getExpirationTime(body);
17+
body.push_time = PushController.getPushTime(body);
1718
// TODO: If the req can pass the checking, we return immediately instead of waiting
1819
// pushes to be sent. We probably change this behaviour in the future.
1920
let badgeUpdate = () => {
@@ -49,6 +50,9 @@ export class PushController {
4950
onPushStatusSaved(pushStatus.objectId);
5051
return badgeUpdate();
5152
}).then(() => {
53+
if (body.push_time && config.hasPushScheduledSupport) {
54+
return Promise.resolve();
55+
}
5256
return config.pushControllerQueue.enqueue(body, where, config, auth, pushStatus);
5357
}).catch((err) => {
5458
return pushStatus.fail(err).then(() => {
@@ -63,7 +67,7 @@ export class PushController {
6367
* @returns {Number|undefined} The expiration time if it exists in the request
6468
*/
6569
static getExpirationTime(body = {}) {
66-
var hasExpirationTime = !!body['expiration_time'];
70+
var hasExpirationTime = body.hasOwnProperty('expiration_time');
6771
if (!hasExpirationTime) {
6872
return;
6973
}
@@ -84,6 +88,34 @@ export class PushController {
8488
}
8589
return expirationTime.valueOf();
8690
}
91+
92+
/**
93+
* Get push time from the request body.
94+
* @param {Object} request A request object
95+
* @returns {Number|undefined} The push time if it exists in the request
96+
*/
97+
static getPushTime(body = {}) {
98+
var hasPushTime = body.hasOwnProperty('push_time');
99+
if (!hasPushTime) {
100+
return;
101+
}
102+
var pushTimeParam = body['push_time'];
103+
var pushTime;
104+
if (typeof pushTimeParam === 'number') {
105+
pushTime = new Date(pushTimeParam * 1000);
106+
} else if (typeof pushTimeParam === 'string') {
107+
pushTime = new Date(pushTimeParam);
108+
} else {
109+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
110+
body['push_time'] + ' is not valid time.');
111+
}
112+
// Check pushTime is valid or not, if it is not valid, pushTime is NaN
113+
if (!isFinite(pushTime)) {
114+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
115+
body['push_time'] + ' is not valid time.');
116+
}
117+
return pushTime;
118+
}
87119
}
88120

89121
export default PushController;

src/ParseServer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class ParseServer {
9595
analyticsAdapter,
9696
filesAdapter,
9797
push,
98+
scheduledPush = false,
9899
loggerAdapter,
99100
jsonLogs = defaults.jsonLogs,
100101
logsFolder = defaults.logsFolder,
@@ -182,6 +183,7 @@ class ParseServer {
182183
const pushController = new PushController();
183184

184185
const hasPushSupport = pushAdapter && push;
186+
const hasPushScheduledSupport = pushAdapter && push && scheduledPush;
185187

186188
const {
187189
disablePushWorker
@@ -259,7 +261,8 @@ class ParseServer {
259261
userSensitiveFields,
260262
pushWorker,
261263
pushControllerQueue,
262-
hasPushSupport
264+
hasPushSupport,
265+
hasPushScheduledSupport
263266
});
264267

265268
Config.validate(AppCache.get(appId));

src/Routers/FeaturesRouter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class FeaturesRouter extends PromiseRouter {
3030
},
3131
push: {
3232
immediatePush: req.config.hasPushSupport,
33-
scheduledPush: false,
33+
scheduledPush: req.config.hasPushScheduledSupport,
3434
storedPushData: req.config.hasPushSupport,
3535
pushAudiences: false,
3636
},

src/StatusHandler.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ export function pushStatusHandler(config, objectId = newObjectId()) {
110110
const handler = statusHandler(PUSH_STATUS_COLLECTION, database);
111111
const setInitial = function(body = {}, where, options = {source: 'rest'}) {
112112
const now = new Date();
113+
let pushTime = new Date();
114+
let status = 'pending';
115+
if (body.hasOwnProperty('push_time')) {
116+
if (config.hasPushScheduledSupport) {
117+
pushTime = body.push_time;
118+
status = 'scheduled';
119+
} else {
120+
logger.warn('Trying to schedule a push while server is not configured.');
121+
logger.warn('Push will be sent immediately');
122+
}
123+
}
124+
113125
const data = body.data || {};
114126
const payloadString = JSON.stringify(data);
115127
let pushHash;
@@ -123,13 +135,13 @@ export function pushStatusHandler(config, objectId = newObjectId()) {
123135
const object = {
124136
objectId,
125137
createdAt: now,
126-
pushTime: now.toISOString(),
138+
pushTime: pushTime.toISOString(),
127139
query: JSON.stringify(where),
128140
payload: payloadString,
129141
source: options.source,
130142
title: options.title,
131143
expiry: body.expiration_time,
132-
status: "pending",
144+
status: status,
133145
numSent: 0,
134146
pushHash,
135147
// lockdown!

src/cli/definitions/parse-server.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ export default {
8181
help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push",
8282
action: objectParser
8383
},
84+
"scheduledPush": {
85+
env: "PARSE_SERVER_SCHEDULED_PUSH",
86+
help: "Configuration for push scheduling. Defaults to false.",
87+
action: booleanParser
88+
},
8489
"oauth": {
8590
env: "PARSE_SERVER_OAUTH_PROVIDERS",
8691
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",

0 commit comments

Comments
 (0)