Adds support to store push results
This commit is contained in:
@@ -3,6 +3,30 @@ var PushController = require('../src/Controllers/PushController').PushController
|
|||||||
|
|
||||||
var Config = require('../src/Config');
|
var Config = require('../src/Config');
|
||||||
|
|
||||||
|
const successfulTransmissions = function(body, installations) {
|
||||||
|
|
||||||
|
let promises = installations.map((device) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
transmitted: true,
|
||||||
|
device: device,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
const successfulIOS = function(body, installations) {
|
||||||
|
|
||||||
|
let promises = installations.map((device) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
transmitted: device.deviceType == "ios",
|
||||||
|
device: device,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
describe('PushController', () => {
|
describe('PushController', () => {
|
||||||
it('can validate device type when no device type is set', (done) => {
|
it('can validate device type when no device type is set', (done) => {
|
||||||
// Make query condition
|
// Make query condition
|
||||||
@@ -142,10 +166,7 @@ describe('PushController', () => {
|
|||||||
expect(installation.badge).toBeUndefined();
|
expect(installation.badge).toBeUndefined();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return Promise.resolve({
|
return successfulTransmissions(body, installations);
|
||||||
error: null,
|
|
||||||
payload: body,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
getValidPushTypes: function() {
|
getValidPushTypes: function() {
|
||||||
return ["ios", "android"];
|
return ["ios", "android"];
|
||||||
@@ -194,10 +215,7 @@ describe('PushController', () => {
|
|||||||
expect(installation.badge).toEqual(badge);
|
expect(installation.badge).toEqual(badge);
|
||||||
expect(1).toEqual(installation.badge);
|
expect(1).toEqual(installation.badge);
|
||||||
})
|
})
|
||||||
return Promise.resolve({
|
return successfulTransmissions(body, installations);
|
||||||
payload: body,
|
|
||||||
error: null
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
getValidPushTypes: function() {
|
getValidPushTypes: function() {
|
||||||
return ["ios"];
|
return ["ios"];
|
||||||
@@ -224,6 +242,24 @@ describe('PushController', () => {
|
|||||||
|
|
||||||
it('properly creates _PushStatus', (done) => {
|
it('properly creates _PushStatus', (done) => {
|
||||||
|
|
||||||
|
var installations = [];
|
||||||
|
while(installations.length != 10) {
|
||||||
|
var installation = new Parse.Object("_Installation");
|
||||||
|
installation.set("installationId", "installation_"+installations.length);
|
||||||
|
installation.set("deviceToken","device_token_"+installations.length)
|
||||||
|
installation.set("badge", installations.length);
|
||||||
|
installation.set("originalBadge", installations.length);
|
||||||
|
installation.set("deviceType", "ios");
|
||||||
|
installations.push(installation);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(installations.length != 15) {
|
||||||
|
var installation = new Parse.Object("_Installation");
|
||||||
|
installation.set("installationId", "installation_"+installations.length);
|
||||||
|
installation.set("deviceToken","device_token_"+installations.length)
|
||||||
|
installation.set("deviceType", "android");
|
||||||
|
installations.push(installation);
|
||||||
|
}
|
||||||
var payload = {data: {
|
var payload = {data: {
|
||||||
alert: "Hello World!",
|
alert: "Hello World!",
|
||||||
badge: 1,
|
badge: 1,
|
||||||
@@ -231,12 +267,7 @@ describe('PushController', () => {
|
|||||||
|
|
||||||
var pushAdapter = {
|
var pushAdapter = {
|
||||||
send: function(body, installations) {
|
send: function(body, installations) {
|
||||||
var badge = body.data.badge;
|
return successfulIOS(body, installations);
|
||||||
return Promise.resolve({
|
|
||||||
error: null,
|
|
||||||
response: "OK!",
|
|
||||||
payload: body
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
getValidPushTypes: function() {
|
getValidPushTypes: function() {
|
||||||
return ["ios"];
|
return ["ios"];
|
||||||
@@ -249,7 +280,9 @@ describe('PushController', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||||
pushController.sendPush(payload, {}, config, auth).then((result) => {
|
Parse.Object.saveAll(installations).then(() => {
|
||||||
|
return pushController.sendPush(payload, {}, config, auth);
|
||||||
|
}).then((result) => {
|
||||||
let query = new Parse.Query('_PushStatus');
|
let query = new Parse.Query('_PushStatus');
|
||||||
return query.find({useMasterKey: true});
|
return query.find({useMasterKey: true});
|
||||||
}).then((results) => {
|
}).then((results) => {
|
||||||
@@ -258,7 +291,15 @@ describe('PushController', () => {
|
|||||||
expect(result.get('source')).toEqual('rest');
|
expect(result.get('source')).toEqual('rest');
|
||||||
expect(result.get('query')).toEqual(JSON.stringify({}));
|
expect(result.get('query')).toEqual(JSON.stringify({}));
|
||||||
expect(result.get('payload')).toEqual(payload.data);
|
expect(result.get('payload')).toEqual(payload.data);
|
||||||
expect(result.get('status')).toEqual("running");
|
expect(result.get('status')).toEqual('succeeded');
|
||||||
|
expect(result.get('numSent')).toEqual(10);
|
||||||
|
expect(result.get('sentPerType')).toEqual({
|
||||||
|
'ios': 10 // 10 ios
|
||||||
|
});
|
||||||
|
expect(result.get('numFailed')).toEqual(5);
|
||||||
|
expect(result.get('failedPerType')).toEqual({
|
||||||
|
'android': 5 // android
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,9 +313,7 @@ describe('PushController', () => {
|
|||||||
|
|
||||||
var pushAdapter = {
|
var pushAdapter = {
|
||||||
send: function(body, installations) {
|
send: function(body, installations) {
|
||||||
return Promise.resolve({
|
return successfulTransmissions(body, installations);
|
||||||
error:null
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
getValidPushTypes: function() {
|
getValidPushTypes: function() {
|
||||||
return ["ios"];
|
return ["ios"];
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import PushAdapter from './PushAdapter';
|
|||||||
import { classifyInstallations } from './PushAdapterUtils';
|
import { classifyInstallations } from './PushAdapterUtils';
|
||||||
|
|
||||||
export class ParsePushAdapter extends PushAdapter {
|
export class ParsePushAdapter extends PushAdapter {
|
||||||
|
|
||||||
|
supportsPushTracking = true;
|
||||||
|
|
||||||
constructor(pushConfig = {}) {
|
constructor(pushConfig = {}) {
|
||||||
super(pushConfig);
|
super(pushConfig);
|
||||||
this.validPushTypes = ['ios', 'android'];
|
this.validPushTypes = ['ios', 'android'];
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import rest from '../rest';
|
|||||||
import AdaptableController from './AdaptableController';
|
import AdaptableController from './AdaptableController';
|
||||||
import { PushAdapter } from '../Adapters/Push/PushAdapter';
|
import { PushAdapter } from '../Adapters/Push/PushAdapter';
|
||||||
import deepcopy from 'deepcopy';
|
import deepcopy from 'deepcopy';
|
||||||
import { md5Hash } from '../cryptoUtils';
|
|
||||||
import features from '../features';
|
import features from '../features';
|
||||||
import RestQuery from '../RestQuery';
|
import RestQuery from '../RestQuery';
|
||||||
import RestWrite from '../RestWrite';
|
import pushStatusHandler from '../pushStatusHandler';
|
||||||
|
|
||||||
const FEATURE_NAME = 'push';
|
const FEATURE_NAME = 'push';
|
||||||
const UNSUPPORTED_BADGE_KEY = "unsupported";
|
const UNSUPPORTED_BADGE_KEY = "unsupported";
|
||||||
@@ -40,7 +39,7 @@ export class PushController extends AdaptableController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPush(body = {}, where = {}, config, auth) {
|
sendPush(body = {}, where = {}, config, auth, wait) {
|
||||||
var pushAdapter = this.adapter;
|
var pushAdapter = this.adapter;
|
||||||
if (!pushAdapter) {
|
if (!pushAdapter) {
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
@@ -83,67 +82,56 @@ export class PushController extends AdaptableController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let pushStatus;
|
let pushStatus = pushStatusHandler(config);
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
return this.saveInitialPushStatus(body, where, config);
|
return pushStatus.setInitial(body, where);
|
||||||
}).then((res) => {
|
}).then(() => {
|
||||||
pushStatus = res.response;
|
|
||||||
return badgeUpdate();
|
return badgeUpdate();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return rest.find(config, auth, '_Installation', where);
|
return rest.find(config, auth, '_Installation', where);
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
this.updatePushStatus({status: "running"}, {status:"pending", objectId: pushStatus.objectId}, config);
|
pushStatus.setRunning();
|
||||||
if (body.data && body.data.badge && body.data.badge == "Increment") {
|
return this.sendToAdapter(body, response.results, pushStatus, config);
|
||||||
// Collect the badges to reduce the # of calls
|
|
||||||
let badgeInstallationsMap = response.results.reduce((map, installation) => {
|
|
||||||
let badge = installation.badge;
|
|
||||||
if (installation.deviceType != "ios") {
|
|
||||||
badge = UNSUPPORTED_BADGE_KEY;
|
|
||||||
}
|
|
||||||
map[badge+''] = map[badge+''] || [];
|
|
||||||
map[badge+''].push(installation);
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// Map the on the badges count and return the send result
|
|
||||||
let promises = Object.keys(badgeInstallationsMap).map((badge) => {
|
|
||||||
let payload = deepcopy(body);
|
|
||||||
if (badge == UNSUPPORTED_BADGE_KEY) {
|
|
||||||
delete payload.data.badge;
|
|
||||||
} else {
|
|
||||||
payload.data.badge = parseInt(badge);
|
|
||||||
}
|
|
||||||
return pushAdapter.send(payload, badgeInstallationsMap[badge], pushStatus);
|
|
||||||
});
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
return pushAdapter.send(body, response.results, pushStatus);
|
|
||||||
}).then((results) => {
|
}).then((results) => {
|
||||||
// TODO: handle push results
|
return pushStatus.complete(results);
|
||||||
return Promise.resolve(results);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveInitialPushStatus(body, where, config, options = {source: 'rest'}) {
|
sendToAdapter(body, installations, pushStatus, config) {
|
||||||
let pushStatus = {
|
if (body.data && body.data.badge && body.data.badge == "Increment") {
|
||||||
pushTime: (new Date()).toISOString(),
|
// Collect the badges to reduce the # of calls
|
||||||
query: JSON.stringify(where),
|
let badgeInstallationsMap = installations.reduce((map, installation) => {
|
||||||
payload: body.data,
|
let badge = installation.badge;
|
||||||
source: options.source,
|
if (installation.deviceType != "ios") {
|
||||||
title: options.title,
|
badge = UNSUPPORTED_BADGE_KEY;
|
||||||
expiry: body.expiration_time,
|
}
|
||||||
status: "pending",
|
map[badge+''] = map[badge+''] || [];
|
||||||
numSent: 0,
|
map[badge+''].push(installation);
|
||||||
pushHash: md5Hash(JSON.stringify(body.data)),
|
return map;
|
||||||
ACL: new Parse.ACL() // lockdown!
|
}, {});
|
||||||
}
|
|
||||||
let restWrite = new RestWrite(config, {isMaster: true},'_PushStatus',null, pushStatus);
|
|
||||||
return restWrite.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePushStatus(update, where, config) {
|
// Map the on the badges count and return the send result
|
||||||
let restWrite = new RestWrite(config, {isMaster: true}, '_PushStatus', where, update);
|
let promises = Object.keys(badgeInstallationsMap).map((badge) => {
|
||||||
return restWrite.execute();
|
let payload = deepcopy(body);
|
||||||
|
if (badge == UNSUPPORTED_BADGE_KEY) {
|
||||||
|
delete payload.data.badge;
|
||||||
|
} else {
|
||||||
|
payload.data.badge = parseInt(badge);
|
||||||
|
}
|
||||||
|
return this.adapter.send(payload, badgeInstallationsMap[badge]);
|
||||||
|
});
|
||||||
|
// Flatten the promises results
|
||||||
|
return Promise.all(promises).then((results) => {
|
||||||
|
if (Array.isArray(results)) {
|
||||||
|
return Promise.resolve(results.reduce((memo, result) => {
|
||||||
|
return memo.concat(result);
|
||||||
|
},[]));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(results);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.adapter.send(body, installations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ var defaultColumns = {
|
|||||||
"expiry": {type:'Number'},
|
"expiry": {type:'Number'},
|
||||||
"status": {type:'String'},
|
"status": {type:'String'},
|
||||||
"numSent": {type:'Number'},
|
"numSent": {type:'Number'},
|
||||||
|
"numFailed": {type:'Number'},
|
||||||
"pushHash": {type:'String'},
|
"pushHash": {type:'String'},
|
||||||
"errorMessage": {type:'Object'},
|
"errorMessage": {type:'Object'},
|
||||||
"sentPerType": {type:'Object'}
|
"sentPerType": {type:'Object'},
|
||||||
|
"failedPerType":{type:'Object'},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
76
src/pushStatusHandler.js
Normal file
76
src/pushStatusHandler.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import RestWrite from './RestWrite';
|
||||||
|
import { md5Hash } from './cryptoUtils';
|
||||||
|
|
||||||
|
export default function pushStatusHandler(config) {
|
||||||
|
|
||||||
|
let initialPromise;
|
||||||
|
let pushStatus;
|
||||||
|
let setInitial = function(body, where, options = {source: 'rest'}) {
|
||||||
|
let object = {
|
||||||
|
pushTime: (new Date()).toISOString(),
|
||||||
|
query: JSON.stringify(where),
|
||||||
|
payload: body.data,
|
||||||
|
source: options.source,
|
||||||
|
title: options.title,
|
||||||
|
expiry: body.expiration_time,
|
||||||
|
status: "pending",
|
||||||
|
numSent: 0,
|
||||||
|
pushHash: md5Hash(JSON.stringify(body.data)),
|
||||||
|
ACL: new Parse.ACL() // lockdown!
|
||||||
|
}
|
||||||
|
let restWrite = new RestWrite(config, {isMaster: true},'_PushStatus',null, object);
|
||||||
|
initialPromise = restWrite.execute().then((res) => {
|
||||||
|
pushStatus = res.response;
|
||||||
|
return Promise.resolve(pushStatus);
|
||||||
|
});
|
||||||
|
return initialPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let setRunning = function() {
|
||||||
|
return initialPromise.then(() => {
|
||||||
|
let restWrite = new RestWrite(config, {isMaster: true}, '_PushStatus', {status:"pending", objectId: pushStatus.objectId}, {status: "running"});
|
||||||
|
return restWrite.execute();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let complete = function(results) {
|
||||||
|
let update = {
|
||||||
|
status: 'succeeded',
|
||||||
|
numSent: 0,
|
||||||
|
numFailed: 0,
|
||||||
|
};
|
||||||
|
if (Array.isArray(results)) {
|
||||||
|
results.reduce((memo, result) => {
|
||||||
|
// Cannot handle that
|
||||||
|
if (!result.device || !result.device.deviceType) {
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
let deviceType = result.device.deviceType;
|
||||||
|
if (result.transmitted)
|
||||||
|
{
|
||||||
|
memo.numSent++;
|
||||||
|
memo.sentPerType = memo.sentPerType || {};
|
||||||
|
memo.sentPerType[deviceType] = memo.sentPerType[deviceType] || 0;
|
||||||
|
memo.sentPerType[deviceType]++;
|
||||||
|
} else {
|
||||||
|
memo.numFailed++;
|
||||||
|
memo.failedPerType = memo.failedPerType || {};
|
||||||
|
memo.failedPerType[deviceType] = memo.failedPerType[deviceType] || 0;
|
||||||
|
memo.failedPerType[deviceType]++;
|
||||||
|
}
|
||||||
|
return memo;
|
||||||
|
}, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialPromise.then(() => {
|
||||||
|
let restWrite = new RestWrite(config, {isMaster: true}, '_PushStatus', {status:"running", objectId: pushStatus.objectId}, update);
|
||||||
|
return restWrite.execute();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.freeze({
|
||||||
|
setInitial,
|
||||||
|
setRunning,
|
||||||
|
complete
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user