Files
kami-parse-server/src/Adapters/Push/OneSignalPushAdapter.js

231 lines
6.0 KiB
JavaScript

"use strict";
// ParsePushAdapter is the default implementation of
// PushAdapter, it uses GCM for android push and APNS
// for ios push.
const Parse = require('parse/node').Parse;
var deepcopy = require('deepcopy');
function OneSignalPushAdapter(pushConfig) {
this.https = require('https');
this.validPushTypes = ['ios', 'android'];
this.senderMap = {};
pushConfig = pushConfig || {};
this.OneSignalConfig = {};
this.OneSignalConfig['appId'] = pushConfig['oneSignalAppId'];
this.OneSignalConfig['apiKey'] = pushConfig['oneSignalApiKey'];
this.senderMap['ios'] = this.sendToAPNS.bind(this);
this.senderMap['android'] = this.sendToGCM.bind(this);
}
/**
* Get an array of valid push types.
* @returns {Array} An array of valid push types
*/
OneSignalPushAdapter.prototype.getValidPushTypes = function() {
return this.validPushTypes;
}
OneSignalPushAdapter.prototype.send = function(data, installations) {
console.log("Sending notification to "+installations.length+" devices.")
let deviceMap = classifyInstallation(installations, this.validPushTypes);
let sendPromises = [];
for (let pushType in deviceMap) {
let sender = this.senderMap[pushType];
if (!sender) {
console.log('Can not find sender for push type %s, %j', pushType, data);
continue;
}
let devices = deviceMap[pushType];
if(devices.length > 0) {
sendPromises.push(sender(data, devices));
}
}
return Parse.Promise.when(sendPromises);
}
OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) {
data= deepcopy(data['data']);
var post = {};
if(data['badge']) {
if(data['badge'] == "Increment") {
post['ios_badgeType'] = 'Increase';
post['ios_badgeCount'] = 1;
} else {
post['ios_badgeType'] = 'SetTo';
post['ios_badgeCount'] = data['badge'];
}
delete data['badge'];
}
if(data['alert']) {
post['contents'] = {en: data['alert']};
delete data['alert'];
}
if(data['sound']) {
post['ios_sound'] = data['sound'];
delete data['sound'];
}
if(data['content-available'] == 1) {
post['content_available'] = true;
delete data['content-available'];
}
post['data'] = data;
let promise = new Parse.Promise();
var chunk = 2000 // OneSignal can process 2000 devices at a time
var tokenlength=tokens.length;
var offset = 0
// handle onesignal response. Start next batch if there's not an error.
let handleResponse = function(wasSuccessful) {
if (!wasSuccessful) {
return promise.reject("OneSignal Error");
}
if(offset >= tokenlength) {
promise.resolve()
} else {
this.sendNext();
}
}.bind(this)
this.sendNext = function() {
post['include_ios_tokens'] = [];
tokens.slice(offset,offset+chunk).forEach(function(i) {
post['include_ios_tokens'].push(i['deviceToken'])
})
offset+=chunk;
this.sendToOneSignal(post, handleResponse);
}.bind(this)
this.sendNext()
return promise;
}
OneSignalPushAdapter.prototype.sendToGCM = function(data,tokens) {
data= deepcopy(data['data']);
var post = {};
if(data['alert']) {
post['contents'] = {en: data['alert']};
delete data['alert'];
}
if(data['title']) {
post['title'] = {en: data['title']};
delete data['title'];
}
if(data['uri']) {
post['url'] = data['uri'];
}
post['data'] = data;
let promise = new Parse.Promise();
var chunk = 2000 // OneSignal can process 2000 devices at a time
var tokenlength=tokens.length;
var offset = 0
// handle onesignal response. Start next batch if there's not an error.
let handleResponse = function(wasSuccessful) {
if (!wasSuccessful) {
return promise.reject("OneSIgnal Error");
}
if(offset >= tokenlength) {
promise.resolve()
} else {
this.sendNext();
}
}.bind(this);
this.sendNext = function() {
post['include_android_reg_ids'] = [];
tokens.slice(offset,offset+chunk).forEach(function(i) {
post['include_android_reg_ids'].push(i['deviceToken'])
})
offset+=chunk;
this.sendToOneSignal(post, handleResponse);
}.bind(this)
this.sendNext();
return promise;
}
OneSignalPushAdapter.prototype.sendToOneSignal = function(data, cb) {
let headers = {
"Content-Type": "application/json",
"Authorization": "Basic "+this.OneSignalConfig['apiKey']
};
let options = {
host: "onesignal.com",
port: 443,
path: "/api/v1/notifications",
method: "POST",
headers: headers
};
data['app_id'] = this.OneSignalConfig['appId'];
let request = this.https.request(options, function(res) {
if(res.statusCode < 299) {
cb(true);
} else {
console.log('OneSignal Error');
res.on('data', function(chunk) {
console.log(chunk.toString())
});
cb(false)
}
});
request.on('error', function(e) {
console.log("Error connecting to OneSignal")
console.log(e);
cb(false);
});
request.write(JSON.stringify(data))
request.end();
}
/**g
* Classify the device token of installations based on its device type.
* @param {Object} installations An array of installations
* @param {Array} validPushTypes An array of valid push types(string)
* @returns {Object} A map whose key is device type and value is an array of device
*/
function classifyInstallation(installations, validPushTypes) {
// Init deviceTokenMap, create a empty array for each valid pushType
let deviceMap = {};
for (let validPushType of validPushTypes) {
deviceMap[validPushType] = [];
}
for (let installation of installations) {
// No deviceToken, ignore
if (!installation.deviceToken) {
continue;
}
let pushType = installation.deviceType;
if (deviceMap[pushType]) {
deviceMap[pushType].push({
deviceToken: installation.deviceToken
});
} else {
console.log('Unknown push type from installation %j', installation);
}
}
return deviceMap;
}
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
OneSignalPushAdapter.classifyInstallation = classifyInstallation;
}
module.exports = OneSignalPushAdapter;