270 lines
8.0 KiB
JavaScript
270 lines
8.0 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;
|