1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/rocketchat_ynh.git synced 2024-09-03 20:16:25 +02:00
rocketchat_ynh/sources/programs/server/packages/raix_push.js
2016-04-29 16:32:48 +02:00

1012 lines
113 KiB
JavaScript

(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var ECMAScript = Package.ecmascript.ECMAScript;
var EventState = Package['raix:eventstate'].EventState;
var check = Package.check.check;
var Match = Package.check.Match;
var MongoInternals = Package.mongo.MongoInternals;
var Mongo = Package.mongo.Mongo;
var _ = Package.underscore._;
var EJSON = Package.ejson.EJSON;
var babelHelpers = Package['babel-runtime'].babelHelpers;
var Symbol = Package['ecmascript-runtime'].Symbol;
var Map = Package['ecmascript-runtime'].Map;
var Set = Package['ecmascript-runtime'].Set;
var Promise = Package.promise.Promise;
/* Package-scope variables */
var Push, _matchToken, checkClientSecurity, _replaceToken, _removeToken, initPushUpdates;
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/raix_push/lib/common/main.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// The push object is an event emitter //
Push = new EventState(); // 2
//
// This is the match pattern for tokens //
_matchToken = Match.OneOf({ apn: String }, { gcm: String }); // 5
//
// Client-side security warnings, used to check options //
checkClientSecurity = function (options) { // 9
//
// Warn if certificates or keys are added here on client. We dont allow the //
// user to do this for security reasons. //
if (options.apn && options.apn.certData) { // 13
throw new Error('Push.init: Dont add your APN certificate in client code!'); // 14
} //
//
if (options.apn && options.apn.keyData) { // 17
throw new Error('Push.init: Dont add your APN key in client code!'); // 18
} //
//
if (options.apn && options.apn.passphrase) { // 21
throw new Error('Push.init: Dont add your APN passphrase in client code!'); // 22
} //
//
if (options.gcm && options.gcm.apiKey) { // 25
throw new Error('Push.init: Dont add your GCM api key in client code!'); // 26
} //
}; //
//
// DEPRECATED //
Push.init = function () { // 31
console.warn('Push.init have been deprecated in favor of "config.push.json" please migrate'); // 32
}; //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/raix_push/lib/common/notifications.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Notifications collection //
Push.notifications = new Mongo.Collection('_raix_push_notifications'); // 2
//
// This is a general function to validate that the data added to notifications //
// is in the correct format. If not this function will throw errors //
var _validateDocument = function (notification) { // 6
//
// Check the general notification //
check(notification, { // 9
from: String, // 10
title: String, // 11
text: String, // 12
badge: Match.Optional(Number), // 13
sound: Match.Optional(String), // 14
notId: Match.Optional(Match.Integer), // 15
apn: Match.Optional({ // 16
from: Match.Optional(String), // 17
title: Match.Optional(String), // 18
text: Match.Optional(String), // 19
badge: Match.Optional(Number), // 20
sound: Match.Optional(String), // 21
notId: Match.Optional(Match.Integer) // 22
}), //
gcm: Match.Optional({ // 24
from: Match.Optional(String), // 25
title: Match.Optional(String), // 26
text: Match.Optional(String), // 27
badge: Match.Optional(Number), // 28
sound: Match.Optional(String), // 29
notId: Match.Optional(Match.Integer) // 30
}), //
query: Match.Optional(String), // 32
token: Match.Optional(_matchToken), // 33
tokens: Match.Optional([_matchToken]), // 34
payload: Match.Optional(Object), // 35
delayUntil: Match.Optional(Date), // 36
createdAt: Date, // 37
createdBy: Match.OneOf(String, null) // 38
}); //
//
// Make sure a token selector or query have been set //
if (!notification.token && !notification.tokens && !notification.query) { // 42
throw new Error('No token selector or query found'); // 43
} //
//
// If tokens array is set it should not be empty //
if (notification.tokens && !notification.tokens.length) { // 47
throw new Error('No tokens in array'); // 48
} //
}; //
//
Push.send = function (options) { // 52
// If on the client we set the user id - on the server we need an option //
// set or we default to "<SERVER>" as the creator of the notification //
// If current user not set see if we can set it to the logged in user //
// this will only run on the client if Meteor.userId is available //
var currentUser = Meteor.isClient && Meteor.userId && Meteor.userId() || Meteor.isServer && (options.createdBy || '<SERVER>') || null;
//
// Rig the notification object //
var notification = _.extend({ // 61
createdAt: new Date(), // 62
createdBy: currentUser // 63
}, _.pick(options, 'from', 'title', 'text')); //
//
// Add extra //
_.extend(notification, _.pick(options, 'payload', 'badge', 'sound', 'notId', 'delayUntil')); // 67
//
if (Match.test(options.apn, Object)) { // 69
notification.apn = _.pick(options.apn, 'from', 'title', 'text', 'badge', 'sound', 'notId'); // 70
} //
//
if (Match.test(options.gcm, Object)) { // 73
notification.gcm = _.pick(options.gcm, 'from', 'title', 'text', 'badge', 'sound', 'notId'); // 74
} //
//
// Set one token selector, this can be token, array of tokens or query //
if (options.query) { // 78
// Set query to the json string version fixing #43 and #39 //
notification.query = JSON.stringify(options.query); // 80
} else if (options.token) { //
// Set token //
notification.token = options.token; // 83
} else if (options.tokens) { //
// Set tokens //
notification.tokens = options.tokens; // 86
} //
//
// Validate the notification //
_validateDocument(notification); // 90
//
// Try to add the notification to send, we return an id to keep track //
return Push.notifications.insert(notification); // 93
}; //
//
Push.allow = function (rules) { // 96
if (rules.send) { // 97
Push.notifications.allow({ // 98
'insert': function (userId, notification) { // 99
// Validate the notification //
_validateDocument(notification); // 101
// Set the user defined "send" rules //
return rules.send.apply(this, [userId, notification]); // 103
} //
}); //
} //
}; //
//
Push.deny = function (rules) { // 109
if (rules.send) { // 110
Push.notifications.deny({ // 111
'insert': function (userId, notification) { // 112
// Validate the notification //
_validateDocument(notification); // 114
// Set the user defined "send" rules //
return rules.send.apply(this, [userId, notification]); // 116
} //
}); //
} //
}; //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/raix_push/lib/server/push.api.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
/* //
A general purpose user CordovaPush //
ios, android, mail, twitter?, facebook?, sms?, snailMail? :) //
//
Phonegap generic : //
https://github.com/phonegap-build/PushPlugin //
*/ //
//
// getText / getBinary //
//
Push.setBadge = function () /* id, count */{ // 11
// throw new Error('Push.setBadge not implemented on the server'); //
}; //
//
var isConfigured = false; // 15
//
Push.Configure = function (options) { // 17
var self = this; // 18
// https://npmjs.org/package/apn //
//
// After requesting the certificate from Apple, export your private key as //
// a .p12 file anddownload the .cer file from the iOS Provisioning Portal. //
//
// gateway.push.apple.com, port 2195 //
// gateway.sandbox.push.apple.com, port 2195 //
//
// Now, in the directory containing cert.cer and key.p12 execute the //
// following commands to generate your .pem files: //
// $ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem //
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes //
//
// Block multiple calls //
if (isConfigured) { // 33
throw new Error('Push.Configure should not be called more than once!'); // 34
} //
//
isConfigured = true; // 37
//
// Add debug info //
if (Push.debug) { // 40
console.log('Push.Configure', options); // 41
} //
//
// This function is called when a token is replaced on a device - normally //
// this should not happen, but if it does we should take action on it //
_replaceToken = function (currentToken, newToken) { // 46
// console.log('Replace token: ' + currentToken + ' -- ' + newToken); //
// If the server gets a token event its passing in the current token and //
// the new value - if new value is undefined this empty the token //
self.emitState('token', currentToken, newToken); // 50
}; //
//
// Rig the removeToken callback //
_removeToken = function (token) { // 54
// console.log('Remove token: ' + token); //
// Invalidate the token //
self.emitState('token', token, null); // 57
}; //
//
if (options.apn) { // 61
if (Push.debug) { // 62
console.log('Push: APN configured'); // 63
} //
//
// Allow production to be a general option for push notifications //
if (options.production === Boolean(options.production)) { // 67
options.apn.production = options.production; // 68
} //
//
// Give the user warnings about development settings //
if (options.apn.development) { // 72
// This flag is normally set by the configuration file //
console.warn('WARNING: Push APN is using development key and certificate'); // 74
} else { //
// We check the apn gateway i the options, we could risk shipping //
// server into production while using the production configuration. //
// On the other hand we could be in development but using the production //
// configuration. And finally we could have configured an unknown apn //
// gateway (this could change in the future - but a warning about typos //
// can save hours of debugging) //
// //
// Warn about gateway configurations - it's more a guide //
if (options.apn.gateway) { // 84
//
if (options.apn.gateway === 'gateway.sandbox.push.apple.com') { // 86
// Using the development sandbox //
console.warn('WARNING: Push APN is in development mode'); // 88
} else if (options.apn.gateway === 'gateway.push.apple.com') { //
// In production - but warn if we are running on localhost //
if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) { // 91
console.warn('WARNING: Push APN is configured to production mode - but server is running' + ' from localhost');
} //
} else { //
// Warn about gateways we dont know about //
console.warn('WARNING: Push APN unkown gateway "' + options.apn.gateway + '"'); // 97
} //
} else { //
if (options.apn.production) { // 101
if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) { // 102
console.warn('WARNING: Push APN is configured to production mode - but server is running' + ' from localhost');
} //
} else { //
console.warn('WARNING: Push APN is in development mode'); // 107
} //
} //
} //
//
// Check certificate data //
if (!options.apn.certData || !options.apn.certData.length) { // 114
console.error('ERROR: Push server could not find certData'); // 115
} //
//
// Check key data //
if (!options.apn.keyData || !options.apn.keyData.length) { // 119
console.error('ERROR: Push server could not find keyData'); // 120
} //
//
// Rig apn connection //
var apn = Npm.require('apn'); // 124
var apnConnection = new apn.Connection(options.apn); // 125
//
// Listen to transmission errors - should handle the same way as feedback. //
apnConnection.on('transmissionError', Meteor.bindEnvironment(function (errCode, notification, recipient) { // 128
if (Push.debug) { // 129
console.log('Got error code %d for token %s', errCode, notification.token); // 130
} //
if ([2, 5, 8].indexOf(errCode) >= 0) { // 132
//
// Invalid token errors... //
_removeToken({ // 136
apn: notification.token // 137
}); //
} //
})); //
// XXX: should we do a test of the connection? It would be nice to know //
// That the server/certificates/network are correct configured //
//
// apnConnection.connect().then(function() { //
// console.info('CHECK: Push APN connection OK'); //
// }, function(err) { //
// console.warn('CHECK: Push APN connection FAILURE'); //
// }); //
// Note: the above code spoils the connection - investigate how to //
// shutdown/close it. //
//
self.sendAPN = function (userToken, notification) { // 152
if (Match.test(notification.apn, Object)) { // 153
notification = _.extend({}, notification, notification.apn); // 154
} //
//
// console.log('sendAPN', notification.from, userToken, notification.title, notification.text, //
// notification.badge, notification.priority); //
var priority = notification.priority || notification.priority === 0 ? notification.priority : 10; // 159
//
var myDevice = new apn.Device(userToken); // 161
//
var note = new apn.Notification(); // 163
//
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now. // 165
if (typeof notification.badge !== 'undefined') { // 166
note.badge = notification.badge; // 167
} //
if (typeof notification.sound !== 'undefined') { // 169
note.sound = notification.sound; // 170
} //
//
// adds category support for iOS8 custom actions as described here: //
// https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/ //
// RemoteNotificationsPG/Chapters/IPhoneOSClientImp.html#//apple_ref/doc/uid/TP40008194-CH103-SW36 //
if (typeof notification.category !== 'undefined') { // 176
note.category = notification.category; // 177
} //
//
note.alert = notification.text; // 180
// Allow the user to set payload data //
note.payload = notification.payload ? { ejson: EJSON.stringify(notification.payload) } : {}; // 182
//
note.payload.messageFrom = notification.from; // 184
note.priority = priority; // 185
//
// Store the token on the note so we can reference it if there was an error //
note.token = userToken; // 189
//
// console.log('I:Send message to: ' + userToken + ' count=' + count); //
//
apnConnection.pushNotification(note, myDevice); // 193
}; //
//
var initFeedback = function () { // 198
var apn = Npm.require('apn'); // 199
// console.log('Init feedback'); //
var feedbackOptions = { // 201
'batchFeedback': true, // 202
//
// Time in SECONDS //
'interval': 5, // 205
production: !options.apn.development, // 206
cert: options.certData, // 207
key: options.keyData, // 208
passphrase: options.passphrase // 209
}; //
//
var feedback = new apn.Feedback(feedbackOptions); // 212
feedback.on('feedback', function (devices) { // 213
devices.forEach(function (item) { // 214
// Do something with item.device and item.time; //
// console.log('A:PUSH FEEDBACK ' + item.device + ' - ' + item.time); //
// The app is most likely removed from the device, we should //
// remove the token //
_removeToken({ // 219
apn: item.device // 220
}); //
}); //
}); //
//
feedback.start(); // 225
}; //
//
// Init feedback from apn server //
// This will help keep the appCollection up-to-date, it will help update //
// and remove token from appCollection. //
initFeedback(); // 231
} // EO ios notification //
//
if (options.gcm && options.gcm.apiKey) { // 235
if (Push.debug) { // 236
console.log('GCM configured'); // 237
} //
//self.sendGCM = function(options.from, userTokens, options.title, options.text, options.badge, options.priority) {
self.sendGCM = function (userTokens, notification) { // 240
if (Match.test(notification.gcm, Object)) { // 241
notification = _.extend({}, notification, notification.gcm); // 242
} //
//
// Make sure userTokens are an array of strings //
if (userTokens === '' + userTokens) { // 246
userTokens = [userTokens]; // 247
} //
//
// Check if any tokens in there to send //
if (!userTokens.length) { // 251
if (Push.debug) { // 252
console.log('sendGCM no push tokens found'); // 253
} //
return; // 255
} //
//
if (Push.debug) { // 258
console.log('sendGCM', userTokens, notification); // 259
} //
//
var gcm = Npm.require('node-gcm'); // 262
var Fiber = Npm.require('fibers'); // 263
//
// Allow user to set payload //
var data = notification.payload ? { ejson: EJSON.stringify(notification.payload) } : {}; // 266
//
data.title = notification.title; // 268
data.message = notification.text; // 269
//
// Set extra details //
if (typeof notification.badge !== 'undefined') { // 272
data.msgcnt = notification.badge; // 273
} //
if (typeof notification.sound !== 'undefined') { // 275
data.soundname = notification.sound; // 276
} //
if (typeof notification.notId !== 'undefined') { // 278
data.notId = notification.notId; // 279
} //
//
//var message = new gcm.Message(); //
var message = new gcm.Message({ // 283
collapseKey: notification.from, // 284
// delayWhileIdle: true, //
// timeToLive: 4, //
// restricted_package_name: 'dk.gi2.app' //
data: data // 288
}); //
//
if (Push.debug) { // 291
console.log('Create GCM Sender using "' + options.gcm.apiKey + '"'); // 292
} //
var sender = new gcm.Sender(options.gcm.apiKey); // 294
//
_.each(userTokens, function (value /*, key */) { // 296
if (Push.debug) { // 297
console.log('A:Send message to: ' + value); // 298
} //
}); //
//
/*message.addData('title', title); //
message.addData('message', text); //
message.addData('msgcnt', '1'); //
message.collapseKey = 'sitDrift'; //
message.delayWhileIdle = true; //
message.timeToLive = 3;*/ //
//
// /** //
// * Parameters: message-literal, userTokens-array, No. of retries, callback-function //
// */ //
//
var userToken = userTokens.length === 1 ? userTokens[0] : null; // 313
//
sender.send(message, userTokens, 5, function (err, result) { // 315
if (err) { // 316
if (Push.debug) { // 317
console.log('ANDROID ERROR: result of sender: ' + result); // 318
} //
} else { //
if (result === null) { // 321
if (Push.debug) { // 322
console.log('ANDROID: Result of sender is null'); // 323
} //
return; // 325
} //
if (Push.debug) { // 327
console.log('ANDROID: Result of sender: ' + JSON.stringify(result)); // 328
} //
if (result.canonical_ids === 1 && userToken) { // 330
// jshint ignore:line //
//
// This is an old device, token is replaced //
Fiber(function (self) { // 333
// Run in fiber //
try { // 335
self.callback(self.oldToken, self.newToken); // 336
} catch (err) {} //
}).run({ //
oldToken: { gcm: userToken }, // 342
newToken: { gcm: result.results[0].registration_id }, // jshint ignore:line // 343
callback: _replaceToken // 344
}); //
//_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id }); //
} //
// We cant send to that token - might not be registred //
// ask the user to remove the token from the list //
if (result.failure !== 0 && userToken) { // 351
//
// This is an old device, token is replaced //
Fiber(function (self) { // 354
// Run in fiber //
try { // 356
self.callback(self.token); // 357
} catch (err) {} //
}).run({ //
token: { gcm: userToken }, // 363
callback: _removeToken // 364
}); //
//_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id }); //
} //
} //
}); //
// /** Use the following line if you want to send the message without retries //
// sender.sendNoRetry(message, userTokens, function (result) { //
// console.log('ANDROID: ' + JSON.stringify(result)); //
// }); //
// **/ //
}; // EO sendAndroid //
} // EO Android //
//
// Universal send function //
var _querySend = function (query, options) { // 382
//
var countApn = []; // 384
var countGcm = []; // 385
//
Push.appCollection.find(query).forEach(function (app) { // 387
//
if (Push.debug) { // 389
console.log('send to token', app.token); // 390
} //
//
if (app.token.apn) { // 393
countApn.push(app._id); // 394
// Send to APN //
if (self.sendAPN) { // 396
self.sendAPN(app.token.apn, options); // 397
} //
} else if (app.token.gcm) { //
countGcm.push(app._id); // 401
//
// Send to GCM //
// We do support multiple here - so we should construct an array //
// and send it bulk - Investigate limit count of id's //
if (self.sendGCM) { // 406
self.sendGCM(app.token.gcm, options); // 407
} //
} else { //
throw new Error('Push.send got a faulty query'); // 411
} //
}); //
//
if (Push.debug) { // 416
//
console.log('Push: Sent message "' + options.title + '" to ' + countApn.length + ' ios apps ' + countGcm.length + ' android apps');
//
// Add some verbosity about the send result, making sure the developer //
// understands what just happened. //
if (!countApn.length && !countGcm.length) { // 423
if (Push.appCollection.find().count() === 0) { // 424
console.log('Push, GUIDE: The "Push.appCollection" is empty -' + ' No clients have registred on the server yet...');
} //
} else if (!countApn.length) { //
if (Push.appCollection.find({ 'token.apn': { $exists: true } }).count() === 0) { // 429
console.log('Push, GUIDE: The "Push.appCollection" - No APN clients have registred on the server yet...');
} //
} else if (!countGcm.length) { //
if (Push.appCollection.find({ 'token.gcm': { $exists: true } }).count() === 0) { // 433
console.log('Push, GUIDE: The "Push.appCollection" - No GCM clients have registred on the server yet...');
} //
} //
} //
//
return { // 440
apn: countApn, // 441
gcm: countGcm // 442
}; //
}; //
//
self.serverSend = function (options) { // 446
options = options || { badge: 0 }; // 447
var query; // 448
//
// Check basic options //
if (options.from !== '' + options.from) { // 451
throw new Error('Push.send: option "from" not a string'); // 452
} //
//
if (options.title !== '' + options.title) { // 455
throw new Error('Push.send: option "title" not a string'); // 456
} //
//
if (options.text !== '' + options.text) { // 459
throw new Error('Push.send: option "text" not a string'); // 460
} //
//
if (options.token || options.tokens) { // 463
//
// The user set one token or array of tokens //
var tokenList = options.token ? [options.token] : options.tokens; // 466
//
if (Push.debug) { // 468
console.log('Push: Send message "' + options.title + '" via token(s)', tokenList); // 469
} //
//
query = { // 472
$or: [ // 473
// XXX: Test this query: can we hand in a list of push tokens? //
{ $and: [{ token: { $in: tokenList } }, // 475
// And is not disabled //
{ enabled: { $ne: false } }] // 478
}, //
// XXX: Test this query: does this work on app id? //
{ $and: [{ _in: { $in: tokenList } }, // one of the app ids // 482
{ $or: [{ 'token.apn': { $exists: true } }, // got apn token // 484
{ 'token.gcm': { $exists: true } } // got gcm token // 486
] }, //
// And is not disabled //
{ enabled: { $ne: false } }] // 489
}] //
}; //
} else if (options.query) { //
//
if (Push.debug) { // 497
console.log('Push: Send message "' + options.title + '" via query', options.query); // 498
} //
//
query = { // 501
$and: [options.query, // query object // 502
{ $or: [{ 'token.apn': { $exists: true } }, // got apn token // 504
{ 'token.gcm': { $exists: true } } // got gcm token // 506
] }, //
// And is not disabled //
{ enabled: { $ne: false } }] // 509
}; //
} //
//
if (query) { // 515
//
// Convert to querySend and return status //
return _querySend(query, options); // 518
} else { //
throw new Error('Push.send: please set option "token"/"tokens" or "query"'); // 521
} //
}; //
//
// This interval will allow only one notification to be sent at a time, it //
// will check for new notifications at every `options.sendInterval` //
// (default interval is 15000 ms) //
// //
// It looks in notifications collection to see if theres any pending //
// notifications, if so it will try to reserve the pending notification. //
// If successfully reserved the send is started. //
// //
// If notification.query is type string, it's assumed to be a json string //
// version of the query selector. Making it able to carry `$` properties in //
// the mongo collection. //
// //
// Pr. default notifications are removed from the collection after send have //
// completed. Setting `options.keepNotifications` will update and keep the //
// notification eg. if needed for historical reasons. //
// //
// After the send have completed a "send" event will be emitted with a //
// status object containing notification id and the send result object. //
// //
var isSendingNotification = false; // 546
//
if (options.sendInterval !== null) { // 548
//
// This will require index since we sort notifications by createdAt //
Push.notifications._ensureIndex({ createdAt: 1 }); // 551
//
Meteor.setInterval(function () { // 553
//
if (isSendingNotification) { // 555
return; // 556
} //
// Set send fence //
isSendingNotification = true; // 559
//
// var countSent = 0; //
var batchSize = options.sendBatchSize || 1; // 562
//
// Find notifications that are not being or already sent //
var pendingNotifications = Push.notifications.find({ $and: [ // 565
// Message is not sent //
{ sent: { $ne: true } }, // 567
// And not being sent by other instances //
{ sending: { $ne: true } }, // 569
// And not queued for future //
{ $or: [{ delayUntil: { $exists: false } }, { delayUntil: { $lte: new Date() } }] }] }, { // 571
// Sort by created date //
sort: { createdAt: 1 }, // 574
limit: batchSize // 575
}); //
//
pendingNotifications.forEach(function (notification) { // 578
// Reserve notification //
var reserved = Push.notifications.update({ $and: [ // 580
// Try to reserve the current notification //
{ _id: notification._id }, // 582
// Make sure no other instances have reserved it //
{ sending: { $ne: true } }] }, { // 584
$set: { // 586
// Try to reserve //
sending: true // 588
} //
}); //
//
// Make sure we only handle notifications reserved by this //
// instance //
if (reserved) { // 594
//
// Check if query is set and is type String //
if (notification.query && notification.query === '' + notification.query) { // 597
try { // 598
// The query is in string json format - we need to parse it //
notification.query = JSON.parse(notification.query); // 600
} catch (err) { //
// Did the user tamper with this?? //
throw new Error('Push: Error while parsing query string, Error: ' + err.message); // 603
} //
} //
//
// Send the notification //
var result = Push.serverSend(notification); // 608
//
if (!options.keepNotifications) { // 610
// Pr. Default we will remove notifications //
Push.notifications.remove({ _id: notification._id }); // 612
} else { //
//
// Update the notification //
Push.notifications.update({ _id: notification._id }, { // 616
$set: { // 617
// Mark as sent //
sent: true, // 619
// Set the sent date //
sentAt: new Date(), // 621
// Count //
count: result, // 623
// Not being sent anymore //
sending: false // 625
} //
}); //
} //
//
// Emit the send //
self.emit('send', { notification: notification._id, result: result }); // 632
} // Else could not reserve //
}); // EO forEach //
//
// Remove the send fence //
isSendingNotification = false; // 639
}, options.sendInterval || 15000); // Default every 15th sec //
} else { //
if (Push.debug) { // 643
console.log('Push: Send server is disabled'); // 644
} //
} //
}; //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/raix_push/lib/server/server.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
Push.appCollection = new Mongo.Collection('_raix_push_app_tokens'); // 1
//
Push.addListener('token', function (currentToken, value) { // 3
if (value) { // 4
// Update the token for app //
Push.appCollection.update({ token: currentToken }, { $set: { token: value } }, { multi: true }); // 6
} else if (value === null) { //
// Remove the token for app //
Push.appCollection.update({ token: currentToken }, { $unset: { token: true } }, { multi: true }); // 9
} //
}); //
//
Meteor.methods({ // 13
'raix:push-update': function (options) { // 14
if (Push.debug) { // 15
console.log('Push: Got push token from app:', options); // 16
} //
//
check(options, { // 19
id: Match.Optional(String), // 20
token: _matchToken, // 21
appName: String, // 22
userId: Match.OneOf(String, null), // 23
metadata: Match.Optional(Object) // 24
}); //
//
// The if user id is set then user id should match on client and connection //
if (options.userId && options.userId !== this.userId) { // 28
throw new Meteor.Error(403, 'Forbidden access'); // 29
} //
//
var doc; // 32
//
// lookup app by id if one was included //
if (options.id) { // 35
doc = Push.appCollection.findOne({ _id: options.id }); // 36
} //
//
// No doc was found - we check the database to see if //
// we can find a match for the app via token and appName //
if (!doc) { // 41
doc = Push.appCollection.findOne({ // 42
$and: [{ token: options.token }, // Match token // 43
{ appName: options.appName }, // Match appName // 45
{ token: { $exists: true } } // Make sure token exists // 46
] //
}); //
} //
//
// if we could not find the id or token then create it //
if (!doc) { // 52
// Rig default doc //
doc = { // 54
token: options.token, // 55
appName: options.appName, // 56
userId: options.userId, // 57
enabled: true, // 58
createdAt: new Date(), // 59
updatedAt: new Date() // 60
}; //
//
if (options.id) { // 63
// XXX: We might want to check the id - Why isnt there a match for id //
// in the Meteor check... Normal length 17 (could be larger), and //
// numbers+letters are used in Random.id() with exception of 0 and 1 //
doc._id = options.id; // 67
// The user wanted us to use a specific id, we didn't find this while //
// searching. The client could depend on the id eg. as reference so //
// we respect this and try to create a document with the selected id; //
Push.appCollection._collection.insert(doc); // 71
} else { //
// Get the id from insert //
doc._id = Push.appCollection.insert(doc); // 74
} //
} else { //
// We found the app so update the updatedAt and set the token //
Push.appCollection.update({ _id: doc._id }, { // 78
$set: { // 79
updatedAt: new Date(), // 80
token: options.token // 81
} //
}); //
} //
//
if (doc) { // 86
// xxx: Hack //
// Clean up mech making sure tokens are uniq - android sometimes generate //
// new tokens resulting in duplicates //
var removed = Push.appCollection.remove({ // 90
$and: [{ _id: { $ne: doc._id } }, { token: doc.token }, // Match token // 91
{ appName: doc.appName }, // Match appName // 94
{ token: { $exists: true } } // Make sure token exists // 95
] //
}); //
//
if (removed && Push.debug) { // 99
console.log('Push: Removed ' + removed + ' existing app items'); // 100
} //
} //
//
if (doc && Push.debug) { // 104
console.log('Push: updated', doc); // 105
} //
//
if (!doc) { // 108
throw new Meteor.Error(500, 'setPushToken could not create record'); // 109
} //
// Return the doc we want to use //
return doc; // 112
}, //
'raix:push-setuser': function (id) { // 114
check(id, String); // 115
//
if (Push.debug) { // 117
console.log('Push: Settings userId "' + this.userId + '" for app:', id); // 118
} //
// We update the appCollection id setting the Meteor.userId //
var found = Push.appCollection.update({ _id: id }, { $set: { userId: this.userId } }); // 121
//
// Note that the app id might not exist because no token is set yet. //
// We do create the new app id for the user since we might store additional //
// metadata for the app / user //
//
// If id not found then create it? //
// We dont, its better to wait until the user wants to //
// store metadata or token - We could end up with unused data in the //
// collection at every app re-install / update //
// //
// The user could store some metadata in appCollectin but only if they //
// have created the app and provided a token. //
// If not the metadata should be set via ground:db //
//
return !!found; // 136
}, //
'raix:push-metadata': function (data) { // 138
check(data, { // 139
id: String, // 140
metadata: Object // 141
}); //
//
// Set the metadata //
var found = Push.appCollection.update({ _id: data.id }, { $set: { metadata: data.metadata } }); // 145
//
return !!found; // 147
}, //
'raix:push-enable': function (data) { // 149
check(data, { // 150
id: String, // 151
enabled: Boolean // 152
}); //
//
if (Push.debug) { // 155
console.log('Push: Setting enabled to "' + data.enabled + '" for app:', data.id); // 156
} //
//
var found = Push.appCollection.update({ _id: data.id }, { $set: { enabled: data.enabled } }); // 159
//
return !!found; // 161
} //
}); //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['raix:push'] = {
Push: Push,
_matchToken: _matchToken,
checkClientSecurity: checkClientSecurity,
initPushUpdates: initPushUpdates,
_replaceToken: _replaceToken,
_removeToken: _removeToken
};
})();
//# sourceMappingURL=raix_push.js.map