Files
kami-parse-server/src/Controllers/PushController.js
Ross Bayer ced6b76ef5 Support incrementing push badge value by more than 1 (#4889)
* Support 'IncrementByN' badge value for higher push badge increments

* Fix test

* Rely on object for badge incrementation (i.e. {increment: 3}) rather than string (IncrementBy3)

* For badge incrementation, utilize format similar to other operation notation
2018-07-12 20:34:08 +02:00

204 lines
7.4 KiB
JavaScript

import { Parse } from 'parse/node';
import RestQuery from '../RestQuery';
import RestWrite from '../RestWrite';
import { master } from '../Auth';
import { pushStatusHandler } from '../StatusHandler';
import { applyDeviceTokenExists } from '../Push/utils';
export class PushController {
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);
}
// 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 = () => {
return Promise.resolve();
}
if (body.data && body.data.badge) {
const badge = body.data.badge;
let restUpdate = {};
if (typeof badge == 'string' && badge.toLowerCase() === 'increment') {
restUpdate = { badge: { __op: 'Increment', amount: 1 } }
} else if (typeof badge == 'object' && typeof badge.__op == 'string' &&
badge.__op.toLowerCase() == 'increment' && Number(badge.amount)) {
restUpdate = { badge: { __op: 'Increment', amount: badge.amount } }
} else if (Number(badge)) {
restUpdate = { badge: badge }
} else {
throw "Invalid value for badge, expected number or 'Increment' or {increment: number}";
}
// Force filtering on only valid device tokens
const updateWhere = applyDeviceTokenExists(where);
badgeUpdate = () => {
// Build a real RestQuery so we can use it in RestWrite
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
return restQuery.buildRestWhere().then(() => {
const write = new RestWrite(config, master(config), '_Installation', restQuery.restWhere, restUpdate);
write.runOptions.many = true;
return write.execute();
});
}
}
const pushStatus = pushStatusHandler(config);
return Promise.resolve().then(() => {
return pushStatus.setInitial(body, where);
}).then(() => {
onPushStatusSaved(pushStatus.objectId);
return badgeUpdate();
}).then(() => {
// Update audience lastUsed and timesUsed
if (body.audience_id) {
const audienceId = body.audience_id;
var updateAudience = {
lastUsed: { __type: "Date", iso: new Date().toISOString() },
timesUsed: { __op: "Increment", "amount": 1 }
};
const write = new RestWrite(config, master(config), '_Audience', {objectId: audienceId}, updateAudience);
write.execute();
}
// Don't wait for the audience update promise to resolve.
return Promise.resolve();
}).then(() => {
if (body.hasOwnProperty('push_time') && config.hasPushScheduledSupport) {
return Promise.resolve();
}
return config.pushControllerQueue.enqueue(body, where, config, auth, pushStatus);
}).catch((err) => {
return pushStatus.fail(err).then(() => {
throw err;
});
});
}
/**
* Get expiration time from the request body.
* @param {Object} request A request object
* @returns {Number|undefined} The expiration time if it exists in the request
*/
static getExpirationTime(body = {}) {
var hasExpirationTime = body.hasOwnProperty('expiration_time');
if (!hasExpirationTime) {
return;
}
var expirationTimeParam = body['expiration_time'];
var expirationTime;
if (typeof expirationTimeParam === 'number') {
expirationTime = new Date(expirationTimeParam * 1000);
} else if (typeof expirationTimeParam === 'string') {
expirationTime = new Date(expirationTimeParam);
} else {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
body['expiration_time'] + ' is not valid time.');
}
// Check expirationTime is valid or not, if it is not valid, expirationTime is NaN
if (!isFinite(expirationTime)) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
body['expiration_time'] + ' is not valid time.');
}
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
* @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 date;
var isLocalTime = true;
if (typeof pushTimeParam === 'number') {
date = new Date(pushTimeParam * 1000);
} else if (typeof pushTimeParam === 'string') {
isLocalTime = !PushController.pushTimeHasTimezoneComponent(pushTimeParam);
date = 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(date)) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
body['push_time'] + ' is not valid time.');
}
return {
date,
isLocalTime,
};
}
/**
* Checks if a ISO8601 formatted date contains a timezone component
* @param pushTimeParam {string}
* @returns {boolean}
*/
static pushTimeHasTimezoneComponent(pushTimeParam: string): boolean {
const offsetPattern = /(.+)([+-])\d\d:\d\d$/;
return pushTimeParam.indexOf('Z') === pushTimeParam.length - 1 // 2007-04-05T12:30Z
|| offsetPattern.test(pushTimeParam); // 2007-04-05T12:30.000+02:00, 2007-04-05T12:30.000-02:00
}
/**
* Converts a date to ISO format in UTC time and strips the timezone if `isLocalTime` is true
* @param date {Date}
* @param isLocalTime {boolean}
* @returns {string}
*/
static formatPushTime({ date, isLocalTime }: { date: Date, isLocalTime: boolean }) {
if (isLocalTime) { // Strip 'Z'
const isoString = date.toISOString();
return isoString.substring(0, isoString.indexOf('Z'));
}
return date.toISOString();
}
}
export default PushController;